diff options
416 files changed, 19186 insertions, 8909 deletions
diff --git a/Android.bp b/Android.bp index 286be8212694..e63463c07313 100644 --- a/Android.bp +++ b/Android.bp @@ -191,6 +191,10 @@ java_defaults { "core/java/android/hardware/input/IInputDevicesChangedListener.aidl", "core/java/android/hardware/input/ITabletModeChangedListener.aidl", "core/java/android/hardware/iris/IIrisService.aidl", + "core/java/android/hardware/location/IActivityRecognitionHardware.aidl", + "core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl", + "core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl", + "core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl", "core/java/android/hardware/location/IGeofenceHardware.aidl", "core/java/android/hardware/location/IGeofenceHardwareCallback.aidl", "core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl", @@ -207,7 +211,6 @@ java_defaults { "core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl", "core/java/android/hardware/usb/IUsbManager.aidl", "core/java/android/hardware/usb/IUsbSerialReader.aidl", - "core/java/android/net/ICaptivePortal.aidl", "core/java/android/net/IConnectivityManager.aidl", "core/java/android/hardware/ISensorPrivacyListener.aidl", "core/java/android/hardware/ISensorPrivacyManager.aidl", @@ -762,8 +765,6 @@ java_defaults { required: [ // TODO: remove gps_debug when the build system propagates "required" properly. "gps_debug.conf", - // Loaded with System.loadLibrary by android.view.textclassifier - "libmedia2_jni", ], dxflags: [ @@ -884,6 +885,7 @@ aidl_interface { srcs: [ "core/java/android/net/ApfCapabilitiesParcelable.aidl", "core/java/android/net/DhcpResultsParcelable.aidl", + "core/java/android/net/ICaptivePortal.aidl", "core/java/android/net/INetworkMonitor.aidl", "core/java/android/net/INetworkMonitorCallbacks.aidl", "core/java/android/net/IIpMemoryStore.aidl", diff --git a/api/current.txt b/api/current.txt index b8283ddac9eb..520d5bf93790 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3941,7 +3941,8 @@ package android.app { method public boolean isBackgroundRestricted(); method @Deprecated public boolean isInLockTaskMode(); method public boolean isLowRamDevice(); - method public static boolean isRunningInTestHarness(); + method @Deprecated public static boolean isRunningInTestHarness(); + method public static boolean isRunningInUserTestHarness(); method public static boolean isUserAMonkey(); method @RequiresPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) public void killBackgroundProcesses(String); method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int); @@ -6822,6 +6823,7 @@ package android.app.admin { field public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION"; field public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES"; field public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN"; + field @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) public static final String EXTRA_PASSWORD_COMPLEXITY = "android.app.extra.PASSWORD_COMPLEXITY"; field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE"; field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE"; field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME"; @@ -9625,7 +9627,7 @@ package android.content { public abstract class Context { ctor public Context(); - method public abstract boolean bindIsolatedService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String); + method public boolean bindIsolatedService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String); method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int); method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String); method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int); @@ -9678,7 +9680,7 @@ package android.content { method public abstract java.io.File getNoBackupFilesDir(); method public abstract java.io.File getObbDir(); method public abstract java.io.File[] getObbDirs(); - method public abstract String getOpPackageName(); + method public String getOpPackageName(); method public abstract String getPackageCodePath(); method public abstract android.content.pm.PackageManager getPackageManager(); method public abstract String getPackageName(); @@ -9745,7 +9747,7 @@ package android.content { method public abstract void unbindService(@NonNull android.content.ServiceConnection); method public void unregisterComponentCallbacks(android.content.ComponentCallbacks); method public abstract void unregisterReceiver(android.content.BroadcastReceiver); - method public abstract void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int); + method public void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int); field public static final String ACCESSIBILITY_SERVICE = "accessibility"; field public static final String ACCOUNT_SERVICE = "account"; field public static final String ACTIVITY_SERVICE = "activity"; @@ -9839,7 +9841,6 @@ package android.content { public class ContextWrapper extends android.content.Context { ctor public ContextWrapper(android.content.Context); method protected void attachBaseContext(android.content.Context); - method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, String); method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int); method public int checkCallingOrSelfPermission(String); method public int checkCallingOrSelfUriPermission(android.net.Uri, int); @@ -9889,7 +9890,6 @@ package android.content { method public java.io.File getNoBackupFilesDir(); method public java.io.File getObbDir(); method public java.io.File[] getObbDirs(); - method public String getOpPackageName(); method public String getPackageCodePath(); method public android.content.pm.PackageManager getPackageManager(); method public String getPackageName(); @@ -9945,7 +9945,6 @@ package android.content { method public boolean stopService(android.content.Intent); method public void unbindService(android.content.ServiceConnection); method public void unregisterReceiver(android.content.BroadcastReceiver); - method public void updateServiceGroup(android.content.ServiceConnection, int, int); } @Deprecated public class CursorLoader extends android.content.AsyncTaskLoader<android.database.Cursor> { @@ -10221,7 +10220,7 @@ package android.content { field public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED"; field public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED"; field public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED"; - field public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"; + field @Deprecated public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"; field public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT"; field public static final String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE"; field public static final String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED"; @@ -10349,6 +10348,7 @@ package android.content { field public static final int EXTRA_DOCK_STATE_LE_DESK = 3; // 0x3 field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0 field public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP"; + field public static final String EXTRA_DURATION_MILLIS = "android.intent.extra.DURATION_MILLIS"; field public static final String EXTRA_EMAIL = "android.intent.extra.EMAIL"; field public static final String EXTRA_EXCLUDE_COMPONENTS = "android.intent.extra.EXCLUDE_COMPONENTS"; field public static final String EXTRA_FROM_STORAGE = "android.intent.extra.FROM_STORAGE"; @@ -11655,6 +11655,7 @@ package android.content.pm { field public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape"; field public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait"; field public static final String FEATURE_SECURELY_REMOVES_USERS = "android.software.securely_removes_users"; + field public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen"; field public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer"; field public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature"; field public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer"; @@ -21650,7 +21651,7 @@ package android.icu.util { ctor public JapaneseCalendar(int, int, int, int); ctor public JapaneseCalendar(int, int, int); ctor public JapaneseCalendar(int, int, int, int, int, int); - field public static final int CURRENT_ERA; + field @Deprecated public static final int CURRENT_ERA; field public static final int HEISEI; field public static final int MEIJI; field public static final int SHOWA; @@ -25915,6 +25916,23 @@ package android.media { method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle); } + public abstract class MediaSession2Service extends android.app.Service { + ctor public MediaSession2Service(); + method public final void addSession(@NonNull android.media.MediaSession2); + method @NonNull public final java.util.List<android.media.MediaSession2> getSessions(); + method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); + method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession(); + method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2); + method public final void removeSession(@NonNull android.media.MediaSession2); + field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service"; + } + + public static class MediaSession2Service.MediaNotification { + ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification); + method @NonNull public android.app.Notification getNotification(); + method public int getNotificationId(); + } + public final class MediaSync { ctor public MediaSync(); method @NonNull public android.view.Surface createInputSurface(); @@ -26577,12 +26595,9 @@ package android.media.audiofx { field public static final int SUCCESS = 0; // 0x0 } - public static final class AudioEffect.Descriptor implements android.os.Parcelable { + public static class AudioEffect.Descriptor { ctor public AudioEffect.Descriptor(); ctor public AudioEffect.Descriptor(String, String, String, String, String); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.media.audiofx.AudioEffect.Descriptor> CREATOR; field public String connectMode; field public String implementor; field public String name; @@ -27426,18 +27441,26 @@ package android.media.session { public final class MediaSessionManager { method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName); method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, @Nullable android.os.Handler); + method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener); + method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener, @NonNull android.os.Handler); method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessions(@Nullable android.content.ComponentName); + method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens(); method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo); + method public void notifySession2Created(@NonNull android.media.Session2Token); method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener); + method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener); } public static interface MediaSessionManager.OnActiveSessionsChangedListener { method public void onActiveSessionsChanged(@Nullable java.util.List<android.media.session.MediaController>); } + public static interface MediaSessionManager.OnSession2TokensChangedListener { + method public void onSession2TokensChanged(@NonNull java.util.List<android.media.Session2Token>); + } + public static final class MediaSessionManager.RemoteUserInfo { ctor public MediaSessionManager.RemoteUserInfo(@NonNull String, int, int); - ctor public MediaSessionManager.RemoteUserInfo(String, int, int, android.os.IBinder); method public String getPackageName(); method public int getPid(); method public int getUid(); @@ -28555,6 +28578,28 @@ package android.net { field public int serverAddress; } + public final class DnsResolver { + method public static android.net.DnsResolver getInstance(); + method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException; + method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException; + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException; + field public static final int CLASS_IN = 1; // 0x1 + field public static final int FLAG_EMPTY = 0; // 0x0 + field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 + field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 + field public static final int FLAG_NO_RETRY = 1; // 0x1 + field public static final int TYPE_A = 1; // 0x1 + field public static final int TYPE_AAAA = 28; // 0x1c + } + + public static interface DnsResolver.InetAddressAnswerListener { + method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>); + } + + public static interface DnsResolver.RawAnswerListener { + method public void onAnswer(@Nullable byte[]); + } + public class InetAddresses { method public static boolean isNumericAddress(String); method public static java.net.InetAddress parseNumericAddress(String); @@ -28903,24 +28948,24 @@ package android.net { field public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR; } - public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { + @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { ctor @Deprecated public SSLCertificateSocketFactory(int); - method public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException; - method public java.net.Socket createSocket(java.net.InetAddress, int, java.net.InetAddress, int) throws java.io.IOException; - method public java.net.Socket createSocket(java.net.InetAddress, int) throws java.io.IOException; - method public java.net.Socket createSocket(String, int, java.net.InetAddress, int) throws java.io.IOException; - method public java.net.Socket createSocket(String, int) throws java.io.IOException; - method public static javax.net.SocketFactory getDefault(int); - method public static javax.net.ssl.SSLSocketFactory getDefault(int, android.net.SSLSessionCache); - method public String[] getDefaultCipherSuites(); - method public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache); - method public byte[] getNpnSelectedProtocol(java.net.Socket); - method public String[] getSupportedCipherSuites(); - method public void setHostname(java.net.Socket, String); - method public void setKeyManagers(javax.net.ssl.KeyManager[]); - method public void setNpnProtocols(byte[][]); - method public void setTrustManagers(javax.net.ssl.TrustManager[]); - method public void setUseSessionTickets(java.net.Socket, boolean); + method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException; + method @Deprecated public java.net.Socket createSocket(java.net.InetAddress, int, java.net.InetAddress, int) throws java.io.IOException; + method @Deprecated public java.net.Socket createSocket(java.net.InetAddress, int) throws java.io.IOException; + method @Deprecated public java.net.Socket createSocket(String, int, java.net.InetAddress, int) throws java.io.IOException; + method @Deprecated public java.net.Socket createSocket(String, int) throws java.io.IOException; + method @Deprecated public static javax.net.SocketFactory getDefault(int); + method @Deprecated public static javax.net.ssl.SSLSocketFactory getDefault(int, android.net.SSLSessionCache); + method @Deprecated public String[] getDefaultCipherSuites(); + method @Deprecated public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache); + method @Deprecated public byte[] getNpnSelectedProtocol(java.net.Socket); + method @Deprecated public String[] getSupportedCipherSuites(); + method @Deprecated public void setHostname(java.net.Socket, String); + method @Deprecated public void setKeyManagers(javax.net.ssl.KeyManager[]); + method @Deprecated public void setNpnProtocols(byte[][]); + method @Deprecated public void setTrustManagers(javax.net.ssl.TrustManager[]); + method @Deprecated public void setUseSessionTickets(java.net.Socket, boolean); } public final class SSLSessionCache { @@ -29109,6 +29154,8 @@ package android.net { public class VpnService extends android.app.Service { ctor public VpnService(); + method public final boolean isAlwaysOn(); + method public final boolean isLockdownEnabled(); method public android.os.IBinder onBind(android.content.Intent); method public void onRevoke(); method public static android.content.Intent prepare(android.content.Context); @@ -34864,10 +34911,13 @@ package android.os { method public static final void setThreadPriority(int, int) throws java.lang.IllegalArgumentException, java.lang.SecurityException; method public static final void setThreadPriority(int) throws java.lang.IllegalArgumentException, java.lang.SecurityException; method @Deprecated public static final boolean supportsProcesses(); + field public static final int BLUETOOTH_UID = 1002; // 0x3ea field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710 field public static final int INVALID_UID = -1; // 0xffffffff field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f field public static final int PHONE_UID = 1001; // 0x3e9 + field public static final int ROOT_UID = 0; // 0x0 + field public static final int SHELL_UID = 2000; // 0x7d0 field public static final int SIGNAL_KILL = 9; // 0x9 field public static final int SIGNAL_QUIT = 3; // 0x3 field public static final int SIGNAL_USR1 = 10; // 0xa @@ -37882,18 +37932,18 @@ package android.provider { method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException; method @Deprecated public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; method public static String getDocumentId(android.net.Uri); - method public static android.os.Bundle getDocumentMetadata(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException; + method @Nullable public static android.os.Bundle getDocumentMetadata(@NonNull android.content.ContentInterface, @NonNull android.net.Uri) throws java.io.FileNotFoundException; method @Deprecated public static android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentInterface, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; method @Deprecated 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 String getRootId(android.net.Uri); method public static String getSearchDocumentsQuery(android.net.Uri); method public static 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 isChildDocument(@NonNull android.content.ContentInterface, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException; method @Deprecated public static boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; method public static boolean isDocumentUri(android.content.Context, @Nullable android.net.Uri); - method public static boolean isRootUri(android.content.Context, @Nullable android.net.Uri); - method public static boolean isRootsUri(android.content.Context, @Nullable android.net.Uri); + method public static boolean isRootUri(@NonNull android.content.Context, @Nullable android.net.Uri); + method public static boolean isRootsUri(@NonNull android.content.Context, @Nullable android.net.Uri); method public static boolean isTreeUri(android.net.Uri); method public static android.net.Uri moveDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; method @Deprecated public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; @@ -37964,6 +38014,7 @@ package android.provider { field public static final String COLUMN_FLAGS = "flags"; field public static final String COLUMN_ICON = "icon"; field public static final String COLUMN_MIME_TYPES = "mime_types"; + field public static final String COLUMN_QUERY_ARGS = "query_args"; field public static final String COLUMN_ROOT_ID = "root_id"; field public static final String COLUMN_SUMMARY = "summary"; field public static final String COLUMN_TITLE = "title"; @@ -37986,7 +38037,7 @@ package android.provider { method public void deleteDocument(String) throws java.io.FileNotFoundException; method public void ejectRoot(String); method public android.provider.DocumentsContract.Path findDocumentPath(@Nullable String, String) throws java.io.FileNotFoundException; - method @Nullable public android.os.Bundle getDocumentMetadata(String) throws java.io.FileNotFoundException; + method @Nullable public android.os.Bundle getDocumentMetadata(@NonNull String) throws java.io.FileNotFoundException; method public String[] getDocumentStreamTypes(String, String); method public String getDocumentType(String) throws java.io.FileNotFoundException; method public final String getType(android.net.Uri); @@ -38011,7 +38062,7 @@ package android.provider { method public android.database.Cursor queryRecentDocuments(String, String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(String, String, String[]) throws java.io.FileNotFoundException; - method public android.database.Cursor querySearchDocuments(String, String[], android.os.Bundle) throws java.io.FileNotFoundException; + method public android.database.Cursor querySearchDocuments(@NonNull String, @Nullable String[], @NonNull android.os.Bundle) throws java.io.FileNotFoundException; method public void removeDocument(String, String) throws java.io.FileNotFoundException; method public String renameDocument(String, String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(String); @@ -43584,6 +43635,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccountHandle getSimCallManager(); method public String getSystemDialerPackage(); + method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle); @@ -45011,7 +45063,9 @@ package android.telephony.data { field public static final int PROTOCOL_IP = 0; // 0x0 field public static final int PROTOCOL_IPV4V6 = 2; // 0x2 field public static final int PROTOCOL_IPV6 = 1; // 0x1 + field public static final int PROTOCOL_NON_IP = 4; // 0x4 field public static final int PROTOCOL_PPP = 3; // 0x3 + field public static final int PROTOCOL_UNSTRUCTURED = 5; // 0x5 field public static final int TYPE_CBS = 128; // 0x80 field public static final int TYPE_DEFAULT = 17; // 0x11 field public static final int TYPE_DUN = 8; // 0x8 @@ -49660,6 +49714,7 @@ package android.view { } public class Surface implements android.os.Parcelable { + ctor public Surface(android.view.SurfaceControl); ctor public Surface(android.graphics.SurfaceTexture); method public int describeContents(); method public boolean isValid(); @@ -49682,6 +49737,38 @@ package android.view { ctor public Surface.OutOfResourcesException(String); } + public final class SurfaceControl implements android.os.Parcelable { + method public int describeContents(); + method public boolean isValid(); + method public void readFromParcel(android.os.Parcel); + method public void release(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR; + } + + public static class SurfaceControl.Builder { + ctor public SurfaceControl.Builder(); + method public android.view.SurfaceControl build(); + method public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int); + method @NonNull public android.view.SurfaceControl.Builder setFormat(int); + method public android.view.SurfaceControl.Builder setName(String); + method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean); + method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl); + } + + public static class SurfaceControl.Transaction implements java.io.Closeable { + ctor public SurfaceControl.Transaction(); + method public void apply(); + method public void close(); + method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction); + method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl); + method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float); + method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int); + method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int); + method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int); + method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean); + } + public interface SurfaceHolder { method public void addCallback(android.view.SurfaceHolder.Callback); method public android.view.Surface getSurface(); @@ -49726,6 +49813,7 @@ package android.view { ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int); method public boolean gatherTransparentRegion(android.graphics.Region); method public android.view.SurfaceHolder getHolder(); + method public android.view.SurfaceControl getSurfaceControl(); method public void setSecure(boolean); method public void setZOrderMediaOverlay(boolean); method public void setZOrderOnTop(boolean); diff --git a/api/removed.txt b/api/removed.txt index 2c567e05ca0b..e23222719ea9 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -325,7 +325,7 @@ package android.net { @IntDef({0x0, 0xa, 0x14, 0x1e}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NetworkBadging.Badging { } - public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { + @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { method @Deprecated public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(int, android.net.SSLSessionCache); } diff --git a/api/system-current.txt b/api/system-current.txt index 2088eaf32d0a..cdd2d804844b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1136,19 +1136,46 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect(); method public boolean isBleScanAlwaysAvailable(); method public boolean isLeEnabled(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean registerMetadataListener(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothAdapter.MetadataListener, android.os.Handler); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean unregisterMetadataListener(android.bluetooth.BluetoothDevice); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; } + public abstract class BluetoothAdapter.MetadataListener { + ctor public BluetoothAdapter.MetadataListener(); + method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String); + } + public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); field public static final int ACCESS_ALLOWED = 1; // 0x1 field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 + field public static final int METADATA_COMPANION_APP = 4; // 0x4 + field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10 + field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3 + field public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; // 0x6 + field public static final int METADATA_MAIN_ICON = 5; // 0x5 + field public static final int METADATA_MANUFACTURER_NAME = 0; // 0x0 + field public static final int METADATA_MAX_LENGTH = 2048; // 0x800 + field public static final int METADATA_MODEL_NAME = 1; // 0x1 + field public static final int METADATA_SOFTWARE_VERSION = 2; // 0x2 + field public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; // 0xc + field public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; // 0xf + field public static final int METADATA_UNTHETHERED_CASE_ICON = 9; // 0x9 + field public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; // 0xa + field public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; // 0xd + field public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; // 0x7 + field public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; // 0xb + field public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; // 0xe + field public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; // 0x8 } public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile { @@ -1322,6 +1349,7 @@ package android.content.om { public class OverlayManager { method public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(@Nullable String, int); + method public boolean setEnabled(@Nullable String, boolean, int); method public boolean setEnabledExclusiveInCategory(@Nullable String, int); } @@ -1776,8 +1804,13 @@ package android.hardware.display { } public final class ColorDisplayManager { + method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getTransformCapabilities(); method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setAppSaturationLevel(@NonNull String, @IntRange(from=0, to=100) int); method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setSaturationLevel(@IntRange(from=0, to=100) int); + field public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 2; // 0x2 + field public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 4; // 0x4 + field public static final int CAPABILITY_NONE = 0; // 0x0 + field public static final int CAPABILITY_PROTECTED_CONTENT = 1; // 0x1 } public final class DisplayManager { @@ -3204,12 +3237,14 @@ package android.location { method public int getQuality(); method public float getSmallestDisplacement(); method public android.os.WorkSource getWorkSource(); + method public boolean isLocationSettingsIgnored(); method public boolean isLowPowerMode(); method public android.location.LocationRequest setExpireAt(long); method public android.location.LocationRequest setExpireIn(long); method public android.location.LocationRequest setFastestInterval(long); method public void setHideFromAppOps(boolean); method public android.location.LocationRequest setInterval(long); + method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean); method public android.location.LocationRequest setLowPowerMode(boolean); method public android.location.LocationRequest setNumUpdates(int); method public android.location.LocationRequest setProvider(String); @@ -3530,6 +3565,76 @@ package android.media.session { method public void unregisterCallback(@NonNull android.media.session.ControllerCallbackLink); } + public abstract static class MediaSession.Callback { + method public void onSetMediaButtonEventDelegate(@NonNull android.media.session.MediaSessionEngine.MediaButtonEventDelegate); + } + + public final class MediaSessionEngine implements java.lang.AutoCloseable { + ctor public MediaSessionEngine(@NonNull android.content.Context, @NonNull android.media.session.SessionLink, @NonNull android.media.session.SessionCallbackLink, @NonNull android.media.session.MediaSessionEngine.CallbackStub, int); + method public void close(); + method public String getCallingPackage(); + method @NonNull public android.media.session.MediaController getController(); + method @NonNull public android.media.session.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo(); + method @NonNull public android.media.session.MediaSession.Token getSessionToken(); + method public boolean isActive(); + method public static boolean isActiveState(int); + method public void sendSessionEvent(@NonNull String, @Nullable android.os.Bundle); + method public void setActive(boolean); + method public void setCallback(@Nullable android.media.session.MediaSession.Callback); + method public void setCallback(@Nullable android.media.session.MediaSession.Callback, @NonNull android.os.Handler); + method public void setExtras(@Nullable android.os.Bundle); + method public void setFlags(int); + method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent); + method public void setMetadata(@Nullable android.media.MediaMetadata); + method public void setPlaybackState(@Nullable android.media.session.PlaybackState); + method public void setPlaybackToLocal(android.media.AudioAttributes); + method public void setPlaybackToRemote(@NonNull android.media.VolumeProvider); + method public void setQueue(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>); + method public void setQueueTitle(@Nullable CharSequence); + method public void setRatingType(int); + method public void setSessionActivity(@Nullable android.app.PendingIntent); + } + + public static final class MediaSessionEngine.CallbackStub { + ctor public MediaSessionEngine.CallbackStub(); + method public void onAdjustVolume(String, int, int, android.media.session.ControllerCallbackLink, int); + method public void onCommand(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle, android.os.ResultReceiver); + method public void onCustomAction(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle); + method public void onFastForward(String, int, int, android.media.session.ControllerCallbackLink); + method public void onMediaButton(String, int, int, android.content.Intent, int, android.os.ResultReceiver); + method public void onMediaButtonFromController(String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent); + method public void onNext(String, int, int, android.media.session.ControllerCallbackLink); + method public void onPause(String, int, int, android.media.session.ControllerCallbackLink); + method public void onPlay(String, int, int, android.media.session.ControllerCallbackLink); + method public void onPlayFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle); + method public void onPlayFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle); + method public void onPlayFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle); + method public void onPrepare(String, int, int, android.media.session.ControllerCallbackLink); + method public void onPrepareFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle); + method public void onPrepareFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle); + method public void onPrepareFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle); + method public void onPrevious(String, int, int, android.media.session.ControllerCallbackLink); + method public void onRate(String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating); + method public void onRewind(String, int, int, android.media.session.ControllerCallbackLink); + method public void onSeekTo(String, int, int, android.media.session.ControllerCallbackLink, long); + method public void onSetVolumeTo(String, int, int, android.media.session.ControllerCallbackLink, int); + method public void onSkipToTrack(String, int, int, android.media.session.ControllerCallbackLink, long); + method public void onStop(String, int, int, android.media.session.ControllerCallbackLink); + } + + public static interface MediaSessionEngine.MediaButtonEventDelegate { + method public boolean onMediaButtonIntent(android.content.Intent); + } + + public static final class MediaSessionEngine.QueueItem { + ctor public MediaSessionEngine.QueueItem(android.media.MediaDescription, long); + ctor public MediaSessionEngine.QueueItem(android.os.Parcel); + method public android.media.MediaDescription getDescription(); + method public long getQueueId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int UNKNOWN_ID = -1; // 0xffffffff + } + public final class MediaSessionManager { method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler); method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler); @@ -3895,12 +4000,15 @@ package android.metrics { package android.net { public class CaptivePortal implements android.os.Parcelable { + ctor public CaptivePortal(android.os.IBinder); + method public void useNetwork(); field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 } public class ConnectivityManager { + method public boolean getAvoidBadWifi(); method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean); @@ -3962,14 +4070,30 @@ package android.net { public final class LinkProperties implements android.os.Parcelable { ctor public LinkProperties(); + method public boolean addDnsServer(java.net.InetAddress); method public boolean addRoute(android.net.RouteInfo); method public void clear(); + method public String getTcpBufferSizes(); + method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); + method public boolean hasGlobalIPv6Address(); + method public boolean hasIPv4Address(); + method public boolean hasIPv6DefaultRoute(); + method public boolean isIPv4Provisioned(); + method public boolean isIPv6Provisioned(); + method public boolean isProvisioned(); + method public boolean isReachable(java.net.InetAddress); + method public boolean removeDnsServer(java.net.InetAddress); + method public boolean removeRoute(android.net.RouteInfo); method public void setDnsServers(java.util.Collection<java.net.InetAddress>); method public void setDomains(String); method public void setHttpProxy(android.net.ProxyInfo); method public void setInterfaceName(String); method public void setLinkAddresses(java.util.Collection<android.net.LinkAddress>); method public void setMtu(int); + method public void setPrivateDnsServerName(@Nullable String); + method public void setTcpBufferSizes(String); + method public void setUsePrivateDns(boolean); + method public void setValidatedPrivateDnsServers(java.util.Collection<java.net.InetAddress>); } public class Network implements android.os.Parcelable { @@ -3978,6 +4102,8 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { method public int getSignalStrength(); + method public int[] getTransportTypes(); + method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities); field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 } @@ -4153,6 +4279,7 @@ package android.net.metrics { public class IpConnectivityLog { method public boolean log(long, android.net.metrics.IpConnectivityLog.Event); method public boolean log(String, android.net.metrics.IpConnectivityLog.Event); + method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event); method public boolean log(int, int[], android.net.metrics.IpConnectivityLog.Event); method public boolean log(android.net.metrics.IpConnectivityLog.Event); } @@ -4220,6 +4347,17 @@ package android.net.metrics { } +package android.net.util { + + public class SocketUtils { + method public static void bindSocketToInterface(java.io.FileDescriptor, String) throws android.system.ErrnoException; + method public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); + method public static java.net.SocketAddress makePacketSocketAddress(short, int); + method public static java.net.SocketAddress makePacketSocketAddress(int, byte[]); + } + +} + package android.net.wifi { public abstract class EasyConnectStatusCallback { @@ -4461,7 +4599,6 @@ package android.net.wifi { method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(int, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, android.net.wifi.WifiManager.ActionListener); - method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disableEphemeralNetwork(String); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void forget(int, android.net.wifi.WifiManager.ActionListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>); @@ -4695,7 +4832,7 @@ package android.net.wifi.hotspot2 { method public abstract void onProvisioningStatus(int); field public static final int OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION = 22; // 0x16 field public static final int OSU_FAILURE_AP_CONNECTION = 1; // 0x1 - field public static final int OSU_FAILURE_INVALID_SERVER_URL = 8; // 0x8 + field public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8; // 0x8 field public static final int OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE = 17; // 0x11 field public static final int OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE = 21; // 0x15 field public static final int OSU_FAILURE_NO_OSU_ACTIVITY_FOUND = 14; // 0xe @@ -5053,6 +5190,12 @@ package android.os { method public void onResult(android.os.Bundle); } + public class ServiceSpecificException extends java.lang.RuntimeException { + ctor public ServiceSpecificException(int, String); + ctor public ServiceSpecificException(int); + field public final int errorCode; + } + public final class StatsDimensionsValue implements android.os.Parcelable { method public int describeContents(); method public boolean getBooleanValue(); @@ -5406,6 +5549,7 @@ package android.provider { field public static final String NAMESPACE_GAME_DRIVER = "game_driver"; field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot"; field public static final String NAMESPACE_NETD_NATIVE = "netd_native"; + field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant"; } public static interface DeviceConfig.OnPropertyChangedListener { @@ -6047,12 +6191,15 @@ package android.service.euicc { method public abstract int onSwitchToSubscription(int, @Nullable String, boolean); method public abstract int onUpdateSubscriptionNickname(int, String, String); field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE"; + field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE"; field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM"; field public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES"; field public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS = "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS"; + field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI"; field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService"; field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES"; @@ -6630,6 +6777,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging(); method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.WRITE_SECURE_SETTINGS}) public boolean setDefaultDialer(String); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(android.telecom.PhoneAccountHandle); field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT"; field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT"; field public static final String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE"; @@ -6714,42 +6862,211 @@ package android.telephony { } public final class DataFailCause { + field public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 2219; // 0x8ab + field public static final int ACCESS_BLOCK = 2087; // 0x827 + field public static final int ACCESS_BLOCK_ALL = 2088; // 0x828 + field public static final int ACCESS_CLASS_DSAC_REJECTION = 2108; // 0x83c + field public static final int ACCESS_CONTROL_LIST_CHECK_FAILURE = 2128; // 0x850 + field public static final int ACTIVATION_REJECTED_BCM_VIOLATION = 48; // 0x30 field public static final int ACTIVATION_REJECT_GGSN = 30; // 0x1e field public static final int ACTIVATION_REJECT_UNSPECIFIED = 31; // 0x1f field public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 65; // 0x41 + field public static final int APN_DISABLED = 2045; // 0x7fd + field public static final int APN_DISALLOWED_ON_ROAMING = 2059; // 0x80b + field public static final int APN_MISMATCH = 2054; // 0x806 + field public static final int APN_PARAMETERS_CHANGED = 2060; // 0x80c + field public static final int APN_PENDING_HANDOVER = 2041; // 0x7f9 field public static final int APN_TYPE_CONFLICT = 112; // 0x70 field public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 122; // 0x7a + field public static final int BEARER_HANDLING_NOT_SUPPORTED = 60; // 0x3c + field public static final int CALL_DISALLOWED_IN_ROAMING = 2068; // 0x814 + field public static final int CALL_PREEMPT_BY_EMERGENCY_APN = 127; // 0x7f + field public static final int CANNOT_ENCODE_OTA_MESSAGE = 2159; // 0x86f + field public static final int CDMA_ALERT_STOP = 2077; // 0x81d + field public static final int CDMA_INCOMING_CALL = 2076; // 0x81c + field public static final int CDMA_INTERCEPT = 2073; // 0x819 + field public static final int CDMA_LOCK = 2072; // 0x818 + field public static final int CDMA_RELEASE_DUE_TO_SO_REJECTION = 2075; // 0x81b + field public static final int CDMA_REORDER = 2074; // 0x81a + field public static final int CDMA_RETRY_ORDER = 2086; // 0x826 + field public static final int CHANNEL_ACQUISITION_FAILURE = 2078; // 0x81e + field public static final int CLOSE_IN_PROGRESS = 2030; // 0x7ee + field public static final int COLLISION_WITH_NETWORK_INITIATED_REQUEST = 56; // 0x38 field public static final int COMPANION_IFACE_IN_USE = 118; // 0x76 + field public static final int CONCURRENT_SERVICES_INCOMPATIBLE = 2083; // 0x823 + field public static final int CONCURRENT_SERVICES_NOT_ALLOWED = 2091; // 0x82b + field public static final int CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 2080; // 0x820 field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64 + field public static final int CONGESTION = 2106; // 0x83a + field public static final int CONNECTION_RELEASED = 2113; // 0x841 + field public static final int CS_DOMAIN_NOT_AVAILABLE = 2181; // 0x885 + field public static final int CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 2188; // 0x88c + field public static final int DATA_PLAN_EXPIRED = 2198; // 0x896 + field public static final int DATA_ROAMING_SETTINGS_DISABLED = 2064; // 0x810 + field public static final int DATA_SETTINGS_DISABLED = 2063; // 0x80f + field public static final int DBM_OR_SMS_IN_PROGRESS = 2211; // 0x8a3 + field public static final int DDS_SWITCHED = 2065; // 0x811 + field public static final int DDS_SWITCH_IN_PROGRESS = 2067; // 0x813 + field public static final int DRB_RELEASED_BY_RRC = 2112; // 0x840 + field public static final int DS_EXPLICIT_DEACTIVATION = 2125; // 0x84d + field public static final int DUAL_SWITCH = 2227; // 0x8b3 + field public static final int DUN_CALL_DISALLOWED = 2056; // 0x808 + field public static final int DUPLICATE_BEARER_ID = 2118; // 0x846 + field public static final int EHRPD_TO_HRPD_FALLBACK = 2049; // 0x801 + field public static final int EMBMS_NOT_ENABLED = 2193; // 0x891 + field public static final int EMBMS_REGULAR_DEACTIVATION = 2195; // 0x893 field public static final int EMERGENCY_IFACE_ONLY = 116; // 0x74 + field public static final int EMERGENCY_MODE = 2221; // 0x8ad field public static final int EMM_ACCESS_BARRED = 115; // 0x73 field public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 121; // 0x79 + field public static final int EMM_ATTACH_FAILED = 2115; // 0x843 + field public static final int EMM_ATTACH_STARTED = 2116; // 0x844 + field public static final int EMM_DETACHED = 2114; // 0x842 + field public static final int EMM_T3417_EXPIRED = 2130; // 0x852 + field public static final int EMM_T3417_EXT_EXPIRED = 2131; // 0x853 + field public static final int EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 2178; // 0x882 + field public static final int EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 2179; // 0x883 field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff + field public static final int ESM_BAD_OTA_MESSAGE = 2122; // 0x84a + field public static final int ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 2120; // 0x848 + field public static final int ESM_COLLISION_SCENARIOS = 2119; // 0x847 + field public static final int ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 2124; // 0x84c + field public static final int ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 2123; // 0x84b + field public static final int ESM_FAILURE = 2182; // 0x886 field public static final int ESM_INFO_NOT_RECEIVED = 53; // 0x35 + field public static final int ESM_LOCAL_CAUSE_NONE = 2126; // 0x84e + field public static final int ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 2121; // 0x849 + field public static final int ESM_PROCEDURE_TIME_OUT = 2155; // 0x86b + field public static final int ESM_UNKNOWN_EPS_BEARER_CONTEXT = 2111; // 0x83f + field public static final int EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 2201; // 0x899 + field public static final int EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 2200; // 0x898 + field public static final int EVDO_HDR_CHANGED = 2202; // 0x89a + field public static final int EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 2206; // 0x89e + field public static final int EVDO_HDR_EXITED = 2203; // 0x89b + field public static final int EVDO_HDR_NO_SESSION = 2204; // 0x89c + field public static final int EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 2205; // 0x89d + field public static final int FADE = 2217; // 0x8a9 + field public static final int FAILED_TO_ACQUIRE_COLOCATED_HDR = 2207; // 0x89f field public static final int FEATURE_NOT_SUPP = 40; // 0x28 field public static final int FILTER_SEMANTIC_ERROR = 44; // 0x2c field public static final int FILTER_SYTAX_ERROR = 45; // 0x2d + field public static final int FORBIDDEN_APN_NAME = 2066; // 0x812 field public static final int GPRS_REGISTRATION_FAIL = -2; // 0xfffffffe + field public static final int GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 2097; // 0x831 + field public static final int GPRS_SERVICES_NOT_ALLOWED = 2098; // 0x832 + field public static final int GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 2103; // 0x837 + field public static final int HANDOFF_PREFERENCE_CHANGED = 2251; // 0x8cb + field public static final int HDR_ACCESS_FAILURE = 2213; // 0x8a5 + field public static final int HDR_FADE = 2212; // 0x8a4 + field public static final int HDR_NO_LOCK_GRANTED = 2210; // 0x8a2 field public static final int IFACE_AND_POL_FAMILY_MISMATCH = 120; // 0x78 field public static final int IFACE_MISMATCH = 117; // 0x75 + field public static final int ILLEGAL_ME = 2096; // 0x830 + field public static final int ILLEGAL_MS = 2095; // 0x82f + field public static final int IMEI_NOT_ACCEPTED = 2177; // 0x881 + field public static final int IMPLICITLY_DETACHED = 2100; // 0x834 + field public static final int IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 2176; // 0x880 + field public static final int INCOMING_CALL_REJECTED = 2092; // 0x82c field public static final int INSUFFICIENT_RESOURCES = 26; // 0x1a + field public static final int INTERFACE_IN_USE = 2058; // 0x80a field public static final int INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN = 114; // 0x72 + field public static final int INTERNAL_EPC_NONEPC_TRANSITION = 2057; // 0x809 + field public static final int INVALID_CONNECTION_ID = 2156; // 0x86c + field public static final int INVALID_DNS_ADDR = 123; // 0x7b + field public static final int INVALID_EMM_STATE = 2190; // 0x88e field public static final int INVALID_MANDATORY_INFO = 96; // 0x60 + field public static final int INVALID_MODE = 2223; // 0x8af field public static final int INVALID_PCSCF_ADDR = 113; // 0x71 + field public static final int INVALID_PCSCF_OR_DNS_ADDRESS = 124; // 0x7c + field public static final int INVALID_PRIMARY_NSAPI = 2158; // 0x86e + field public static final int INVALID_SIM_STATE = 2224; // 0x8b0 field public static final int INVALID_TRANSACTION_ID = 81; // 0x51 + field public static final int IPV6_ADDRESS_TRANSFER_FAILED = 2047; // 0x7ff + field public static final int IPV6_PREFIX_UNAVAILABLE = 2250; // 0x8ca field public static final int IP_ADDRESS_MISMATCH = 119; // 0x77 + field public static final int IP_VERSION_MISMATCH = 2055; // 0x807 + field public static final int IRAT_HANDOVER_FAILED = 2194; // 0x892 + field public static final int IS707B_MAX_ACCESS_PROBES = 2089; // 0x829 + field public static final int LIMITED_TO_IPV4 = 2234; // 0x8ba + field public static final int LIMITED_TO_IPV6 = 2235; // 0x8bb field public static final int LLC_SNDCP = 25; // 0x19 + field public static final int LOCAL_END = 2215; // 0x8a7 + field public static final int LOCATION_AREA_NOT_ALLOWED = 2102; // 0x836 field public static final int LOST_CONNECTION = 65540; // 0x10004 + field public static final int LOWER_LAYER_REGISTRATION_FAILURE = 2197; // 0x895 + field public static final int LOW_POWER_MODE_OR_POWERING_DOWN = 2044; // 0x7fc + field public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 2117; // 0x845 + field public static final int LTE_THROTTLING_NOT_REQUIRED = 2127; // 0x84f + field public static final int MAC_FAILURE = 2183; // 0x887 + field public static final int MAXIMIUM_NSAPIS_EXCEEDED = 2157; // 0x86d + field public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 2166; // 0x876 + field public static final int MAX_ACCESS_PROBE = 2079; // 0x81f + field public static final int MAX_IPV4_CONNECTIONS = 2052; // 0x804 + field public static final int MAX_IPV6_CONNECTIONS = 2053; // 0x805 + field public static final int MAX_PPP_INACTIVITY_TIMER_EXPIRED = 2046; // 0x7fe field public static final int MESSAGE_INCORRECT_SEMANTIC = 95; // 0x5f field public static final int MESSAGE_TYPE_UNSUPPORTED = 97; // 0x61 + field public static final int MIP_CONFIG_FAILURE = 2050; // 0x802 + field public static final int MIP_FA_ADMIN_PROHIBITED = 2001; // 0x7d1 + field public static final int MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 2012; // 0x7dc + field public static final int MIP_FA_ENCAPSULATION_UNAVAILABLE = 2008; // 0x7d8 + field public static final int MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 2004; // 0x7d4 + field public static final int MIP_FA_INSUFFICIENT_RESOURCES = 2002; // 0x7d2 + field public static final int MIP_FA_MALFORMED_REPLY = 2007; // 0x7d7 + field public static final int MIP_FA_MALFORMED_REQUEST = 2006; // 0x7d6 + field public static final int MIP_FA_MISSING_CHALLENGE = 2017; // 0x7e1 + field public static final int MIP_FA_MISSING_HOME_ADDRESS = 2015; // 0x7df + field public static final int MIP_FA_MISSING_HOME_AGENT = 2014; // 0x7de + field public static final int MIP_FA_MISSING_NAI = 2013; // 0x7dd + field public static final int MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2003; // 0x7d3 + field public static final int MIP_FA_REASON_UNSPECIFIED = 2000; // 0x7d0 + field public static final int MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 2005; // 0x7d5 + field public static final int MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 2011; // 0x7db + field public static final int MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 2010; // 0x7da + field public static final int MIP_FA_STALE_CHALLENGE = 2018; // 0x7e2 + field public static final int MIP_FA_UNKNOWN_CHALLENGE = 2016; // 0x7e0 + field public static final int MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 2009; // 0x7d9 + field public static final int MIP_HA_ADMIN_PROHIBITED = 2020; // 0x7e4 + field public static final int MIP_HA_ENCAPSULATION_UNAVAILABLE = 2029; // 0x7ed + field public static final int MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 2023; // 0x7e7 + field public static final int MIP_HA_INSUFFICIENT_RESOURCES = 2021; // 0x7e5 + field public static final int MIP_HA_MALFORMED_REQUEST = 2025; // 0x7e9 + field public static final int MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 2022; // 0x7e6 + field public static final int MIP_HA_REASON_UNSPECIFIED = 2019; // 0x7e3 + field public static final int MIP_HA_REGISTRATION_ID_MISMATCH = 2024; // 0x7e8 + field public static final int MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 2028; // 0x7ec + field public static final int MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 2027; // 0x7eb + field public static final int MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 2026; // 0x7ea field public static final int MISSING_UNKNOWN_APN = 27; // 0x1b + field public static final int MODEM_APP_PREEMPTED = 2032; // 0x7f0 + field public static final int MODEM_RESTART = 2037; // 0x7f5 + field public static final int MSC_TEMPORARILY_NOT_REACHABLE = 2180; // 0x884 field public static final int MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE = 101; // 0x65 field public static final int MSG_TYPE_NONCOMPATIBLE_STATE = 98; // 0x62 + field public static final int MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 2099; // 0x833 + field public static final int MULTIPLE_PDP_CALL_NOT_ALLOWED = 2192; // 0x890 field public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 55; // 0x37 + field public static final int NAS_LAYER_FAILURE = 2191; // 0x88f + field public static final int NAS_REQUEST_REJECTED_BY_NETWORK = 2167; // 0x877 field public static final int NAS_SIGNALLING = 14; // 0xe field public static final int NETWORK_FAILURE = 38; // 0x26 + field public static final int NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 2154; // 0x86a + field public static final int NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 2153; // 0x869 + field public static final int NETWORK_INITIATED_TERMINATION = 2031; // 0x7ef field public static final int NONE = 0; // 0x0 + field public static final int NON_IP_NOT_SUPPORTED = 2069; // 0x815 + field public static final int NORMAL_RELEASE = 2218; // 0x8aa + field public static final int NO_CDMA_SERVICE = 2084; // 0x824 + field public static final int NO_COLLOCATED_HDR = 2225; // 0x8b1 + field public static final int NO_EPS_BEARER_CONTEXT_ACTIVATED = 2189; // 0x88d + field public static final int NO_GPRS_CONTEXT = 2094; // 0x82e + field public static final int NO_HYBRID_HDR_SERVICE = 2209; // 0x8a1 + field public static final int NO_PDP_CONTEXT_ACTIVATED = 2107; // 0x83b + field public static final int NO_RESPONSE_FROM_BASE_STATION = 2081; // 0x821 + field public static final int NO_SERVICE = 2216; // 0x8a8 + field public static final int NO_SERVICE_ON_GATEWAY = 2093; // 0x82d field public static final int NSAPI_IN_USE = 35; // 0x23 + field public static final int NULL_APN_DISALLOWED = 2061; // 0x80d field public static final int OEM_DCFAILCAUSE_1 = 4097; // 0x1001 field public static final int OEM_DCFAILCAUSE_10 = 4106; // 0x100a field public static final int OEM_DCFAILCAUSE_11 = 4107; // 0x100b @@ -6765,33 +7082,126 @@ package android.telephony { field public static final int OEM_DCFAILCAUSE_7 = 4103; // 0x1007 field public static final int OEM_DCFAILCAUSE_8 = 4104; // 0x1008 field public static final int OEM_DCFAILCAUSE_9 = 4105; // 0x1009 + field public static final int ONLY_IPV4V6_ALLOWED = 57; // 0x39 field public static final int ONLY_IPV4_ALLOWED = 50; // 0x32 field public static final int ONLY_IPV6_ALLOWED = 51; // 0x33 + field public static final int ONLY_NON_IP_ALLOWED = 58; // 0x3a field public static final int ONLY_SINGLE_BEARER_ALLOWED = 52; // 0x34 field public static final int OPERATOR_BARRED = 8; // 0x8 + field public static final int OTASP_COMMIT_IN_PROGRESS = 2208; // 0x8a0 field public static final int PDN_CONN_DOES_NOT_EXIST = 54; // 0x36 + field public static final int PDN_INACTIVITY_TIMER_EXPIRED = 2051; // 0x803 + field public static final int PDN_IPV4_CALL_DISALLOWED = 2033; // 0x7f1 + field public static final int PDN_IPV4_CALL_THROTTLED = 2034; // 0x7f2 + field public static final int PDN_IPV6_CALL_DISALLOWED = 2035; // 0x7f3 + field public static final int PDN_IPV6_CALL_THROTTLED = 2036; // 0x7f4 + field public static final int PDN_NON_IP_CALL_DISALLOWED = 2071; // 0x817 + field public static final int PDN_NON_IP_CALL_THROTTLED = 2070; // 0x816 + field public static final int PDP_ACTIVATE_MAX_RETRY_FAILED = 2109; // 0x83d + field public static final int PDP_DUPLICATE = 2104; // 0x838 + field public static final int PDP_ESTABLISH_TIMEOUT_EXPIRED = 2161; // 0x871 + field public static final int PDP_INACTIVE_TIMEOUT_EXPIRED = 2163; // 0x873 + field public static final int PDP_LOWERLAYER_ERROR = 2164; // 0x874 + field public static final int PDP_MODIFY_COLLISION = 2165; // 0x875 + field public static final int PDP_MODIFY_TIMEOUT_EXPIRED = 2162; // 0x872 + field public static final int PDP_PPP_NOT_SUPPORTED = 2038; // 0x7f6 field public static final int PDP_WITHOUT_ACTIVE_TFT = 46; // 0x2e + field public static final int PHONE_IN_USE = 2222; // 0x8ae + field public static final int PHYSICAL_LINK_CLOSE_IN_PROGRESS = 2040; // 0x7f8 + field public static final int PLMN_NOT_ALLOWED = 2101; // 0x835 + field public static final int PPP_AUTH_FAILURE = 2229; // 0x8b5 + field public static final int PPP_CHAP_FAILURE = 2232; // 0x8b8 + field public static final int PPP_CLOSE_IN_PROGRESS = 2233; // 0x8b9 + field public static final int PPP_OPTION_MISMATCH = 2230; // 0x8b6 + field public static final int PPP_PAP_FAILURE = 2231; // 0x8b7 + field public static final int PPP_TIMEOUT = 2228; // 0x8b4 field public static final int PREF_RADIO_TECH_CHANGED = -4; // 0xfffffffc + field public static final int PROFILE_BEARER_INCOMPATIBLE = 2042; // 0x7fa field public static final int PROTOCOL_ERRORS = 111; // 0x6f field public static final int QOS_NOT_ACCEPTED = 37; // 0x25 + field public static final int RADIO_ACCESS_BEARER_FAILURE = 2110; // 0x83e + field public static final int RADIO_ACCESS_BEARER_SETUP_FAILURE = 2160; // 0x870 field public static final int RADIO_NOT_AVAILABLE = 65537; // 0x10001 field public static final int RADIO_POWER_OFF = -5; // 0xfffffffb + field public static final int REDIRECTION_OR_HANDOFF_IN_PROGRESS = 2220; // 0x8ac field public static final int REGISTRATION_FAIL = -1; // 0xffffffff field public static final int REGULAR_DEACTIVATION = 36; // 0x24 + field public static final int REJECTED_BY_BASE_STATION = 2082; // 0x822 + field public static final int RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 2173; // 0x87d + field public static final int RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 2174; // 0x87e + field public static final int RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 2171; // 0x87b + field public static final int RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 2175; // 0x87f + field public static final int RRC_CONNECTION_ABORT_REQUEST = 2151; // 0x867 + field public static final int RRC_CONNECTION_ACCESS_BARRED = 2139; // 0x85b + field public static final int RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 2137; // 0x859 + field public static final int RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 2138; // 0x85a + field public static final int RRC_CONNECTION_CELL_NOT_CAMPED = 2144; // 0x860 + field public static final int RRC_CONNECTION_CELL_RESELECTION = 2140; // 0x85c + field public static final int RRC_CONNECTION_CONFIG_FAILURE = 2141; // 0x85d + field public static final int RRC_CONNECTION_INVALID_REQUEST = 2168; // 0x878 + field public static final int RRC_CONNECTION_LINK_FAILURE = 2143; // 0x85f + field public static final int RRC_CONNECTION_NORMAL_RELEASE = 2147; // 0x863 + field public static final int RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 2150; // 0x866 + field public static final int RRC_CONNECTION_RADIO_LINK_FAILURE = 2148; // 0x864 + field public static final int RRC_CONNECTION_REESTABLISHMENT_FAILURE = 2149; // 0x865 + field public static final int RRC_CONNECTION_REJECT_BY_NETWORK = 2146; // 0x862 + field public static final int RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 2172; // 0x87c + field public static final int RRC_CONNECTION_RF_UNAVAILABLE = 2170; // 0x87a + field public static final int RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 2152; // 0x868 + field public static final int RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 2145; // 0x861 + field public static final int RRC_CONNECTION_TIMER_EXPIRED = 2142; // 0x85e + field public static final int RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 2169; // 0x879 + field public static final int RRC_UPLINK_CONNECTION_RELEASE = 2134; // 0x856 + field public static final int RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 2132; // 0x854 + field public static final int RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 2133; // 0x855 + field public static final int RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 2136; // 0x858 + field public static final int RRC_UPLINK_RADIO_LINK_FAILURE = 2135; // 0x857 + field public static final int RUIM_NOT_PRESENT = 2085; // 0x825 + field public static final int SECURITY_MODE_REJECTED = 2186; // 0x88a + field public static final int SERVICE_NOT_ALLOWED_ON_PLMN = 2129; // 0x851 field public static final int SERVICE_OPTION_NOT_SUBSCRIBED = 33; // 0x21 field public static final int SERVICE_OPTION_NOT_SUPPORTED = 32; // 0x20 field public static final int SERVICE_OPTION_OUT_OF_ORDER = 34; // 0x22 field public static final int SIGNAL_LOST = -3; // 0xfffffffd + field public static final int SIM_CARD_CHANGED = 2043; // 0x7fb + field public static final int SYNCHRONIZATION_FAILURE = 2184; // 0x888 + field public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 2196; // 0x894 field public static final int TETHERED_CALL_ACTIVE = -6; // 0xfffffffa field public static final int TFT_SEMANTIC_ERROR = 41; // 0x29 field public static final int TFT_SYTAX_ERROR = 42; // 0x2a + field public static final int THERMAL_EMERGENCY = 2090; // 0x82a + field public static final int THERMAL_MITIGATION = 2062; // 0x80e + field public static final int TRAT_SWAP_FAILED = 2048; // 0x800 + field public static final int UE_INITIATED_DETACH_OR_DISCONNECT = 128; // 0x80 + field public static final int UE_IS_ENTERING_POWERSAVE_MODE = 2226; // 0x8b2 + field public static final int UE_RAT_CHANGE = 2105; // 0x839 + field public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 2185; // 0x889 + field public static final int UMTS_HANDOVER_TO_IWLAN = 2199; // 0x897 field public static final int UMTS_REACTIVATION_REQ = 39; // 0x27 + field public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 2187; // 0x88b field public static final int UNKNOWN = 65536; // 0x10000 field public static final int UNKNOWN_INFO_ELEMENT = 99; // 0x63 field public static final int UNKNOWN_PDP_ADDRESS_TYPE = 28; // 0x1c field public static final int UNKNOWN_PDP_CONTEXT = 43; // 0x2b + field public static final int UNPREFERRED_RAT = 2039; // 0x7f7 + field public static final int UNSUPPORTED_1X_PREV = 2214; // 0x8a6 field public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 66; // 0x42 + field public static final int UNSUPPORTED_QCI_VALUE = 59; // 0x3b field public static final int USER_AUTHENTICATION = 29; // 0x1d + field public static final int VSNCP_ADMINISTRATIVELY_PROHIBITED = 2245; // 0x8c5 + field public static final int VSNCP_APN_UNATHORIZED = 2238; // 0x8be + field public static final int VSNCP_GEN_ERROR = 2237; // 0x8bd + field public static final int VSNCP_INSUFFICIENT_PARAMETERS = 2243; // 0x8c3 + field public static final int VSNCP_NO_PDN_GATEWAY_ADDRESS = 2240; // 0x8c0 + field public static final int VSNCP_PDN_EXISTS_FOR_THIS_APN = 2248; // 0x8c8 + field public static final int VSNCP_PDN_GATEWAY_REJECT = 2242; // 0x8c2 + field public static final int VSNCP_PDN_GATEWAY_UNREACHABLE = 2241; // 0x8c1 + field public static final int VSNCP_PDN_ID_IN_USE = 2246; // 0x8c6 + field public static final int VSNCP_PDN_LIMIT_EXCEEDED = 2239; // 0x8bf + field public static final int VSNCP_RECONNECT_NOT_ALLOWED = 2249; // 0x8c9 + field public static final int VSNCP_RESOURCE_UNAVAILABLE = 2244; // 0x8c4 + field public static final int VSNCP_SUBSCRIBER_LIMITATION = 2247; // 0x8c7 + field public static final int VSNCP_TIMEOUT = 2236; // 0x8bc } public class DisconnectCause { @@ -7533,9 +7943,12 @@ package android.telephony.euicc { method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus(); + field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED"; field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION"; field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2 field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1 field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3 @@ -7546,7 +7959,10 @@ package android.telephony.euicc { field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3 field public static final String EXTRA_ACTIVATION_TYPE = "android.telephony.euicc.extra.ACTIVATION_TYPE"; field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS"; + field public static final String EXTRA_ENABLE_SUBSCRIPTION = "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION"; field public static final String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION"; + field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.euicc.extra.SUBSCRIPTION_ID"; + field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME"; } @IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus { @@ -8601,6 +9017,22 @@ package android.telephony.mbms.vendor { package android.util { + public class DocumentsStatsLog { + method public static void logActivityLaunch(int, boolean, int, int); + method public static void logFileOperation(int, int); + method public static void logFileOperationCanceled(int); + method public static void logFileOperationCopyMoveMode(int, int); + method public static void logFileOperationFailure(int, int); + method public static void logFilePick(int, long, int, boolean, int, int, int); + method public static void logInvalidScopedAccessRequest(int); + method public static void logPickerLaunchedFrom(String); + method public static void logRootVisited(int, int); + method public static void logSearchMode(int); + method public static void logSearchType(int); + method public static void logStartupMs(int); + method public static void logUserAction(int); + } + public class EventLog { method public static void readEventsOnWrapping(int[], long, java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException; } @@ -9096,6 +9528,10 @@ package android.webkit { field public final android.content.pm.Signature[] signatures; } + public abstract class WebViewRenderer { + ctor public WebViewRenderer(); + } + public final class WebViewUpdateService { method public static android.webkit.WebViewProviderInfo[] getAllWebViewPackages(); method public static String getCurrentWebViewPackageName(); diff --git a/api/test-current.txt b/api/test-current.txt index 0d88937f6b7a..eca7a7a54edb 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -772,6 +772,11 @@ package android.media.audiofx { field public static final java.util.UUID EFFECT_TYPE_NULL; } + public static class AudioEffect.Descriptor { + ctor public AudioEffect.Descriptor(android.os.Parcel); + method public void writeToParcel(android.os.Parcel); + } + public static interface AudioEffect.OnParameterChangeListener { method public void onParameterChange(android.media.audiofx.AudioEffect, int, byte[], byte[]); } @@ -781,6 +786,8 @@ package android.media.audiofx { package android.net { public class CaptivePortal implements android.os.Parcelable { + ctor public CaptivePortal(android.os.IBinder); + method public void useNetwork(); field public static final int APP_RETURN_DISMISSED = 0; // 0x0 field public static final int APP_RETURN_UNWANTED = 1; // 0x1 field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 @@ -802,6 +809,25 @@ package android.net { method public boolean isSameAddressAs(android.net.LinkAddress); } + public final class LinkProperties implements android.os.Parcelable { + method public boolean addDnsServer(java.net.InetAddress); + method public String getTcpBufferSizes(); + method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); + method public boolean hasGlobalIPv6Address(); + method public boolean hasIPv4Address(); + method public boolean hasIPv6DefaultRoute(); + method public boolean isIPv4Provisioned(); + method public boolean isIPv6Provisioned(); + method public boolean isProvisioned(); + method public boolean isReachable(java.net.InetAddress); + method public boolean removeDnsServer(java.net.InetAddress); + method public boolean removeRoute(android.net.RouteInfo); + method public void setPrivateDnsServerName(@Nullable String); + method public void setTcpBufferSizes(String); + method public void setUsePrivateDns(boolean); + method public void setValidatedPrivateDnsServers(java.util.Collection<java.net.InetAddress>); + } + public class Network implements android.os.Parcelable { method public android.net.Network getPrivateDnsBypassingCopy(); } @@ -809,6 +835,7 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { method public int[] getCapabilities(); method public int[] getTransportTypes(); + method public boolean satisfiedByNetworkCapabilities(android.net.NetworkCapabilities); } public final class RouteInfo implements android.os.Parcelable { @@ -900,6 +927,7 @@ package android.net.metrics { public class IpConnectivityLog { method public boolean log(long, android.net.metrics.IpConnectivityLog.Event); method public boolean log(String, android.net.metrics.IpConnectivityLog.Event); + method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event); method public boolean log(int, int[], android.net.metrics.IpConnectivityLog.Event); method public boolean log(android.net.metrics.IpConnectivityLog.Event); } diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index f8825ac94b65..1942b2e94f51 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -194,6 +194,16 @@ message Atom { GnssNfwNotificationReported gnss_nfw_notification_reported = 131; GnssConfigurationReported gnss_configuration_reported = 132; UsbPortOverheatEvent usb_port_overheat_event_reported = 133; + NfcErrorOccurred nfc_error_occurred = 134; + NfcStateChanged nfc_state_changed = 135; + NfcBeamOccurred nfc_beam_occurred = 136; + NfcCardemulationOccurred nfc_cardemulation_occurred = 137; + NfcTagOccurred nfc_tag_occurred = 138; + NfcHceTransactionOccurred nfc_hce_transaction_occurred = 139; + SeStateChanged se_state_changed = 140; + SeOmapiReported se_omapi_reported = 141; + BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142; + AttentionManagerServiceResultReported attention_manager_service_result_reported = 143; } // Pulled events will start at field 10000. @@ -244,6 +254,7 @@ message Atom { ProcessMemoryHighWaterMark process_memory_high_water_mark = 10042; BatteryLevel battery_level = 10043; BuildInformation build_information = 10044; + BatteryCycleCount battery_cycle_count = 10045; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -2514,6 +2525,19 @@ message UsbPortOverheatEvent { optional int32 time_to_inactive_secs = 5; }; +/** + * Logs total effective full charge and discharge cycles on a battery. + * Here are some examples of one effective cycle: + * 1) the battery charges from 0% to 100% and drains back to 0%, + * 2) charging from 50% to 100% and draining back to 50% twice. + * Pulled from: + * frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp + */ +message BatteryCycleCount { + /* Number of total charge and discharge cycles on the system battery. */ + optional int32 cycle_count = 1; +} + /* * Logs when a connection becomes available and lost. * Logged in StatsCompanionService.java @@ -4309,3 +4333,167 @@ message GnssConfigurationReported { // with spaces as separators. optional string enabled_proxy_app_package_name_list = 13; } + +/** + * Logs when a NFC device's error occurred. + * Logged from: + * system/nfc/src/nfc/nfc/nfc_ncif.cc + * packages/apps/Nfc/src/com/android/nfc/cardemulation/AidRoutingManager.java + */ +message NfcErrorOccurred { + enum Type { + UNKNOWN = 0; + CMD_TIMEOUT = 1; + ERROR_NOTIFICATION = 2; + AID_OVERFLOW = 3; + } + optional Type type = 1; + // If it's nci cmd timeout, log the timeout command. + optional uint32 nci_cmd = 2; + + optional uint32 error_ntf_status_code = 3; +} + +/** + * Logs when a NFC device's state changed event + * Logged from: + * packages/apps/Nfc/src/com/android/nfc/NfcService.java + */ +message NfcStateChanged { + enum State { + UNKNOWN = 0; + OFF = 1; + ON = 2; + ON_LOCKED = 3; // Secure Nfc enabled. + CRASH_RESTART = 4; // NfcService watchdog timeout restart. + } + optional State state = 1; +} + +/** + * Logs when a NFC Beam Transaction occurred. + * Logged from: + * packages/apps/Nfc/src/com/android/nfc/P2pLinkManager.java + */ +message NfcBeamOccurred { + enum Operation { + UNKNOWN = 0; + SEND = 1; + RECEIVE = 2; + } + optional Operation operation = 1; +} + +/** + * Logs when a NFC Card Emulation Transaction occurred. + * Logged from: + * packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java + * packages/apps/Nfc/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java + */ +message NfcCardemulationOccurred { + enum Category { + UNKNOWN = 0; + HCE_PAYMENT = 1; + HCE_OTHER = 2; + OFFHOST = 3; + } + // Transaction belongs to HCE payment or HCE other category, or offhost. + optional Category category = 1; + // SeName from transaction: SIMx, eSEx, HCE, HCEF. + optional string se_name = 2; +} + +/** + * Logs when a NFC Tag event occurred. + * Logged from: + * packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java + */ +message NfcTagOccurred { + enum Type { + UNKNOWN = 0; + URL = 1; + BT_PAIRING = 2; + PROVISION = 3; + WIFI_CONNECT = 4; + APP_LAUNCH = 5; + OTHERS = 6; + } + optional Type type = 1; +} + +/** + * Logs when Hce transaction triggered + * Logged from: + * system/nfc/src/nfc/nfc/nfc_ncif.cc + */ +message NfcHceTransactionOccurred { + // The latency period(in microseconds) it took for the first HCE data + // exchange. + optional uint32 latency_micros = 1; +} + +/** + * Logs when SecureElement state event changed + * Logged from: + * packages/apps/SecureElement/src/com/android/se/Terminal.java + */ +message SeStateChanged { + enum State { + UNKNOWN = 0; + INITIALIZED = 1; + DISCONNECTED = 2; + CONNECTED = 3; + HALCRASH = 4; + } + optional State state = 1; + + optional string state_change_reason = 2; + // SIMx or eSEx. + optional string terminal = 3; +} + +/** + * Logs when Omapi API used + * Logged from: + * packages/apps/SecureElement/src/com/android/se/Terminal.java + */ +message SeOmapiReported { + enum Operation { + UNKNOWN = 0; + OPEN_CHANNEL = 1; + } + optional Operation operation = 1; + // SIMx or eSEx. + optional string terminal = 2; + + optional string packageName = 3; +} + +/** + * Logs the dispatch latency of a broadcast during processing of BOOT_COMPLETED. + * The dispatch latency is the dispatchClockTime - enqueueClockTime. + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java + */ +message BroadcastDispatchLatencyReported { + optional int64 dispatch_latency_millis = 1; +} + +/** + * Logs AttentionManagerService attention check result. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/attention/AttentionManagerService.java + */ +message AttentionManagerServiceResultReported { + // See core/java/android/service/attention/AttentionService.java + enum AttentionCheckResult { + UNKNOWN = 20; + ATTENTION_SUCCESS_ABSENT = 0; + ATTENTION_SUCCESS_PRESENT = 1; + ATTENTION_FAILURE_PREEMPTED = 2; + ATTENTION_FAILURE_TIMED_OUT = 3; + ATTENTION_FAILURE_UNKNOWN = 4; + } + optional AttentionCheckResult attention_check_result = 1 [default = UNKNOWN]; +} diff --git a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp index b878652f5cf1..75b63f4b5f9e 100644 --- a/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp +++ b/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp @@ -24,16 +24,16 @@ #include "ResourceHealthManagerPuller.h" #include "logd/LogEvent.h" -#include "statslog.h" #include "stats_log_util.h" +#include "statslog.h" using android::hardware::hidl_vec; +using android::hardware::Return; +using android::hardware::Void; using android::hardware::health::V2_0::get_health_service; using android::hardware::health::V2_0::HealthInfo; using android::hardware::health::V2_0::IHealth; using android::hardware::health::V2_0::Result; -using android::hardware::Return; -using android::hardware::Void; using std::make_shared; using std::shared_ptr; @@ -75,35 +75,41 @@ bool ResourceHealthManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* dat } if (mTagId == android::util::REMAINING_BATTERY_CAPACITY) { auto ptr = make_shared<LogEvent>(android::util::REMAINING_BATTERY_CAPACITY, - wallClockTimestampNs, elapsedTimestampNs); + wallClockTimestampNs, elapsedTimestampNs); ptr->write(v.legacy.batteryChargeCounter); ptr->init(); data->push_back(ptr); } else if (mTagId == android::util::FULL_BATTERY_CAPACITY) { auto ptr = make_shared<LogEvent>(android::util::FULL_BATTERY_CAPACITY, - wallClockTimestampNs, elapsedTimestampNs); + wallClockTimestampNs, elapsedTimestampNs); ptr->write(v.legacy.batteryFullCharge); ptr->init(); data->push_back(ptr); } else if (mTagId == android::util::BATTERY_VOLTAGE) { - auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE, - wallClockTimestampNs, elapsedTimestampNs); + auto ptr = make_shared<LogEvent>(android::util::BATTERY_VOLTAGE, wallClockTimestampNs, + elapsedTimestampNs); ptr->write(v.legacy.batteryVoltage); ptr->init(); data->push_back(ptr); } else if (mTagId == android::util::BATTERY_LEVEL) { - auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL, - wallClockTimestampNs, elapsedTimestampNs); - ptr->write(v.legacy.batteryLevel); - ptr->init(); - data->push_back(ptr); + auto ptr = make_shared<LogEvent>(android::util::BATTERY_LEVEL, wallClockTimestampNs, + elapsedTimestampNs); + ptr->write(v.legacy.batteryLevel); + ptr->init(); + data->push_back(ptr); + } else if (mTagId == android::util::BATTERY_CYCLE_COUNT) { + auto ptr = make_shared<LogEvent>(android::util::BATTERY_CYCLE_COUNT, + wallClockTimestampNs, elapsedTimestampNs); + ptr->write(v.legacy.batteryCycleCount); + ptr->init(); + data->push_back(ptr); } else { ALOGE("Unsupported tag in ResourceHealthManagerPuller: %d", mTagId); } }); if (!result_success || !ret.isOk()) { ALOGE("getHealthHal() failed: health HAL service not available. Description: %s", - ret.description().c_str()); + ret.description().c_str()); if (!ret.isOk() && ret.isDeadObject()) { gHealthHal = nullptr; } diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 4a716cf3fa77..19a7389cb3d9 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -131,9 +131,12 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { // battery_voltage {android::util::BATTERY_VOLTAGE, {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}}, - // battery_voltage + // battery_level {android::util::BATTERY_LEVEL, {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}}, + // battery_cycle_count + {android::util::BATTERY_CYCLE_COUNT, + {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}}, // process_memory_state {android::util::PROCESS_MEMORY_STATE, {.additiveFields = {4, 5, 6, 7, 8, 9}, diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index 4174ad7cd586..1b7fbfe0e32c 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -23,8 +23,10 @@ import android.os.IUserManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.telecom.Log; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; +import android.text.TextUtils; import com.android.internal.os.BaseCommand; import com.android.internal.telecom.ITelecomService; @@ -45,6 +47,8 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled"; private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled"; private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account"; + private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT = + "set-user-selected-outgoing-phone-account"; private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account"; private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app"; private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app"; @@ -70,6 +74,8 @@ public final class Telecom extends BaseCommand { + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n" + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n" + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n" + + "usage: telecom set-user-selected-outgoing-phone-account <COMPONENT> <ID> " + + "<USER_SN>\n" + "usage: telecom set-test-call-redirection-app <PACKAGE>\n" + "usage: telecom set-test-call-screening-app <PACKAGE>\n" + "usage: telecom set-test-auto-mode-app <PACKAGE>\n" @@ -104,16 +110,18 @@ public final class Telecom extends BaseCommand { mTelecomService = ITelecomService.Stub.asInterface( ServiceManager.getService(Context.TELECOM_SERVICE)); if (mTelecomService == null) { + Log.w(this, "onRun: Can't access telecom manager."); showError("Error: Could not access the Telecom Manager. Is the system running?"); return; } mUserManager = IUserManager.Stub .asInterface(ServiceManager.getService(Context.USER_SERVICE)); if (mUserManager == null) { + Log.w(this, "onRun: Can't access user manager."); showError("Error: Could not access the User Manager. Is the system running?"); return; } - + Log.i(this, "onRun: parsing command."); String command = nextArgRequired(); switch (command) { case COMMAND_SET_PHONE_ACCOUNT_ENABLED: @@ -143,6 +151,9 @@ public final class Telecom extends BaseCommand { case COMMAND_REGISTER_SIM_PHONE_ACCOUNT: runRegisterSimPhoneAccount(); break; + case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT: + runSetUserSelectedOutgoingPhoneAccount(); + break; case COMMAND_UNREGISTER_PHONE_ACCOUNT: runUnregisterPhoneAccount(); break; @@ -159,6 +170,7 @@ public final class Telecom extends BaseCommand { runWaitOnHandler(); break; default: + Log.w(this, "onRun: unknown command: %s", command); throw new IllegalArgumentException ("unknown command '" + command + "'"); } } @@ -227,6 +239,13 @@ public final class Telecom extends BaseCommand { mTelecomService.setTestPhoneAcctSuggestionComponent(componentName); } + private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException { + Log.i(this, "runSetUserSelectedOutgoingPhoneAccount"); + final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); + mTelecomService.setUserSelectedOutgoingPhoneAccount(handle); + System.out.println("Success - " + handle + " set as default outgoing account."); + } + private void runUnregisterPhoneAccount() throws RemoteException { final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs(); mTelecomService.unregisterPhoneAccount(handle); @@ -256,7 +275,10 @@ public final class Telecom extends BaseCommand { } - private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException{ + private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException { + if (TextUtils.isEmpty(mArgs.peekNextArg())) { + return null; + } final ComponentName component = parseComponentName(nextArgRequired()); final String accountId = nextArgRequired(); final String userSnInStr = nextArgRequired(); @@ -265,6 +287,7 @@ public final class Telecom extends BaseCommand { final int userSn = Integer.parseInt(userSnInStr); userHandle = UserHandle.of(mUserManager.getUserHandle(userSn)); } catch (NumberFormatException ex) { + Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr); throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr); } return new PhoneAccountHandle(component, accountId, userHandle); @@ -277,4 +300,4 @@ public final class Telecom extends BaseCommand { } return cn; } -}
\ No newline at end of file +} diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt index 3ec0db4d99ce..c2e441b35f48 100644 --- a/config/boot-image-profile.txt +++ b/config/boot-image-profile.txt @@ -2768,6 +2768,11 @@ HPLandroid/hardware/location/GeofenceHardwareService$1;->getStatusOfMonitoringTy HPLandroid/hardware/location/GeofenceHardwareService$1;->registerForMonitorStateChangeCallback(ILandroid/hardware/location/IGeofenceHardwareMonitorCallback;)Z HPLandroid/hardware/location/GeofenceHardwareService$1;->removeGeofence(II)Z HPLandroid/hardware/location/GeofenceHardwareService;->checkPermission(III)V +HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V +HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V +HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IActivityRecognitionHardwareClient; +HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z +HPLandroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V HPLandroid/hardware/location/IContextHubCallback$Stub;->asBinder()Landroid/os/IBinder; HPLandroid/hardware/location/IContextHubCallback$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z HPLandroid/hardware/location/IContextHubService$Stub$Proxy;->findNanoAppOnHub(ILandroid/hardware/location/NanoAppFilter;)[I @@ -21777,6 +21782,7 @@ HSPLandroid/hardware/input/KeyboardLayout$1;-><init>()V HSPLandroid/hardware/input/TouchCalibration$1;-><init>()V HSPLandroid/hardware/input/TouchCalibration;-><init>()V HSPLandroid/hardware/input/TouchCalibration;->getAffineTransform()[F +HSPLandroid/hardware/location/ActivityRecognitionHardware;->isSupported()Z HSPLandroid/hardware/location/ContextHubInfo$1;-><init>()V HSPLandroid/hardware/location/ContextHubInfo;-><init>(Landroid/hardware/contexthub/V1_0/ContextHub;)V HSPLandroid/hardware/location/ContextHubMessage$1;-><init>()V @@ -21796,6 +21802,13 @@ HSPLandroid/hardware/location/GeofenceHardwareService$1;->setGpsGeofenceHardware HSPLandroid/hardware/location/GeofenceHardwareService;-><init>()V HSPLandroid/hardware/location/GeofenceHardwareService;->onBind(Landroid/content/Intent;)Landroid/os/IBinder; HSPLandroid/hardware/location/GeofenceHardwareService;->onCreate()V +HSPLandroid/hardware/location/IActivityRecognitionHardware;->disableActivityEvent(Ljava/lang/String;I)Z +HSPLandroid/hardware/location/IActivityRecognitionHardware;->enableActivityEvent(Ljava/lang/String;IJ)Z +HSPLandroid/hardware/location/IActivityRecognitionHardware;->flush()Z +HSPLandroid/hardware/location/IActivityRecognitionHardware;->getSupportedActivities()[Ljava/lang/String; +HSPLandroid/hardware/location/IActivityRecognitionHardware;->isActivitySupported(Ljava/lang/String;)Z +HSPLandroid/hardware/location/IActivityRecognitionHardware;->registerSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z +HSPLandroid/hardware/location/IActivityRecognitionHardware;->unregisterSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->asBinder()Landroid/os/IBinder; HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V HSPLandroid/hardware/location/IContextHubCallback;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V @@ -55637,6 +55650,7 @@ Landroid/hardware/input/InputManagerInternal; Landroid/hardware/input/KeyboardLayout$1; Landroid/hardware/input/TouchCalibration$1; Landroid/hardware/input/TouchCalibration; +Landroid/hardware/location/ActivityRecognitionHardware; Landroid/hardware/location/ContextHubInfo$1; Landroid/hardware/location/ContextHubInfo; Landroid/hardware/location/ContextHubManager; @@ -55652,6 +55666,11 @@ Landroid/hardware/location/GeofenceHardwareMonitorEvent; Landroid/hardware/location/GeofenceHardwareRequestParcelable$1; Landroid/hardware/location/GeofenceHardwareService$1; Landroid/hardware/location/GeofenceHardwareService; +Landroid/hardware/location/IActivityRecognitionHardware$Stub; +Landroid/hardware/location/IActivityRecognitionHardware; +Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy; +Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub; +Landroid/hardware/location/IActivityRecognitionHardwareClient; Landroid/hardware/location/IContextHubCallback$Stub$Proxy; Landroid/hardware/location/IContextHubCallback; Landroid/hardware/location/IContextHubClient$Stub; diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index 700df641659c..fc47f67f174c 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -462,6 +462,8 @@ Landroid/hardware/input/IInputManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/input/IInputManager; Landroid/hardware/input/IInputManager$Stub;->TRANSACTION_injectInputEvent:I Landroid/hardware/input/IInputManager;->injectInputEvent(Landroid/view/InputEvent;I)Z +Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V +Landroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V Landroid/hardware/location/IContextHubService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IContextHubService; Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/hardware/usb/IUsbManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/usb/IUsbManager; @@ -921,7 +923,6 @@ Landroid/os/PowerManager;->isLightDeviceIdleMode()Z Landroid/os/PowerManager;->mService:Landroid/os/IPowerManager; Landroid/os/PowerManager;->validateWakeLockParameters(ILjava/lang/String;)V Landroid/os/PowerManager;->wakeUp(JLjava/lang/String;)V -Landroid/os/Process;->BLUETOOTH_UID:I Landroid/os/Process;->DRM_UID:I Landroid/os/Process;->getFreeMemory()J Landroid/os/Process;->getParentPid(I)I @@ -948,10 +949,8 @@ Landroid/os/Process;->PROC_TERM_MASK:I Landroid/os/Process;->PROC_ZERO_TERM:I Landroid/os/Process;->readProcFile(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z Landroid/os/Process;->readProcLines(Ljava/lang/String;[Ljava/lang/String;[J)V -Landroid/os/Process;->ROOT_UID:I Landroid/os/Process;->setArgV0(Ljava/lang/String;)V Landroid/os/Process;->setProcessGroup(II)V -Landroid/os/Process;->SHELL_UID:I Landroid/os/Process;->VPN_UID:I Landroid/os/Process;->WIFI_UID:I Landroid/os/RecoverySystem;->verifyPackageCompatibility(Ljava/io/InputStream;)Z @@ -993,8 +992,6 @@ Landroid/os/ServiceManager;->sServiceManager:Landroid/os/IServiceManager; Landroid/os/ServiceManagerNative;->asInterface(Landroid/os/IBinder;)Landroid/os/IServiceManager; Landroid/os/ServiceManagerProxy;->getService(Ljava/lang/String;)Landroid/os/IBinder; Landroid/os/ServiceManagerProxy;->mRemote:Landroid/os/IBinder; -Landroid/os/ServiceSpecificException;-><init>(ILjava/lang/String;)V -Landroid/os/ServiceSpecificException;->errorCode:I Landroid/os/SharedMemory;->getFd()I Landroid/os/ShellCommand;->peekNextArg()Ljava/lang/String; Landroid/os/StatFs;->mStat:Landroid/system/StructStatVfs; diff --git a/config/preloaded-classes b/config/preloaded-classes index cd798adf5d44..c8a2a9c19b28 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -1405,7 +1405,10 @@ android.hardware.input.InputManager android.hardware.input.InputManager$InputDeviceListener android.hardware.input.InputManager$InputDeviceListenerDelegate android.hardware.input.InputManager$InputDevicesChangedListener +android.hardware.location.ActivityRecognitionHardware android.hardware.location.ContextHubManager +android.hardware.location.IActivityRecognitionHardware +android.hardware.location.IActivityRecognitionHardware$Stub android.hardware.radio.RadioManager android.hardware.soundtrigger.SoundTrigger android.hardware.soundtrigger.SoundTrigger$ConfidenceLevel diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 35098a0ff5d9..cebe6e1211e0 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -37,6 +37,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.view.Display; import android.view.KeyEvent; import android.view.WindowManager; import android.view.WindowManagerImpl; @@ -382,7 +383,8 @@ public abstract class AccessibilityService extends Service { void init(int connectionId, IBinder windowToken); boolean onGesture(int gestureId); boolean onKeyEvent(KeyEvent event); - void onMagnificationChanged(@NonNull Region region, + /** Magnification changed callbacks for different displays */ + void onMagnificationChanged(int displayId, @NonNull Region region, float scale, float centerX, float centerY); void onSoftKeyboardShowModeChanged(int showMode); void onPerformGestureResult(int sequence, boolean completedSuccessfully); @@ -452,7 +454,9 @@ public abstract class AccessibilityService extends Service { private WindowManager mWindowManager; - private MagnificationController mMagnificationController; + /** List of magnification controllers, mapping from displayId -> MagnificationController. */ + private final SparseArray<MagnificationController> mMagnificationControllers = + new SparseArray<>(0); private SoftKeyboardController mSoftKeyboardController; private AccessibilityButtonController mAccessibilityButtonController; @@ -483,8 +487,10 @@ public abstract class AccessibilityService extends Service { * client code. */ private void dispatchServiceConnected() { - if (mMagnificationController != null) { - mMagnificationController.onServiceConnected(); + synchronized (mLock) { + for (int i = 0; i < mMagnificationControllers.size(); i++) { + mMagnificationControllers.valueAt(i).onServiceConnectedLocked(); + } } if (mSoftKeyboardController != null) { mSoftKeyboardController.onServiceConnected(); @@ -652,11 +658,34 @@ public abstract class AccessibilityService extends Service { */ @NonNull public final MagnificationController getMagnificationController() { + return getMagnificationController(Display.DEFAULT_DISPLAY); + } + + /** + * Returns the magnification controller of specified logical display, which may be used to + * query and modify the state of display magnification. + * <p> + * <strong>Note:</strong> In order to control magnification, your service + * must declare the capability by setting the + * {@link android.R.styleable#AccessibilityService_canControlMagnification} + * property in its meta-data. For more information, see + * {@link #SERVICE_META_DATA}. + * + * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for + * default display. + * @return the magnification controller + * + * @hide + */ + @NonNull + public final MagnificationController getMagnificationController(int displayId) { synchronized (mLock) { - if (mMagnificationController == null) { - mMagnificationController = new MagnificationController(this, mLock); + MagnificationController controller = mMagnificationControllers.get(displayId); + if (controller == null) { + controller = new MagnificationController(this, mLock, displayId); + mMagnificationControllers.put(displayId, controller); } - return mMagnificationController; + return controller; } } @@ -765,11 +794,14 @@ public abstract class AccessibilityService extends Service { } } - private void onMagnificationChanged(@NonNull Region region, float scale, + private void onMagnificationChanged(int displayId, @NonNull Region region, float scale, float centerX, float centerY) { - if (mMagnificationController != null) { - mMagnificationController.dispatchMagnificationChanged( - region, scale, centerX, centerY); + MagnificationController controller; + synchronized (mLock) { + controller = mMagnificationControllers.get(displayId); + } + if (controller != null) { + controller.dispatchMagnificationChanged(region, scale, centerX, centerY); } } @@ -794,6 +826,7 @@ public abstract class AccessibilityService extends Service { */ public static final class MagnificationController { private final AccessibilityService mService; + private final int mDisplayId; /** * Map of listeners to their handlers. Lazily created when adding the @@ -802,19 +835,19 @@ public abstract class AccessibilityService extends Service { private ArrayMap<OnMagnificationChangedListener, Handler> mListeners; private final Object mLock; - MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock) { + MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock, + int displayId) { mService = service; mLock = lock; + mDisplayId = displayId; } /** * Called when the service is connected. */ - void onServiceConnected() { - synchronized (mLock) { - if (mListeners != null && !mListeners.isEmpty()) { - setMagnificationCallbackEnabled(true); - } + void onServiceConnectedLocked() { + if (mListeners != null && !mListeners.isEmpty()) { + setMagnificationCallbackEnabled(true); } } @@ -891,7 +924,7 @@ public abstract class AccessibilityService extends Service { mService.mConnectionId); if (connection != null) { try { - connection.setMagnificationCallbackEnabled(enabled); + connection.setMagnificationCallbackEnabled(mDisplayId, enabled); } catch (RemoteException re) { throw new RuntimeException(re); } @@ -952,7 +985,7 @@ public abstract class AccessibilityService extends Service { mService.mConnectionId); if (connection != null) { try { - return connection.getMagnificationScale(); + return connection.getMagnificationScale(mDisplayId); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to obtain scale", re); re.rethrowFromSystemServer(); @@ -981,7 +1014,7 @@ public abstract class AccessibilityService extends Service { mService.mConnectionId); if (connection != null) { try { - return connection.getMagnificationCenterX(); + return connection.getMagnificationCenterX(mDisplayId); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to obtain center X", re); re.rethrowFromSystemServer(); @@ -1010,7 +1043,7 @@ public abstract class AccessibilityService extends Service { mService.mConnectionId); if (connection != null) { try { - return connection.getMagnificationCenterY(); + return connection.getMagnificationCenterY(mDisplayId); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to obtain center Y", re); re.rethrowFromSystemServer(); @@ -1044,7 +1077,7 @@ public abstract class AccessibilityService extends Service { mService.mConnectionId); if (connection != null) { try { - return connection.getMagnificationRegion(); + return connection.getMagnificationRegion(mDisplayId); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to obtain magnified region", re); re.rethrowFromSystemServer(); @@ -1073,7 +1106,7 @@ public abstract class AccessibilityService extends Service { mService.mConnectionId); if (connection != null) { try { - return connection.resetMagnification(animate); + return connection.resetMagnification(mDisplayId, animate); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to reset", re); re.rethrowFromSystemServer(); @@ -1101,7 +1134,7 @@ public abstract class AccessibilityService extends Service { mService.mConnectionId); if (connection != null) { try { - return connection.setMagnificationScaleAndCenter( + return connection.setMagnificationScaleAndCenter(mDisplayId, scale, Float.NaN, Float.NaN, animate); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to set scale", re); @@ -1133,7 +1166,7 @@ public abstract class AccessibilityService extends Service { mService.mConnectionId); if (connection != null) { try { - return connection.setMagnificationScaleAndCenter( + return connection.setMagnificationScaleAndCenter(mDisplayId, Float.NaN, centerX, centerY, animate); } catch (RemoteException re) { Log.w(LOG_TAG, "Failed to set center", re); @@ -1624,9 +1657,10 @@ public abstract class AccessibilityService extends Service { } @Override - public void onMagnificationChanged(@NonNull Region region, + public void onMagnificationChanged(int displayId, @NonNull Region region, float scale, float centerX, float centerY) { - AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY); + AccessibilityService.this.onMagnificationChanged(displayId, region, scale, + centerX, centerY); } @Override @@ -1729,13 +1763,15 @@ public abstract class AccessibilityService extends Service { mCaller.sendMessage(message); } - public void onMagnificationChanged(@NonNull Region region, + /** Magnification changed callbacks for different displays */ + public void onMagnificationChanged(int displayId, @NonNull Region region, float scale, float centerX, float centerY) { final SomeArgs args = SomeArgs.obtain(); args.arg1 = region; args.arg2 = scale; args.arg3 = centerX; args.arg4 = centerY; + args.argi1 = displayId; final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args); mCaller.sendMessage(message); @@ -1865,7 +1901,10 @@ public abstract class AccessibilityService extends Service { final float scale = (float) args.arg2; final float centerX = (float) args.arg3; final float centerY = (float) args.arg4; - mCallback.onMagnificationChanged(region, scale, centerX, centerY); + final int displayId = args.argi1; + args.recycle(); + mCallback.onMagnificationChanged(displayId, region, scale, + centerX, centerY); } } return; diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index 4e96b8f11628..1dae4fca111e 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -41,7 +41,7 @@ import android.view.KeyEvent; void onKeyEvent(in KeyEvent event, int sequence); - void onMagnificationChanged(in Region region, float scale, float centerX, float centerY); + void onMagnificationChanged(int displayId, in Region region, float scale, float centerX, float centerY); void onSoftKeyboardShowModeChanged(int showMode); diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 276131f9d0d6..8c38fe4aaf6f 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -70,20 +70,20 @@ interface IAccessibilityServiceConnection { oneway void setOnKeyEventResult(boolean handled, int sequence); - float getMagnificationScale(); + float getMagnificationScale(int displayId); - float getMagnificationCenterX(); + float getMagnificationCenterX(int displayId); - float getMagnificationCenterY(); + float getMagnificationCenterY(int displayId); - Region getMagnificationRegion(); + Region getMagnificationRegion(int displayId); - boolean resetMagnification(boolean animate); + boolean resetMagnification(int displayId, boolean animate); - boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY, + boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate); - void setMagnificationCallbackEnabled(boolean enabled); + void setMagnificationCallbackEnabled(int displayId, boolean enabled); boolean setSoftKeyboardShowMode(int showMode); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 836627efb379..1063be4c5c7d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -74,6 +74,7 @@ import android.os.RemoteException; import android.os.ServiceManager.ServiceNotFoundException; import android.os.StrictMode; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.text.Selection; import android.text.SpannableStringBuilder; @@ -1049,33 +1050,56 @@ public class Activity extends ContextThemeWrapper @Retention(RetentionPolicy.SOURCE) @interface ContentCaptureNotificationType{} - - private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) { - final ContentCaptureManager cm = getContentCaptureManager(); - if (cm == null) return; - + private String getContentCaptureTypeAsString(@ContentCaptureNotificationType int type) { switch (type) { case CONTENT_CAPTURE_START: - //TODO(b/111276913): decide whether the InteractionSessionId should be - // saved / restored in the activity bundle - probably not - int flags = 0; - if ((getWindow().getAttributes().flags - & WindowManager.LayoutParams.FLAG_SECURE) != 0) { - flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE; - } - cm.onActivityStarted(mToken, getComponentName(), flags); - break; + return "START"; case CONTENT_CAPTURE_PAUSE: - cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED); - break; + return "PAUSE"; case CONTENT_CAPTURE_RESUME: - cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED); - break; + return "RESUME"; case CONTENT_CAPTURE_STOP: - cm.onActivityStopped(); - break; + return "STOP"; default: - Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type); + return "UNKNOW-" + type; + } + } + + private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "notifyContentCapture(" + getContentCaptureTypeAsString(type) + ") for " + + mComponent.toShortString()); + } + try { + final ContentCaptureManager cm = getContentCaptureManager(); + if (cm == null) return; + + switch (type) { + case CONTENT_CAPTURE_START: + //TODO(b/111276913): decide whether the InteractionSessionId should be + // saved / restored in the activity bundle - probably not + int flags = 0; + if ((getWindow().getAttributes().flags + & WindowManager.LayoutParams.FLAG_SECURE) != 0) { + flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE; + } + cm.onActivityStarted(mToken, getComponentName(), flags); + break; + case CONTENT_CAPTURE_PAUSE: + cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED); + break; + case CONTENT_CAPTURE_RESUME: + cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED); + break; + case CONTENT_CAPTURE_STOP: + cm.onActivityStopped(); + break; + default: + Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e0b8d78ebabc..1045b7aa0890 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -3529,12 +3529,32 @@ public class ActivityManager { /** * Returns "true" if device is running in a test harness. + * + * @deprecated this method is false for all user builds. Users looking to check if their device + * is running in a device farm should see {@link #isRunningInUserTestHarness()}. */ + @Deprecated public static boolean isRunningInTestHarness() { return SystemProperties.getBoolean("ro.test_harness", false); } /** + * Returns "true" if the device is running in Test Harness Mode. + * + * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a + * device farm/testing harness (such as Firebase Test Lab). You should check this method if you + * want your app to behave differently when running in a test harness to skip setup screens that + * would impede UI testing. e.g. a keyboard application that has a full screen setup page for + * the first time it is launched. + * + * <p>Note that you should <em>not</em> use this to determine whether or not your app is running + * an instrumentation test, as it is not set for a standard device running a test. + */ + public static boolean isRunningInUserTestHarness() { + return SystemProperties.getBoolean("persist.sys.test_harness", false); + } + + /** * Unsupported compiled sdk warning should always be shown for the intput activity * even in cases where the system would normally not show the warning. E.g. when running in a * test harness. diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7767f0491a16..a3243a5de72a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5939,6 +5939,11 @@ public final class ActivityThread extends ClientTransactionHandler { Binder.enableTracing(); } + // Initialize heap profiling. + if (isAppProfileable || Build.IS_DEBUGGABLE) { + nInitZygoteChildHeapProfiling(); + } + // Allow renderer debugging features if we're debuggable. boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE); @@ -6965,4 +6970,5 @@ public final class ActivityThread extends ClientTransactionHandler { // ------------------ Regular JNI ------------------------ private native void nPurgePendingResources(); private native void nDumpGraphicsInfo(FileDescriptor fd); + private native void nInitZygoteChildHeapProfiling(); } diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index ab8f234766d6..4d3711ae7ff5 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -328,7 +328,7 @@ public class ActivityView extends ViewGroup { } } else { mTmpTransaction.reparent(mRootSurfaceControl, - mSurfaceView.getSurfaceControl().getHandle()).apply(); + mSurfaceView.getSurfaceControl()).apply(); } if (mVirtualDisplay != null) { @@ -390,7 +390,7 @@ public class ActivityView extends ViewGroup { .build(); try { - wm.reparentDisplayContent(displayId, mRootSurfaceControl.getHandle()); + wm.reparentDisplayContent(displayId, mRootSurfaceControl); wm.dontOverrideDisplayInfo(displayId); if (mSingleTaskInstance) { mActivityTaskManager.setDisplayToSingleTaskInstance(displayId); diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 1622c06b0a34..853b45e0a80d 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -1324,7 +1324,8 @@ public class DownloadManager { * @param description the description that would appear for this file in Downloads App. * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files * scanned by MediaScanner appear in the applications used to view media (for example, - * Gallery app). + * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is + * ignored and the file is always scanned by MediaScanner. * @param mimeType mimetype of the file. * @param path absolute pathname to the file. The file should be world-readable, so that it can * be managed by the Downloads App and any other app that is used to read it (for example, @@ -1353,7 +1354,8 @@ public class DownloadManager { * @param description the description that would appear for this file in Downloads App. * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files * scanned by MediaScanner appear in the applications used to view media (for example, - * Gallery app). + * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is + * ignored and the file is always scanned by MediaScanner. * @param mimeType mimetype of the file. * @param path absolute pathname to the file. The file should be world-readable, so that it can * be managed by the Downloads App and any other app that is used to read it (for example, diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 1ae0f52ac74e..75c90542ebce 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -19,6 +19,7 @@ package android.app; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -124,6 +125,7 @@ public class KeyguardManager { * * @return the intent for launching the activity or null if no password is required. **/ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { if (!isDeviceSecure()) return null; Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL); @@ -176,6 +178,7 @@ public class KeyguardManager { * @throws IllegalStateException if the device has already been provisioned * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @SystemApi public Intent createConfirmFactoryResetCredentialIntent( CharSequence title, CharSequence description, CharSequence alternateButtonLabel) { @@ -231,6 +234,7 @@ public class KeyguardManager { * secure notifications cannot be shown if {@code false} * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) @SystemApi public void setPrivateNotificationsAllowed(boolean allow) { @@ -249,6 +253,7 @@ public class KeyguardManager { * By default, private notifications are allowed. * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) @SystemApi public boolean getPrivateNotificationsAllowed() { diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 3f9627ed807c..a021e3cb2d78 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -1240,7 +1240,7 @@ public final class UiAutomation { } @Override - public void onMagnificationChanged(@NonNull Region region, + public void onMagnificationChanged(int displayId, @NonNull Region region, float scale, float centerX, float centerY) { /* do nothing */ } diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java index 5b87de48210f..5f1a94c835c3 100644 --- a/core/java/android/app/VrManager.java +++ b/core/java/android/app/VrManager.java @@ -215,19 +215,12 @@ public class VrManager { } /** - * Start VR Input method for the packageName in {@link ComponentName}. - * This method notifies InputMethodManagerService to use VR IME instead of - * regular phone IME. - * @param componentName ComponentName of a VR InputMethod that should be set as selected - * input by InputMethodManagerService. + * This method is not implemented. + * + * @param componentName not used */ @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public void setVrInputMethod(ComponentName componentName) { - try { - mService.setVrInputMethod(componentName); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } } /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6d7c547f7cf1..b0a08edfcaaf 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1362,16 +1362,23 @@ public class DevicePolicyManager { public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION"; /** - * Activity action: have the user enter a new password. This activity should - * be launched after using {@link #setPasswordQuality(ComponentName, int)}, - * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user - * enter a new password that meets the current requirements. You can use - * {@link #isActivePasswordSufficient()} to determine whether you need to - * have the user select a new password in order to meet the current - * constraints. Upon being resumed from this activity, you can check the new + * Activity action: have the user enter a new password. + * + * <p>For admin apps, this activity should be launched after using {@link + * #setPasswordQuality(ComponentName, int)}, or {@link + * #setPasswordMinimumLength(ComponentName, int)} to have the user enter a new password that + * meets the current requirements. You can use {@link #isActivePasswordSufficient()} to + * determine whether you need to have the user select a new password in order to meet the + * current constraints. Upon being resumed from this activity, you can check the new * password characteristics to see if they are sufficient. * - * If the intent is launched from within a managed profile with a profile + * <p>Non-admin apps can use {@link #getPasswordComplexity()} to check the current screen lock + * complexity, and use this activity with extra {@link #EXTRA_PASSWORD_COMPLEXITY} to suggest + * to users how complex the app wants the new screen lock to be. Note that both {@link + * #getPasswordComplexity()} and the extra {@link #EXTRA_PASSWORD_COMPLEXITY} require the + * calling app to have the permission {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY}. + * + * <p>If the intent is launched from within a managed profile with a profile * owner built against {@link android.os.Build.VERSION_CODES#M} or before, * this will trigger entering a new password for the parent of the profile. * For all other cases it will trigger entering a new password for the user @@ -1384,6 +1391,24 @@ public class DevicePolicyManager { = "android.app.action.SET_NEW_PASSWORD"; /** + * An integer indicating the complexity level of the new password an app would like the user to + * set when launching the action {@link #ACTION_SET_NEW_PASSWORD}. + * + * <p>Must be one of + * <ul> + * <li>{@link #PASSWORD_COMPLEXITY_HIGH} + * <li>{@link #PASSWORD_COMPLEXITY_MEDIUM} + * <li>{@link #PASSWORD_COMPLEXITY_LOW} + * <li>{@link #PASSWORD_COMPLEXITY_NONE} + * </ul> + * + * <p>If an invalid value is used, it will be treated as {@link #PASSWORD_COMPLEXITY_NONE}. + */ + @RequiresPermission(android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY) + public static final String EXTRA_PASSWORD_COMPLEXITY = + "android.app.extra.PASSWORD_COMPLEXITY"; + + /** * Constant for {@link #getPasswordComplexity()}: no password. * * <p>Note that these complexity constants are ordered so that higher values are more complex. @@ -2513,6 +2538,9 @@ public class DevicePolicyManager { * requested quality constant (between the policy set here, the user's preference, and any other * considerations) is the one that is in effect. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2548,6 +2576,9 @@ public class DevicePolicyManager { * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. * + * <p>Note: on devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, + * the password is always treated as empty. + * * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ @@ -2580,6 +2611,9 @@ public class DevicePolicyManager { * {@link #PASSWORD_QUALITY_ALPHANUMERIC}, or {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2609,11 +2643,13 @@ public class DevicePolicyManager { * restrictions on this user and its participating profiles. Restrictions on profiles that have * a separate challenge are not taken into account. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. * - * user and its profiles or a particular one. * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ @@ -2644,6 +2680,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2678,6 +2717,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2714,6 +2756,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2748,6 +2793,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2784,6 +2832,9 @@ public class DevicePolicyManager { * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2818,6 +2869,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2853,6 +2907,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2887,6 +2944,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2922,6 +2982,9 @@ public class DevicePolicyManager { * only imposed if the administrator has also requested {@link #PASSWORD_QUALITY_COMPLEX} with * {@link #setPasswordQuality}. The default value is 1. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -2955,6 +3018,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -2990,6 +3056,9 @@ public class DevicePolicyManager { * setting this value. This constraint is only imposed if the administrator has also requested * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The default value is 0. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3024,6 +3093,9 @@ public class DevicePolicyManager { * and only applies when the password quality is * {@link #PASSWORD_QUALITY_COMPLEX}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * <p>This method can be called on the {@link DevicePolicyManager} instance * returned by {@link #getParentProfileInstance(ComponentName)} in order to retrieve * restrictions on the parent profile. @@ -3060,6 +3132,9 @@ public class DevicePolicyManager { * , {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX} {@link #PASSWORD_QUALITY_ALPHABETIC}, or * {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3112,6 +3187,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not an active administrator or {@code admin} * does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setPasswordExpirationTimeout(@NonNull ComponentName admin, long timeout) { if (mService != null) { try { @@ -3136,6 +3212,7 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate all admins. * @return The timeout for the given admin or the minimum of all timeouts */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getPasswordExpirationTimeout(@Nullable ComponentName admin) { if (mService != null) { try { @@ -3160,6 +3237,7 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate all admins. * @return The password expiration time, in milliseconds since epoch. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getPasswordExpiration(@Nullable ComponentName admin) { if (mService != null) { try { @@ -3184,12 +3262,14 @@ public class DevicePolicyManager { * all admins. * @return The length of the password history */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getPasswordHistoryLength(@Nullable ComponentName admin) { return getPasswordHistoryLength(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getPasswordHistoryLength(@Nullable ComponentName admin, int userHandle) { if (mService != null) { try { @@ -3204,10 +3284,16 @@ public class DevicePolicyManager { /** * Return the maximum password length that the device supports for a * particular password quality. + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always empty. * @param quality The quality being interrogated. * @return Returns the maximum length that the user can enter. */ public int getPasswordMaximumLength(int quality) { + PackageManager pm = mContext.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) { + return 0; + } // Kind-of arbitrary. return 16; } @@ -3218,6 +3304,10 @@ public class DevicePolicyManager { * user and its participating profiles. Restrictions on profiles that have a separate challenge * are not taken into account. The user must be unlocked in order to perform the check. * <p> + * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty - i.e. this method will always return false on such + * devices, provided any password requirements were set. + * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has * not, a security exception will be thrown. @@ -3250,6 +3340,9 @@ public class DevicePolicyManager { * explicitly querying the parent profile screen lock complexity via {@link * #getParentProfileInstance}. * + * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the + * password is always treated as empty. + * * @throws IllegalStateException if the user is not unlocked. * @throws SecurityException if the calling application does not have the permission * {@link permission#GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY} @@ -3329,6 +3422,7 @@ public class DevicePolicyManager { * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getCurrentFailedPasswordAttempts() { return getCurrentFailedPasswordAttempts(myUserId()); } @@ -3396,6 +3490,7 @@ public class DevicePolicyManager { * both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setMaximumFailedPasswordsForWipe(@NonNull ComponentName admin, int num) { if (mService != null) { try { @@ -3419,12 +3514,14 @@ public class DevicePolicyManager { * @param admin The name of the admin component to check, or {@code null} to aggregate * all admins. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin) { return getMaximumFailedPasswordsForWipe(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getMaximumFailedPasswordsForWipe(@Nullable ComponentName admin, int userHandle) { if (mService != null) { try { @@ -3444,6 +3541,7 @@ public class DevicePolicyManager { * user passed in. * @hide Used only by Keyguard */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) { if (mService != null) { try { @@ -3514,6 +3612,7 @@ public class DevicePolicyManager { * that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} * @throws IllegalStateException if the calling user is locked or has a managed profile. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean resetPassword(String password, int flags) { throwIfParentInstance("resetPassword"); if (mService != null) { @@ -3557,6 +3656,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalArgumentException if the supplied token is invalid. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean setResetPasswordToken(ComponentName admin, byte[] token) { throwIfParentInstance("setResetPasswordToken"); if (mService != null) { @@ -3576,6 +3676,7 @@ public class DevicePolicyManager { * @return true if the operation is successful, false otherwise. * @throws SecurityException if admin is not a device or profile owner. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean clearResetPasswordToken(ComponentName admin) { throwIfParentInstance("clearResetPasswordToken"); if (mService != null) { @@ -3596,6 +3697,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalStateException if no token has been set. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean isResetPasswordTokenActive(ComponentName admin) { throwIfParentInstance("isResetPasswordTokenActive"); if (mService != null) { @@ -3637,6 +3739,7 @@ public class DevicePolicyManager { * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalStateException if the provided token is not valid. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password, byte[] token, int flags) { throwIfParentInstance("resetPassword"); @@ -3742,6 +3845,7 @@ public class DevicePolicyManager { * * @throws SecurityException if {@code admin} is not a device or profile owner. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin, long timeoutMs) { if (mService != null) { @@ -3766,12 +3870,14 @@ public class DevicePolicyManager { * across all participating admins. * @return The timeout in milliseconds or 0 if not configured for the provided admin. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) { return getRequiredStrongAuthTimeout(admin, myUserId()); } /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin, @UserIdInt int userId) { if (mService != null) { try { @@ -5350,6 +5456,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setActivePasswordState(PasswordMetrics metrics, int userHandle) { if (mService != null) { try { @@ -5363,6 +5470,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportPasswordChanged(@UserIdInt int userId) { if (mService != null) { try { @@ -5377,6 +5485,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportFailedPasswordAttempt(int userHandle) { if (mService != null) { try { @@ -5391,6 +5500,7 @@ public class DevicePolicyManager { * @hide */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportSuccessfulPasswordAttempt(int userHandle) { if (mService != null) { try { @@ -5404,6 +5514,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportFailedBiometricAttempt(int userHandle) { if (mService != null) { try { @@ -5417,6 +5528,7 @@ public class DevicePolicyManager { /** * @hide */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void reportSuccessfulBiometricAttempt(int userHandle) { if (mService != null) { try { @@ -6383,6 +6495,7 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not an active administrator or does not use * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public void setTrustAgentConfiguration(@NonNull ComponentName admin, @NonNull ComponentName target, PersistableBundle configuration) { if (mService != null) { @@ -6412,6 +6525,7 @@ public class DevicePolicyManager { * @param agent Which component to get enabled features for. * @return configuration for the given trust agent. */ + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public @Nullable List<PersistableBundle> getTrustAgentConfiguration( @Nullable ComponentName admin, @NonNull ComponentName agent) { return getTrustAgentConfiguration(admin, agent, myUserId()); @@ -6419,6 +6533,7 @@ public class DevicePolicyManager { /** @hide per-user version */ @UnsupportedAppUsage + @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) public @Nullable List<PersistableBundle> getTrustAgentConfiguration( @Nullable ComponentName admin, @NonNull ComponentName agent, int userHandle) { if (mService != null) { @@ -9403,12 +9518,18 @@ public class DevicePolicyManager { } /** - * Allows the device owner to enable or disable the backup service. + * Allows the device owner or profile owner to enable or disable the backup service. * - * <p> Backup service manages all backup and restore mechanisms on the device. Setting this to - * false will prevent data from being backed up or restored. + * <p> Each user has its own backup service which manages the backup and restore mechanisms in + * that user. Disabling the backup service will prevent data from being backed up or restored. * - * <p> Backup service is off by default when device owner is present. + * <p> Device owner calls this API to control backup services across all users on the device. + * Profile owner can use this API to enable or disable the profile's backup service. However, + * for a managed profile its backup functionality is only enabled if both the device owner + * and the profile owner have enabled the backup service. + * + * <p> By default, backup service is disabled on a device with device owner, and within a + * managed profile. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled {@code true} to enable the backup service, {@code false} to disable it. @@ -9424,7 +9545,12 @@ public class DevicePolicyManager { } /** - * Return whether the backup service is enabled by the device owner. + * Return whether the backup service is enabled by the device owner or profile owner for the + * current user, as previously set by {@link #setBackupServiceEnabled(ComponentName, boolean)}. + * + * <p> Whether the backup functionality is actually enabled or not depends on settings from both + * the current user and the device owner, please see + * {@link #setBackupServiceEnabled(ComponentName, boolean)} for details. * * <p> Backup service manages all backup and restore mechanisms on the device. * diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java index 8b41755f6dec..e5df2c70eeec 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -20,6 +20,11 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; import android.annotation.IntDef; import android.annotation.NonNull; @@ -27,6 +32,8 @@ import android.app.admin.DevicePolicyManager.PasswordComplexity; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -85,6 +92,101 @@ public class PasswordMetrics implements Parcelable { nonLetter = in.readInt(); } + /** Returns the min quality allowed by {@code complexityLevel}. */ + public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) { + // this would be the quality of the first metrics since mMetrics is sorted in ascending + // order of quality + return PasswordComplexityBucket + .complexityLevelToBucket(complexityLevel).mMetrics[0].quality; + } + + /** + * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet + * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code + * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements, + * and {@code complexityLevel}. + * + * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are + * more than one set of metrics to meet the minimum complexity requirement and inspecting what + * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics + * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or + * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI + * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering + * an alphanumeric password so we would update the min complexity required min length to 6. + */ + public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel, + int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric, + boolean requiresLettersOrSymbols) { + int targetQuality = Math.max( + userEnteredPasswordQuality, + getActualRequiredQuality( + requestedQuality, requiresNumeric, requiresLettersOrSymbols)); + return getTargetQualityMetrics(complexityLevel, targetQuality); + } + + /** + * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality + * is the same as {@code targetQuality}. + * + * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics + * with the min quality at {@code complexityLevel}. + */ + // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private + @VisibleForTesting + public static PasswordMetrics getTargetQualityMetrics( + @PasswordComplexity int complexityLevel, int targetQuality) { + PasswordComplexityBucket targetBucket = + PasswordComplexityBucket.complexityLevelToBucket(complexityLevel); + for (PasswordMetrics metrics : targetBucket.mMetrics) { + if (targetQuality == metrics.quality) { + return metrics; + } + } + // none of the metrics at complexityLevel has targetQuality, return metrics with min quality + // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where + // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and + // requested quality is NUMERIC + return targetBucket.mMetrics[0]; + } + + /** + * Finds out the actual quality requirement based on whether quality is {@link + * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are + * required. + */ + @VisibleForTesting + // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private + public static int getActualRequiredQuality( + int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) { + if (requestedQuality != PASSWORD_QUALITY_COMPLEX) { + return requestedQuality; + } + + // find out actual password quality from complex requirements + if (requiresNumeric && requiresLettersOrSymbols) { + return PASSWORD_QUALITY_ALPHANUMERIC; + } + if (requiresLettersOrSymbols) { + return PASSWORD_QUALITY_ALPHABETIC; + } + if (requiresNumeric) { + // cannot specify numeric complex using complex quality so this must be numeric + return PASSWORD_QUALITY_NUMERIC; + } + + // reaching here means dpm sets quality to complex without specifying any requirements + return PASSWORD_QUALITY_UNSPECIFIED; + } + + /** + * Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE} + * if {@code complexityLevel} is not valid. + */ + @PasswordComplexity + public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) { + return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel; + } + public boolean isDefault() { return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0 @@ -280,7 +382,7 @@ public class PasswordMetrics implements Parcelable { @PasswordComplexity public int determineComplexity() { for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) { - if (satisfiesBucket(bucket.getMetrics())) { + if (satisfiesBucket(bucket.mMetrics)) { return bucket.mComplexityLevel; } } @@ -290,7 +392,7 @@ public class PasswordMetrics implements Parcelable { /** * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}. */ - public static class PasswordComplexityBucket { + private static class PasswordComplexityBucket { /** * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of * {@link PasswordMetrics}. @@ -299,12 +401,13 @@ public class PasswordMetrics implements Parcelable { new PasswordComplexityBucket( PASSWORD_COMPLEXITY_HIGH, new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 6), + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 8), new PasswordMetrics( DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6), new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ - 8)); + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ + 6)); /** * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of @@ -314,11 +417,12 @@ public class PasswordMetrics implements Parcelable { new PasswordComplexityBucket( PASSWORD_COMPLEXITY_MEDIUM, new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4), + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + 4), new PasswordMetrics( DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4), new PasswordMetrics( - DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ + DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ 4)); /** @@ -328,11 +432,11 @@ public class PasswordMetrics implements Parcelable { private static final PasswordComplexityBucket LOW = new PasswordComplexityBucket( PASSWORD_COMPLEXITY_LOW, - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC), - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING), new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC), - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)); + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), + new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC)); /** * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}. @@ -348,19 +452,27 @@ public class PasswordMetrics implements Parcelable { private final int mComplexityLevel; private final PasswordMetrics[] mMetrics; + /** + * @param metricsArray must be sorted in ascending order of {@link #quality}. + */ private PasswordComplexityBucket(@PasswordComplexity int complexityLevel, - PasswordMetrics... metrics) { + PasswordMetrics... metricsArray) { + int previousQuality = PASSWORD_QUALITY_UNSPECIFIED; + for (PasswordMetrics metrics : metricsArray) { + if (metrics.quality < previousQuality) { + throw new IllegalArgumentException("metricsArray must be sorted in ascending" + + " order of quality"); + } + previousQuality = metrics.quality; + } + + this.mMetrics = metricsArray; this.mComplexityLevel = complexityLevel; - this.mMetrics = metrics; - } - /** Returns the {@link PasswordMetrics} that meet the min requirements of this bucket. */ - public PasswordMetrics[] getMetrics() { - return mMetrics; } /** Returns the bucket that {@code complexityLevel} represents. */ - public static PasswordComplexityBucket complexityLevelToBucket( + private static PasswordComplexityBucket complexityLevelToBucket( @PasswordComplexity int complexityLevel) { for (PasswordComplexityBucket bucket : BUCKETS) { if (bucket.mComplexityLevel == complexityLevel) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 1945b2fd51de..97bc0796e9ce 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -36,6 +36,7 @@ import android.bluetooth.le.ScanSettings; import android.content.Context; import android.os.BatteryStats; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; @@ -648,6 +649,32 @@ public final class BluetoothAdapter { private final Object mLock = new Object(); private final Map<LeScanCallback, ScanCallback> mLeScanClients; + private static final Map<BluetoothDevice, List<Pair<MetadataListener, Handler>>> + sMetadataListeners = new HashMap<>(); + + /** + * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener + * implementation. + */ + private static final IBluetoothMetadataListener sBluetoothMetadataListener = + new IBluetoothMetadataListener.Stub() { + @Override + public void onMetadataChanged(BluetoothDevice device, int key, String value) { + synchronized (sMetadataListeners) { + if (sMetadataListeners.containsKey(device)) { + List<Pair<MetadataListener, Handler>> list = sMetadataListeners.get(device); + for (Pair<MetadataListener, Handler> pair : list) { + MetadataListener listener = pair.first; + Handler handler = pair.second; + handler.post(() -> { + listener.onMetadataChanged(device, key, value); + }); + } + } + } + return; + } + }; /** * Get a handle to the default local Bluetooth adapter. @@ -2607,6 +2634,16 @@ public final class BluetoothAdapter { } } } + synchronized (sMetadataListeners) { + sMetadataListeners.forEach((device, pair) -> { + try { + mService.registerMetadataListener(sBluetoothMetadataListener, + device); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register metadata listener", e); + } + }); + } } public void onBluetoothServiceDown() { @@ -3090,4 +3127,142 @@ public final class BluetoothAdapter { + "listenUsingInsecureL2capChannel"); return listenUsingInsecureL2capChannel(); } + + /** + * Register a {@link #MetadataListener} to receive update about metadata + * changes for this {@link BluetoothDevice}. + * Registration must be done when Bluetooth is ON and will last until + * {@link #unregisterMetadataListener(BluetoothDevice)} is called, even when Bluetooth + * restarted in the middle. + * All input parameters should not be null or {@link NullPointerException} will be triggered. + * The same {@link BluetoothDevice} and {@link #MetadataListener} pair can only be registered + * once, double registration would cause {@link IllegalArgumentException}. + * + * @param device {@link BluetoothDevice} that will be registered + * @param listener {@link #MetadataListener} that will receive asynchronous callbacks + * @param handler the handler for listener callback + * @return true on success, false on error + * @throws NullPointerException If one of {@code listener}, {@code device} or {@code handler} + * is null. + * @throws IllegalArgumentException The same {@link #MetadataListener} and + * {@link BluetoothDevice} are registered twice. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean registerMetadataListener(BluetoothDevice device, MetadataListener listener, + Handler handler) { + if (DBG) Log.d(TAG, "registerMetdataListener()"); + + final IBluetooth service = mService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener"); + return false; + } + if (listener == null) { + throw new NullPointerException("listener is null"); + } + if (device == null) { + throw new NullPointerException("device is null"); + } + if (handler == null) { + throw new NullPointerException("handler is null"); + } + + synchronized (sMetadataListeners) { + List<Pair<MetadataListener, Handler>> listenerList = sMetadataListeners.get(device); + if (listenerList == null) { + // Create new listener/handler list for registeration + listenerList = new ArrayList<>(); + sMetadataListeners.put(device, listenerList); + } else { + // Check whether this device was already registed by the lisenter + if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) { + throw new IllegalArgumentException("listener was already regestered" + + " for the device"); + } + } + + Pair<MetadataListener, Handler> listenerPair = new Pair(listener, handler); + listenerList.add(listenerPair); + + boolean ret = false; + try { + ret = service.registerMetadataListener(sBluetoothMetadataListener, device); + } catch (RemoteException e) { + Log.e(TAG, "registerMetadataListener fail", e); + } finally { + if (!ret) { + // Remove listener registered earlier when fail. + listenerList.remove(listenerPair); + if (listenerList.isEmpty()) { + // Remove the device if its listener list is empty + sMetadataListeners.remove(device); + } + } + } + return ret; + } + } + + /** + * Unregister all {@link MetadataListener} from this {@link BluetoothDevice}. + * Unregistration can be done when Bluetooth is either ON or OFF. + * {@link #registerMetadataListener(MetadataListener, BluetoothDevice, Handler)} must + * be called before unregisteration. + * Unregistering a device that is not regestered would cause {@link IllegalArgumentException}. + * + * @param device {@link BluetoothDevice} that will be unregistered. it + * should not be null or {@link NullPointerException} will be triggered. + * @return true on success, false on error + * @throws NullPointerException If {@code device} is null. + * @throws IllegalArgumentException If {@code device} has not been registered before. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean unregisterMetadataListener(BluetoothDevice device) { + if (DBG) Log.d(TAG, "unregisterMetdataListener()"); + if (device == null) { + throw new NullPointerException("device is null"); + } + + synchronized (sMetadataListeners) { + if (sMetadataListeners.containsKey(device)) { + sMetadataListeners.remove(device); + } else { + throw new IllegalArgumentException("device was not registered"); + } + + final IBluetooth service = mService; + if (service == null) { + // Bluetooth is OFF, do nothing to Bluetooth service. + return true; + } + try { + return service.unregisterMetadataListener(device); + } catch (RemoteException e) { + Log.e(TAG, "unregisterMetadataListener fail", e); + return false; + } + } + } + + /** + * This abstract class is used to implement {@link BluetoothAdapter} metadata listener. + * @hide + */ + @SystemApi + public abstract class MetadataListener { + /** + * Callback triggered if the metadata of {@link BluetoothDevice} registered in + * {@link #registerMetadataListener}. + * + * @param device changed {@link BluetoothDevice}. + * @param key changed metadata key, one of BluetoothDevice.METADATA_*. + * @param value the new value of metadata. + */ + public void onMetadataChanged(BluetoothDevice device, int key, String value) { + } + } } diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 235dc5c59c2a..17cf702bbdea 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -341,6 +341,137 @@ public final class BluetoothDevice implements Parcelable { "android.bluetooth.device.action.SDP_RECORD"; /** + * Maximum length of a metadata entry, this is to avoid exploding Bluetooth + * disk usage + * @hide + */ + @SystemApi + public static final int METADATA_MAX_LENGTH = 2048; + + /** + * Manufacturer name of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_MANUFACTURER_NAME = 0; + + /** + * Model name of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_MODEL_NAME = 1; + + /** + * Software version of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_SOFTWARE_VERSION = 2; + + /** + * Hardware version of this Bluetooth device + * @hide + */ + @SystemApi + public static final int METADATA_HARDWARE_VERSION = 3; + + /** + * Package name of the companion app, if any + * @hide + */ + @SystemApi + public static final int METADATA_COMPANION_APP = 4; + + /** + * URI to the main icon shown on the settings UI + * @hide + */ + @SystemApi + public static final int METADATA_MAIN_ICON = 5; + + /** + * Whether this device is an untethered headset with left, right and case + * @hide + */ + @SystemApi + public static final int METADATA_IS_UNTHETHERED_HEADSET = 6; + + /** + * URI to icon of the left headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_ICON = 7; + + /** + * URI to icon of the right headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_ICON = 8; + + /** + * URI to icon of the headset charging case + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_ICON = 9; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the left headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_BATTERY = 10; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the right headset + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_BATTERY = 11; + + /** + * Battery level (0-100), {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} + * is invalid, of the headset charging case + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_BATTERY = 12; + + /** + * Whether the left headset is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_LEFT_CHARGING = 13; + + /** + * Whether the right headset is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_RIGHT_CHARGING = 14; + + /** + * Whether the headset charging case is charging + * @hide + */ + @SystemApi + public static final int METADATA_UNTHETHERED_CASE_CHARGING = 15; + + /** + * URI to the enhanced settings UI slice, null or empty String means + * the UI does not exist + * @hide + */ + @SystemApi + public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; + + /** * Broadcast Action: This intent is used to broadcast the {@link UUID} * wrapped as a {@link android.os.ParcelUuid} of the remote device after it * has been fetched. This intent is sent only when the UUIDs of the remote @@ -2026,4 +2157,61 @@ public final class BluetoothDevice implements Parcelable { Log.e(TAG, "createL2capCocSocket: PLEASE USE THE OFFICIAL API, createInsecureL2capChannel"); return createInsecureL2capChannel(psm); } + + /** + * Set a keyed metadata of this {@link BluetoothDevice} to a + * {@link String} value. + * Only bonded devices's metadata will be persisted across Bluetooth + * restart. + * Metadata will be removed when the device's bond state is moved to + * {@link #BOND_NONE}. + * + * @param key must be within the list of BluetoothDevice.METADATA_* + * @param value the string data to set for key. Must be less than + * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length + * @return true on success, false on error + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setMetadata(int key, String value) { + final IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata"); + return false; + } + if (value.length() > METADATA_MAX_LENGTH) { + throw new IllegalArgumentException("value length is " + value.length() + + ", should not over " + METADATA_MAX_LENGTH); + } + try { + return service.setMetadata(this, key, value); + } catch (RemoteException e) { + Log.e(TAG, "setMetadata fail", e); + return false; + } + } + + /** + * Get a keyed metadata for this {@link BluetoothDevice} as {@link String} + * + * @param key must be within the list of BluetoothDevice.METADATA_* + * @return Metadata of the key as string, null on error or not found + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public String getMetadata(int key) { + final IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata"); + return null; + } + try { + return service.getMetadata(this, key); + } catch (RemoteException e) { + Log.e(TAG, "getMetadata fail", e); + return null; + } + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index cefc700d372a..280f1ac9c067 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -771,7 +771,9 @@ public abstract class Context { * <p> * This is not generally intended for third party application developers. */ - public abstract String getOpPackageName(); + public String getOpPackageName() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } /** Return the full application info for this context's package. */ public abstract ApplicationInfo getApplicationInfo(); @@ -2980,9 +2982,11 @@ public abstract class Context { * * @see #bindService */ - public abstract boolean bindIsolatedService(@RequiresPermission Intent service, + public boolean bindIsolatedService(@RequiresPermission Intent service, @NonNull ServiceConnection conn, @BindServiceFlags int flags, - @NonNull String instanceName); + @NonNull String instanceName) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } /** * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle @@ -3037,8 +3041,10 @@ public abstract class Context { * a related groups -- higher importance values will be killed before * lower ones. */ - public abstract void updateServiceGroup(@NonNull ServiceConnection conn, int group, - int importance); + public void updateServiceGroup(@NonNull ServiceConnection conn, int group, + int importance) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } /** * Disconnect from an application service. You will no longer receive diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 8497656df2be..a6b47ff1320d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2008,6 +2008,15 @@ public class Intent implements Parcelable, Cloneable { "android.intent.extra.PERMISSION_GROUP_NAME"; /** + * Intent extra: The number of milliseconds. + * <p> + * Type: long + * </p> + */ + public static final String EXTRA_DURATION_MILLIS = + "android.intent.extra.DURATION_MILLIS"; + + /** * Activity action: Launch UI to review app uses of permissions. * <p> * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission name @@ -2020,11 +2029,16 @@ public class Intent implements Parcelable, Cloneable { * {@link #EXTRA_PERMISSION_NAME}. * </p> * <p> + * Input: {@link #EXTRA_DURATION_MILLIS} specifies the minimum number of milliseconds of recent + * activity to show (optional). Must be non-negative. + * </p> + * <p> * Output: Nothing. * </p> * * @see #EXTRA_PERMISSION_NAME * @see #EXTRA_PERMISSION_GROUP_NAME + * @see #EXTRA_DURATION_MILLIS * * @hide */ @@ -3192,7 +3206,18 @@ public class Intent implements Parcelable, Cloneable { * * <p class="note">This is a protected intent that can only be sent * by the system. + * + * <p class="note">If the user has chosen a {@link android.telecom.CallRedirectionService} to + * handle redirection of outgoing calls, this intent will NOT be sent as an ordered broadcast. + * This means that attempts to re-write the outgoing call by other apps using this intent will + * be ignored. + * </p> + * + * @deprecated Apps that redirect outgoing calls should use the + * {@link android.telecom.CallRedirectionService} API. Apps that perform call screening + * should use the {@link android.telecom.CallScreeningService} API. */ + @Deprecated @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"; diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 7a2220bfddb6..8e72fa5e1cfd 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -54,6 +54,7 @@ public class OverlayManager { this(context, IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE))); } + /** * Request that an overlay package is enabled and any other overlay packages with the same * target package and category are disabled. @@ -75,6 +76,26 @@ public class OverlayManager { } /** + * Request that an overlay package is enabled. + * + * @param packageName the name of the overlay package to enable. + * @param enable {@code false} if the overlay should be turned off. + * @param userId The user for which to change the overlay. + * @return true if the system successfully registered the request, false otherwise. + * + * @hide + */ + @SystemApi + public boolean setEnabled(@Nullable final String packageName, final boolean enable, + int userId) { + try { + return mService.setEnabled(packageName, enable, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns information about all overlays for the given target package for * the specified user. The returned list is ordered according to the * overlay priority with the highest priority at the end of the list. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e73322cc86ca..9e2f31684395 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2060,6 +2060,14 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a secure implementation of keyguard, meaning the + * device supports PIN, pattern and password as defined in Android CDD + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device includes an accelerometer. */ @SdkConstant(SdkConstantType.FEATURE) diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index c7320b0d6ccf..c9a4c8270390 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -794,6 +794,12 @@ public abstract class PackageManagerInternal { "android.content.pm.extra.ENABLE_ROLLBACK_INSTALL_FLAGS"; /** + * Extra field name for the set of installed users for a given rollback package. + */ + public static final String EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS = + "android.content.pm.extra.ENABLE_ROLLBACK_INSTALLED_USERS"; + + /** * Used as the {@code enableRollbackCode} argument for * {@link PackageManagerInternal#setEnableRollbackCode} to indicate that * enabling rollback succeeded. @@ -827,4 +833,10 @@ public abstract class PackageManagerInternal { * Ask the package manager to compile layouts in the given package. */ public abstract boolean compileLayouts(String packageName); + + /* + * Inform the package manager that the pending package install identified by + * {@code token} can be completed. + */ + public abstract void finishPackageInstall(int token, boolean didLaunch); } diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl index 7f557cd8bbe8..420bcb69e0c4 100644 --- a/core/java/android/content/rollback/IRollbackManager.aidl +++ b/core/java/android/content/rollback/IRollbackManager.aidl @@ -33,6 +33,12 @@ interface IRollbackManager { void executeRollback(in RollbackInfo rollback, String callerPackageName, in IntentSender statusReceiver); + // Exposed for use from the system server only. Callback from the package + // manager during the install flow when user data can be restored for a given + // package. + void restoreUserData(String packageName, int userId, int appId, long ceDataInode, + String seInfo, int token); + // Exposed for test purposes only. void reloadPersistedData(); diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index 70a9f08ea58a..fa335c8b9924 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -17,6 +17,7 @@ package android.hardware.display; import android.Manifest; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -30,6 +31,9 @@ import android.os.ServiceManager.ServiceNotFoundException; import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Manages the display's color transforms and modes. * @@ -39,6 +43,44 @@ import com.android.internal.R; @SystemService(Context.COLOR_DISPLAY_SERVICE) public final class ColorDisplayManager { + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CAPABILITY_NONE, CAPABILITY_PROTECTED_CONTENT, CAPABILITY_HARDWARE_ACCELERATION_GLOBAL, + CAPABILITY_HARDWARE_ACCELERATION_PER_APP}) + public @interface CapabilityType {} + + /** + * The device does not support color transforms. + * + * @hide + */ + @SystemApi + public static final int CAPABILITY_NONE = 0x0; + /** + * The device can properly apply transforms over protected content. + * + * @hide + */ + @SystemApi + public static final int CAPABILITY_PROTECTED_CONTENT = 0x1; + /** + * The device's hardware can efficiently apply transforms to the entire display. + * + * @hide + */ + @SystemApi + public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 0x2; + /** + * The device's hardware can efficiently apply transforms to a specific Surface (window) so + * that apps can be transformed independently of one another. + * + * @hide + */ + @SystemApi + public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 0x4; + private final ColorDisplayManagerInternal mManager; /** @@ -114,6 +156,17 @@ public final class ColorDisplayManager { return context.getResources().getBoolean(R.bool.config_setColorTransformAccelerated); } + /** + * Returns the available software and hardware color transform capabilities of this device. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public @CapabilityType int getTransformCapabilities() { + return mManager.getTransformCapabilities(); + } + private static class ColorDisplayManagerInternal { private static ColorDisplayManagerInternal sInstance; @@ -162,5 +215,13 @@ public final class ColorDisplayManager { throw e.rethrowFromSystemServer(); } } + + int getTransformCapabilities() { + try { + return mCdm.getTransformCapabilities(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } } diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl index 644f510d45f9..53cb8db8cc3d 100644 --- a/core/java/android/hardware/display/IColorDisplayManager.aidl +++ b/core/java/android/hardware/display/IColorDisplayManager.aidl @@ -22,4 +22,6 @@ interface IColorDisplayManager { boolean setSaturationLevel(int saturationLevel); boolean setAppSaturationLevel(String packageName, int saturationLevel); + + int getTransformCapabilities(); }
\ No newline at end of file diff --git a/core/java/android/hardware/hdmi/HdmiUtils.java b/core/java/android/hardware/hdmi/HdmiUtils.java index 308173816f13..8c94b7841a80 100644 --- a/core/java/android/hardware/hdmi/HdmiUtils.java +++ b/core/java/android/hardware/hdmi/HdmiUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,18 @@ package android.hardware.hdmi; +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** - * Various utilities to handle HDMI CEC messages. + * Various utilities related to HDMI CEC. * * TODO(b/110094868): unhide for Q * @hide */ -public class HdmiUtils { - +public final class HdmiUtils { /** * Return value of {@link #getLocalPortFromPhysicalAddress(int, int)} */ @@ -78,4 +82,164 @@ public class HdmiUtils { } return port; } + + /** + * TODO(b/110094868): unhide for Q + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({HDMI_RELATIVE_POSITION_UNKNOWN, HDMI_RELATIVE_POSITION_DIRECTLY_BELOW, + HDMI_RELATIVE_POSITION_BELOW, HDMI_RELATIVE_POSITION_SAME, + HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE, HDMI_RELATIVE_POSITION_ABOVE, + HDMI_RELATIVE_POSITION_SIBLING, HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH}) + public @interface HdmiAddressRelativePosition {} + /** + * HDMI relative position is not determined. + * TODO(b/110094868): unhide for Q + * @hide + */ + public static final int HDMI_RELATIVE_POSITION_UNKNOWN = 0; + /** + * HDMI relative position: directly blow the device. + * TODO(b/110094868): unhide for Q + * @hide + */ + public static final int HDMI_RELATIVE_POSITION_DIRECTLY_BELOW = 1; + /** + * HDMI relative position: indirectly below the device. + * TODO(b/110094868): unhide for Q + * @hide + */ + public static final int HDMI_RELATIVE_POSITION_BELOW = 2; + /** + * HDMI relative position: the same device. + * TODO(b/110094868): unhide for Q + * @hide + */ + public static final int HDMI_RELATIVE_POSITION_SAME = 3; + /** + * HDMI relative position: directly above the device. + * TODO(b/110094868): unhide for Q + * @hide + */ + public static final int HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE = 4; + /** + * HDMI relative position: indirectly above the device. + * TODO(b/110094868): unhide for Q + * @hide + */ + public static final int HDMI_RELATIVE_POSITION_ABOVE = 5; + /** + * HDMI relative position: directly below a same device. + * TODO(b/110094868): unhide for Q + * @hide + */ + public static final int HDMI_RELATIVE_POSITION_SIBLING = 6; + /** + * HDMI relative position: different branch. + * TODO(b/110094868): unhide for Q + * @hide + */ + public static final int HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH = 7; + + private static final int NPOS = -1; + + /** + * Check if the given physical address is valid. + * + * @param address physical address + * @return {@code true} if the given address is valid + */ + public static boolean isValidPhysicalAddress(int address) { + if (address < 0 || address >= 0xFFFF) { + return false; + } + int mask = 0xF000; + boolean hasZero = false; + for (int i = 0; i < 4; i++) { + if ((address & mask) == 0) { + hasZero = true; + } else if (hasZero) { + // only 0s are valid after a 0. + // e.g. 0x1012 is not valid. + return false; + } + mask >>= 4; + } + return true; + } + + + /** + * Returns the relative position of two physical addresses. + */ + @HdmiAddressRelativePosition + public static int getHdmiAddressRelativePosition(int src, int dest) { + if (src == 0xFFFF || dest == 0xFFFF) { + // address not assigned + return HDMI_RELATIVE_POSITION_UNKNOWN; + } + try { + int firstDiffPos = physicalAddressFirstDifferentDigitPos(src, dest); + if (firstDiffPos == NPOS) { + return HDMI_RELATIVE_POSITION_SAME; + } + int mask = (0xF000 >> (firstDiffPos * 4)); + int nextPos = firstDiffPos + 1; + if ((src & mask) == 0) { + // src is above dest + if (nextPos == 4) { + // last digits are different + return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE; + } + if (((0xF000 >> (nextPos * 4)) & dest) == 0) { + // next digit is 0 + return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE; + } + return HDMI_RELATIVE_POSITION_ABOVE; + } + + if ((dest & mask) == 0) { + // src is below dest + if (nextPos == 4) { + // last digits are different + return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW; + } + if (((0xF000 >> (nextPos * 4)) & src) == 0) { + // next digit is 0 + return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW; + } + return HDMI_RELATIVE_POSITION_BELOW; + } + if (nextPos == 4) { + // last digits are different + return HDMI_RELATIVE_POSITION_SIBLING; + } + if (((0xF000 >> (nextPos * 4)) & src) == 0 && ((0xF000 >> (nextPos * 4)) & dest) == 0) { + return HDMI_RELATIVE_POSITION_SIBLING; + } + return HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH; + } catch (IllegalArgumentException e) { + // invalid address + return HDMI_RELATIVE_POSITION_UNKNOWN; + } + } + + private static int physicalAddressFirstDifferentDigitPos(int address1, int address2) + throws IllegalArgumentException { + if (!isValidPhysicalAddress(address1)) { + throw new IllegalArgumentException(address1 + " is not a valid address."); + } + if (!isValidPhysicalAddress(address2)) { + throw new IllegalArgumentException(address2 + " is not a valid address."); + } + int mask = 0xF000; + for (int i = 0; i < 4; i++) { + if ((address1 & mask) != (address2 & mask)) { + return i; + } + mask = mask >> 4; + } + return NPOS; + } } diff --git a/core/java/android/hardware/location/ActivityChangedEvent.aidl b/core/java/android/hardware/location/ActivityChangedEvent.aidl new file mode 100644 index 000000000000..21f24459ac85 --- /dev/null +++ b/core/java/android/hardware/location/ActivityChangedEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.hardware.location; + +parcelable ActivityChangedEvent;
\ No newline at end of file diff --git a/core/java/android/hardware/location/ActivityChangedEvent.java b/core/java/android/hardware/location/ActivityChangedEvent.java new file mode 100644 index 000000000000..16cfe6e23e88 --- /dev/null +++ b/core/java/android/hardware/location/ActivityChangedEvent.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.hardware.location; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.security.InvalidParameterException; +import java.util.Arrays; +import java.util.List; + +/** + * A class representing an event for Activity changes. + * + * @hide + */ +public class ActivityChangedEvent implements Parcelable { + private final List<ActivityRecognitionEvent> mActivityRecognitionEvents; + + public ActivityChangedEvent(ActivityRecognitionEvent[] activityRecognitionEvents) { + if (activityRecognitionEvents == null) { + throw new InvalidParameterException( + "Parameter 'activityRecognitionEvents' must not be null."); + } + + mActivityRecognitionEvents = Arrays.asList(activityRecognitionEvents); + } + + @NonNull + public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() { + return mActivityRecognitionEvents; + } + + public static final Creator<ActivityChangedEvent> CREATOR = + new Creator<ActivityChangedEvent>() { + @Override + public ActivityChangedEvent createFromParcel(Parcel source) { + int activityRecognitionEventsLength = source.readInt(); + ActivityRecognitionEvent[] activityRecognitionEvents = + new ActivityRecognitionEvent[activityRecognitionEventsLength]; + source.readTypedArray(activityRecognitionEvents, ActivityRecognitionEvent.CREATOR); + + return new ActivityChangedEvent(activityRecognitionEvents); + } + + @Override + public ActivityChangedEvent[] newArray(int size) { + return new ActivityChangedEvent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + ActivityRecognitionEvent[] activityRecognitionEventArray = + mActivityRecognitionEvents.toArray(new ActivityRecognitionEvent[0]); + parcel.writeInt(activityRecognitionEventArray.length); + parcel.writeTypedArray(activityRecognitionEventArray, flags); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:"); + + for (ActivityRecognitionEvent event : mActivityRecognitionEvents) { + builder.append("\n "); + builder.append(event.toString()); + } + builder.append("\n]"); + + return builder.toString(); + } +} diff --git a/core/java/android/hardware/location/ActivityRecognitionEvent.java b/core/java/android/hardware/location/ActivityRecognitionEvent.java new file mode 100644 index 000000000000..190030a82d6b --- /dev/null +++ b/core/java/android/hardware/location/ActivityRecognitionEvent.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.location; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class that represents an Activity Recognition Event. + * + * @hide + */ +public class ActivityRecognitionEvent implements Parcelable { + private final String mActivity; + private final int mEventType; + private final long mTimestampNs; + + public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) { + mActivity = activity; + mEventType = eventType; + mTimestampNs = timestampNs; + } + + public String getActivity() { + return mActivity; + } + + public int getEventType() { + return mEventType; + } + + public long getTimestampNs() { + return mTimestampNs; + } + + public static final Creator<ActivityRecognitionEvent> CREATOR = + new Creator<ActivityRecognitionEvent>() { + @Override + public ActivityRecognitionEvent createFromParcel(Parcel source) { + String activity = source.readString(); + int eventType = source.readInt(); + long timestampNs = source.readLong(); + + return new ActivityRecognitionEvent(activity, eventType, timestampNs); + } + + @Override + public ActivityRecognitionEvent[] newArray(int size) { + return new ActivityRecognitionEvent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mActivity); + parcel.writeInt(mEventType); + parcel.writeLong(mTimestampNs); + } + + @Override + public String toString() { + return String.format( + "Activity='%s', EventType=%s, TimestampNs=%s", + mActivity, + mEventType, + mTimestampNs); + } +} diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java new file mode 100644 index 000000000000..8acd1ff27917 --- /dev/null +++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.hardware.location; + +import android.Manifest; +import android.content.Context; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +/** + * A class that implements an {@link IActivityRecognitionHardware} backed up by the Activity + * Recognition HAL. + * + * @hide + */ +public class ActivityRecognitionHardware extends IActivityRecognitionHardware.Stub { + private static final String TAG = "ActivityRecognitionHW"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE; + private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '" + + HARDWARE_PERMISSION + "' not granted to access ActivityRecognitionHardware"; + + private static final int INVALID_ACTIVITY_TYPE = -1; + private static final int NATIVE_SUCCESS_RESULT = 0; + private static final int EVENT_TYPE_DISABLED = 0; + private static final int EVENT_TYPE_ENABLED = 1; + + /** + * Contains the number of supported Event Types. + * + * NOTE: increment this counter every time a new EVENT_TYPE_ is added to + * com.android.location.provider.ActivityRecognitionProvider + */ + private static final int EVENT_TYPE_COUNT = 3; + + private static ActivityRecognitionHardware sSingletonInstance; + private static final Object sSingletonInstanceLock = new Object(); + + private final Context mContext; + private final int mSupportedActivitiesCount; + private final String[] mSupportedActivities; + private final int[][] mSupportedActivitiesEnabledEvents; + private final SinkList mSinks = new SinkList(); + + private static class Event { + public int activity; + public int type; + public long timestamp; + } + + private ActivityRecognitionHardware(Context context) { + nativeInitialize(); + + mContext = context; + mSupportedActivities = fetchSupportedActivities(); + mSupportedActivitiesCount = mSupportedActivities.length; + mSupportedActivitiesEnabledEvents = new int[mSupportedActivitiesCount][EVENT_TYPE_COUNT]; + } + + public static ActivityRecognitionHardware getInstance(Context context) { + synchronized (sSingletonInstanceLock) { + if (sSingletonInstance == null) { + sSingletonInstance = new ActivityRecognitionHardware(context); + } + + return sSingletonInstance; + } + } + + public static boolean isSupported() { + return nativeIsSupported(); + } + + @Override + public String[] getSupportedActivities() { + checkPermissions(); + return mSupportedActivities; + } + + @Override + public boolean isActivitySupported(String activity) { + checkPermissions(); + int activityType = getActivityType(activity); + return activityType != INVALID_ACTIVITY_TYPE; + } + + @Override + public boolean registerSink(IActivityRecognitionHardwareSink sink) { + checkPermissions(); + return mSinks.register(sink); + } + + @Override + public boolean unregisterSink(IActivityRecognitionHardwareSink sink) { + checkPermissions(); + return mSinks.unregister(sink); + } + + @Override + public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) { + checkPermissions(); + + int activityType = getActivityType(activity); + if (activityType == INVALID_ACTIVITY_TYPE) { + return false; + } + + int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs); + if (result == NATIVE_SUCCESS_RESULT) { + mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_ENABLED; + return true; + } + return false; + } + + @Override + public boolean disableActivityEvent(String activity, int eventType) { + checkPermissions(); + + int activityType = getActivityType(activity); + if (activityType == INVALID_ACTIVITY_TYPE) { + return false; + } + + int result = nativeDisableActivityEvent(activityType, eventType); + if (result == NATIVE_SUCCESS_RESULT) { + mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED; + return true; + } + return false; + } + + @Override + public boolean flush() { + checkPermissions(); + int result = nativeFlush(); + return result == NATIVE_SUCCESS_RESULT; + } + + /** + * Called by the Activity-Recognition HAL. + */ + private void onActivityChanged(Event[] events) { + if (events == null || events.length == 0) { + if (DEBUG) Log.d(TAG, "No events to broadcast for onActivityChanged."); + return; + } + + int eventsLength = events.length; + ActivityRecognitionEvent activityRecognitionEventArray[] = + new ActivityRecognitionEvent[eventsLength]; + for (int i = 0; i < eventsLength; ++i) { + Event event = events[i]; + String activityName = getActivityName(event.activity); + activityRecognitionEventArray[i] = + new ActivityRecognitionEvent(activityName, event.type, event.timestamp); + } + ActivityChangedEvent activityChangedEvent = + new ActivityChangedEvent(activityRecognitionEventArray); + + int size = mSinks.beginBroadcast(); + for (int i = 0; i < size; ++i) { + IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i); + try { + sink.onActivityChanged(activityChangedEvent); + } catch (RemoteException e) { + Log.e(TAG, "Error delivering activity changed event.", e); + } + } + mSinks.finishBroadcast(); + } + + private String getActivityName(int activityType) { + if (activityType < 0 || activityType >= mSupportedActivities.length) { + String message = String.format( + "Invalid ActivityType: %d, SupportedActivities: %d", + activityType, + mSupportedActivities.length); + Log.e(TAG, message); + return null; + } + + return mSupportedActivities[activityType]; + } + + private int getActivityType(String activity) { + if (TextUtils.isEmpty(activity)) { + return INVALID_ACTIVITY_TYPE; + } + + int supportedActivitiesLength = mSupportedActivities.length; + for (int i = 0; i < supportedActivitiesLength; ++i) { + if (activity.equals(mSupportedActivities[i])) { + return i; + } + } + + return INVALID_ACTIVITY_TYPE; + } + + private void checkPermissions() { + mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE); + } + + private String[] fetchSupportedActivities() { + String[] supportedActivities = nativeGetSupportedActivities(); + if (supportedActivities != null) { + return supportedActivities; + } + + return new String[0]; + } + + private class SinkList extends RemoteCallbackList<IActivityRecognitionHardwareSink> { + @Override + public void onCallbackDied(IActivityRecognitionHardwareSink callback) { + int callbackCount = mSinks.getRegisteredCallbackCount(); + if (DEBUG) Log.d(TAG, "RegisteredCallbackCount: " + callbackCount); + if (callbackCount != 0) { + return; + } + // currently there is only one client for this, so if all its sinks have died, we clean + // up after them, this ensures that the AR HAL is not out of sink + for (int activity = 0; activity < mSupportedActivitiesCount; ++activity) { + for (int event = 0; event < EVENT_TYPE_COUNT; ++event) { + disableActivityEventIfEnabled(activity, event); + } + } + } + + private void disableActivityEventIfEnabled(int activityType, int eventType) { + if (mSupportedActivitiesEnabledEvents[activityType][eventType] != EVENT_TYPE_ENABLED) { + return; + } + + int result = nativeDisableActivityEvent(activityType, eventType); + mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED; + String message = String.format( + "DisableActivityEvent: activityType=%d, eventType=%d, result=%d", + activityType, + eventType, + result); + Log.e(TAG, message); + } + } + + // native bindings + static { nativeClassInit(); } + + private static native void nativeClassInit(); + private static native boolean nativeIsSupported(); + + private native void nativeInitialize(); + private native void nativeRelease(); + private native String[] nativeGetSupportedActivities(); + private native int nativeEnableActivityEvent( + int activityType, + int eventType, + long reportLatenceNs); + private native int nativeDisableActivityEvent(int activityType, int eventType); + private native int nativeFlush(); +} diff --git a/core/java/android/hardware/location/IActivityRecognitionHardware.aidl b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl new file mode 100644 index 000000000000..bc6b1830cd4f --- /dev/null +++ b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/license/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.location; + +import android.hardware.location.IActivityRecognitionHardwareSink; + +/** + * Activity Recognition Hardware provider interface. + * This interface can be used to implement hardware based activity recognition. + * + * @hide + */ +interface IActivityRecognitionHardware { + /** + * Gets an array of supported activities by hardware. + */ + String[] getSupportedActivities(); + + /** + * Returns true if the given activity is supported, false otherwise. + */ + boolean isActivitySupported(in String activityType); + + /** + * Registers a sink with Hardware Activity-Recognition. + */ + boolean registerSink(in IActivityRecognitionHardwareSink sink); + + /** + * Unregisters a sink with Hardware Activity-Recognition. + */ + boolean unregisterSink(in IActivityRecognitionHardwareSink sink); + + /** + * Enables tracking of a given activity/event type, if the activity is supported. + */ + boolean enableActivityEvent(in String activityType, int eventType, long reportLatencyNs); + + /** + * Disables tracking of a given activity/eventy type. + */ + boolean disableActivityEvent(in String activityType, int eventType); + + /** + * Requests hardware for all the activity events detected up to the given point in time. + */ + boolean flush(); +}
\ No newline at end of file diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl new file mode 100644 index 000000000000..3fe645c59a30 --- /dev/null +++ b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/license/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.location; + +import android.hardware.location.IActivityRecognitionHardware; + +/** + * Activity Recognition Hardware client interface. + * This interface can be used to receive interfaces to implementations of + * {@link IActivityRecognitionHardware}. + * + * @hide + */ +oneway interface IActivityRecognitionHardwareClient { + /** + * Hardware Activity-Recognition availability event. + * + * @param isSupported whether the platform has hardware support for the feature + * @param instance the available instance to provide access to the feature + */ + void onAvailabilityChanged(in boolean isSupported, in IActivityRecognitionHardware instance); +} diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl new file mode 100644 index 000000000000..21c8e87e6c6d --- /dev/null +++ b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/license/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.location; + +import android.hardware.location.ActivityChangedEvent; + +/** + * Activity Recognition Hardware provider Sink interface. + * This interface can be used to implement sinks to receive activity notifications. + * + * @hide + */ +interface IActivityRecognitionHardwareSink { + /** + * Activity changed event. + */ + void onActivityChanged(in ActivityChangedEvent event); +}
\ No newline at end of file diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl new file mode 100644 index 000000000000..12e3117259e1 --- /dev/null +++ b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/license/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.location; + +import android.hardware.location.IActivityRecognitionHardware; + +/** + * Activity Recognition Hardware watcher. This interface can be used to receive interfaces to + * implementations of {@link IActivityRecognitionHardware}. + * + * @deprecated use {@link IActivityRecognitionHardwareClient} instead. + + * @hide + */ +interface IActivityRecognitionHardwareWatcher { + /** + * Hardware Activity-Recognition availability event. + */ + void onInstanceChanged(in IActivityRecognitionHardware instance); +} diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java index 4047068f1c7b..3b0126673779 100644 --- a/core/java/android/net/CaptivePortal.java +++ b/core/java/android/net/CaptivePortal.java @@ -45,6 +45,8 @@ public class CaptivePortal implements Parcelable { private final IBinder mBinder; /** @hide */ + @SystemApi + @TestApi public CaptivePortal(IBinder binder) { mBinder = binder; } @@ -107,6 +109,8 @@ public class CaptivePortal implements Parcelable { * connectivity for apps because the captive portal is still in place. * @hide */ + @SystemApi + @TestApi public void useNetwork() { try { ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index cee3a409fc23..c809ccad5907 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -3699,6 +3699,19 @@ public class ConnectivityManager { } /** + * Determine whether the device is configured to avoid bad wifi. + * @hide + */ + @SystemApi + public boolean getAvoidBadWifi() { + try { + return mService.getAvoidBadWifi(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * It is acceptable to briefly use multipath data to provide seamless connectivity for * time-sensitive user-facing operations when the system default network is temporarily * unresponsive. The amount of data should be limited (less than one megabyte for every call to diff --git a/core/java/android/net/DnsPacket.java b/core/java/android/net/DnsPacket.java new file mode 100644 index 000000000000..458fb340b196 --- /dev/null +++ b/core/java/android/net/DnsPacket.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.annotation.Nullable; +import android.text.TextUtils; + +import com.android.internal.util.BitUtils; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +/** + * Defines basic data for DNS protocol based on RFC 1035. + * Subclasses create the specific format used in DNS packet. + * + * @hide + */ +public abstract class DnsPacket { + public class DnsHeader { + private static final String TAG = "DnsHeader"; + public final int id; + public final int flags; + public final int rcode; + private final int[] mSectionCount; + + /** + * Create a new DnsHeader from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS header. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + */ + DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { + id = BitUtils.uint16(buf.getShort()); + flags = BitUtils.uint16(buf.getShort()); + rcode = flags & 0xF; + mSectionCount = new int[NUM_SECTIONS]; + for (int i = 0; i < NUM_SECTIONS; ++i) { + mSectionCount[i] = BitUtils.uint16(buf.getShort()); + } + } + + /** + * Get section count by section type. + */ + public int getSectionCount(int sectionType) { + return mSectionCount[sectionType]; + } + } + + public class DnsSection { + private static final int MAXNAMESIZE = 255; + private static final int MAXLABELSIZE = 63; + private static final int MAXLABELCOUNT = 128; + private static final int NAME_NORMAL = 0; + private static final int NAME_COMPRESSION = 0xC0; + private final DecimalFormat byteFormat = new DecimalFormat(); + private final FieldPosition pos = new FieldPosition(0); + + private static final String TAG = "DnsSection"; + + public final String dName; + public final int nsType; + public final int nsClass; + public final long ttl; + private final byte[] mRR; + + /** + * Create a new DnsSection from a positioned ByteBuffer. + * + * The ByteBuffer must be in network byte order (which is the default). + * Reads the passed ByteBuffer from its current position and decodes a DNS section. + * When this constructor returns, the reading position of the ByteBuffer has been + * advanced to the end of the DNS header record. + * This is meant to chain with other methods reading a DNS response in sequence. + * + */ + DnsSection(int sectionType, @NonNull ByteBuffer buf) + throws BufferUnderflowException, ParseException { + dName = parseName(buf, 0 /* Parse depth */); + if (dName.length() > MAXNAMESIZE) { + throw new ParseException("Parse name fail, name size is too long"); + } + nsType = BitUtils.uint16(buf.getShort()); + nsClass = BitUtils.uint16(buf.getShort()); + + if (sectionType != QDSECTION) { + ttl = BitUtils.uint32(buf.getInt()); + final int length = BitUtils.uint16(buf.getShort()); + mRR = new byte[length]; + buf.get(mRR); + } else { + ttl = 0; + mRR = null; + } + } + + /** + * Get a copy of rr. + */ + @Nullable public byte[] getRR() { + return (mRR == null) ? null : mRR.clone(); + } + + /** + * Convert label from {@code byte[]} to {@code String} + * + * It follows the same converting rule as native layer. + * (See ns_name.c in libc) + * + */ + private String labelToString(@NonNull byte[] label) { + final StringBuffer sb = new StringBuffer(); + for (int i = 0; i < label.length; ++i) { + int b = BitUtils.uint8(label[i]); + // Control characters and non-ASCII characters. + if (b <= 0x20 || b >= 0x7f) { + sb.append('\\'); + byteFormat.format(b, sb, pos); + } else if (b == '"' || b == '.' || b == ';' || b == '\\' + || b == '(' || b == ')' || b == '@' || b == '$') { + sb.append('\\'); + sb.append((char) b); + } else { + sb.append((char) b); + } + } + return sb.toString(); + } + + private String parseName(@NonNull ByteBuffer buf, int depth) throws + BufferUnderflowException, ParseException { + if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels"); + final int len = BitUtils.uint8(buf.get()); + final int mask = len & NAME_COMPRESSION; + if (0 == len) { + return ""; + } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) { + throw new ParseException("Parse name fail, bad label type"); + } else if (mask == NAME_COMPRESSION) { + // Name compression based on RFC 1035 - 4.1.4 Message compression + final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get()); + final int oldPos = buf.position(); + if (offset >= oldPos - 2) { + throw new ParseException("Parse compression name fail, invalid compression"); + } + buf.position(offset); + final String pointed = parseName(buf, depth + 1); + buf.position(oldPos); + return pointed; + } else { + final byte[] label = new byte[len]; + buf.get(label); + final String head = labelToString(label); + if (head.length() > MAXLABELSIZE) { + throw new ParseException("Parse name fail, invalid label length"); + } + final String tail = parseName(buf, depth + 1); + return TextUtils.isEmpty(tail) ? head : head + "." + tail; + } + } + } + + public static final int QDSECTION = 0; + public static final int ANSECTION = 1; + public static final int NSSECTION = 2; + public static final int ARSECTION = 3; + private static final int NUM_SECTIONS = ARSECTION + 1; + + private static final String TAG = DnsPacket.class.getSimpleName(); + + protected final DnsHeader mHeader; + protected final List<DnsSection>[] mSections; + + public static class ParseException extends Exception { + public ParseException(String msg) { + super(msg); + } + + public ParseException(String msg, Throwable cause) { + super(msg, cause); + } + } + + protected DnsPacket(@NonNull byte[] data) throws ParseException { + if (null == data) throw new ParseException("Parse header failed, null input data"); + final ByteBuffer buffer; + try { + buffer = ByteBuffer.wrap(data); + mHeader = new DnsHeader(buffer); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse Header fail, bad input data", e); + } + + mSections = new ArrayList[NUM_SECTIONS]; + + for (int i = 0; i < NUM_SECTIONS; ++i) { + final int count = mHeader.getSectionCount(i); + if (count > 0) { + mSections[i] = new ArrayList(count); + } + for (int j = 0; j < count; ++j) { + try { + mSections[i].add(new DnsSection(i, buffer)); + } catch (BufferUnderflowException e) { + throw new ParseException("Parse section fail", e); + } + } + } + } +} diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java new file mode 100644 index 000000000000..6d54264cd89f --- /dev/null +++ b/core/java/android/net/DnsResolver.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 static android.net.NetworkUtils.resNetworkQuery; +import static android.net.NetworkUtils.resNetworkResult; +import static android.net.NetworkUtils.resNetworkSend; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + + +/** + * Dns resolver class for asynchronous dns querying + * + */ +public final class DnsResolver { + private static final String TAG = "DnsResolver"; + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + private static final int MAXPACKET = 8 * 1024; + + @IntDef(prefix = { "CLASS_" }, value = { + CLASS_IN + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryClass {} + public static final int CLASS_IN = 1; + + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_A, + TYPE_AAAA + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryType {} + public static final int TYPE_A = 1; + public static final int TYPE_AAAA = 28; + + @IntDef(prefix = { "FLAG_" }, value = { + FLAG_EMPTY, + FLAG_NO_RETRY, + FLAG_NO_CACHE_STORE, + FLAG_NO_CACHE_LOOKUP + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryFlag {} + public static final int FLAG_EMPTY = 0; + public static final int FLAG_NO_RETRY = 1 << 0; + public static final int FLAG_NO_CACHE_STORE = 1 << 1; + public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2; + + private static final int DNS_RAW_RESPONSE = 1; + + private static final int NETID_UNSET = 0; + + private static final DnsResolver sInstance = new DnsResolver(); + + /** + * listener for receiving raw answers + */ + public interface RawAnswerListener { + /** + * {@code byte[]} is {@code null} if query timed out + */ + void onAnswer(@Nullable byte[] answer); + } + + /** + * listener for receiving parsed answers + */ + public interface InetAddressAnswerListener { + /** + * Will be called exactly once with all the answers to the query. + * size of addresses will be zero if no available answer could be parsed. + */ + void onAnswer(@NonNull List<InetAddress> addresses); + } + + /** + * Get instance for DnsResolver + */ + public static DnsResolver getInstance() { + return sInstance; + } + + private DnsResolver() {} + + /** + * Pass in a blob and corresponding setting, + * get a blob back asynchronously with the entire raw answer. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param query blob message + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link RawAnswerListener} will be invoked. + * @param listener a {@link RawAnswerListener} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags, + @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException { + final FileDescriptor queryfd = resNetworkSend((network != null + ? network.netId : NETID_UNSET), query, query.length, flags); + registerFDListener(handler.getLooper().getQueue(), queryfd, + answerbuf -> listener.onAnswer(answerbuf)); + } + + /** + * Pass in a domain name and corresponding setting, + * get a blob back asynchronously with the entire raw answer. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param domain domain name for querying + * @param nsClass dns class as one of the CLASS_* constants + * @param nsType dns resource record (RR) type as one of the TYPE_* constants + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link RawAnswerListener} will be invoked. + * @param listener a {@link RawAnswerListener} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass, + @QueryType int nsType, @QueryFlag int flags, + @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException { + final FileDescriptor queryfd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags); + registerFDListener(handler.getLooper().getQueue(), queryfd, + answerbuf -> listener.onAnswer(answerbuf)); + } + + /** + * Pass in a domain name and corresponding setting, + * get back a set of InetAddresses asynchronously. + * + * @param network {@link Network} specifying which network for querying. + * {@code null} for query on default network. + * @param domain domain name for querying + * @param flags flags as a combination of the FLAGS_* constants + * @param handler {@link Handler} to specify the thread + * upon which the {@link InetAddressAnswerListener} will be invoked. + * @param listener an {@link InetAddressAnswerListener} which will be called to + * notify the caller of the result of dns query. + * + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags, + @NonNull Handler handler, @NonNull InetAddressAnswerListener listener) + throws ErrnoException { + final FileDescriptor v4fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags); + final FileDescriptor v6fd = resNetworkQuery((network != null + ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags); + + final InetAddressAnswerAccumulator accmulator = + new InetAddressAnswerAccumulator(2, listener); + final Consumer<byte[]> consumer = answerbuf -> + accmulator.accumulate(parseAnswers(answerbuf)); + + registerFDListener(handler.getLooper().getQueue(), v4fd, consumer); + registerFDListener(handler.getLooper().getQueue(), v6fd, consumer); + } + + private void registerFDListener(@NonNull MessageQueue queue, + @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) { + queue.addOnFileDescriptorEventListener( + queryfd, + FD_EVENTS, + (fd, events) -> { + byte[] answerbuf = null; + try { + // TODO: Implement result function in Java side instead of using JNI + // Because JNI method close fd prior than unregistering fd on + // event listener. + answerbuf = resNetworkResult(fd); + } catch (ErrnoException e) { + Log.e(TAG, "resNetworkResult:" + e.toString()); + } + answerConsumer.accept(answerbuf); + + // Unregister this fd listener + return 0; + }); + } + + private class DnsAddressAnswer extends DnsPacket { + private static final String TAG = "DnsResolver.DnsAddressAnswer"; + private static final boolean DBG = false; + + private final int mQueryType; + + DnsAddressAnswer(@NonNull byte[] data) throws ParseException { + super(data); + if ((mHeader.flags & (1 << 15)) == 0) { + throw new ParseException("Not an answer packet"); + } + if (mHeader.rcode != 0) { + throw new ParseException("Response error, rcode:" + mHeader.rcode); + } + if (mHeader.getSectionCount(ANSECTION) == 0) { + throw new ParseException("No available answer"); + } + if (mHeader.getSectionCount(QDSECTION) == 0) { + throw new ParseException("No question found"); + } + // Assume only one question per answer packet. (RFC1035) + mQueryType = mSections[QDSECTION].get(0).nsType; + } + + public @NonNull List<InetAddress> getAddresses() { + final List<InetAddress> results = new ArrayList<InetAddress>(); + for (final DnsSection ansSec : mSections[ANSECTION]) { + // Only support A and AAAA, also ignore answers if query type != answer type. + int nsType = ansSec.nsType; + if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) { + continue; + } + try { + results.add(InetAddress.getByAddress(ansSec.getRR())); + } catch (UnknownHostException e) { + if (DBG) { + Log.w(TAG, "rr to address fail"); + } + } + } + return results; + } + } + + private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) { + try { + return (data == null) ? null : new DnsAddressAnswer(data).getAddresses(); + } catch (DnsPacket.ParseException e) { + Log.e(TAG, "Parse answer fail " + e.getMessage()); + return null; + } + } + + private class InetAddressAnswerAccumulator { + private final List<InetAddress> mAllAnswers; + private final InetAddressAnswerListener mAnswerListener; + private final int mTargetAnswerCount; + private int mReceivedAnswerCount = 0; + + InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) { + mTargetAnswerCount = size; + mAllAnswers = new ArrayList<>(); + mAnswerListener = listener; + } + + public void accumulate(@Nullable List<InetAddress> answer) { + if (null != answer) { + mAllAnswers.addAll(answer); + } + if (++mReceivedAnswerCount == mTargetAnswerCount) { + mAnswerListener.onAnswer(mAllAnswers); + } + } + } +} diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl index a013e79106e7..56ae57dc0e8d 100644 --- a/core/java/android/net/ICaptivePortal.aidl +++ b/core/java/android/net/ICaptivePortal.aidl @@ -20,7 +20,6 @@ package android.net; * Interface to inform NetworkMonitor of decisions of app handling captive portal. * @hide */ -interface ICaptivePortal -{ - oneway void appResponse(int response); +oneway interface ICaptivePortal { + void appResponse(int response); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index da5d96e49d02..131925ec28e9 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -165,6 +165,7 @@ interface IConnectivityManager void setAvoidUnvalidated(in Network network); void startCaptivePortalApp(in Network network); + boolean getAvoidBadWifi(); int getMultipathPreference(in Network Network); NetworkRequest getDefaultRequest(); @@ -187,4 +188,6 @@ interface IConnectivityManager byte[] getNetworkWatchlistConfigHash(); int getConnectionOwnerUid(in ConnectionInfo connectionInfo); + boolean isCallerCurrentAlwaysOnVpnApp(); + boolean isCallerCurrentAlwaysOnVpnLockdownApp(); } diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index c2963fd605c0..21b6a8eb1990 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -368,7 +369,8 @@ public final class LinkProperties implements Parcelable { * @return true if the DNS server was added, false if it was already present. * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean addDnsServer(InetAddress dnsServer) { if (dnsServer != null && !mDnses.contains(dnsServer)) { mDnses.add(dnsServer); @@ -384,7 +386,8 @@ public final class LinkProperties implements Parcelable { * @return true if the DNS server was removed, false if it did not exist. * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean removeDnsServer(InetAddress dnsServer) { if (dnsServer != null) { return mDnses.remove(dnsServer); @@ -423,6 +426,8 @@ public final class LinkProperties implements Parcelable { * @param usePrivateDns The private DNS state. * @hide */ + @TestApi + @SystemApi public void setUsePrivateDns(boolean usePrivateDns) { mUsePrivateDns = usePrivateDns; } @@ -448,6 +453,8 @@ public final class LinkProperties implements Parcelable { * @param privateDnsServerName The private DNS server name. * @hide */ + @TestApi + @SystemApi public void setPrivateDnsServerName(@Nullable String privateDnsServerName) { mPrivateDnsServerName = privateDnsServerName; } @@ -510,6 +517,8 @@ public final class LinkProperties implements Parcelable { * object. * @hide */ + @TestApi + @SystemApi public void setValidatedPrivateDnsServers(Collection<InetAddress> dnsServers) { mValidatedPrivateDnses.clear(); for (InetAddress dnsServer: dnsServers) { @@ -525,6 +534,8 @@ public final class LinkProperties implements Parcelable { * DNS servers on this link. * @hide */ + @TestApi + @SystemApi public List<InetAddress> getValidatedPrivateDnsServers() { return Collections.unmodifiableList(mValidatedPrivateDnses); } @@ -636,7 +647,8 @@ public final class LinkProperties implements Parcelable { * * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public void setTcpBufferSizes(String tcpBufferSizes) { mTcpBufferSizes = tcpBufferSizes; } @@ -648,7 +660,8 @@ public final class LinkProperties implements Parcelable { * * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public String getTcpBufferSizes() { return mTcpBufferSizes; } @@ -699,7 +712,8 @@ public final class LinkProperties implements Parcelable { * * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean removeRoute(RouteInfo route) { return route != null && Objects.equals(mIfaceName, route.getInterface()) && @@ -960,7 +974,8 @@ public final class LinkProperties implements Parcelable { * @return {@code true} if there is an IPv4 address, {@code false} otherwise. * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean hasIPv4Address() { for (LinkAddress address : mLinkAddresses) { if (address.getAddress() instanceof Inet4Address) { @@ -988,7 +1003,8 @@ public final class LinkProperties implements Parcelable { * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise. * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean hasGlobalIPv6Address() { for (LinkAddress address : mLinkAddresses) { if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) { @@ -1020,7 +1036,8 @@ public final class LinkProperties implements Parcelable { * @return {@code true} if there is an IPv6 default route, {@code false} otherwise. * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean hasIPv6DefaultRoute() { for (RouteInfo r : mRoutes) { if (r.isIPv6Default()) { @@ -1099,6 +1116,8 @@ public final class LinkProperties implements Parcelable { * @return {@code true} if the link is provisioned, {@code false} otherwise. * @hide */ + @TestApi + @SystemApi public boolean isIPv4Provisioned() { return (hasIPv4Address() && hasIPv4DefaultRoute() && @@ -1112,7 +1131,8 @@ public final class LinkProperties implements Parcelable { * @return {@code true} if the link is provisioned, {@code false} otherwise. * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean isIPv6Provisioned() { return (hasGlobalIPv6Address() && hasIPv6DefaultRoute() && @@ -1126,7 +1146,8 @@ public final class LinkProperties implements Parcelable { * @return {@code true} if the link is provisioned, {@code false} otherwise. * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean isProvisioned() { return (isIPv4Provisioned() || isIPv6Provisioned()); } @@ -1138,7 +1159,8 @@ public final class LinkProperties implements Parcelable { * {@code false} otherwise. * @hide */ - @UnsupportedAppUsage + @TestApi + @SystemApi public boolean isReachable(InetAddress ip) { final List<RouteInfo> allRoutes = getAllRoutes(); // If we don't have a route to this IP address, it's not reachable. diff --git a/core/java/android/net/LinkPropertiesParcelable.aidl b/core/java/android/net/LinkPropertiesParcelable.aidl index b153dc70e1b8..6b52239b4168 100644 --- a/core/java/android/net/LinkPropertiesParcelable.aidl +++ b/core/java/android/net/LinkPropertiesParcelable.aidl @@ -35,5 +35,4 @@ parcelable LinkPropertiesParcelable { int mtu; String tcpBufferSizes; IpPrefixParcelable nat64Prefix; - LinkPropertiesParcelable[] stackedLinks; }
\ No newline at end of file diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 1b44c920a205..7e9bda14b199 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -712,6 +712,7 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ @TestApi + @SystemApi public @Transport int[] getTransportTypes() { return BitUtils.unpackBits(mTransportTypes); } @@ -1312,6 +1313,8 @@ public final class NetworkCapabilities implements Parcelable { * * @hide */ + @TestApi + @SystemApi public boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc) { return satisfiedByNetworkCapabilities(nc, false); } diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 4eab49cd0fdf..c996d01fe8e4 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -39,6 +39,8 @@ import java.util.Collection; import java.util.Locale; import java.util.TreeSet; +import android.system.ErrnoException; + /** * Native methods for managing network interfaces. * @@ -138,6 +140,32 @@ public class NetworkUtils { public native static boolean queryUserAccess(int uid, int netId); /** + * DNS resolver series jni method. + * Issue the query {@code msg} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkSend( + int netId, byte[] msg, int msglen, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated + * with Domain Name {@code dname} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkQuery( + int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Read a result for the query associated with the {@code fd}. + * @return a byte array containing blob answer + */ + public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException; + + /** * Add an entry into the ARP cache. */ public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname, diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index abc1cac02c7e..90dccb5b82d5 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -86,7 +86,16 @@ import javax.net.ssl.X509TrustManager; * <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all * SSL certificate and hostname checks for testing purposes. This setting * requires root access. + * + * @deprecated This class has less error-prone replacements using standard APIs. To create an + * {@code SSLSocket}, obtain an {@link SSLSocketFactory} from {@link SSLSocketFactory#getDefault()} + * or {@link javax.net.ssl.SSLContext#getSocketFactory()}. To verify hostnames, pass + * {@code "HTTPS"} to + * {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}. To enable ALPN, + * use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(String[])}. To enable SNI, + * use {@link javax.net.ssl.SSLParameters#setServerNames(java.util.List)}. */ +@Deprecated public class SSLCertificateSocketFactory extends SSLSocketFactory { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private static final String TAG = "SSLCertificateSocketFactory"; diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index f0c0462cec18..37bf3a71ce62 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -368,6 +368,29 @@ public class VpnService extends Service { } /** + * Returns whether the service is running in always-on VPN mode. + */ + public final boolean isAlwaysOn() { + try { + return getService().isCallerCurrentAlwaysOnVpnApp(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns whether the service is running in always-on VPN mode blocking connections without + * VPN. + */ + public final boolean isLockdownEnabled() { + try { + return getService().isCallerCurrentAlwaysOnVpnLockdownApp(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the communication interface to the service. This method returns * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE} * action. Applications overriding this method must identify the intent diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl index 57f59a17cfe7..fb4ca3b97895 100644 --- a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl +++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl @@ -25,6 +25,6 @@ oneway interface IOnNetworkAttributesRetrieved { * Network attributes were fetched for the specified L2 key. While the L2 key will never * be null, the attributes may be if no data is stored about this L2 key. */ - void onL2KeyResponse(in StatusParcelable status, in String l2Key, + void onNetworkAttributesRetrieved(in StatusParcelable status, in String l2Key, in NetworkAttributesParcelable attributes); } diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java index b932d2197f85..6a9eae00e3ff 100644 --- a/core/java/android/net/ipmemorystore/NetworkAttributes.java +++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java @@ -37,27 +37,57 @@ import java.util.StringJoiner; public class NetworkAttributes { private static final boolean DBG = true; + // Weight cutoff for grouping. To group, a similarity score is computed with the following + // algorithm : if both fields are non-null and equals() then add their assigned weight, else if + // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT), + // otherwise add nothing. + // As a guideline, this should be something like 60~75% of the total weights in this class. The + // design states "in essence a reader should imagine that if two important columns don't match, + // or one important and several unimportant columns don't match then the two records are + // considered a different group". + private static final float TOTAL_WEIGHT_CUTOFF = 520.0f; + // The portion of the weight that is earned when scoring group-sameness by having both columns + // being null. This is because some networks rightfully don't have some attributes (e.g. a + // V6-only network won't have an assigned V4 address) and both being null should count for + // something, but attributes may also be null just because data is unavailable. + private static final float NULL_MATCH_WEIGHT = 0.25f; + // The v4 address that was assigned to this device the last time it joined this network. // This typically comes from DHCP but could be something else like static configuration. // This does not apply to IPv6. // TODO : add a list of v6 prefixes for the v6 case. @Nullable public final Inet4Address assignedV4Address; + private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f; // Optionally supplied by the client if it has an opinion on L3 network. For example, this // could be a hash of the SSID + security type on WiFi. @Nullable public final String groupHint; + private static final float WEIGHT_GROUPHINT = 300.0f; // The list of DNS server addresses. @Nullable public final List<InetAddress> dnsAddresses; + private static final float WEIGHT_DNSADDRESSES = 200.0f; // The mtu on this network. @Nullable public final Integer mtu; + private static final float WEIGHT_MTU = 50.0f; - NetworkAttributes( + // The sum of all weights in this class. Tests ensure that this stays equal to the total of + // all weights. + /** @hide */ + @VisibleForTesting + public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR + + WEIGHT_GROUPHINT + + WEIGHT_DNSADDRESSES + + WEIGHT_MTU; + + /** @hide */ + @VisibleForTesting + public NetworkAttributes( @Nullable final Inet4Address assignedV4Address, @Nullable final String groupHint, @Nullable final List<InetAddress> dnsAddresses, @@ -126,6 +156,34 @@ public class NetworkAttributes { return parcelable; } + private float samenessContribution(final float weight, + @Nullable final Object o1, @Nullable final Object o2) { + if (null == o1) { + return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f; + } + return Objects.equals(o1, o2) ? weight : 0f; + } + + /** @hide */ + public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) { + final float samenessScore = + samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address) + + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint) + + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses) + + samenessContribution(WEIGHT_MTU, mtu, o.mtu); + // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and + // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that + // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be). + // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff + // between 0.5 and 1.0. + if (samenessScore < TOTAL_WEIGHT_CUTOFF) { + return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2); + } else { + return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2 + + 0.5f; + } + } + /** @hide */ public static class Builder { @Nullable @@ -194,6 +252,12 @@ public class NetworkAttributes { } } + /** @hide */ + public boolean isEmpty() { + return (null == assignedV4Address) && (null == groupHint) + && (null == dnsAddresses) && (null == mtu); + } + @Override public boolean equals(@Nullable final Object o) { if (!(o instanceof NetworkAttributes)) return false; diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java index d040dcc3d608..291aca8fc611 100644 --- a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java +++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java @@ -91,7 +91,8 @@ public class SameL3NetworkResponse { return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT; } - SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2, + /** @hide */ + public SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2, final float confidence) { this.l2Key1 = l2Key1; this.l2Key2 = l2Key2; diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java index c04fcdcadb10..16aea31b97c9 100644 --- a/core/java/android/net/metrics/IpConnectivityLog.java +++ b/core/java/android/net/metrics/IpConnectivityLog.java @@ -21,6 +21,7 @@ import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; +import android.net.Network; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; @@ -128,6 +129,18 @@ public class IpConnectivityLog { /** * Log an IpConnectivity event. + * @param network the network associated with the event. + * @param transports the current transports of the network associated with the event, as defined + * in NetworkCapabilities. + * @param data is a Parcelable instance representing the event. + * @return true if the event was successfully logged. + */ + public boolean log(Network network, int[] transports, Event data) { + return log(network.netId, transports, data); + } + + /** + * Log an IpConnectivity event. * @param netid the id of the network associated with the event. * @param transports the current transports of the network associated with the event, as defined * in NetworkCapabilities. diff --git a/core/java/android/net/util/SocketUtils.java b/core/java/android/net/util/SocketUtils.java new file mode 100644 index 000000000000..de67cf5952f4 --- /dev/null +++ b/core/java/android/net/util/SocketUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.util; + +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_BINDTODEVICE; + +import android.annotation.SystemApi; +import android.net.NetworkUtils; +import android.system.ErrnoException; +import android.system.NetlinkSocketAddress; +import android.system.Os; +import android.system.PacketSocketAddress; + +import java.io.FileDescriptor; +import java.net.SocketAddress; + +/** + * Collection of utilities to interact with raw sockets. + * @hide + */ +@SystemApi +public class SocketUtils { + /** + * Create a raw datagram socket that is bound to an interface. + * + * <p>Data sent through the socket will go directly to the underlying network, ignoring VPNs. + */ + public static void bindSocketToInterface(FileDescriptor socket, String iface) + throws ErrnoException { + // SO_BINDTODEVICE actually takes a string. This works because the first member + // of struct ifreq is a NULL-terminated interface name. + // TODO: add a setsockoptString() + Os.setsockoptIfreq(socket, SOL_SOCKET, SO_BINDTODEVICE, iface); + NetworkUtils.protectFromVpn(socket); + } + + /** + * Make a socket address to communicate with netlink. + */ + public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) { + return new NetlinkSocketAddress(portId, groupsMask); + } + + /** + * Make a socket address to bind to packet sockets. + */ + public static SocketAddress makePacketSocketAddress(short protocol, int ifIndex) { + return new PacketSocketAddress(protocol, ifIndex); + } + + /** + * Make a socket address to send raw packets. + */ + public static SocketAddress makePacketSocketAddress(int ifIndex, byte[] hwAddr) { + return new PacketSocketAddress(ifIndex, hwAddr); + } + + private SocketUtils() {} +} diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index ca3905148ede..51c3c4c25ce0 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -72,6 +72,7 @@ import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Collection; import java.util.Comparator; import java.util.Objects; import java.util.concurrent.Executor; @@ -831,6 +832,16 @@ public class FileUtils { return false; } + /** {@hide} */ + public static boolean contains(Collection<File> dirs, File file) { + for (File dir : dirs) { + if (contains(dir, file)) { + return true; + } + } + return false; + } + /** * Test if a file lives under the given directory, either as a direct child * or a distant grandchild. diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java index a4d5c6f4b202..92fcbb65b106 100644 --- a/core/java/android/os/HandlerThread.java +++ b/core/java/android/os/HandlerThread.java @@ -20,8 +20,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; /** - * Handy class for starting a new thread that has a looper. The looper can then be - * used to create handler classes. Note that start() must still be called. + * A {@link Thread} that has a {@link Looper}. + * The {@link Looper} can then be used to create {@link Handler}s. + * <p> + * Note that just like with a regular {@link Thread}, {@link #start()} must still be called. */ public class HandlerThread extends Thread { int mPriority; diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl index 827170144dea..fe17c6bb14ca 100644 --- a/core/java/android/os/IDeviceIdleController.aidl +++ b/core/java/android/os/IDeviceIdleController.aidl @@ -45,4 +45,6 @@ interface IDeviceIdleController { void exitIdle(String reason); boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener); void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener); + int setPreIdleTimeoutMode(int Mode); + void resetPreIdleTimeoutMode(); } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 0942d97df6c7..4ce760f2c4a6 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1724,6 +1724,25 @@ public final class PowerManager { = "android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED"; /** + * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) . + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_NORMAL = 0; + + /** + * Constant for PreIdleTimeout long mode (extend timeout to keep in inactive mode + * longer). + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_LONG = 1; + + /** + * Constant for PreIdleTimeout short mode (short timeout to go to doze mode quickly) + * @hide + */ + public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2; + + /** * A wake lock is a mechanism to indicate that your application needs * to have the device stay on. * <p> diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 760fef7566c7..f2a9adb6feea 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -48,7 +48,6 @@ public class Process { /** * Defines the root UID. - * @hide */ public static final int ROOT_UID = 0; @@ -64,7 +63,6 @@ public class Process { /** * Defines the UID/GID for the user shell. - * @hide */ public static final int SHELL_UID = 2000; @@ -118,7 +116,6 @@ public class Process { /** * Defines the UID/GID for the Bluetooth service process. - * @hide */ public static final int BLUETOOTH_UID = 1002; diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java index 3e0f6dae04d8..3b0f26ae8867 100644 --- a/core/java/android/os/ServiceSpecificException.java +++ b/core/java/android/os/ServiceSpecificException.java @@ -15,6 +15,8 @@ */ package android.os; +import android.annotation.SystemApi; + /** * An exception specific to a service. * @@ -28,6 +30,7 @@ package android.os; * * @hide */ +@SystemApi public class ServiceSpecificException extends RuntimeException { public final int errorCode; diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index 2299ab2a23ff..76fe560ed902 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -140,6 +140,21 @@ public class WorkSource implements Parcelable { return mUids[index]; } + /** + * Return the UID to which this WorkSource should be attributed to, i.e, the UID that + * initiated the work and not the UID performing it. If the WorkSource has no UIDs, returns -1 + * instead. + * + * @hide + */ + public int getAttributionUid() { + if (isEmpty()) { + return -1; + } + + return mNum > 0 ? mUids[0] : mChains.get(0).getAttributionUid(); + } + /** @hide */ @TestApi public String getName(int index) { @@ -912,17 +927,18 @@ public class WorkSource implements Parcelable { /** * Return the UID to which this WorkChain should be attributed to, i.e, the UID that - * initiated the work and not the UID performing it. + * initiated the work and not the UID performing it. Returns -1 if the chain is empty. */ public int getAttributionUid() { - return mUids[0]; + return mSize > 0 ? mUids[0] : -1; } /** * Return the tag associated with the attribution UID. See (@link #getAttributionUid}. + * Returns null if the chain is empty. */ public String getAttributionTag() { - return mTags[0]; + return mTags.length > 0 ? mTags[0] : null; } // TODO: The following three trivial getters are purely for testing and will be removed diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index df1a7131a7ae..714a06126ef7 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -35,6 +35,7 @@ import com.android.internal.util.Preconditions; import java.io.CharArrayWriter; import java.io.File; +import java.util.Locale; /** * Information about a shared/external storage volume for a specific user. @@ -263,6 +264,11 @@ public final class StorageVolume implements Parcelable { return mFsUuid; } + /** {@hide} */ + public @Nullable String getNormalizedUuid() { + return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null; + } + /** * Parse and return volume UUID as FAT volume ID, or return -1 if unable to * parse or UUID is unknown. diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 8c3aa1750acf..5d310e1c2db9 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -42,6 +42,7 @@ import com.android.internal.util.Preconditions; import java.io.CharArrayWriter; import java.io.File; import java.util.Comparator; +import java.util.Locale; import java.util.Objects; /** @@ -254,6 +255,10 @@ public class VolumeInfo implements Parcelable { return fsUuid; } + public @Nullable String getNormalizedFsUuid() { + return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null; + } + @UnsupportedAppUsage public int getMountUserId() { return mountUserId; diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index a7e6601b040b..cd3efb495034 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -94,6 +94,15 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_NETD_NATIVE = "netd_native"; + /** + * Namespace for features related to the ExtServices Notification Assistant. + * These features are applied immediately. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index a323ed1a51cb..5f1c56016fa7 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -16,7 +16,6 @@ package android.provider; -import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkCollectionNotEmpty; @@ -50,6 +49,8 @@ import android.os.ParcelableException; import android.os.RemoteException; import android.util.Log; +import com.android.internal.util.Preconditions; + import dalvik.system.VMRuntime; import java.io.File; @@ -640,6 +641,28 @@ public final class DocumentsContract { public static final String COLUMN_MIME_TYPES = "mime_types"; /** + * Query arguments supported by this root. This column is optional + * and related to {@link #COLUMN_FLAGS} and {@link #FLAG_SUPPORTS_SEARCH}. + * If the flags include {@link #FLAG_SUPPORTS_SEARCH}, and the column is + * {@code null}, the root is assumed to support {@link #QUERY_ARG_DISPLAY_NAME} + * search of {@link Document#COLUMN_DISPLAY_NAME}. Multiple query arguments + * can be separated by a newline. For example, a root supporting + * {@link #QUERY_ARG_MIME_TYPES} and {@link #QUERY_ARG_DISPLAY_NAME} might + * return "android:query-arg-mime-types\nandroid:query-arg-display-name". + * <p> + * Type: STRING + * @see #COLUMN_FLAGS + * @see #FLAG_SUPPORTS_SEARCH + * @see #QUERY_ARG_DISPLAY_NAME + * @see #QUERY_ARG_FILE_SIZE_OVER + * @see #QUERY_ARG_LAST_MODIFIED_AFTER + * @see #QUERY_ARG_MIME_TYPES + * @see DocumentsProvider#querySearchDocuments(String, String[], + * Bundle) + */ + public static final String COLUMN_QUERY_ARGS = "query_args"; + + /** * MIME type for a root. */ public static final String MIME_TYPE_ITEM = "vnd.android.document/root"; @@ -680,6 +703,8 @@ public final class DocumentsContract { * String) * @see DocumentsProvider#querySearchDocuments(String, String, * String[]) + * @see DocumentsProvider#querySearchDocuments(String, String[], + * Bundle) */ public static final int FLAG_SUPPORTS_SEARCH = 1 << 3; @@ -1109,11 +1134,13 @@ public final class DocumentsContract { } /** - * Test if the given URI represents roots backed by {@link DocumentsProvider}. + * Test if the given URI represents all roots of the authority + * backed by {@link DocumentsProvider}. * * @see #buildRootsUri(String) */ - public static boolean isRootsUri(Context context, @Nullable Uri uri) { + public static boolean isRootsUri(@NonNull Context context, @Nullable Uri uri) { + Preconditions.checkNotNull(context, "context can not be null"); return isRootUri(context, uri, 1 /* pathSize */); } @@ -1122,7 +1149,8 @@ public final class DocumentsContract { * * @see #buildRootUri(String, String) */ - public static boolean isRootUri(Context context, @Nullable Uri uri) { + public static boolean isRootUri(@NonNull Context context, @Nullable Uri uri) { + Preconditions.checkNotNull(context, "context can not be null"); return isRootUri(context, uri, 2 /* pathSize */); } @@ -1215,6 +1243,7 @@ public final class DocumentsContract { * {@hide} */ public static String getSearchDocumentsQuery(@NonNull Bundle bundle) { + Preconditions.checkNotNull(bundle, "bundle can not be null"); return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */); } @@ -1315,8 +1344,12 @@ public final class DocumentsContract { * @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 { + public static boolean isChildDocument(@NonNull ContentInterface content, + @NonNull Uri parentDocumentUri, @NonNull Uri childDocumentUri) + throws FileNotFoundException { + Preconditions.checkNotNull(content, "content can not be null"); + Preconditions.checkNotNull(parentDocumentUri, "parentDocumentUri can not be null"); + Preconditions.checkNotNull(childDocumentUri, "childDocumentUri can not be null"); try { final Bundle in = new Bundle(); in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); @@ -1325,7 +1358,7 @@ public final class DocumentsContract { 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."); + throw new RemoteException("Failed to get a response from isChildDocument query."); } if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) { throw new RemoteException("Response did not include result field.."); @@ -1559,8 +1592,10 @@ public final class DocumentsContract { * @param documentUri a Document URI * @return a Bundle of Bundles. */ - public static Bundle getDocumentMetadata(ContentInterface content, Uri documentUri) - throws FileNotFoundException { + public static @Nullable Bundle getDocumentMetadata(@NonNull ContentInterface content, + @NonNull Uri documentUri) throws FileNotFoundException { + Preconditions.checkNotNull(content, "content can not be null"); + Preconditions.checkNotNull(documentUri, "documentUri can not be null"); try { final Bundle in = new Bundle(); in.putParcelable(EXTRA_URI, documentUri); @@ -1595,8 +1630,6 @@ public final class DocumentsContract { */ public static Path findDocumentPath(ContentInterface content, Uri treeUri) throws FileNotFoundException { - checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri."); - try { final Bundle in = new Bundle(); in.putParcelable(DocumentsContract.EXTRA_URI, treeUri); diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 70c84f8cc324..9b9e2dee8f9b 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -37,6 +37,7 @@ import static android.provider.DocumentsContract.isTreeUri; import android.Manifest; import android.annotation.CallSuper; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AuthenticationRequiredException; import android.content.ClipDescription; @@ -63,6 +64,8 @@ import android.provider.DocumentsContract.Path; import android.provider.DocumentsContract.Root; import android.util.Log; +import com.android.internal.util.Preconditions; + import libcore.io.IoUtils; import java.io.FileNotFoundException; @@ -687,14 +690,17 @@ public abstract class DocumentsProvider extends ContentProvider { * extras {@link Bundle} when any QUERY_ARG_* value was honored * during the preparation of the results. * + * @see Root#COLUMN_QUERY_ARGS * @see ContentResolver#EXTRA_HONORED_ARGS * @see DocumentsContract#EXTRA_LOADING * @see DocumentsContract#EXTRA_INFO * @see DocumentsContract#EXTRA_ERROR */ @SuppressWarnings("unused") - public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs) - throws FileNotFoundException { + public Cursor querySearchDocuments(@NonNull String rootId, @Nullable String[] projection, + @NonNull Bundle queryArgs) throws FileNotFoundException { + Preconditions.checkNotNull(rootId, "rootId can not be null"); + Preconditions.checkNotNull(queryArgs, "queryArgs can not be null"); return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs), projection); } @@ -732,7 +738,7 @@ public abstract class DocumentsProvider extends ContentProvider { * @return a Bundle of Bundles. * @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri) */ - public @Nullable Bundle getDocumentMetadata(String documentId) + public @Nullable Bundle getDocumentMetadata(@NonNull 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 3a4998630544..487198ba4d45 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -70,6 +70,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; @@ -1224,7 +1226,7 @@ public final class MediaStore { if (sv.isPrimary()) { return VOLUME_EXTERNAL; } else { - return checkArgumentVolumeName(sv.getUuid()); + return checkArgumentVolumeName(sv.getNormalizedUuid()); } } throw new IllegalStateException("Unknown volume at " + path); @@ -2919,7 +2921,7 @@ public final class MediaStore { if (vi.isPrimary()) { volumeNames.add(VOLUME_EXTERNAL); } else { - volumeNames.add(vi.getFsUuid()); + volumeNames.add(vi.getNormalizedFsUuid()); } } } @@ -2953,8 +2955,7 @@ public final class MediaStore { // When not one of the well-known values above, it must be a hex UUID for (int i = 0; i < volumeName.length(); i++) { final char c = volumeName.charAt(i); - if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F') - || ('0' <= c && c <= '9') || (c == '-')) { + if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) { continue; } else { throw new IllegalArgumentException("Invalid volume name: " + volumeName); @@ -2963,23 +2964,26 @@ public final class MediaStore { return volumeName; } - /** {@hide} */ + /** + * Return path where the given volume is mounted. Not valid for + * {@link #VOLUME_INTERNAL}. + * + * @hide + */ public static @NonNull File getVolumePath(@NonNull String volumeName) throws FileNotFoundException { if (TextUtils.isEmpty(volumeName)) { throw new IllegalArgumentException(); } - if (VOLUME_INTERNAL.equals(volumeName)) { - return Environment.getDataDirectory(); - } else if (VOLUME_EXTERNAL.equals(volumeName)) { + if (VOLUME_EXTERNAL.equals(volumeName)) { return Environment.getExternalStorageDirectory(); } final StorageManager sm = AppGlobals.getInitialApplication() .getSystemService(StorageManager.class); for (VolumeInfo vi : sm.getVolumes()) { - if (Objects.equals(vi.getFsUuid(), volumeName)) { + if (Objects.equals(vi.getNormalizedFsUuid(), volumeName)) { final File path = vi.getPathForUser(UserHandle.myUserId()); if (path != null) { return path; @@ -2992,6 +2996,33 @@ public final class MediaStore { } /** + * Return paths that should be scanned for the given volume. + * + * @hide + */ + public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName) + throws FileNotFoundException { + if (TextUtils.isEmpty(volumeName)) { + throw new IllegalArgumentException(); + } + + final ArrayList<File> res = new ArrayList<>(); + if (VOLUME_INTERNAL.equals(volumeName)) { + res.add(new File(Environment.getRootDirectory(), "media")); + res.add(new File(Environment.getOemDirectory(), "media")); + res.add(new File(Environment.getProductDirectory(), "media")); + } else { + res.add(getVolumePath(volumeName)); + final UserManager um = AppGlobals.getInitialApplication() + .getSystemService(UserManager.class); + if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) { + res.add(Environment.getDataPreloadsMediaDirectory()); + } + } + return res; + } + + /** * Uri for querying the state of the media scanner. */ public static Uri getMediaScannerUri() { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 516f49ccc910..643c473f8417 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3300,6 +3300,14 @@ public final class Settings { public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1; /** + * Control whether to enable adaptive sleep mode. + * @hide + */ + public static final String ADAPTIVE_SLEEP = "adaptive_sleep"; + + private static final Validator ADAPTIVE_SLEEP_VALIDATOR = BOOLEAN_VALIDATOR; + + /** * Control whether the process CPU usage meter should be shown. * * @deprecated This functionality is no longer available as of @@ -4232,6 +4240,7 @@ public final class Settings { SCREEN_BRIGHTNESS_MODE, SCREEN_AUTO_BRIGHTNESS_ADJ, SCREEN_BRIGHTNESS_FOR_VR, + ADAPTIVE_SLEEP, VIBRATE_INPUT_DEVICES, MODE_RINGER_STREAMS_AFFECTED, TEXT_AUTO_REPLACE, @@ -4307,6 +4316,7 @@ public final class Settings { PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS); PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FOR_VR); PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE); + PUBLIC_SETTINGS.add(ADAPTIVE_SLEEP); PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED); PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED); PUBLIC_SETTINGS.add(VIBRATE_ON); @@ -4411,6 +4421,7 @@ public final class Settings { VALIDATORS.put(SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT_VALIDATOR); VALIDATORS.put(SCREEN_BRIGHTNESS_FOR_VR, SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR); VALIDATORS.put(SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_VALIDATOR); + VALIDATORS.put(ADAPTIVE_SLEEP, ADAPTIVE_SLEEP_VALIDATOR); VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR); VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR); VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR); @@ -13917,11 +13928,12 @@ public final class Settings { * The following keys are supported: * * <pre> - * enabled (boolean) - * requires_targeting_p (boolean) - * max_squeeze_remeasure_attempts (int) - * edit_choices_before_sending (boolean) - * show_in_heads_up (boolean) + * enabled (boolean) + * requires_targeting_p (boolean) + * max_squeeze_remeasure_attempts (int) + * edit_choices_before_sending (boolean) + * show_in_heads_up (boolean) + * min_num_system_generated_replies (int) * </pre> * @see com.android.systemui.statusbar.policy.SmartReplyConstants * @hide diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index 4dc10cd2e4cc..ffb524d5c2e4 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -103,10 +103,23 @@ public abstract class EuiccService extends Service { */ public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; + /** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */ public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + /** @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; + + /** @see android.telephony.euicc.EuiccManager#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; + + /** @see android.telephony.euicc.EuiccManager#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED */ + public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = + "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + // LUI resolution actions. These are called by the platform to resolve errors in situations that // require user interaction. // TODO(b/33075886): Define extras for any input parameters to these dialogs once they are diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl index f7acfc5918a8..b0269e3325bf 100644 --- a/core/java/android/service/vr/IVrManager.aidl +++ b/core/java/android/service/vr/IVrManager.aidl @@ -110,13 +110,5 @@ interface IVrManager { * @param standy True if the device is entering standby, false if it's exiting standby. */ void setStandbyEnabled(boolean standby); - - /** - * Start VR Input method for the given packageName in {@param componentName}. - * This method notifies InputMethodManagerService to use VR IME instead of - * regular phone IME. - */ - void setVrInputMethod(in ComponentName componentName); - } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 0d7223d67907..b197c8a8a388 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -784,8 +784,11 @@ public abstract class WallpaperService extends Service { // only internal implementations like ImageWallpaper DisplayInfo displayInfo = new DisplayInfo(); mDisplay.getDisplayInfo(displayInfo); - mLayout.width = Math.max(displayInfo.logicalWidth, myWidth); - mLayout.height = Math.max(displayInfo.logicalHeight, myHeight); + final float layoutScale = Math.max( + (float) displayInfo.logicalHeight / (float) myHeight, + (float) displayInfo.logicalWidth / (float) myWidth); + mLayout.height = (int) (myHeight * layoutScale); + mLayout.width = (int) (myWidth * layoutScale); mWindowFlags |= WindowManager.LayoutParams.FLAG_SCALED; } diff --git a/core/java/android/util/DocumentsStatsLog.java b/core/java/android/util/DocumentsStatsLog.java new file mode 100644 index 000000000000..f483944c97c7 --- /dev/null +++ b/core/java/android/util/DocumentsStatsLog.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.util; + +import android.annotation.SystemApi; +import android.provider.DocumentsContract; +import android.provider.DocumentsProvider; + +/** + * DocumentsStatsLog provides APIs to send DocumentsUI related events to statsd. + * @hide + */ +@SystemApi +public class DocumentsStatsLog { + + private DocumentsStatsLog() {} + + /** + * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up. + * + * @param action action that launches DocumentsUI. + * @param hasInitialUri is DocumentsUI launched with + * {@link DocumentsContract#EXTRA_INITIAL_URI}. + * @param mimeType the requested mime type. + * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't + * support {@link DocumentsProvider#findDocumentPath(String, String)} + */ + public static void logActivityLaunch( + int action, boolean hasInitialUri, int mimeType, int rootUri) { + StatsLog.write(StatsLog.DOCS_UI_LAUNCH_REPORTED, action, hasInitialUri, mimeType, rootUri); + } + + /** + * Logs root visited event. + * + * @param scope whether it's in FILES or PICKER mode. + * @param root the root that user visited + */ + public static void logRootVisited(int scope, int root) { + StatsLog.write(StatsLog.DOCS_UI_ROOT_VISITED, scope, root); + } + + /** + * Logs file operation stats. Call this when a file operation has completed. + * + * @param provider whether it's system or external provider + * @param fileOp the file operation + */ + public static void logFileOperation(int provider, int fileOp) { + StatsLog.write(StatsLog.DOCS_UI_PROVIDER_FILE_OP, provider, fileOp); + } + + /** + * Logs file operation stats. Call this when a copy/move operation has completed with a specific + * mode. + * + * @param fileOp copy or move file operation + * @param mode the mode for copy and move operation + */ + public static void logFileOperationCopyMoveMode(int fileOp, int mode) { + StatsLog.write(StatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, fileOp, mode); + } + + /** + * Logs file sub operation stats. Call this when a file operation has failed. + * + * @param authority the authority of the source document + * @param subOp the sub-file operation + */ + public static void logFileOperationFailure(int authority, int subOp) { + StatsLog.write(StatsLog.DOCS_UI_FILE_OP_FAILURE, authority, subOp); + } + + /** + * Logs the cancellation of a file operation. Call this when a job is canceled + * + * @param fileOp the file operation. + */ + public static void logFileOperationCanceled(int fileOp) { + StatsLog.write(StatsLog.DOCS_UI_FILE_OP_CANCELED, fileOp); + } + + /** + * Logs startup time in milliseconds. + * + * @param startupMs + */ + public static void logStartupMs(int startupMs) { + StatsLog.write(StatsLog.DOCS_UI_STARTUP_MS, startupMs); + } + + /** + * Logs the action that was started by user. + * + * @param userAction + */ + public static void logUserAction(int userAction) { + StatsLog.write(StatsLog.DOCS_UI_USER_ACTION_REPORTED, userAction); + } + + /** + * Logs the invalid type when invalid scoped access is requested. + * + * @param type the type of invalid scoped access request. + */ + public static void logInvalidScopedAccessRequest(int type) { + StatsLog.write(StatsLog.DOCS_UI_INVALID_SCOPED_ACCESS_REQUEST, type); + } + + /** + * Logs the package name that launches docsui picker mode. + * + * @param packageName + */ + public static void logPickerLaunchedFrom(String packageName) { + StatsLog.write(StatsLog.DOCS_UI_PICKER_LAUNCHED_FROM_REPORTED, packageName); + } + + /** + * Logs the search type. + * + * @param searchType + */ + public static void logSearchType(int searchType) { + StatsLog.write(StatsLog.DOCS_UI_SEARCH_TYPE_REPORTED, searchType); + } + + /** + * Logs the search mode. + * + * @param searchMode + */ + public static void logSearchMode(int searchMode) { + StatsLog.write(StatsLog.DOCS_UI_SEARCH_MODE_REPORTED, searchMode); + } + + /** + * Logs the pick result information. + * + * @param actionCount total user action count during pick process. + * @param duration total time spent on pick process. + * @param fileCount number of picked files. + * @param isSearching are the picked files found by search. + * @param root the root where the picked files located. + * @param mimeType the mime type of the picked file. Only for single-select case. + * @param repeatedlyPickTimes number of times that the file has been picked before. Only for + * single-select case. + */ + public static void logFilePick(int actionCount, long duration, int fileCount, + boolean isSearching, int root, int mimeType, int repeatedlyPickTimes) { + StatsLog.write(StatsLog.DOCS_UI_PICK_RESULT_REPORTED, actionCount, duration, fileCount, + isSearching, root, mimeType, repeatedlyPickTimes); + } +} diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 33b3ff4fef59..7d9ec70a4599 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -237,6 +237,16 @@ public class GestureDetector { private static final int LONG_PRESS = 2; private static final int TAP = 3; + /** + * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain actions, such as + * scrolling, will be inhibited. However, to account for the possibility of incorrect + * classification, the default scrolling will only be inhibited if the gesture moves beyond + * (default touch slop * AMBIGUOUS_GESTURE_MULTIPLIER). Likewise, the default long press + * timeout will be increased for some situations where the default behaviour + * is to cancel it. + */ + private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2; + private final Handler mHandler; @UnsupportedAppUsage private final OnGestureListener mListener; @@ -292,27 +302,27 @@ public class GestureDetector { @Override public void handleMessage(Message msg) { switch (msg.what) { - case SHOW_PRESS: - mListener.onShowPress(mCurrentDownEvent); - break; - - case LONG_PRESS: - dispatchLongPress(); - break; - - case TAP: - // If the user's finger is still down, do not count it as a tap - if (mDoubleTapListener != null) { - if (!mStillDown) { - mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); - } else { - mDeferConfirmSingleTap = true; + case SHOW_PRESS: + mListener.onShowPress(mCurrentDownEvent); + break; + + case LONG_PRESS: + dispatchLongPress(); + break; + + case TAP: + // If the user's finger is still down, do not count it as a tap + if (mDoubleTapListener != null) { + if (!mStillDown) { + mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); + } else { + mDeferConfirmSingleTap = true; + } } - } - break; + break; - default: - throw new RuntimeException("Unknown message " + msg); //never + default: + throw new RuntimeException("Unknown message " + msg); //never } } } @@ -427,7 +437,7 @@ public class GestureDetector { if (context == null) { //noinspection deprecation touchSlop = ViewConfiguration.getTouchSlop(); - doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this + doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this doubleTapSlop = ViewConfiguration.getDoubleTapSlop(); //noinspection deprecation mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); @@ -605,6 +615,10 @@ public class GestureDetector { if (mInLongPress || mInContextClick) { break; } + + final int motionClassification = ev.getClassification(); + final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS); + final float scrollX = mLastFocusX - focusX; final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { @@ -615,6 +629,31 @@ public class GestureDetector { final int deltaY = (int) (focusY - mDownFocusY); int distance = (deltaX * deltaX) + (deltaY * deltaY); int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare; + + final boolean ambiguousGesture = + motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; + final boolean shouldInhibitDefaultAction = + hasPendingLongPress && ambiguousGesture; + if (shouldInhibitDefaultAction) { + // Inhibit default long press + if (distance > slopSquare) { + // The default action here is to remove long press. But if the touch + // slop below gets increased, and we never exceed the modified touch + // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing* + // will happen in response to user input. To prevent this, + // reschedule long press with a modified timeout. + mHandler.removeMessages(LONG_PRESS); + final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); + mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime() + + longPressTimeout * AMBIGUOUS_GESTURE_MULTIPLIER); + } + // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll + // until the gesture is resolved. + // However, for safety, simply increase the touch slop in case the + // classification is erroneous. Since the value is squared, multiply twice. + slopSquare *= AMBIGUOUS_GESTURE_MULTIPLIER * AMBIGUOUS_GESTURE_MULTIPLIER; + } + if (distance > slopSquare) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; @@ -633,6 +672,12 @@ public class GestureDetector { mLastFocusX = focusX; mLastFocusY = focusY; } + final boolean deepPress = + motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; + if (deepPress && hasPendingLongPress) { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendEmptyMessage(LONG_PRESS); + } break; case MotionEvent.ACTION_UP: diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 330d72f139db..42ac8801629f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -51,6 +51,7 @@ import android.view.IInputFilter; import android.view.AppTransitionAnimationSpec; import android.view.WindowContentFrameStats; import android.view.WindowManager; +import android.view.SurfaceControl; /** * System private interface to the window manager. @@ -555,8 +556,8 @@ interface IWindowManager * display content info to any SurfaceControl, as this would be a security issue. * * @param displayId The id of the display. - * @param surfaceControlHandle The SurfaceControl handle that the top level layers for the + * @param surfaceControlHandle The SurfaceControl that the top level layers for the * display should be re-parented to. */ - void reparentDisplayContent(int displayId, in IBinder surfaceControlHandle); + void reparentDisplayContent(int displayId, in SurfaceControl sc); } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index ce71b07da805..7c1465b443e4 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -161,8 +161,9 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private Insets getInsetsFromState(InsetsState state, Rect frame, @Nullable @InsetSide SparseIntArray typeSideMap) { return state.calculateInsets(frame, false /* isScreenRound */, - false /* alwaysConsumerNavBar */, null /* displayCutout */, typeSideMap) - .getInsets(mTypes); + false /* alwaysConsumerNavBar */, null /* displayCutout */, + null /* legacyContentInsets */, null /* legacyStableInsets */, typeSideMap) + .getInsets(mTypes); } private Insets sanitize(Insets insets) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index c2ade764ca81..4b1d1ec15750 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -56,6 +56,9 @@ public class InsetsController implements WindowInsetsController { private final Runnable mAnimCallback; + private final Rect mLastLegacyContentInsets = new Rect(); + private final Rect mLastLegacyStableInsets = new Rect(); + public InsetsController(ViewRootImpl viewRoot) { mViewRoot = viewRoot; mAnimCallback = () -> { @@ -70,6 +73,7 @@ public class InsetsController implements WindowInsetsController { } WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(), + mLastLegacyContentInsets, mLastLegacyStableInsets, null /* typeSideMap */); mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets); }; @@ -102,8 +106,12 @@ public class InsetsController implements WindowInsetsController { */ @VisibleForTesting public WindowInsets calculateInsets(boolean isScreenRound, - boolean alwaysConsumeNavBar, DisplayCutout cutout) { + boolean alwaysConsumeNavBar, DisplayCutout cutout, Rect legacyContentInsets, + Rect legacyStableInsets) { + mLastLegacyContentInsets.set(legacyContentInsets); + mLastLegacyStableInsets.set(legacyStableInsets); mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout, + legacyContentInsets, legacyStableInsets, null /* typeSideMap */); return mLastInsets; } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index cf8c0707828d..529776e542ee 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.WindowInsets.Type.indexOf; import android.annotation.IntDef; @@ -119,11 +120,17 @@ public class InsetsState implements Parcelable { */ public WindowInsets calculateInsets(Rect frame, boolean isScreenRound, boolean alwaysConsumeNavBar, DisplayCutout cutout, + @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets, @Nullable @InsetSide SparseIntArray typeSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME + && legacyContentInsets != null && legacyStableInsets != null) { + WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets); + WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets); + } for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { InsetsSource source = mSources.get(type); if (source == null) { diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index f3cb3767ec9d..7fcb2afa48a9 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -185,6 +185,18 @@ public class Surface implements Parcelable { } /** + * Create a Surface assosciated with a given {@link SurfaceControl}. Buffers submitted to this + * surface will be displayed by the system compositor according to the parameters + * specified by the control. Multiple surfaces may be constructed from one SurfaceControl, + * but only one can be connected (e.g. have an active EGL context) at a time. + * + * @param from The SurfaceControl to assosciate this Surface with + */ + public Surface(SurfaceControl from) { + copyFrom(from); + } + + /** * Create Surface from a {@link SurfaceTexture}. * * Images drawn to the Surface will be made available to the {@link @@ -494,7 +506,6 @@ public class Surface implements Parcelable { * in to it. * * @param other {@link SurfaceControl} to copy from. - * * @hide */ @UnsupportedAppUsage diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 5e98236f7535..863b717008d2 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -27,6 +27,10 @@ import static android.view.Surface.ROTATION_90; import static android.view.SurfaceControlProto.HASH_CODE; import static android.view.SurfaceControlProto.NAME; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.Size; import android.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; @@ -58,10 +62,16 @@ import libcore.util.NativeAllocationRegistry; import java.io.Closeable; /** - * SurfaceControl - * @hide + * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is + * a combination of a buffer source, and metadata about how to display the buffers. + * By constructing a {@link Surface} from this SurfaceControl you can submit buffers to be + * composited. Using {@link SurfaceControl.Transaction} you can manipulate various + * properties of how the buffer will be displayed on-screen. SurfaceControl's are + * arranged into a scene-graph like hierarchy, and as such any SurfaceControl may have + * a parent. Geometric properties like transform, crop, and Z-ordering will be inherited + * from the parent, as if the child were content in the parents buffer stream. */ -public class SurfaceControl implements Parcelable { +public final class SurfaceControl implements Parcelable { private static final String TAG = "SurfaceControl"; private static native long nativeCreate(SurfaceSession session, String name, @@ -103,6 +113,8 @@ public class SurfaceControl implements Parcelable { float dtdy, float dsdy); private static native void nativeSetColorTransform(long transactionObj, long nativeObject, float[] matrix, float[] translation); + private static native void nativeSetGeometry(long transactionObj, long nativeObject, + Rect sourceCrop, Rect dest, long orientation); private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color); private static native void nativeSetFlags(long transactionObj, long nativeObject, int flags, int mask); @@ -156,7 +168,7 @@ public class SurfaceControl implements Parcelable { private static native void nativeReparentChildren(long transactionObj, long nativeObject, IBinder handle); private static native void nativeReparent(long transactionObj, long nativeObject, - IBinder parentHandle); + long newParentNativeObject); private static native void nativeSeverChildren(long transactionObj, long nativeObject); private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject, int scalingMode); @@ -331,8 +343,7 @@ public class SurfaceControl implements Parcelable { */ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1; - /* Display power modes * / - + // Display power modes. /** * Display power mode off: used while blanking the screen. * Use only with {@link SurfaceControl#setDisplayPowerMode}. @@ -403,7 +414,6 @@ public class SurfaceControl implements Parcelable { /** * Builder class for {@link SurfaceControl} objects. - * @hide */ public static class Builder { private SurfaceSession mSession; @@ -427,8 +437,14 @@ public class SurfaceControl implements Parcelable { } /** - * Construct a new {@link SurfaceControl} with the set parameters. - * @hide + * Begin building a SurfaceControl. + */ + public Builder() { + } + + /** + * Construct a new {@link SurfaceControl} with the set parameters. The builder + * remains valid. */ public SurfaceControl build() { if (mWidth < 0 || mHeight < 0) { @@ -447,7 +463,6 @@ public class SurfaceControl implements Parcelable { * Set a debugging-name for the SurfaceControl. * * @param name A name to identify the Surface in debugging. - * @hide */ public Builder setName(String name) { mName = name; @@ -459,9 +474,9 @@ public class SurfaceControl implements Parcelable { * * @param width The buffer width in pixels. * @param height The buffer height in pixels. - * @hide */ - public Builder setBufferSize(int width, int height) { + public Builder setBufferSize(@IntRange(from = 0) int width, + @IntRange(from = 0) int height) { if (width < 0 || height < 0) { throw new IllegalArgumentException( "width and height must be positive"); @@ -474,8 +489,8 @@ public class SurfaceControl implements Parcelable { /** * Set the pixel format of the controlled surface's buffers, using constants from * {@link android.graphics.PixelFormat}. - * @hide */ + @NonNull public Builder setFormat(@PixelFormat.Format int format) { mFormat = format; return this; @@ -490,6 +505,7 @@ public class SurfaceControl implements Parcelable { * @param protectedContent Whether to require a protected sink. * @hide */ + @NonNull public Builder setProtected(boolean protectedContent) { if (protectedContent) { mFlags |= PROTECTED_APP; @@ -506,6 +522,7 @@ public class SurfaceControl implements Parcelable { * not a complete prevention of readback as {@link #setProtected}. * @hide */ + @NonNull public Builder setSecure(boolean secure) { if (secure) { mFlags |= SECURE; @@ -537,8 +554,8 @@ public class SurfaceControl implements Parcelable { * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true) * were set automatically. * @param opaque Whether the Surface is OPAQUE. - * @hide */ + @NonNull public Builder setOpaque(boolean opaque) { if (opaque) { mFlags |= OPAQUE; @@ -556,9 +573,9 @@ public class SurfaceControl implements Parcelable { * of the parent. * * @param parent The parent control. - * @hide */ - public Builder setParent(SurfaceControl parent) { + @NonNull + public Builder setParent(@Nullable SurfaceControl parent) { mParent = parent; return this; } @@ -673,9 +690,6 @@ public class SurfaceControl implements Parcelable { private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, SurfaceControl parent, int windowType, int ownerUid) throws OutOfResourcesException, IllegalArgumentException { - if (session == null) { - throw new IllegalArgumentException("session must not be null"); - } if (name == null) { throw new IllegalArgumentException("name must not be null"); } @@ -729,9 +743,6 @@ public class SurfaceControl implements Parcelable { mCloseGuard.open("release"); } - /** - * @hide - */ public void readFromParcel(Parcel in) { if (in == null) { throw new IllegalArgumentException("source must not be null"); @@ -748,17 +759,11 @@ public class SurfaceControl implements Parcelable { assignNativeObject(object); } - /** - * @hide - */ @Override public int describeContents() { return 0; } - /** - * @hide - */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mName); @@ -791,9 +796,6 @@ public class SurfaceControl implements Parcelable { proto.end(token); } - /** - * @hide - */ public static final Creator<SurfaceControl> CREATOR = new Creator<SurfaceControl>() { public SurfaceControl createFromParcel(Parcel in) { @@ -823,10 +825,12 @@ public class SurfaceControl implements Parcelable { } /** - * Release the local reference to the server-side surface. - * Always call release() when you're done with a Surface. - * This will make the surface invalid. - * @hide + * Release the local reference to the server-side surface. The surface + * may continue to exist on-screen as long as its parent continues + * to exist. To explicitly remove a surface from the screen use + * {@link Transaction#reparent} with a null-parent. + * + * Always call release() when you're done with a SurfaceControl. */ public void release() { if (mNativeObject != 0) { @@ -866,7 +870,10 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * Check whether this instance points to a valid layer with the system-compositor. For + * example this may be false if construction failed, or the layer was released. + * + * @return Whether this SurfaceControl is valid. */ public boolean isValid() { return mNativeObject != 0; @@ -962,9 +969,9 @@ public class SurfaceControl implements Parcelable { /** * @hide */ - public void reparent(IBinder newParentHandle) { + public void reparent(SurfaceControl newParent) { synchronized(SurfaceControl.class) { - sGlobalTransaction.reparent(this, newParentHandle); + sGlobalTransaction.reparent(this, newParent); } } @@ -1270,9 +1277,6 @@ public class SurfaceControl implements Parcelable { } } - /** - * @hide - */ @Override public String toString() { return "Surface(name=" + mName + ")/@0x" + @@ -1286,6 +1290,7 @@ public class SurfaceControl implements Parcelable { /** * Describes the properties of a physical display known to surface flinger. + * @hide */ public static final class PhysicalDisplayInfo { /** @@ -1777,9 +1782,12 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * An atomic set of changes to a set of SurfaceControl. */ public static class Transaction implements Closeable { + /** + * @hide + */ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( Transaction.class.getClassLoader(), nativeGetNativeTransactionFinalizer(), 512); @@ -1789,9 +1797,12 @@ public class SurfaceControl implements Parcelable { Runnable mFreeNativeResources; /** - * @hide + * Open a new transaction object. The transaction may be filed with commands to + * manipulate {@link SurfaceControl} instances, and then applied atomically with + * {@link #apply}. Eventually the user should invoke {@link #close}, when the object + * is no longer required. Note however that re-using a transaction after a call to apply + * is allowed as a convenience. */ - @UnsupportedAppUsage public Transaction() { mNativeObject = nativeCreateTransaction(); mFreeNativeResources @@ -1801,9 +1812,7 @@ public class SurfaceControl implements Parcelable { /** * Apply the transaction, clearing it's state, and making it usable * as a new transaction. - * @hide */ - @UnsupportedAppUsage public void apply() { apply(false); } @@ -1811,7 +1820,6 @@ public class SurfaceControl implements Parcelable { /** * Close the transaction, if the transaction was not already applied this will cancel the * transaction. - * @hide */ @Override public void close() { @@ -1841,6 +1849,27 @@ public class SurfaceControl implements Parcelable { } /** + * Toggle the visibility of a given Layer and it's sub-tree. + * + * @param sc The SurfaceControl for which to set the visibility + * @param visible The new visibility + * @return This transaction object. + */ + @NonNull + public Transaction setVisibility(@NonNull SurfaceControl sc, boolean visible) { + sc.checkNotReleased(); + if (visible) { + return show(sc); + } else { + return hide(sc); + } + } + + /** + * Request that a given surface and it's sub-tree be shown. + * + * @param sc The surface to show. + * @return This transaction. * @hide */ @UnsupportedAppUsage @@ -1851,6 +1880,10 @@ public class SurfaceControl implements Parcelable { } /** + * Request that a given surface and it's sub-tree be hidden. + * + * @param sc The surface to hidden. + * @return This transaction. * @hide */ @UnsupportedAppUsage @@ -1871,10 +1904,17 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * Set the default buffer size for the SurfaceControl, if there is an + * {@link Surface} assosciated with the control, then + * this will be the default size for buffers dequeued from it. + * @param sc The surface to set the buffer size for. + * @param w The default width + * @param h The default height + * @return This Transaction */ - @UnsupportedAppUsage - public Transaction setBufferSize(SurfaceControl sc, int w, int h) { + @NonNull + public Transaction setBufferSize(@NonNull SurfaceControl sc, + @IntRange(from = 0) int w, @IntRange(from = 0) int h) { sc.checkNotReleased(); mResizedSurfaces.put(sc, new Point(w, h)); nativeSetSize(mNativeObject, sc.mNativeObject, w, h); @@ -1882,10 +1922,17 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * Set the Z-order for a given SurfaceControl, relative to it's siblings. + * If two siblings share the same Z order the ordering is undefined. Surfaces + * with a negative Z will be placed below the parent surface. + * + * @param sc The SurfaceControl to set the Z order on + * @param z The Z-order + * @return This Transaction. */ - @UnsupportedAppUsage - public Transaction setLayer(SurfaceControl sc, int z) { + @NonNull + public Transaction setLayer(@NonNull SurfaceControl sc, + @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) { sc.checkNotReleased(); nativeSetLayer(mNativeObject, sc.mNativeObject, z); return this; @@ -1912,10 +1959,15 @@ public class SurfaceControl implements Parcelable { } /** - * @hide + * Set the alpha for a given surface. If the alpha is non-zero the SurfaceControl + * will be blended with the Surfaces under it according to the specified ratio. + * + * @param sc The given SurfaceControl. + * @param alpha The alpha to set. */ - @UnsupportedAppUsage - public Transaction setAlpha(SurfaceControl sc, float alpha) { + @NonNull + public Transaction setAlpha(@NonNull SurfaceControl sc, + @FloatRange(from = 0.0, to = 1.0) float alpha) { sc.checkNotReleased(); nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha); return this; @@ -1947,6 +1999,25 @@ public class SurfaceControl implements Parcelable { } /** + * Specify how the buffer assosciated with this Surface is mapped in to the + * parent coordinate space. The source frame will be scaled to fit the destination + * frame, after being rotated according to the orientation parameter. + * + * @param sc The SurfaceControl to specify the geometry of + * @param sourceCrop The source rectangle in buffer space. Or null for the entire buffer. + * @param destFrame The destination rectangle in parent space. Or null for the source frame. + * @param orientation The buffer rotation + * @return This transaction object. + */ + @NonNull + public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop, + @Nullable Rect destFrame, @Surface.Rotation int orientation) { + sc.checkNotReleased(); + nativeSetGeometry(mNativeObject, sc.mNativeObject, sourceCrop, destFrame, orientation); + return this; + } + + /** * @hide */ @UnsupportedAppUsage @@ -2023,20 +2094,20 @@ public class SurfaceControl implements Parcelable { return this; } - @UnsupportedAppUsage /** * @hide */ + @UnsupportedAppUsage public Transaction setLayerStack(SurfaceControl sc, int layerStack) { sc.checkNotReleased(); nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack); return this; } - @UnsupportedAppUsage /** * @hide */ + @UnsupportedAppUsage public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) { if (frameNumber < 0) { @@ -2047,10 +2118,10 @@ public class SurfaceControl implements Parcelable { return this; } - @UnsupportedAppUsage /** * @hide */ + @UnsupportedAppUsage public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface, long frameNumber) { if (frameNumber < 0) { @@ -2071,13 +2142,25 @@ public class SurfaceControl implements Parcelable { return this; } - /** Re-parents a specific child layer to a new parent - * @hide + /** + * Re-parents a given layer to a new parent. Children inherit transform (position, scaling) + * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the + * parent Surface. + * + * @param sc The SurfaceControl to reparent + * @param newParent The new parent for the given control. + * @return This Transaction */ - public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) { + @NonNull + public Transaction reparent(@NonNull SurfaceControl sc, + @Nullable SurfaceControl newParent) { sc.checkNotReleased(); - nativeReparent(mNativeObject, sc.mNativeObject, - newParentHandle); + long otherObject = 0; + if (newParent != null) { + newParent.checkNotReleased(); + otherObject = newParent.mNativeObject; + } + nativeReparent(mNativeObject, sc.mNativeObject, otherObject); return this; } @@ -2245,9 +2328,12 @@ public class SurfaceControl implements Parcelable { /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. - * @hide + * + * @param other The transaction to merge in to this one. + * @return This transaction. */ - public Transaction merge(Transaction other) { + @NonNull + public Transaction merge(@NonNull Transaction other) { mResizedSurfaces.putAll(other.mResizedSurfaces); other.mResizedSurfaces.clear(); nativeMergeTransaction(mNativeObject, other.mNativeObject); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 61fb00d3fe59..45e6c50d9ada 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -120,10 +120,11 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb final Rect mScreenRect = new Rect(); SurfaceSession mSurfaceSession; - SurfaceControlWithBackground mSurfaceControl; + SurfaceControl mSurfaceControl; // In the case of format changes we switch out the surface in-place // we need to preserve the old one until the new one has drawn. SurfaceControl mDeferredDestroySurfaceControl; + SurfaceControl mBackgroundControl; final Rect mTmpRect = new Rect(); final Configuration mConfiguration = new Configuration(); @@ -487,6 +488,29 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb } } + private void updateBackgroundVisibilityInTransaction() { + if (mBackgroundControl == null) { + return; + } + if ((mSurfaceFlags & PixelFormat.OPAQUE) == 0) { + mBackgroundControl.show(); + mBackgroundControl.setLayer(Integer.MIN_VALUE); + } else { + mBackgroundControl.hide(); + } + } + + private void releaseSurfaces() { + if (mSurfaceControl != null) { + mSurfaceControl.destroy(); + mSurfaceControl = null; + } + if (mBackgroundControl != null) { + mBackgroundControl.destroy(); + mBackgroundControl = null; + } + } + /** @hide */ protected void updateSurface() { if (!mHaveFrame) { @@ -553,14 +577,21 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb updateOpaqueFlag(); final String name = "SurfaceView - " + viewRoot.getTitle().toString(); - mSurfaceControl = new SurfaceControlWithBackground( - name, - (mSurfaceFlags & SurfaceControl.OPAQUE) != 0, - new SurfaceControl.Builder(mSurfaceSession) - .setBufferSize(mSurfaceWidth, mSurfaceHeight) - .setFormat(mFormat) - .setParent(viewRoot.getSurfaceControl()) - .setFlags(mSurfaceFlags)); + mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) + .setName(name) + .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0) + .setBufferSize(mSurfaceWidth, mSurfaceHeight) + .setFormat(mFormat) + .setParent(viewRoot.getSurfaceControl()) + .setFlags(mSurfaceFlags) + .build(); + mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession) + .setName("Background for -" + name) + .setOpaque(true) + .setColorLayer(true) + .setParent(mSurfaceControl) + .build(); + } else if (mSurfaceControl == null) { return; } @@ -577,11 +608,13 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb SurfaceControl.openTransaction(); try { mSurfaceControl.setLayer(mSubLayer); + if (mViewVisibility) { mSurfaceControl.show(); } else { mSurfaceControl.hide(); } + updateBackgroundVisibilityInTransaction(); // While creating the surface, we will set it's initial // geometry. Outside of that though, we should generally @@ -724,8 +757,7 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb if (mSurfaceControl != null && !mSurfaceCreated) { mSurface.release(); - mSurfaceControl.destroy(); - mSurfaceControl = null; + releaseSurfaces(); } } } catch (Exception ex) { @@ -823,7 +855,6 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb final ViewRootImpl viewRoot = getViewRootImpl(); applySurfaceTransforms(mSurfaceControl, position, frameNumber); - applySurfaceTransforms(mSurfaceControl.mBackgroundControl, position, frameNumber); applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface, frameNumber); @@ -950,7 +981,19 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb * @hide */ public void setResizeBackgroundColor(int bgColor) { - mSurfaceControl.setBackgroundColor(bgColor); + if (mBackgroundControl == null) { + return; + } + + final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f, + Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f }; + + SurfaceControl.openTransaction(); + try { + mBackgroundControl.setColor(colorComponents); + } finally { + SurfaceControl.closeTransaction(); + } } @UnsupportedAppUsage @@ -1128,154 +1171,12 @@ public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallb }; /** - * @hide + * Return a SurfaceControl which can be used for parenting Surfaces to + * this SurfaceView. + * + * @return The SurfaceControl for this SurfaceView. */ public SurfaceControl getSurfaceControl() { return mSurfaceControl; } - - class SurfaceControlWithBackground extends SurfaceControl { - SurfaceControl mBackgroundControl; - private boolean mOpaque = true; - public boolean mVisible = false; - - public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b) - throws Exception { - super(b.setName(name).build()); - - mBackgroundControl = b.setName("Background for -" + name) - .setFormat(OPAQUE) - // Unset the buffer size of the background color layer. - .setBufferSize(0, 0) - .setColorLayer(true) - .build(); - mOpaque = opaque; - } - - @Override - public void setAlpha(float alpha) { - super.setAlpha(alpha); - mBackgroundControl.setAlpha(alpha); - } - - @Override - public void setLayer(int zorder) { - super.setLayer(zorder); - // -3 is below all other child layers as SurfaceView never goes below -2 - mBackgroundControl.setLayer(-3); - } - - @Override - public void setPosition(float x, float y) { - super.setPosition(x, y); - mBackgroundControl.setPosition(x, y); - } - - @Override - public void setBufferSize(int w, int h) { - super.setBufferSize(w, h); - // The background surface is a color layer so we do not set a size. - } - - @Override - public void setWindowCrop(Rect crop) { - super.setWindowCrop(crop); - mBackgroundControl.setWindowCrop(crop); - } - - @Override - public void setWindowCrop(int width, int height) { - super.setWindowCrop(width, height); - mBackgroundControl.setWindowCrop(width, height); - } - - @Override - public void setLayerStack(int layerStack) { - super.setLayerStack(layerStack); - mBackgroundControl.setLayerStack(layerStack); - } - - @Override - public void setOpaque(boolean isOpaque) { - super.setOpaque(isOpaque); - mOpaque = isOpaque; - updateBackgroundVisibility(); - } - - @Override - public void setSecure(boolean isSecure) { - super.setSecure(isSecure); - } - - @Override - public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { - super.setMatrix(dsdx, dtdx, dsdy, dtdy); - mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy); - } - - @Override - public void hide() { - super.hide(); - mVisible = false; - updateBackgroundVisibility(); - } - - @Override - public void show() { - super.show(); - mVisible = true; - updateBackgroundVisibility(); - } - - @Override - public void destroy() { - super.destroy(); - mBackgroundControl.destroy(); - } - - @Override - public void release() { - super.release(); - mBackgroundControl.release(); - } - - @Override - public void setTransparentRegionHint(Region region) { - super.setTransparentRegionHint(region); - mBackgroundControl.setTransparentRegionHint(region); - } - - @Override - public void deferTransactionUntil(IBinder handle, long frame) { - super.deferTransactionUntil(handle, frame); - mBackgroundControl.deferTransactionUntil(handle, frame); - } - - @Override - public void deferTransactionUntil(Surface barrier, long frame) { - super.deferTransactionUntil(barrier, frame); - mBackgroundControl.deferTransactionUntil(barrier, frame); - } - - /** Set the color to fill the background with. */ - private void setBackgroundColor(int bgColor) { - final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f, - Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f }; - - SurfaceControl.openTransaction(); - try { - mBackgroundControl.setColor(colorComponents); - } finally { - SurfaceControl.closeTransaction(); - } - } - - void updateBackgroundVisibility() { - if (mOpaque && mVisible) { - mBackgroundControl.show(); - } else { - mBackgroundControl.hide(); - } - } - } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a1aa06e901dc..2014ec2417ac 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static java.lang.Math.max; @@ -5123,7 +5124,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P; - sBrokenInsetsDispatch = !ViewRootImpl.USE_NEW_INSETS + sBrokenInsetsDispatch = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL || targetSdkVersion < Build.VERSION_CODES.Q; sCompatibilityDone = true; @@ -8226,7 +8227,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </ul> */ public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) { - onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags); + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, + "onProvideContentCaptureStructure() for " + getClass().getSimpleName()); + } + try { + onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } /** @hide */ @@ -9016,6 +9025,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </ol> */ private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, + "notifyContentCapture(" + appeared + ") for " + getClass().getSimpleName()); + } + try { + notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(appeared); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + + private void notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(boolean appeared) { // First check if context has client, so it saves a service lookup when it doesn't if (!mContext.isContentCaptureSupported()) return; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 27d4ea4a777a..a031b704627f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -163,13 +163,16 @@ public final class ViewRootImpl implements ViewParent, private static final boolean MT_RENDERER_AVAILABLE = true; /** - * If set to true, the view system will switch from using rectangles retrieved from window to + * If set to 2, the view system will switch from using rectangles retrieved from window to * dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets * directly from the full configuration, enabling richer information about the insets state, as * well as new APIs to control it frame-by-frame, and synchronize animations with it. * <p> - * Only switch this to true once the new insets system is productionized and the old APIs are + * Only set this to 2 once the new insets system is productionized and the old APIs are * fully migrated over. + * <p> + * If set to 1, this will switch to a mode where we only use the new approach for IME, but not + * for the status/navigation bar. */ private static final String USE_NEW_INSETS_PROPERTY = "persist.wm.new_insets"; @@ -177,8 +180,26 @@ public final class ViewRootImpl implements ViewParent, * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final boolean USE_NEW_INSETS = - SystemProperties.getBoolean(USE_NEW_INSETS_PROPERTY, false); + public static final int sNewInsetsMode = + SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0); + + /** + * @see #USE_NEW_INSETS_PROPERTY + * @hide + */ + public static final int NEW_INSETS_MODE_NONE = 0; + + /** + * @see #USE_NEW_INSETS_PROPERTY + * @hide + */ + public static final int NEW_INSETS_MODE_IME = 1; + + /** + * @see #USE_NEW_INSETS_PROPERTY + * @hide + */ + public static final int NEW_INSETS_MODE_FULL = 2; /** * Set this system property to true to force the view hierarchy to render @@ -1367,7 +1388,7 @@ public final class ViewRootImpl implements ViewParent, } void notifyInsetsChanged() { - if (!USE_NEW_INSETS) { + if (sNewInsetsMode == NEW_INSETS_MODE_NONE) { return; } mApplyInsetsRequested = true; @@ -1855,10 +1876,11 @@ public final class ViewRootImpl implements ViewParent, } contentInsets = ensureInsetsNonNegative(contentInsets, "content"); stableInsets = ensureInsetsNonNegative(stableInsets, "stable"); - if (USE_NEW_INSETS) { + if (sNewInsetsMode != NEW_INSETS_MODE_NONE) { mLastWindowInsets = mInsetsController.calculateInsets( mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeNavBar, displayCutout); + mAttachInfo.mAlwaysConsumeNavBar, displayCutout, + contentInsets, stableInsets); } else { mLastWindowInsets = new WindowInsets(contentInsets, stableInsets, mContext.getResources().getConfiguration().isScreenRound(), diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 9ac0ca96ce63..e8088303eac7 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -217,7 +217,10 @@ public final class WindowInsets { return typeInsetMap; } - private static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) { + /** + * @hide + */ + static void assignCompatInsets(Insets[] typeInsetMap, Rect insets) { typeInsetMap[indexOf(TOP_BAR)] = Insets.of(0, insets.top, 0, 0); typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 86c5f188ffab..10b99eca0fa8 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -922,19 +922,6 @@ public final class InputMethodManager { } } - /** - * Returns a list of VR InputMethod currently installed. - * @hide - */ - @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) - public List<InputMethodInfo> getVrInputMethodList() { - try { - return mService.getVrInputMethodList(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - public List<InputMethodInfo> getEnabledInputMethodList() { try { return mService.getEnabledInputMethodList(); diff --git a/core/java/android/webkit/WebViewRenderer.java b/core/java/android/webkit/WebViewRenderer.java index 532825485ed3..fc38cd9bd4a2 100644 --- a/core/java/android/webkit/WebViewRenderer.java +++ b/core/java/android/webkit/WebViewRenderer.java @@ -16,6 +16,8 @@ package android.webkit; +import android.annotation.SystemApi; + /** * WebViewRenderer provides an opaque handle to a {@link WebView} renderer. */ @@ -40,6 +42,7 @@ public abstract class WebViewRenderer { * This class cannot be created by applications. * @hide */ + @SystemApi public WebViewRenderer() { } } diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index a27dbeae2f3a..18c4b467dfba 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -74,6 +74,16 @@ public abstract class FileSystemProvider extends DocumentsProvider { private static final boolean LOG_INOTIFY = false; + protected static final String SUPPORTED_QUERY_ARGS = joinNewline( + DocumentsContract.QUERY_ARG_DISPLAY_NAME, + DocumentsContract.QUERY_ARG_FILE_SIZE_OVER, + DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, + DocumentsContract.QUERY_ARG_MIME_TYPES); + + private static String joinNewline(String... args) { + return TextUtils.join("\n", args); + } + private String[] mDefaultProjection; @GuardedBy("mObservers") diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index c4aa1d7583a3..a691a2403f37 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -25,7 +25,13 @@ import android.view.View; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** - * Log all the things. + * Writes sysui_multi_event records to the system event log. + * + * Prefer the methods write(LogMaker), or count() or histogram(). Replace legacy methods with + * their current equivalents when the opportunity arises. + * + * This class is a lightweight dependency barrier - it is cheap and easy to construct. + * Logging is also cheap, so it is not normally necessary to move logging off of the UI thread. * * @hide */ @@ -52,6 +58,7 @@ public class MetricsLogger { public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN; public static final int LOGTAG = EventLogTags.SYSUI_MULTI_ACTION; + /** Write an event log record, consisting of content.serialize(). */ @UnsupportedAppUsage public void write(LogMaker content) { if (content.getType() == MetricsEvent.TYPE_UNKNOWN) { @@ -60,128 +67,145 @@ public class MetricsLogger { saveLog(content); } + /** Add an integer value to the monotonically increasing counter with the given name. */ + public void count(String name, int value) { + saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) + .setCounterName(name) + .setCounterValue(value)); + } + + /** Increment the bucket with the integer label on the histogram with the given name. */ + public void histogram(String name, int bucket) { + // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp + saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) + .setCounterName(name) + .setCounterBucket(bucket) + .setCounterValue(1)); + } + + /* Legacy logging methods follow. These are all simple shorthands and can be replaced + * with an equivalent write(). */ + + /** Logs an OPEN event on the category. + * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_OPEN)) */ public void visible(int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { throw new IllegalArgumentException("Must define metric category"); } - EventLogTags.writeSysuiViewVisibility(category, 100); saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_OPEN)); } + /** Logs a CLOSE event on the category. + * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_CLOSE)) */ public void hidden(int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { throw new IllegalArgumentException("Must define metric category"); } - EventLogTags.writeSysuiViewVisibility(category, 0); saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_CLOSE)); } - public void visibility(int category, boolean visibile) + /** Logs an OPEN or CLOSE event on the category, depending on visible. + * Equivalent to write(new LogMaker(category) + * .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)) */ + public void visibility(int category, boolean visible) throws IllegalArgumentException { - if (visibile) { + if (visible) { visible(category); } else { hidden(category); } } + /** Logs an OPEN or CLOSE event on the category, depending on vis. + * Equivalent to write(new LogMaker(category) + .setType(vis == View.VISIBLE ? + MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)) */ public void visibility(int category, int vis) throws IllegalArgumentException { visibility(category, vis == View.VISIBLE); } + /** Logs an ACTION event on the category. + * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)) */ public void action(int category) { - EventLogTags.writeSysuiAction(category, ""); saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION)); } + /** Logs an ACTION event on the category. + * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION) + .setSubtype(value) */ public void action(int category, int value) { - EventLogTags.writeSysuiAction(category, Integer.toString(value)); saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setSubtype(value)); } + /** Logs an ACTION event on the category. + * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION) + .setSubtype(value ? 1 : 0) */ public void action(int category, boolean value) { - EventLogTags.writeSysuiAction(category, Boolean.toString(value)); saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setSubtype(value ? 1 : 0)); } + /** Logs an ACTION event on the category. + * Equivalent to write(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION) + .setPackageName(value ? 1 : 0) */ public void action(int category, String pkg) { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { throw new IllegalArgumentException("Must define metric category"); } - EventLogTags.writeSysuiAction(category, pkg); saveLog(new LogMaker(category).setType(MetricsEvent.TYPE_ACTION).setPackageName(pkg)); } - /** Add an integer value to the monotonically increasing counter with the given name. */ - public void count(String name, int value) { - EventLogTags.writeSysuiCount(name, value); - saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) - .setCounterName(name) - .setCounterValue(value)); - } - - /** Increment the bucket with the integer label on the histogram with the given name. */ - public void histogram(String name, int bucket) { - // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp - EventLogTags.writeSysuiHistogram(name, bucket); - saveLog(new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) - .setCounterName(name) - .setCounterBucket(bucket) - .setCounterValue(1)); - } - - /** @deprecated use {@link #visible(int)} */ + /** @deprecated because untestable; use {@link #visible(int)} */ @Deprecated public static void visible(Context context, int category) throws IllegalArgumentException { getLogger().visible(category); } - /** @deprecated use {@link #hidden(int)} */ + /** @deprecated because untestable; use {@link #hidden(int)} */ @Deprecated public static void hidden(Context context, int category) throws IllegalArgumentException { getLogger().hidden(category); } - /** @deprecated use {@link #visibility(int, boolean)} */ + /** @deprecated because untestable; use {@link #visibility(int, boolean)} */ @Deprecated public static void visibility(Context context, int category, boolean visibile) throws IllegalArgumentException { getLogger().visibility(category, visibile); } - /** @deprecated use {@link #visibility(int, int)} */ + /** @deprecated because untestable; use {@link #visibility(int, int)} */ @Deprecated public static void visibility(Context context, int category, int vis) throws IllegalArgumentException { visibility(context, category, vis == View.VISIBLE); } - /** @deprecated use {@link #action(int)} */ + /** @deprecated because untestable; use {@link #action(int)} */ @Deprecated public static void action(Context context, int category) { getLogger().action(category); } - /** @deprecated use {@link #action(int, int)} */ + /** @deprecated because untestable; use {@link #action(int, int)} */ @Deprecated public static void action(Context context, int category, int value) { getLogger().action(category, value); } - /** @deprecated use {@link #action(int, boolean)} */ + /** @deprecated because untestable; use {@link #action(int, boolean)} */ @Deprecated public static void action(Context context, int category, boolean value) { getLogger().action(category, value); } - /** @deprecated use {@link #write(LogMaker)} */ + /** @deprecated because untestable; use {@link #write(LogMaker)} */ @Deprecated public static void action(LogMaker content) { getLogger().write(content); } - /** @deprecated use {@link #action(int, String)} */ + /** @deprecated because untestable; use {@link #action(int, String)} */ @Deprecated public static void action(Context context, int category, String pkg) { getLogger().action(category, pkg); @@ -189,7 +213,7 @@ public class MetricsLogger { /** * Add an integer value to the monotonically increasing counter with the given name. - * @deprecated use {@link #count(String, int)} + * @deprecated because untestable; use {@link #count(String, int)} */ @Deprecated public static void count(Context context, String name, int value) { diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 17cc6afcd8f0..534361e13c7d 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -87,6 +87,10 @@ import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.location.gnssmetrics.GnssMetrics; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; @@ -187,18 +191,19 @@ public class BatteryStatsImpl extends BatteryStats { private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); @VisibleForTesting - protected KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader(); + protected KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader = + new KernelCpuUidUserSysTimeReader(true); @VisibleForTesting protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders; @VisibleForTesting - protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader = - new KernelUidCpuFreqTimeReader(); + protected KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader = + new KernelCpuUidFreqTimeReader(true); @VisibleForTesting - protected KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader = - new KernelUidCpuActiveTimeReader(); + protected KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader = + new KernelCpuUidActiveTimeReader(true); @VisibleForTesting - protected KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader = - new KernelUidCpuClusterTimeReader(); + protected KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader = + new KernelCpuUidClusterTimeReader(true); @VisibleForTesting protected KernelSingleUidTimeReader mKernelSingleUidTimeReader; @@ -248,9 +253,9 @@ public class BatteryStatsImpl extends BatteryStats { /** Last time that RPM stats were updated by updateRpmStatsLocked. */ private long mLastRpmStatsUpdateTimeMs = -RPM_STATS_UPDATE_FREQ_MS; /** - * Use a queue to delay removing UIDs from {@link KernelUidCpuTimeReader}, - * {@link KernelUidCpuActiveTimeReader}, {@link KernelUidCpuClusterTimeReader}, - * {@link KernelUidCpuFreqTimeReader} and from the Kernel. + * Use a queue to delay removing UIDs from {@link KernelCpuUidUserSysTimeReader}, + * {@link KernelCpuUidActiveTimeReader}, {@link KernelCpuUidClusterTimeReader}, + * {@link KernelCpuUidFreqTimeReader} and from the Kernel. * * Isolated and invalid UID info must be removed to conserve memory. However, STATSD and * Batterystats both need to access UID cpu time. To resolve this race condition, only @@ -281,22 +286,22 @@ public class BatteryStatsImpl extends BatteryStats { void remove() { if (startUid == endUid) { - mKernelUidCpuTimeReader.removeUid(startUid); - mKernelUidCpuFreqTimeReader.removeUid(startUid); + mCpuUidUserSysTimeReader.removeUid(startUid); + mCpuUidFreqTimeReader.removeUid(startUid); if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { - mKernelUidCpuActiveTimeReader.removeUid(startUid); - mKernelUidCpuClusterTimeReader.removeUid(startUid); + mCpuUidActiveTimeReader.removeUid(startUid); + mCpuUidClusterTimeReader.removeUid(startUid); } if (mKernelSingleUidTimeReader != null) { mKernelSingleUidTimeReader.removeUid(startUid); } mNumUidsRemoved++; } else if (startUid < endUid) { - mKernelUidCpuFreqTimeReader.removeUidsInRange(startUid, endUid); - mKernelUidCpuTimeReader.removeUidsInRange(startUid, endUid); + mCpuUidFreqTimeReader.removeUidsInRange(startUid, endUid); + mCpuUidUserSysTimeReader.removeUidsInRange(startUid, endUid); if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { - mKernelUidCpuActiveTimeReader.removeUidsInRange(startUid, endUid); - mKernelUidCpuClusterTimeReader.removeUidsInRange(startUid, endUid); + mCpuUidActiveTimeReader.removeUidsInRange(startUid, endUid); + mCpuUidClusterTimeReader.removeUidsInRange(startUid, endUid); } if (mKernelSingleUidTimeReader != null) { mKernelSingleUidTimeReader.removeUidsInRange(startUid, endUid); @@ -496,7 +501,7 @@ public class BatteryStatsImpl extends BatteryStats { } final SparseArray<long[]> allUidCpuFreqTimesMs = - mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs(); + mCpuUidFreqTimeReader.getAllUidCpuFreqTimeMs(); // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to // compute deltas since it might result in mis-attributing cpu times to wrong states. if (mIsPerProcessStateCpuDataStale) { @@ -553,16 +558,16 @@ public class BatteryStatsImpl extends BatteryStats { return false; } if (mCpuFreqs == null) { - mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile); + mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile); } if (mCpuFreqs != null) { mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length); } else { - mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable(); + mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable(); return false; } } - mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable() + mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable() && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable(); return true; } @@ -11926,7 +11931,7 @@ public class BatteryStatsImpl extends BatteryStats { } if (mCpuFreqs == null) { - mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile); + mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile); } // Calculate the wakelocks we have to distribute amongst. The system is excluded as it is @@ -11952,12 +11957,12 @@ public class BatteryStatsImpl extends BatteryStats { // When the battery is not on, we don't attribute the cpu times to any timers but we still // need to take the snapshots. if (!onBattery) { - mKernelUidCpuTimeReader.readDelta(null); - mKernelUidCpuFreqTimeReader.readDelta(null); + mCpuUidUserSysTimeReader.readDelta(null); + mCpuUidFreqTimeReader.readDelta(null); mNumAllUidCpuTimeReads += 2; if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { - mKernelUidCpuActiveTimeReader.readDelta(null); - mKernelUidCpuClusterTimeReader.readDelta(null); + mCpuUidActiveTimeReader.readDelta(null); + mCpuUidClusterTimeReader.readDelta(null); mNumAllUidCpuTimeReads += 2; } for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) { @@ -11967,7 +11972,7 @@ public class BatteryStatsImpl extends BatteryStats { } mUserInfoProvider.refreshUserIds(); - final SparseLongArray updatedUids = mKernelUidCpuFreqTimeReader.perClusterTimesAvailable() + final SparseLongArray updatedUids = mCpuUidFreqTimeReader.perClusterTimesAvailable() ? null : new SparseLongArray(); readKernelUidCpuTimesLocked(partialTimersToConsider, updatedUids, onBattery); // updatedUids=null means /proc/uid_time_in_state provides snapshots of per-cluster cpu @@ -12084,18 +12089,20 @@ public class BatteryStatsImpl extends BatteryStats { final int numWakelocks = partialTimers == null ? 0 : partialTimers.size(); final long startTimeMs = mClocks.uptimeMillis(); - mKernelUidCpuTimeReader.readDelta((uid, userTimeUs, systemTimeUs) -> { + mCpuUidUserSysTimeReader.readDelta((uid, timesUs) -> { + long userTimeUs = timesUs[0], systemTimeUs = timesUs[1]; + uid = mapUid(uid); if (Process.isIsolated(uid)) { // This could happen if the isolated uid mapping was removed before that process // was actually killed. - mKernelUidCpuTimeReader.removeUid(uid); + mCpuUidUserSysTimeReader.removeUid(uid); Slog.d(TAG, "Got readings for an isolated uid with no mapping: " + uid); return; } if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { Slog.d(TAG, "Got readings for an invalid user's uid " + uid); - mKernelUidCpuTimeReader.removeUid(uid); + mCpuUidUserSysTimeReader.removeUid(uid); return; } final Uid u = getUidStatsLocked(uid); @@ -12189,21 +12196,21 @@ public class BatteryStatsImpl extends BatteryStats { public void readKernelUidCpuFreqTimesLocked(@Nullable ArrayList<StopwatchTimer> partialTimers, boolean onBattery, boolean onBatteryScreenOff) { final boolean perClusterTimesAvailable = - mKernelUidCpuFreqTimeReader.perClusterTimesAvailable(); + mCpuUidFreqTimeReader.perClusterTimesAvailable(); final int numWakelocks = partialTimers == null ? 0 : partialTimers.size(); final int numClusters = mPowerProfile.getNumCpuClusters(); mWakeLockAllocationsUs = null; final long startTimeMs = mClocks.uptimeMillis(); - mKernelUidCpuFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> { + mCpuUidFreqTimeReader.readDelta((uid, cpuFreqTimeMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { - mKernelUidCpuFreqTimeReader.removeUid(uid); + mCpuUidFreqTimeReader.removeUid(uid); Slog.d(TAG, "Got freq readings for an isolated uid with no mapping: " + uid); return; } if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid); - mKernelUidCpuFreqTimeReader.removeUid(uid); + mCpuUidFreqTimeReader.removeUid(uid); return; } final Uid u = getUidStatsLocked(uid); @@ -12307,16 +12314,16 @@ public class BatteryStatsImpl extends BatteryStats { @VisibleForTesting public void readKernelUidCpuActiveTimesLocked(boolean onBattery) { final long startTimeMs = mClocks.uptimeMillis(); - mKernelUidCpuActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> { + mCpuUidActiveTimeReader.readDelta((uid, cpuActiveTimesMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { - mKernelUidCpuActiveTimeReader.removeUid(uid); + mCpuUidActiveTimeReader.removeUid(uid); Slog.w(TAG, "Got active times for an isolated uid with no mapping: " + uid); return; } if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { Slog.w(TAG, "Got active times for an invalid user's uid " + uid); - mKernelUidCpuActiveTimeReader.removeUid(uid); + mCpuUidActiveTimeReader.removeUid(uid); return; } final Uid u = getUidStatsLocked(uid); @@ -12336,16 +12343,16 @@ public class BatteryStatsImpl extends BatteryStats { @VisibleForTesting public void readKernelUidCpuClusterTimesLocked(boolean onBattery) { final long startTimeMs = mClocks.uptimeMillis(); - mKernelUidCpuClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> { + mCpuUidClusterTimeReader.readDelta((uid, cpuClusterTimesMs) -> { uid = mapUid(uid); if (Process.isIsolated(uid)) { - mKernelUidCpuClusterTimeReader.removeUid(uid); + mCpuUidClusterTimeReader.removeUid(uid); Slog.w(TAG, "Got cluster times for an isolated uid with no mapping: " + uid); return; } if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) { Slog.w(TAG, "Got cluster times for an invalid user's uid " + uid); - mKernelUidCpuClusterTimeReader.removeUid(uid); + mCpuUidClusterTimeReader.removeUid(uid); return; } final Uid u = getUidStatsLocked(uid); @@ -13344,7 +13351,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false; private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true; private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000; - private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 10_000; + private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000; private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L; private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000; private static final long DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS = 300_000; @@ -13357,7 +13364,9 @@ public class BatteryStatsImpl extends BatteryStats { public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE; public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME; public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS; - public long KERNEL_UID_READERS_THROTTLE_TIME = DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME; + /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an + * update when startObserving. */ + public long KERNEL_UID_READERS_THROTTLE_TIME; public long UID_REMOVE_DELAY_MS = DEFAULT_UID_REMOVE_DELAY_MS; public long EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS; @@ -13464,11 +13473,11 @@ public class BatteryStatsImpl extends BatteryStats { private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) { KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs; if (oldTimeMs != newTimeMs) { - mKernelUidCpuTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME); - mKernelUidCpuFreqTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME); - mKernelUidCpuActiveTimeReader.setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME); - mKernelUidCpuClusterTimeReader - .setThrottleInterval(KERNEL_UID_READERS_THROTTLE_TIME); + mCpuUidUserSysTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME); + mCpuUidFreqTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME); + mCpuUidActiveTimeReader.setThrottle(KERNEL_UID_READERS_THROTTLE_TIME); + mCpuUidClusterTimeReader + .setThrottle(KERNEL_UID_READERS_THROTTLE_TIME); } } diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index 051a96c39f28..64543522893e 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -53,6 +53,7 @@ public class BinderCallsStats implements BinderInternal.Observer { public static final boolean DETAILED_TRACKING_DEFAULT = true; public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100; public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000; + private static final String DEBUG_ENTRY_PREFIX = "__DEBUG_"; private static class OverflowBinder extends Binder {} @@ -347,7 +348,7 @@ public class BinderCallsStats implements BinderInternal.Observer { callStat.callingUid = uid; callStat.recordedCallCount = 1; callStat.callCount = 1; - callStat.methodName = "__DEBUG_" + variableName; + callStat.methodName = DEBUG_ENTRY_PREFIX + variableName; callStat.latencyMicros = value; return callStat; } @@ -398,6 +399,10 @@ public class BinderCallsStats implements BinderInternal.Observer { final List<ExportedCallStat> exportedCallStats = getExportedCallStats(); exportedCallStats.sort(BinderCallsStats::compareByCpuDesc); for (ExportedCallStat e : exportedCallStats) { + if (e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) { + // Do not dump debug entries. + continue; + } sb.setLength(0); sb.append(" ") .append(packageMap.mapUid(e.callingUid)) diff --git a/core/java/com/android/internal/os/KernelCpuProcReader.java b/core/java/com/android/internal/os/KernelCpuProcReader.java deleted file mode 100644 index c233ea8e78b7..000000000000 --- a/core/java/com/android/internal/os/KernelCpuProcReader.java +++ /dev/null @@ -1,162 +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.internal.os; - -import android.os.StrictMode; -import android.os.SystemClock; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; - -/** - * Reads cpu time proc files with throttling (adjustable interval). - * - * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance() - * method will return corresponding reader instance. In order to prevent frequent GC, - * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files. - * - * A KernelCpuProcReader instance keeps an error counter. When the number of read errors within that - * instance accumulates to 5, this instance will reject all further read requests. - * - * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via - * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current - * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from - * the last read timestamp, {@link #readBytes()} will return previous result. - * - * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while - * accessing its instance methods or digesting the return values. - */ -public class KernelCpuProcReader { - private static final String TAG = "KernelCpuProcReader"; - private static final int ERROR_THRESHOLD = 5; - // Throttle interval in milliseconds - private static final long DEFAULT_THROTTLE_INTERVAL = 3000L; - private static final int MAX_BUFFER_SIZE = 1024 * 1024; - private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state"; - private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time"; - private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time"; - - private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader( - PROC_UID_FREQ_TIME); - private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader( - PROC_UID_ACTIVE_TIME); - private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader( - PROC_UID_CLUSTER_TIME); - - public static KernelCpuProcReader getFreqTimeReaderInstance() { - return mFreqTimeReader; - } - - public static KernelCpuProcReader getActiveTimeReaderInstance() { - return mActiveTimeReader; - } - - public static KernelCpuProcReader getClusterTimeReaderInstance() { - return mClusterTimeReader; - } - - private int mErrors; - private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL; - private long mLastReadTime = Long.MIN_VALUE; - private final Path mProc; - private byte[] mBuffer = new byte[8 * 1024]; - private int mContentSize; - - @VisibleForTesting - public KernelCpuProcReader(String procFile) { - mProc = Paths.get(procFile); - } - - /** - * Reads all bytes from the corresponding proc file. - * - * If elapsed time since last call to this method is less than the throttle interval, it will - * return previous result. When IOException accumulates to 5, it will always return null. This - * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this - * object while calling this method and digesting its return value. - * - * @return a {@link ByteBuffer} containing all bytes from the proc file. - */ - public ByteBuffer readBytes() { - if (mErrors >= ERROR_THRESHOLD) { - return null; - } - if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) { - if (mContentSize > 0) { - return ByteBuffer.wrap(mBuffer, 0, mContentSize).asReadOnlyBuffer() - .order(ByteOrder.nativeOrder()); - } - return null; - } - mLastReadTime = SystemClock.elapsedRealtime(); - mContentSize = 0; - final int oldMask = StrictMode.allowThreadDiskReadsMask(); - try (InputStream in = Files.newInputStream(mProc)) { - int numBytes = 0; - int curr; - while ((curr = in.read(mBuffer, numBytes, mBuffer.length - numBytes)) >= 0) { - numBytes += curr; - if (numBytes == mBuffer.length) { - // Hit the limit. Resize mBuffer. - if (mBuffer.length == MAX_BUFFER_SIZE) { - mErrors++; - Slog.e(TAG, "Proc file is too large: " + mProc); - return null; - } - mBuffer = Arrays.copyOf(mBuffer, - Math.min(mBuffer.length << 1, MAX_BUFFER_SIZE)); - } - } - mContentSize = numBytes; - return ByteBuffer.wrap(mBuffer, 0, mContentSize).asReadOnlyBuffer() - .order(ByteOrder.nativeOrder()); - } catch (NoSuchFileException | FileNotFoundException e) { - // Happens when the kernel does not provide this file. Not a big issue. Just log it. - mErrors++; - Slog.w(TAG, "File not exist: " + mProc); - } catch (IOException e) { - mErrors++; - Slog.e(TAG, "Error reading: " + mProc, e); - } finally { - StrictMode.setThreadPolicyMask(oldMask); - } - return null; - } - - /** - * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock - * on this object is recommended. - * - * @param throttleInterval throttle interval in milliseconds - */ - public void setThrottleInterval(long throttleInterval) { - if (throttleInterval >= 0) { - mThrottleInterval = throttleInterval; - } - } -} diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java index 7021b5781347..e6d044f4722b 100644 --- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java @@ -177,6 +177,9 @@ public abstract class KernelCpuUidTimeReader<T> { * The file contains a monotonically increasing count of time for a single boot. This class * maintains the previous results of a call to {@link #readDelta} in order to provide a proper * delta. + * + * The second parameter of the callback is a long[] with 2 elements, [user time in us, system + * time in us]. */ public static class KernelCpuUidUserSysTimeReader extends KernelCpuUidTimeReader<long[]> { private static final String REMOVE_UID_PROC_FILE = "/proc/uid_cputime/remove_uid_range"; diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java index ad628524f443..3c43a11a8e9f 100644 --- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java @@ -16,7 +16,6 @@ package com.android.internal.os; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; -import static com.android.internal.os.KernelUidCpuFreqTimeReader.UID_TIMES_PROC_FILE; import android.annotation.NonNull; import android.util.Slog; @@ -34,11 +33,12 @@ import java.util.Arrays; @VisibleForTesting(visibility = PACKAGE) public class KernelSingleUidTimeReader { - private final String TAG = KernelUidCpuFreqTimeReader.class.getName(); - private final boolean DBG = false; + private static final String TAG = KernelSingleUidTimeReader.class.getName(); + private static final boolean DBG = false; - private final String PROC_FILE_DIR = "/proc/uid/"; - private final String PROC_FILE_NAME = "/time_in_state"; + private static final String PROC_FILE_DIR = "/proc/uid/"; + private static final String PROC_FILE_NAME = "/time_in_state"; + private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state"; @VisibleForTesting public static final int TOTAL_READ_ERROR_COUNT = 5; diff --git a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java deleted file mode 100644 index bd8a67a8bd0e..000000000000 --- a/core/java/com/android/internal/os/KernelUidCpuActiveTimeReader.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import android.annotation.Nullable; -import android.util.Slog; -import android.util.SparseArray; - -import com.android.internal.annotations.VisibleForTesting; - -import java.nio.ByteBuffer; -import java.nio.IntBuffer; -import java.util.function.Consumer; - -/** - * Reads binary proc file /proc/uid_cpupower/concurrent_active_time and reports CPU active time to - * BatteryStats to compute {@link PowerProfile#POWER_CPU_ACTIVE}. - * - * concurrent_active_time is an array of u32's in the following format: - * [n, uid0, time0a, time0b, ..., time0n, - * uid1, time1a, time1b, ..., time1n, - * uid2, time2a, time2b, ..., time2n, etc.] - * where n is the total number of cpus (num_possible_cpus) - * ... - * timeXn means the CPU time that a UID X spent running concurrently with n other processes. - * The file contains a monotonically increasing count of time for a single boot. This class - * maintains the previous results of a call to {@link #readDelta} in order to provide a - * proper delta. - * - * This class uses a throttler to reject any {@link #readDelta} call within - * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader}, - * which has a shorter throttle interval and returns cached result from last read when the request - * is throttled. - * - * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each - * caller has its own view of delta. - */ -public class KernelUidCpuActiveTimeReader extends - KernelUidCpuTimeReaderBase<KernelUidCpuActiveTimeReader.Callback> { - private static final String TAG = KernelUidCpuActiveTimeReader.class.getSimpleName(); - - private final KernelCpuProcReader mProcReader; - private SparseArray<Double> mLastUidCpuActiveTimeMs = new SparseArray<>(); - private int mCores; - - public interface Callback extends KernelUidCpuTimeReaderBase.Callback { - /** - * Notifies when new data is available. - * - * @param uid uid int - * @param cpuActiveTimeMs cpu active time spent by this uid in milliseconds - */ - void onUidCpuActiveTime(int uid, long cpuActiveTimeMs); - } - - public KernelUidCpuActiveTimeReader() { - mProcReader = KernelCpuProcReader.getActiveTimeReaderInstance(); - } - - @VisibleForTesting - public KernelUidCpuActiveTimeReader(KernelCpuProcReader procReader) { - mProcReader = procReader; - } - - @Override - protected void readDeltaImpl(@Nullable Callback callback) { - readImpl((buf) -> { - int uid = buf.get(); - double activeTime = sumActiveTime(buf); - if (activeTime > 0) { - double delta = activeTime - mLastUidCpuActiveTimeMs.get(uid, 0.0); - if (delta > 0) { - mLastUidCpuActiveTimeMs.put(uid, activeTime); - if (callback != null) { - callback.onUidCpuActiveTime(uid, (long) delta); - } - } else if (delta < 0) { - Slog.e(TAG, "Negative delta from active time proc: " + delta); - } - } - }); - } - - public void readAbsolute(Callback callback) { - readImpl((buf) -> { - int uid = buf.get(); - double activeTime = sumActiveTime(buf); - if (activeTime > 0) { - callback.onUidCpuActiveTime(uid, (long) activeTime); - } - }); - } - - private double sumActiveTime(IntBuffer buffer) { - double sum = 0; - boolean corrupted = false; - for (int j = 1; j <= mCores; j++) { - int time = buffer.get(); - if (time < 0) { - // Even if error happens, we still need to continue reading. - // Buffer cannot be skipped. - Slog.e(TAG, "Negative time from active time proc: " + time); - corrupted = true; - } else { - sum += (double) time * 10 / j; // Unit is 10ms. - } - } - return corrupted ? -1 : sum; - } - - /** - * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last - * seen results while processing the buffer, while readAbsolute returns the absolute value read - * from the buffer without storing. So readImpl contains the common logic of the two, leaving - * the difference to a processUid function. - * - * @param processUid the callback function to process the uid entry in the buffer. - */ - private void readImpl(Consumer<IntBuffer> processUid) { - synchronized (mProcReader) { - final ByteBuffer bytes = mProcReader.readBytes(); - if (bytes == null || bytes.remaining() <= 4) { - // Error already logged in mProcReader. - return; - } - if ((bytes.remaining() & 3) != 0) { - Slog.wtf(TAG, - "Cannot parse active time proc bytes to int: " + bytes.remaining()); - return; - } - final IntBuffer buf = bytes.asIntBuffer(); - final int cores = buf.get(); - if (mCores != 0 && cores != mCores) { - Slog.wtf(TAG, "Cpu active time wrong # cores: " + cores); - return; - } - mCores = cores; - if (cores <= 0 || buf.remaining() % (cores + 1) != 0) { - Slog.wtf(TAG, - "Cpu active time format error: " + buf.remaining() + " / " + (cores - + 1)); - return; - } - int numUids = buf.remaining() / (cores + 1); - for (int i = 0; i < numUids; i++) { - processUid.accept(buf); - } - if (DEBUG) { - Slog.d(TAG, "Read uids: " + numUids); - } - } - } - - public void removeUid(int uid) { - mLastUidCpuActiveTimeMs.delete(uid); - } - - public void removeUidsInRange(int startUid, int endUid) { - mLastUidCpuActiveTimeMs.put(startUid, null); - mLastUidCpuActiveTimeMs.put(endUid, null); - final int firstIndex = mLastUidCpuActiveTimeMs.indexOfKey(startUid); - final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid); - mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1); - } -} diff --git a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java deleted file mode 100644 index 3cbfaead4779..000000000000 --- a/core/java/com/android/internal/os/KernelUidCpuClusterTimeReader.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import android.annotation.Nullable; -import android.util.Slog; -import android.util.SparseArray; - -import com.android.internal.annotations.VisibleForTesting; - -import java.nio.ByteBuffer; -import java.nio.IntBuffer; -import java.util.function.Consumer; - -/** - * Reads binary proc file /proc/uid_cpupower/concurrent_policy_time and reports CPU cluster times - * to BatteryStats to compute cluster power. See - * {@link PowerProfile#getAveragePowerForCpuCluster(int)}. - * - * concurrent_policy_time is an array of u32's in the following format: - * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n, - * uid1, time1a, time1b, ..., time1n, - * uid2, time2a, time2b, ..., time2n, etc.] - * where n is the number of policies - * xi is the number cpus on a particular policy - * Each uidX is followed by x0 time entries corresponding to the time UID X spent on cluster0 - * running concurrently with 0, 1, 2, ..., x0 - 1 other processes, then followed by x1, ..., xn - * time entries. - * - * The file contains a monotonically increasing count of time for a single boot. This class - * maintains the previous results of a call to {@link #readDelta} in order to provide a - * proper delta. - * - * This class uses a throttler to reject any {@link #readDelta} call within - * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader}, - * which has a shorter throttle interval and returns cached result from last read when the request - * is throttled. - * - * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each - * caller has its own view of delta. - */ -public class KernelUidCpuClusterTimeReader extends - KernelUidCpuTimeReaderBase<KernelUidCpuClusterTimeReader.Callback> { - private static final String TAG = KernelUidCpuClusterTimeReader.class.getSimpleName(); - - private final KernelCpuProcReader mProcReader; - private SparseArray<double[]> mLastUidPolicyTimeMs = new SparseArray<>(); - - private int mNumClusters = -1; - private int mNumCores; - private int[] mNumCoresOnCluster; - - private double[] mCurTime; // Reuse to avoid GC. - private long[] mDeltaTime; // Reuse to avoid GC. - private long[] mCurTimeRounded; // Reuse to avoid GC. - - public interface Callback extends KernelUidCpuTimeReaderBase.Callback { - /** - * Notifies when new data is available. - * - * @param uid uid int - * @param cpuClusterTimeMs an array of times spent by this uid on corresponding clusters. - * The array index is the cluster index. - */ - void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs); - } - - public KernelUidCpuClusterTimeReader() { - mProcReader = KernelCpuProcReader.getClusterTimeReaderInstance(); - } - - @VisibleForTesting - public KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader) { - mProcReader = procReader; - } - - @Override - protected void readDeltaImpl(@Nullable Callback cb) { - readImpl((buf) -> { - int uid = buf.get(); - double[] lastTimes = mLastUidPolicyTimeMs.get(uid); - if (lastTimes == null) { - lastTimes = new double[mNumClusters]; - mLastUidPolicyTimeMs.put(uid, lastTimes); - } - if (!sumClusterTime(buf, mCurTime)) { - return; - } - boolean valid = true; - boolean notify = false; - for (int i = 0; i < mNumClusters; i++) { - mDeltaTime[i] = (long) (mCurTime[i] - lastTimes[i]); - if (mDeltaTime[i] < 0) { - Slog.e(TAG, "Negative delta from cluster time proc: " + mDeltaTime[i]); - valid = false; - } - notify |= mDeltaTime[i] > 0; - } - if (notify && valid) { - System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters); - if (cb != null) { - cb.onUidCpuPolicyTime(uid, mDeltaTime); - } - } - }); - } - - public void readAbsolute(Callback callback) { - readImpl((buf) -> { - int uid = buf.get(); - if (sumClusterTime(buf, mCurTime)) { - for (int i = 0; i < mNumClusters; i++) { - mCurTimeRounded[i] = (long) mCurTime[i]; - } - callback.onUidCpuPolicyTime(uid, mCurTimeRounded); - } - }); - } - - private boolean sumClusterTime(IntBuffer buffer, double[] clusterTime) { - boolean valid = true; - for (int i = 0; i < mNumClusters; i++) { - clusterTime[i] = 0; - for (int j = 1; j <= mNumCoresOnCluster[i]; j++) { - int time = buffer.get(); - if (time < 0) { - Slog.e(TAG, "Negative time from cluster time proc: " + time); - valid = false; - } - clusterTime[i] += (double) time * 10 / j; // Unit is 10ms. - } - } - return valid; - } - - /** - * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last - * seen results while processing the buffer, while readAbsolute returns the absolute value read - * from the buffer without storing. So readImpl contains the common logic of the two, leaving - * the difference to a processUid function. - * - * @param processUid the callback function to process the uid entry in the buffer. - */ - private void readImpl(Consumer<IntBuffer> processUid) { - synchronized (mProcReader) { - ByteBuffer bytes = mProcReader.readBytes(); - if (bytes == null || bytes.remaining() <= 4) { - // Error already logged in mProcReader. - return; - } - if ((bytes.remaining() & 3) != 0) { - Slog.wtf(TAG, - "Cannot parse cluster time proc bytes to int: " + bytes.remaining()); - return; - } - IntBuffer buf = bytes.asIntBuffer(); - final int numClusters = buf.get(); - if (numClusters <= 0) { - Slog.wtf(TAG, "Cluster time format error: " + numClusters); - return; - } - if (mNumClusters == -1) { - mNumClusters = numClusters; - } - if (buf.remaining() < numClusters) { - Slog.wtf(TAG, "Too few data left in the buffer: " + buf.remaining()); - return; - } - if (mNumCores <= 0) { - if (!readCoreInfo(buf, numClusters)) { - return; - } - } else { - buf.position(buf.position() + numClusters); - } - - if (buf.remaining() % (mNumCores + 1) != 0) { - Slog.wtf(TAG, - "Cluster time format error: " + buf.remaining() + " / " + (mNumCores - + 1)); - return; - } - int numUids = buf.remaining() / (mNumCores + 1); - - for (int i = 0; i < numUids; i++) { - processUid.accept(buf); - } - if (DEBUG) { - Slog.d(TAG, "Read uids: " + numUids); - } - } - } - - // Returns if it has read valid info. - private boolean readCoreInfo(IntBuffer buf, int numClusters) { - int numCores = 0; - int[] numCoresOnCluster = new int[numClusters]; - for (int i = 0; i < numClusters; i++) { - numCoresOnCluster[i] = buf.get(); - numCores += numCoresOnCluster[i]; - } - if (numCores <= 0) { - Slog.e(TAG, "Invalid # cores from cluster time proc file: " + numCores); - return false; - } - mNumCores = numCores; - mNumCoresOnCluster = numCoresOnCluster; - mCurTime = new double[numClusters]; - mDeltaTime = new long[numClusters]; - mCurTimeRounded = new long[numClusters]; - return true; - } - - public void removeUid(int uid) { - mLastUidPolicyTimeMs.delete(uid); - } - - public void removeUidsInRange(int startUid, int endUid) { - mLastUidPolicyTimeMs.put(startUid, null); - mLastUidPolicyTimeMs.put(endUid, null); - final int firstIndex = mLastUidPolicyTimeMs.indexOfKey(startUid); - final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid); - mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1); - } -} diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java deleted file mode 100644 index 5b46d0f29c20..000000000000 --- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import static com.android.internal.util.Preconditions.checkNotNull; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.StrictMode; -import android.util.IntArray; -import android.util.Slog; -import android.util.SparseArray; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.IntBuffer; -import java.util.function.Consumer; - -/** - * Reads /proc/uid_time_in_state which has the format: - * - * uid: [freq1] [freq2] [freq3] ... - * [uid1]: [time in freq1] [time in freq2] [time in freq3] ... - * [uid2]: [time in freq1] [time in freq2] [time in freq3] ... - * ... - * - * Binary variation reads /proc/uid_cpupower/time_in_state in the following format: - * [n, uid0, time0a, time0b, ..., time0n, - * uid1, time1a, time1b, ..., time1n, - * uid2, time2a, time2b, ..., time2n, etc.] - * where n is the total number of frequencies. - * - * This provides the times a UID's processes spent executing at each different cpu frequency. - * The file contains a monotonically increasing count of time for a single boot. This class - * maintains the previous results of a call to {@link #readDelta} in order to provide a proper - * delta. - * - * This class uses a throttler to reject any {@link #readDelta} call within - * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader}, - * which has a shorter throttle interval and returns cached result from last read when the request - * is throttled. - * - * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each - * caller has its own view of delta. - */ -public class KernelUidCpuFreqTimeReader extends - KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> { - private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName(); - static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state"; - - public interface Callback extends KernelUidCpuTimeReaderBase.Callback { - void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs); - } - - private long[] mCpuFreqs; - private long[] mCurTimes; // Reuse to prevent GC. - private long[] mDeltaTimes; // Reuse to prevent GC. - private int mCpuFreqsCount; - private final KernelCpuProcReader mProcReader; - - private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>(); - - // We check the existence of proc file a few times (just in case it is not ready yet when we - // start reading) and if it is not available, we simply ignore further read requests. - private static final int TOTAL_READ_ERROR_COUNT = 5; - private int mReadErrorCounter; - private boolean mPerClusterTimesAvailable; - private boolean mAllUidTimesAvailable = true; - - public KernelUidCpuFreqTimeReader() { - mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance(); - } - - @VisibleForTesting - public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) { - mProcReader = procReader; - } - - public boolean perClusterTimesAvailable() { - return mPerClusterTimesAvailable; - } - - public boolean allUidTimesAvailable() { - return mAllUidTimesAvailable; - } - - public SparseArray<long[]> getAllUidCpuFreqTimeMs() { - return mLastUidCpuFreqTimeMs; - } - - public long[] readFreqs(@NonNull PowerProfile powerProfile) { - checkNotNull(powerProfile); - if (mCpuFreqs != null) { - // No need to read cpu freqs more than once. - return mCpuFreqs; - } - if (!mAllUidTimesAvailable) { - return null; - } - final int oldMask = StrictMode.allowThreadDiskReadsMask(); - try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) { - return readFreqs(reader, powerProfile); - } catch (IOException e) { - if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) { - mAllUidTimesAvailable = false; - } - Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); - return null; - } finally { - StrictMode.setThreadPolicyMask(oldMask); - } - } - - @VisibleForTesting - public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile) - throws IOException { - final String line = reader.readLine(); - if (line == null) { - return null; - } - final String[] freqStr = line.split(" "); - // First item would be "uid: " which needs to be ignored. - mCpuFreqsCount = freqStr.length - 1; - mCpuFreqs = new long[mCpuFreqsCount]; - mCurTimes = new long[mCpuFreqsCount]; - mDeltaTimes = new long[mCpuFreqsCount]; - for (int i = 0; i < mCpuFreqsCount; ++i) { - mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10); - } - - // Check if the freqs in the proc file correspond to per-cluster freqs. - final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs(); - final int numClusters = powerProfile.getNumCpuClusters(); - if (numClusterFreqs.size() == numClusters) { - mPerClusterTimesAvailable = true; - for (int i = 0; i < numClusters; ++i) { - if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) { - mPerClusterTimesAvailable = false; - break; - } - } - } else { - mPerClusterTimesAvailable = false; - } - Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable); - return mCpuFreqs; - } - - @Override - @VisibleForTesting - public void readDeltaImpl(@Nullable Callback callback) { - if (mCpuFreqs == null) { - return; - } - readImpl((buf) -> { - int uid = buf.get(); - long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid); - if (lastTimes == null) { - lastTimes = new long[mCpuFreqsCount]; - mLastUidCpuFreqTimeMs.put(uid, lastTimes); - } - if (!getFreqTimeForUid(buf, mCurTimes)) { - return; - } - boolean notify = false; - boolean valid = true; - for (int i = 0; i < mCpuFreqsCount; i++) { - mDeltaTimes[i] = mCurTimes[i] - lastTimes[i]; - if (mDeltaTimes[i] < 0) { - Slog.e(TAG, "Negative delta from freq time proc: " + mDeltaTimes[i]); - valid = false; - } - notify |= mDeltaTimes[i] > 0; - } - if (notify && valid) { - System.arraycopy(mCurTimes, 0, lastTimes, 0, mCpuFreqsCount); - if (callback != null) { - callback.onUidCpuFreqTime(uid, mDeltaTimes); - } - } - }); - } - - public void readAbsolute(Callback callback) { - readImpl((buf) -> { - int uid = buf.get(); - if (getFreqTimeForUid(buf, mCurTimes)) { - callback.onUidCpuFreqTime(uid, mCurTimes); - } - }); - } - - private boolean getFreqTimeForUid(IntBuffer buffer, long[] freqTime) { - boolean valid = true; - for (int i = 0; i < mCpuFreqsCount; i++) { - freqTime[i] = (long) buffer.get() * 10; // Unit is 10ms. - if (freqTime[i] < 0) { - Slog.e(TAG, "Negative time from freq time proc: " + freqTime[i]); - valid = false; - } - } - return valid; - } - - /** - * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last - * seen results while processing the buffer, while readAbsolute returns the absolute value read - * from the buffer without storing. So readImpl contains the common logic of the two, leaving - * the difference to a processUid function. - * - * @param processUid the callback function to process the uid entry in the buffer. - */ - private void readImpl(Consumer<IntBuffer> processUid) { - synchronized (mProcReader) { - ByteBuffer bytes = mProcReader.readBytes(); - if (bytes == null || bytes.remaining() <= 4) { - // Error already logged in mProcReader. - return; - } - if ((bytes.remaining() & 3) != 0) { - Slog.wtf(TAG, "Cannot parse freq time proc bytes to int: " + bytes.remaining()); - return; - } - IntBuffer buf = bytes.asIntBuffer(); - final int freqs = buf.get(); - if (freqs != mCpuFreqsCount) { - Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs); - return; - } - if (buf.remaining() % (freqs + 1) != 0) { - Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1)); - return; - } - int numUids = buf.remaining() / (freqs + 1); - for (int i = 0; i < numUids; i++) { - processUid.accept(buf); - } - if (DEBUG) { - Slog.d(TAG, "Read uids: #" + numUids); - } - } - } - - public void removeUid(int uid) { - mLastUidCpuFreqTimeMs.delete(uid); - } - - public void removeUidsInRange(int startUid, int endUid) { - mLastUidCpuFreqTimeMs.put(startUid, null); - mLastUidCpuFreqTimeMs.put(endUid, null); - final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid); - final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid); - mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1); - } - - /** - * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs - * read from the proc file. - * - * We need to assume that freqs in each cluster are strictly increasing. - * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means - * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52) - * - * @return an IntArray filled with no. of freqs in each cluster. - */ - private IntArray extractClusterInfoFromProcFileFreqs() { - final IntArray numClusterFreqs = new IntArray(); - int freqsFound = 0; - for (int i = 0; i < mCpuFreqsCount; ++i) { - freqsFound++; - if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) { - numClusterFreqs.add(freqsFound); - freqsFound = 0; - } - } - return numClusterFreqs; - } -} diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java deleted file mode 100644 index 97b7211e5e87..000000000000 --- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.annotation.Nullable; -import android.os.StrictMode; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Slog; -import android.util.SparseLongArray; -import android.util.TimeUtils; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; - -/** - * Reads /proc/uid_cputime/show_uid_stat which has the line format: - * - * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds - * - * This provides the time a UID's processes spent executing in user-space and kernel-space. - * The file contains a monotonically increasing count of time for a single boot. This class - * maintains the previous results of a call to {@link #readDelta} in order to provide a proper - * delta. - */ -public class KernelUidCpuTimeReader extends - KernelUidCpuTimeReaderBase<KernelUidCpuTimeReader.Callback> { - private static final String TAG = KernelUidCpuTimeReader.class.getSimpleName(); - private static final String sProcFile = "/proc/uid_cputime/show_uid_stat"; - private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range"; - - /** - * Callback interface for processing each line of the proc file. - */ - public interface Callback extends KernelUidCpuTimeReaderBase.Callback { - /** - * @param uid UID of the app - * @param userTimeUs time spent executing in user space in microseconds - * @param systemTimeUs time spent executing in kernel space in microseconds - */ - void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs); - } - - private SparseLongArray mLastUserTimeUs = new SparseLongArray(); - private SparseLongArray mLastSystemTimeUs = new SparseLongArray(); - private long mLastTimeReadUs = 0; - - /** - * Reads the proc file, calling into the callback with a delta of time for each UID. - * - * @param callback The callback to invoke for each line of the proc file. If null, - * the data is consumed and subsequent calls to readDelta will provide - * a fresh delta. - */ - @Override - protected void readDeltaImpl(@Nullable Callback callback) { - final int oldMask = StrictMode.allowThreadDiskReadsMask(); - long nowUs = SystemClock.elapsedRealtime() * 1000; - try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) { - TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' '); - String line; - while ((line = reader.readLine()) != null) { - splitter.setString(line); - final String uidStr = splitter.next(); - final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10); - final long userTimeUs = Long.parseLong(splitter.next(), 10); - final long systemTimeUs = Long.parseLong(splitter.next(), 10); - - boolean notifyCallback = false; - long userTimeDeltaUs = userTimeUs; - long systemTimeDeltaUs = systemTimeUs; - // Only report if there is a callback and if this is not the first read. - if (callback != null && mLastTimeReadUs != 0) { - int index = mLastUserTimeUs.indexOfKey(uid); - if (index >= 0) { - userTimeDeltaUs -= mLastUserTimeUs.valueAt(index); - systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index); - - final long timeDiffUs = nowUs - mLastTimeReadUs; - if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0) { - StringBuilder sb = new StringBuilder("Malformed cpu data for UID="); - sb.append(uid).append("!\n"); - sb.append("Time between reads: "); - TimeUtils.formatDuration(timeDiffUs / 1000, sb); - sb.append("\n"); - sb.append("Previous times: u="); - TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb); - sb.append(" s="); - TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb); - - sb.append("\nCurrent times: u="); - TimeUtils.formatDuration(userTimeUs / 1000, sb); - sb.append(" s="); - TimeUtils.formatDuration(systemTimeUs / 1000, sb); - sb.append("\nDelta: u="); - TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb); - sb.append(" s="); - TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb); - Slog.e(TAG, sb.toString()); - - userTimeDeltaUs = 0; - systemTimeDeltaUs = 0; - } - } - - notifyCallback = (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0); - } - mLastUserTimeUs.put(uid, userTimeUs); - mLastSystemTimeUs.put(uid, systemTimeUs); - if (notifyCallback) { - callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs); - } - } - } catch (IOException e) { - Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage()); - } finally { - StrictMode.setThreadPolicyMask(oldMask); - } - mLastTimeReadUs = nowUs; - } - - /** - * Reads the proc file, calling into the callback with raw absolute value of time for each UID. - * @param callback The callback to invoke for each line of the proc file. - */ - public void readAbsolute(Callback callback) { - final int oldMask = StrictMode.allowThreadDiskReadsMask(); - try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) { - TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' '); - String line; - while ((line = reader.readLine()) != null) { - splitter.setString(line); - final String uidStr = splitter.next(); - final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10); - final long userTimeUs = Long.parseLong(splitter.next(), 10); - final long systemTimeUs = Long.parseLong(splitter.next(), 10); - callback.onUidCpuTime(uid, userTimeUs, systemTimeUs); - } - } catch (IOException e) { - Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage()); - } finally { - StrictMode.setThreadPolicyMask(oldMask); - } - } - - /** - * Removes the UID from the kernel module and from internal accounting data. Only - * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is - * visible system wide. - * - * @param uid The UID to remove. - */ - public void removeUid(int uid) { - final int index = mLastSystemTimeUs.indexOfKey(uid); - if (index >= 0) { - mLastSystemTimeUs.removeAt(index); - mLastUserTimeUs.removeAt(index); - } - removeUidsFromKernelModule(uid, uid); - } - - /** - * Removes UIDs in a given range from the kernel module and internal accounting data. Only - * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is - * visible system wide. - * - * @param startUid the first uid to remove - * @param endUid the last uid to remove - */ - public void removeUidsInRange(int startUid, int endUid) { - if (endUid < startUid) { - return; - } - mLastSystemTimeUs.put(startUid, 0); - mLastUserTimeUs.put(startUid, 0); - mLastSystemTimeUs.put(endUid, 0); - mLastUserTimeUs.put(endUid, 0); - final int startIndex = mLastSystemTimeUs.indexOfKey(startUid); - final int endIndex = mLastSystemTimeUs.indexOfKey(endUid); - mLastSystemTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1); - mLastUserTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1); - removeUidsFromKernelModule(startUid, endUid); - } - - private void removeUidsFromKernelModule(int startUid, int endUid) { - Slog.d(TAG, "Removing uids " + startUid + "-" + endUid); - final int oldMask = StrictMode.allowThreadDiskWritesMask(); - try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) { - writer.write(startUid + "-" + endUid); - writer.flush(); - } catch (IOException e) { - Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid - + " from uid_cputime module", e); - } finally { - StrictMode.setThreadPolicyMask(oldMask); - } - } -} diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java b/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java deleted file mode 100644 index 11e50e1ecb95..000000000000 --- a/core/java/com/android/internal/os/KernelUidCpuTimeReaderBase.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import android.annotation.Nullable; -import android.os.SystemClock; -import android.util.Slog; - -/** - * The base class of all KernelUidCpuTimeReaders. - * - * This class is NOT designed to be thread-safe or accessed by more than one caller (due to - * the nature of {@link #readDelta(Callback)}). - */ -public abstract class KernelUidCpuTimeReaderBase<T extends KernelUidCpuTimeReaderBase.Callback> { - protected static final boolean DEBUG = false; - // Throttle interval in milliseconds - private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L; - - private final String TAG = this.getClass().getSimpleName(); - private long mLastTimeReadMs = Long.MIN_VALUE; - private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL; - - // A generic Callback interface (used by readDelta) to be extended by subclasses. - public interface Callback { - } - - public void readDelta(@Nullable T cb) { - if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) { - if (DEBUG) { - Slog.d(TAG, "Throttle"); - } - return; - } - readDeltaImpl(cb); - mLastTimeReadMs = SystemClock.elapsedRealtime(); - } - - protected abstract void readDeltaImpl(@Nullable T cb); - - public void setThrottleInterval(long throttleInterval) { - if (throttleInterval >= 0) { - mThrottleInterval = throttleInterval; - } - } -} diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java index 9a7fb9f2717b..0f0eeddf9a47 100644 --- a/core/java/com/android/internal/os/LooperStats.java +++ b/core/java/com/android/internal/os/LooperStats.java @@ -37,6 +37,7 @@ import java.util.concurrent.ThreadLocalRandom; * @hide Only for use within the system server. */ public class LooperStats implements Looper.Observer { + public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_"; private static final int SESSION_POOL_SIZE = 50; @GuardedBy("mLock") @@ -165,7 +166,7 @@ public class LooperStats implements Looper.Observer { } private ExportedEntry createDebugEntry(String variableName, long value) { - final Entry entry = new Entry("__DEBUG_" + variableName); + final Entry entry = new Entry(DEBUG_ENTRY_PREFIX + variableName); entry.messageCount = 1; entry.recordedMessageCount = 1; entry.totalLatencyMicro = value; diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 705bae4f24ef..b28f4cdd5a87 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -126,7 +126,7 @@ public final class Zygote { private Zygote() {} /** Called for some security initialization before any fork. */ - native static void nativeSecurityInit(); + static native void nativeSecurityInit(); /** * Forks a new VM instance. The current VM must have been started @@ -170,9 +170,9 @@ public final class Zygote { // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( - uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, - fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName, - packagesForUid, visibleVolIds); + uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, + fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName, + packagesForUid, visibleVolIds); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -184,15 +184,20 @@ public final class Zygote { return pid; } - native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags, - int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, - String packageName, String[] packagesForUid, String[] visibleVolIds); + private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids, + int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, + int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, + String appDataDir, String packageName, String[] packagesForUid, String[] visibleVolIds); + + private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids, + int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, + boolean startChildZygote, String instructionSet, String appDataDir, String packageName, + String[] packagesForUid, String[] visibleVolIds); /** * Called to do any initialization before starting an application. */ - native static void nativePreApplicationInit(); + static native void nativePreApplicationInit(); /** * Special method to start the system server process. In addition to the @@ -223,7 +228,8 @@ public final class Zygote { // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkSystemServer( - uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities); + uid, gid, gids, runtimeFlags, rlimits, + permittedCapabilities, effectiveCapabilities); // Enable tracing as soon as we enter the system_server. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -232,13 +238,13 @@ public final class Zygote { return pid; } - native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, + private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); /** * Lets children of the zygote inherit open file descriptors to this path. */ - native protected static void nativeAllowFileAcrossFork(String path); + protected static native void nativeAllowFileAcrossFork(String path); /** * Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range @@ -251,7 +257,22 @@ public final class Zygote { * Zygote unmount storage space on initializing. * This method is called once. */ - native protected static void nativeUnmountStorageOnInit(); + protected static native void nativeUnmountStorageOnInit(); + + protected static native void nativeGetSocketFDs(boolean isPrimary); + + private static native int nativeGetBlastulaPoolCount(); + + private static native int nativeGetBlastulaPoolEventFD(); + + private static native int nativeForkBlastula(int readPipeFD, + int writePipeFD, + int[] sessionSocketRawFDs); + + private static native int[] nativeGetBlastulaPipeFDs(); + + private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID); + private static void callPostForkSystemServerHooks() { // SystemServer specific post fork hooks run before child post fork hooks. diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 197e873a18bc..d61f10e7a195 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -65,7 +65,7 @@ interface IStatusBarService int dismissalSurface, int dismissalSentiment, in NotificationVisibility nv); void onNotificationVisibilityChanged( in NotificationVisibility[] newlyVisibleKeys, in NotificationVisibility[] noLongerVisibleKeys); - void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded); + void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded, in int notificationLocation); void onNotificationDirectReplied(String key); void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount, boolean generatedByAsssistant); diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 605df040da59..f91b837410ba 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -327,10 +327,6 @@ public class CollectionUtils { } } - public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) { - return (val != null) ? val : Collections.emptyList(); - } - /** * @return the first element if not empty/null, null otherwise */ diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index 1aa32cce11e8..276cad9f6d6d 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -53,7 +53,6 @@ public class Protocol { public static final int BASE_WIFI_PASSPOINT_MANAGER = 0x00028000; public static final int BASE_WIFI_PASSPOINT_SERVICE = 0x00028100; public static final int BASE_WIFI_LOGGER = 0x00028300; - public static final int BASE_DHCP = 0x00030000; public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; public static final int BASE_DATA_CONNECTION_TRACKER = 0x00042000; @@ -62,7 +61,6 @@ public class Protocol { public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000; public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000; public static final int BASE_NETWORK_AGENT = 0x00081000; - public static final int BASE_NETWORK_MONITOR = 0x00082000; public static final int BASE_NETWORK_FACTORY = 0x00083000; public static final int BASE_ETHERNET = 0x00084000; public static final int BASE_LOWPAN = 0x00085000; diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 356d178cc4eb..8194a920d331 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -35,7 +35,6 @@ interface IInputMethodManager { // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(); - List<InputMethodInfo> getVrInputMethodList(); // TODO: Use ParceledListSlice instead List<InputMethodInfo> getEnabledInputMethodList(); List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId, diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index d5dc703408e4..8d3c482104f7 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -34,6 +34,7 @@ import android.app.trust.TrustManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.AsyncTask; import android.os.Handler; @@ -176,6 +177,7 @@ public class LockPatternUtils { private UserManager mUserManager; private final Handler mHandler; private final SparseLongArray mLockoutDeadlines = new SparseLongArray(); + private Boolean mHasSecureLockScreen; /** * Use {@link TrustManager#isTrustUsuallyManaged(int)}. @@ -706,6 +708,10 @@ public class LockPatternUtils { * @param userId the user whose pattern is to be saved. */ public void saveLockPattern(List<LockPatternView.Cell> pattern, String savedPattern, int userId) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) { throw new IllegalArgumentException("pattern must not be null and at least " + MIN_LOCK_PATTERN_SIZE + " dots long."); @@ -801,6 +807,10 @@ public class LockPatternUtils { /** Update the encryption password if it is enabled **/ private void updateEncryptionPassword(final int type, final String password) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (!isDeviceEncryptionEnabled()) { return; } @@ -835,6 +845,10 @@ public class LockPatternUtils { */ public void saveLockPassword(String password, String savedPassword, int requestedQuality, int userHandle) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) { throw new IllegalArgumentException("password must not be null and at least " + "of length " + MIN_LOCK_PASSWORD_SIZE); @@ -1621,6 +1635,10 @@ public class LockPatternUtils { */ public boolean setLockCredentialWithToken(String credential, int type, int requestedQuality, long tokenHandle, byte[] token, int userId) { + if (!hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires the lock screen feature."); + } LockSettingsInternal localService = getLockSettingsInternal(); if (type != CREDENTIAL_TYPE_NONE) { if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) { @@ -1854,6 +1872,17 @@ public class LockPatternUtils { return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0; } + /** + * Return true if the device supports the lock screen feature, false otherwise. + */ + public boolean hasSecureLockScreen() { + if (mHasSecureLockScreen == null) { + mHasSecureLockScreen = Boolean.valueOf(mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)); + } + return mHasSecureLockScreen.booleanValue(); + } + public static boolean userOwnsFrpCredential(Context context, UserInfo info) { return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 5be70ef46d31..be127009a21d 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -61,8 +61,11 @@ cc_library_shared { "android_database_SQLiteConnection.cpp", "android_database_SQLiteGlobal.cpp", "android_database_SQLiteDebug.cpp", + "android_graphics_Canvas.cpp", + "android_graphics_ColorSpace.cpp", "android_graphics_drawable_AnimatedVectorDrawable.cpp", "android_graphics_drawable_VectorDrawable.cpp", + "android_graphics_Picture.cpp", "android_view_DisplayEventReceiver.cpp", "android_view_DisplayListCanvas.cpp", "android_view_TextureLayer.cpp", @@ -117,8 +120,6 @@ cc_library_shared { "android_util_StringBlock.cpp", "android_util_XmlBlock.cpp", "android_util_jar_StrictJarFile.cpp", - "android_graphics_Canvas.cpp", - "android_graphics_Picture.cpp", "android/graphics/AnimatedImageDrawable.cpp", "android/graphics/Bitmap.cpp", "android/graphics/BitmapFactory.cpp", @@ -185,6 +186,7 @@ cc_library_shared { "android_hardware_UsbDevice.cpp", "android_hardware_UsbDeviceConnection.cpp", "android_hardware_UsbRequest.cpp", + "android_hardware_location_ActivityRecognitionHardware.cpp", "android_util_FileObserver.cpp", "android/opengl/poly_clip.cpp", // TODO: .arm "android/opengl/util.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index a586dc1e6d7e..18d9b5aeb1c8 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -101,6 +101,7 @@ extern int register_android_hardware_SoundTrigger(JNIEnv *env); extern int register_android_hardware_UsbDevice(JNIEnv *env); extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env); extern int register_android_hardware_UsbRequest(JNIEnv *env); +extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env); extern int register_android_media_AudioEffectDescriptor(JNIEnv *env); extern int register_android_media_AudioRecord(JNIEnv *env); @@ -130,6 +131,7 @@ extern int register_android_content_res_ApkAssets(JNIEnv* env); extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_CanvasProperty(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); +extern int register_android_graphics_ColorSpace(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); extern int register_android_graphics_FontFamily(JNIEnv* env); extern int register_android_graphics_Matrix(JNIEnv* env); @@ -1360,6 +1362,9 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_VintfRuntimeInfo), REG_JNI(register_android_nio_utils), REG_JNI(register_android_graphics_Canvas), + // This needs to be before register_android_graphics_Graphics, or the latter + // will not be able to find the jmethodID for ColorSpace.get(). + REG_JNI(register_android_graphics_ColorSpace), REG_JNI(register_android_graphics_Graphics), REG_JNI(register_android_view_DisplayEventReceiver), REG_JNI(register_android_view_RenderNode), @@ -1456,6 +1461,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_hardware_UsbDevice), REG_JNI(register_android_hardware_UsbDeviceConnection), REG_JNI(register_android_hardware_UsbRequest), + REG_JNI(register_android_hardware_location_ActivityRecognitionHardware), REG_JNI(register_android_media_AudioEffectDescriptor), REG_JNI(register_android_media_AudioSystem), REG_JNI(register_android_media_AudioRecord), diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 876bd4fbfce4..ad51c4701d84 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -334,7 +334,7 @@ static int getPremulBitmapCreateFlags(bool isMutable) { static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, jint offset, jint stride, jint width, jint height, jint configHandle, jboolean isMutable, - jfloatArray xyzD50, jobject transferParameters) { + jlong colorSpacePtr) { SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle); if (NULL != jColors) { size_t n = env->GetArrayLength(jColors); @@ -350,17 +350,8 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, } SkBitmap bitmap; - sk_sp<SkColorSpace> colorSpace; - - if (xyzD50 == nullptr || transferParameters == nullptr) { - colorSpace = SkColorSpace::MakeSRGB(); - } else { - skcms_TransferFunction p = GraphicsJNI::getNativeTransferParameters(env, transferParameters); - skcms_Matrix3x3 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50); - colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix); - } - - bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, colorSpace)); + bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, + GraphicsJNI::getNativeColorSpace(colorSpacePtr))); sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap); if (!nativeBitmap) { @@ -582,17 +573,14 @@ static void Bitmap_erase(JNIEnv* env, jobject, jlong bitmapHandle, jint color) { bitmapErase(skBitmap, SkColor4f::FromColor(color), SkColorSpace::MakeSRGB()); } -static void Bitmap_eraseLong(JNIEnv* env, jobject, jlong bitmapHandle, jobject jColorSpace, - jfloat r, jfloat g, jfloat b, jfloat a) { - sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace); - if (GraphicsJNI::hasException(env)) { - return; - } - +static void Bitmap_eraseLong(JNIEnv* env, jobject, jlong bitmapHandle, + jlong colorSpaceHandle, jlong colorLong) { LocalScopedBitmap bitmap(bitmapHandle); SkBitmap skBitmap; bitmap->getSkBitmap(&skBitmap); - SkColor4f color = SkColor4f{r, g, b, a}; + + SkColor4f color = GraphicsJNI::convertColorLong(colorLong); + sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); bitmapErase(skBitmap, color, cs); } @@ -1141,14 +1129,12 @@ static jobject Bitmap_createHardwareBitmap(JNIEnv* env, jobject, jobject graphic } static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer, - jfloatArray xyzD50, jobject transferParameters) { - skcms_TransferFunction p = GraphicsJNI::getNativeTransferParameters(env, transferParameters); - skcms_Matrix3x3 xyzMatrix = GraphicsJNI::getNativeXYZMatrix(env, xyzD50); - sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeRGB(p, xyzMatrix); + jlong colorSpacePtr) { AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env, hardwareBuffer); sp<GraphicBuffer> buffer(AHardwareBuffer_to_GraphicBuffer(hwBuf)); - sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, colorSpace); + sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, + GraphicsJNI::getNativeColorSpace(colorSpacePtr)); if (!bitmap.get()) { ALOGW("failed to create hardware bitmap from hardware buffer"); return NULL; @@ -1190,7 +1176,7 @@ static void Bitmap_setImmutable(JNIEnv* env, jobject, jlong bitmapHandle) { /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gBitmapMethods[] = { - { "nativeCreate", "([IIIIIIZ[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/Bitmap;", + { "nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;", (void*)Bitmap_creator }, { "nativeCopy", "(JIZ)Landroid/graphics/Bitmap;", (void*)Bitmap_copy }, @@ -1204,7 +1190,7 @@ static const JNINativeMethod gBitmapMethods[] = { { "nativeCompress", "(JIILjava/io/OutputStream;[B)Z", (void*)Bitmap_compress }, { "nativeErase", "(JI)V", (void*)Bitmap_erase }, - { "nativeErase", "(JLandroid/graphics/ColorSpace;FFFF)V", (void*)Bitmap_eraseLong }, + { "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong }, { "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes }, { "nativeConfig", "(J)I", (void*)Bitmap_config }, { "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha }, @@ -1236,7 +1222,7 @@ static const JNINativeMethod gBitmapMethods[] = { (void*)Bitmap_copyPreserveInternalConfig }, { "nativeCreateHardwareBitmap", "(Landroid/graphics/GraphicBuffer;)Landroid/graphics/Bitmap;", (void*) Bitmap_createHardwareBitmap }, - { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/Bitmap;", + { "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;", (void*) Bitmap_wrapHardwareBufferBitmap }, { "nativeCreateGraphicBufferHandle", "(J)Landroid/graphics/GraphicBuffer;", (void*) Bitmap_createGraphicBufferHandle }, diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 4b0ab5b2d6ce..7d0d3d8442a1 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -179,7 +179,7 @@ static bool needsFineScale(const SkISize fullSize, const SkISize decodedSize, } static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, - jobject padding, jobject options) { + jobject padding, jobject options, jlong colorSpaceHandle) { // Set default values for the options parameters. int sampleSize = 1; bool onlyDecodeSize = false; @@ -189,7 +189,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, float scale = 1.0f; bool requireUnpremultiplied = false; jobject javaBitmap = NULL; - sk_sp<SkColorSpace> prefColorSpace = nullptr; + sk_sp<SkColorSpace> prefColorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); // Update with options supplied by the client. if (options != NULL) { @@ -213,8 +213,6 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); - jobject jcolorSpace = env->GetObjectField(options, gOptions_colorSpaceFieldID); - prefColorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace); isHardware = GraphicsJNI::isHardwareConfig(env, jconfig); isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); @@ -515,7 +513,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, } static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, - jobject padding, jobject options) { + jobject padding, jobject options, jlong colorSpaceHandle) { jobject bitmap = NULL; std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage)); @@ -524,13 +522,13 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteA std::unique_ptr<SkStreamRewindable> bufferedStream( SkFrontBufferedStream::Make(std::move(stream), SkCodec::MinBufferedBytesNeeded())); SkASSERT(bufferedStream.get() != NULL); - bitmap = doDecode(env, std::move(bufferedStream), padding, options); + bitmap = doDecode(env, std::move(bufferedStream), padding, options, colorSpaceHandle); } return bitmap; } static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, - jobject padding, jobject bitmapFactoryOptions) { + jobject padding, jobject bitmapFactoryOptions, jlong colorSpaceHandle) { NPE_CHECK_RETURN_ZERO(env, fileDescriptor); @@ -566,7 +564,8 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi // If there is no offset for the file descriptor, we use SkFILEStream directly. if (::lseek(descriptor, 0, SEEK_CUR) == 0) { assert(isSeekable(dupDescriptor)); - return doDecode(env, std::move(fileStream), padding, bitmapFactoryOptions); + return doDecode(env, std::move(fileStream), padding, bitmapFactoryOptions, + colorSpaceHandle); } // Use a buffered stream. Although an SkFILEStream can be rewound, this @@ -575,24 +574,25 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi std::unique_ptr<SkStreamRewindable> stream(SkFrontBufferedStream::Make(std::move(fileStream), SkCodec::MinBufferedBytesNeeded())); - return doDecode(env, std::move(stream), padding, bitmapFactoryOptions); + return doDecode(env, std::move(stream), padding, bitmapFactoryOptions, colorSpaceHandle); } static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset, - jobject padding, jobject options) { + jobject padding, jobject options, jlong colorSpaceHandle) { Asset* asset = reinterpret_cast<Asset*>(native_asset); // since we know we'll be done with the asset when we return, we can // just use a simple wrapper - return doDecode(env, skstd::make_unique<AssetStreamAdaptor>(asset), padding, options); + return doDecode(env, skstd::make_unique<AssetStreamAdaptor>(asset), padding, options, + colorSpaceHandle); } static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, - jint offset, jint length, jobject options) { + jint offset, jint length, jobject options, jlong colorSpaceHandle) { AutoJavaByteArray ar(env, byteArray); return doDecode(env, skstd::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false), - nullptr, options); + nullptr, options, colorSpaceHandle); } static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) { @@ -600,31 +600,26 @@ static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) { return isSeekable(descriptor) ? JNI_TRUE : JNI_FALSE; } -jobject decodeBitmap(JNIEnv* env, void* data, size_t size) { - return doDecode(env, skstd::make_unique<SkMemoryStream>(data, size), - nullptr, nullptr); -} - /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gMethods[] = { { "nativeDecodeStream", - "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", + "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;", (void*)nativeDecodeStream }, { "nativeDecodeFileDescriptor", - "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", + "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;", (void*)nativeDecodeFileDescriptor }, { "nativeDecodeAsset", - "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", + "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;", (void*)nativeDecodeAsset }, { "nativeDecodeByteArray", - "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", + "([BIILandroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;", (void*)nativeDecodeByteArray }, diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h index 1ee49fa0af77..e37c98dc66ff 100644 --- a/core/jni/android/graphics/BitmapFactory.h +++ b/core/jni/android/graphics/BitmapFactory.h @@ -28,6 +28,4 @@ extern jmethodID gBitmapConfig_nativeToConfigMethodID; jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format); -jobject decodeBitmap(JNIEnv* env, void* data, size_t size); - #endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_ diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index f831c051182e..b4ba749b75bf 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -124,7 +124,7 @@ static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz, * reportSizeToVM not supported */ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX, - jint inputY, jint inputWidth, jint inputHeight, jobject options) { + jint inputY, jint inputWidth, jint inputHeight, jobject options, jlong colorSpaceHandle) { // Set default options. int sampleSize = 1; @@ -132,14 +132,12 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in bool requireUnpremul = false; jobject javaBitmap = NULL; bool isHardware = false; - sk_sp<SkColorSpace> colorSpace = nullptr; + sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); // Update the default options with any options supplied by the client. if (NULL != options) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); - jobject jcolorSpace = env->GetObjectField(options, gOptions_colorSpaceFieldID); - colorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace); isHardware = GraphicsJNI::isHardwareConfig(env, jconfig); requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); @@ -255,7 +253,7 @@ static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) { static const JNINativeMethod gBitmapRegionDecoderMethods[] = { { "nativeDecodeRegion", - "(JIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", + "(JIIIILandroid/graphics/BitmapFactory$Options;J)Landroid/graphics/Bitmap;", (void*)nativeDecodeRegion}, { "nativeGetHeight", "(J)I", (void*)nativeGetHeight}, diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 9e74b883a298..6570992b4b23 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -178,23 +178,11 @@ static jclass gVMRuntime_class; static jmethodID gVMRuntime_newNonMovableArray; static jmethodID gVMRuntime_addressOf; -static jfieldID gTransferParams_aFieldID; -static jfieldID gTransferParams_bFieldID; -static jfieldID gTransferParams_cFieldID; -static jfieldID gTransferParams_dFieldID; -static jfieldID gTransferParams_eFieldID; -static jfieldID gTransferParams_fFieldID; -static jfieldID gTransferParams_gFieldID; - static jclass gColorSpace_class; -static jfieldID gColorSpace_IlluminantD50FieldID; -static jmethodID gColorSpace_adaptMethodID; static jmethodID gColorSpace_getMethodID; static jmethodID gColorSpace_matchMethodID; static jclass gColorSpaceRGB_class; -static jmethodID gColorSpaceRGB_getTransferParametersMethodID; -static jmethodID gColorSpaceRGB_getTransformMethodID; static jmethodID gColorSpaceRGB_constructorMethodID; static jclass gColorSpace_Named_class; @@ -424,63 +412,6 @@ jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region) /////////////////////////////////////////////////////////////////////////////// -skcms_TransferFunction GraphicsJNI::getNativeTransferParameters(JNIEnv* env, jobject transferParams) { - skcms_TransferFunction p; - p.a = (float) env->GetDoubleField(transferParams, gTransferParams_aFieldID); - p.b = (float) env->GetDoubleField(transferParams, gTransferParams_bFieldID); - p.c = (float) env->GetDoubleField(transferParams, gTransferParams_cFieldID); - p.d = (float) env->GetDoubleField(transferParams, gTransferParams_dFieldID); - p.e = (float) env->GetDoubleField(transferParams, gTransferParams_eFieldID); - p.f = (float) env->GetDoubleField(transferParams, gTransferParams_fFieldID); - p.g = (float) env->GetDoubleField(transferParams, gTransferParams_gFieldID); - return p; -} - -skcms_Matrix3x3 GraphicsJNI::getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) { - skcms_Matrix3x3 xyzMatrix; - jfloat* array = env->GetFloatArrayElements(xyzD50, NULL); - xyzMatrix.vals[0][0] = array[0]; - xyzMatrix.vals[1][0] = array[1]; - xyzMatrix.vals[2][0] = array[2]; - xyzMatrix.vals[0][1] = array[3]; - xyzMatrix.vals[1][1] = array[4]; - xyzMatrix.vals[2][1] = array[5]; - xyzMatrix.vals[0][2] = array[6]; - xyzMatrix.vals[1][2] = array[7]; - xyzMatrix.vals[2][2] = array[8]; - env->ReleaseFloatArrayElements(xyzD50, array, 0); - return xyzMatrix; -} - -sk_sp<SkColorSpace> GraphicsJNI::getNativeColorSpace(JNIEnv* env, jobject colorSpace) { - if (colorSpace == nullptr) return nullptr; - if (!env->IsInstanceOf(colorSpace, gColorSpaceRGB_class)) { - doThrowIAE(env, "The color space must be an RGB color space"); - return nullptr; - } - - jobject transferParams = env->CallObjectMethod(colorSpace, - gColorSpaceRGB_getTransferParametersMethodID); - if (transferParams == nullptr) { - doThrowIAE(env, "The color space must use an ICC parametric transfer function"); - return nullptr; - } - - jfloatArray illuminantD50 = (jfloatArray) env->GetStaticObjectField(gColorSpace_class, - gColorSpace_IlluminantD50FieldID); - jobject colorSpaceD50 = env->CallStaticObjectMethod(gColorSpace_class, - gColorSpace_adaptMethodID, colorSpace, illuminantD50); - - jfloatArray xyzD50 = (jfloatArray) env->CallObjectMethod(colorSpaceD50, - gColorSpaceRGB_getTransformMethodID); - - skcms_Matrix3x3 xyzMatrix = getNativeXYZMatrix(env, xyzD50); - skcms_TransferFunction transferFunction = getNativeTransferParameters(env, transferParams); - - return SkColorSpace::MakeRGB(transferFunction, xyzMatrix); -} - - jobject GraphicsJNI::getColorSpace(JNIEnv* env, sk_sp<SkColorSpace>& decodeColorSpace, SkColorType decodeColorType) { jobject colorSpace = nullptr; @@ -712,20 +643,7 @@ int register_android_graphics_Graphics(JNIEnv* env) "(Ljava/lang/Class;I)Ljava/lang/Object;"); gVMRuntime_addressOf = GetMethodIDOrDie(env, gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J"); - jclass transfer_params_class = FindClassOrDie(env, "android/graphics/ColorSpace$Rgb$TransferParameters"); - gTransferParams_aFieldID = GetFieldIDOrDie(env, transfer_params_class, "a", "D"); - gTransferParams_bFieldID = GetFieldIDOrDie(env, transfer_params_class, "b", "D"); - gTransferParams_cFieldID = GetFieldIDOrDie(env, transfer_params_class, "c", "D"); - gTransferParams_dFieldID = GetFieldIDOrDie(env, transfer_params_class, "d", "D"); - gTransferParams_eFieldID = GetFieldIDOrDie(env, transfer_params_class, "e", "D"); - gTransferParams_fFieldID = GetFieldIDOrDie(env, transfer_params_class, "f", "D"); - gTransferParams_gFieldID = GetFieldIDOrDie(env, transfer_params_class, "g", "D"); - gColorSpace_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace")); - gColorSpace_IlluminantD50FieldID = GetStaticFieldIDOrDie(env, - gColorSpace_class, "ILLUMINANT_D50", "[F"); - gColorSpace_adaptMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, "adapt", - "(Landroid/graphics/ColorSpace;[F)Landroid/graphics/ColorSpace;"); gColorSpace_getMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, "get", "(Landroid/graphics/ColorSpace$Named;)Landroid/graphics/ColorSpace;"); gColorSpace_matchMethodID = GetStaticMethodIDOrDie(env, gColorSpace_class, "match", @@ -735,10 +653,6 @@ int register_android_graphics_Graphics(JNIEnv* env) FindClassOrDie(env, "android/graphics/ColorSpace$Rgb")); gColorSpaceRGB_constructorMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class, "<init>", "(Ljava/lang/String;[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)V"); - gColorSpaceRGB_getTransferParametersMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class, - "getTransferParameters", "()Landroid/graphics/ColorSpace$Rgb$TransferParameters;"); - gColorSpaceRGB_getTransformMethodID = GetMethodIDOrDie(env, gColorSpaceRGB_class, - "getTransform", "()[F"); gColorSpace_Named_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ColorSpace$Named")); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index 699d153874a2..dc0d022d94c0 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -100,12 +100,27 @@ public: int srcStride, int x, int y, int width, int height, SkBitmap* dstBitmap); - static skcms_TransferFunction getNativeTransferParameters(JNIEnv* env, jobject transferParams); - static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50); - static sk_sp<SkColorSpace> getNativeColorSpace(JNIEnv* env, jobject colorSpace); + /** + * Convert the native SkColorSpace retrieved from ColorSpace.Rgb.getNativeInstance(). + * + * This will never throw an Exception. If the ColorSpace is one that Skia cannot + * use, ColorSpace.Rgb.getNativeInstance() would have thrown an Exception. It may, + * however, be nullptr, which may be acceptable. + */ + static sk_sp<SkColorSpace> getNativeColorSpace(jlong colorSpaceHandle); static jobject getColorSpace(JNIEnv* env, sk_sp<SkColorSpace>& decodeColorSpace, SkColorType decodeColorType); + + /** + * Convert from a Java @ColorLong to an SkColor4f that Skia can use directly. + * + * This ignores the encoded ColorSpace, besides checking to see if it is sRGB, + * which is encoded differently. The color space should be passed down separately + * via ColorSpace#getNativeInstance(), and converted with getNativeColorSpace(), + * above. + */ + static SkColor4f convertColorLong(jlong color); }; class HeapAllocator : public SkBRDAllocator { diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index df735ae12feb..5f126653600e 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -202,7 +202,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong jint desiredWidth, jint desiredHeight, jobject jsubset, jboolean requireMutable, jint allocator, jboolean requireUnpremul, jboolean preferRamOverQuality, - jboolean asAlphaMask, jobject jcolorSpace) { + jboolean asAlphaMask, jlong colorSpaceHandle) { auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); SkAndroidCodec* codec = decoder->mCodec.get(); const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight); @@ -256,7 +256,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong // This is currently the only way to know that we should decode to F16. colorType = codec->computeOutputColorType(colorType); } - sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(env, jcolorSpace); + sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); colorSpace = codec->computeOutputColorSpace(colorType, colorSpace); decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace); @@ -508,7 +508,7 @@ static const JNINativeMethod gImageDecoderMethods[] = { { "nCreate", "([BIILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray }, { "nCreate", "(Ljava/io/InputStream;[BLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream }, { "nCreate", "(Ljava/io/FileDescriptor;Landroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd }, - { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZLandroid/graphics/ColorSpace;)Landroid/graphics/Bitmap;", + { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZJ)Landroid/graphics/Bitmap;", (void*) ImageDecoder_nDecodeBitmap }, { "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize }, { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding }, diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index e2e3042ee5b9..7679c5b63274 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -557,41 +557,6 @@ namespace PaintGlue { return result; } - // FIXME: Make this CriticalNative when we no longer need to use JNIEnv. b/122514935 will allow - // passing the SkColorSpace directly from JNI. - static void setColor(JNIEnv* env, jobject clazz, jlong paintHandle, jobject jColorSpace, - jfloat r, jfloat g, jfloat b, jfloat a) { - sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace); - if (GraphicsJNI::hasException(env)) { - return; - } - - SkColor4f color = SkColor4f{r, g, b, a}; - reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get()); - } - - // FIXME: Make this CriticalNative when we no longer need to use JNIEnv. b/122514935 will allow - // passing the SkColorSpace directly from JNI. - static void setShadowLayer(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat radius, - jfloat dx, jfloat dy, jobject jColorSpace, - jfloat r, jfloat g, jfloat b, jfloat a) { - sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(env, jColorSpace); - if (GraphicsJNI::hasException(env)) { - return; - } - - SkColor4f color = SkColor4f{r, g, b, a}; - - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - if (radius <= 0) { - paint->setLooper(nullptr); - } - else { - SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius); - paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy)); - } - } - // ------------------ @FastNative --------------------------- static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { @@ -787,6 +752,13 @@ namespace PaintGlue { obj->setStyle(style); } + static void setColor(jlong paintHandle, jlong colorSpaceHandle, + jfloat r, jfloat g, jfloat b, jfloat a) { + sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + SkColor4f color = SkColor4f{r, g, b, a}; + reinterpret_cast<Paint*>(paintHandle)->setColor4f(color, cs.get()); + } + static void setAlpha(jlong paintHandle, jint a) { reinterpret_cast<Paint*>(paintHandle)->setAlpha(a); } @@ -1034,6 +1006,22 @@ namespace PaintGlue { return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize); } + static void setShadowLayer(jlong paintHandle, jfloat radius, + jfloat dx, jfloat dy, jlong colorSpaceHandle, + jfloat r, jfloat g, jfloat b, jfloat a) { + sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + SkColor4f color = SkColor4f{r, g, b, a}; + + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + if (radius <= 0) { + paint->setLooper(nullptr); + } + else { + SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius); + paint->setLooper(SkBlurDrawLooper::Make(color, cs.get(), sigma, dx, dy)); + } + } + static jboolean hasShadowLayer(jlong paintHandle) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr); @@ -1082,9 +1070,6 @@ static const JNINativeMethod methods[] = { {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F}, {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I}, - {"nSetColor","(JLandroid/graphics/ColorSpace;FFFF)V", (void*) PaintGlue::setColor}, - {"nSetShadowLayer", "(JFFFLandroid/graphics/ColorSpace;FFFF)V", - (void*)PaintGlue::setShadowLayer}, // --------------- @FastNative ---------------------- @@ -1114,6 +1099,7 @@ static const JNINativeMethod methods[] = { {"nSetDither","(JZ)V", (void*) PaintGlue::setDither}, {"nGetStyle","(J)I", (void*) PaintGlue::getStyle}, {"nSetStyle","(JI)V", (void*) PaintGlue::setStyle}, + {"nSetColor","(JJFFFF)V", (void*) PaintGlue::setColor}, {"nSetAlpha","(JI)V", (void*) PaintGlue::setAlpha}, {"nGetStrokeWidth","(J)F", (void*) PaintGlue::getStrokeWidth}, {"nSetStrokeWidth","(JF)V", (void*) PaintGlue::setStrokeWidth}, @@ -1154,6 +1140,7 @@ static const JNINativeMethod methods[] = { {"nGetUnderlineThickness","(J)F", (void*) PaintGlue::getUnderlineThickness}, {"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition}, {"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness}, + {"nSetShadowLayer", "(JFFFJFFFF)V", (void*)PaintGlue::setShadowLayer}, {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, }; diff --git a/core/jni/android_app_ActivityThread.cpp b/core/jni/android_app_ActivityThread.cpp index d56e4c51124d..93f2525eb29d 100644 --- a/core/jni/android_app_ActivityThread.cpp +++ b/core/jni/android_app_ActivityThread.cpp @@ -24,6 +24,8 @@ #include "core_jni_helpers.h" #include <unistd.h> +#include <bionic_malloc.h> + namespace android { static void android_app_ActivityThread_purgePendingResources(JNIEnv* env, jobject clazz) { @@ -38,13 +40,18 @@ android_app_ActivityThread_dumpGraphics(JNIEnv* env, jobject clazz, jobject java minikin::Layout::dumpMinikinStats(fd); } +static void android_app_ActivityThread_initZygoteChildHeapProfiling(JNIEnv* env, jobject clazz) { + android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0); +} static JNINativeMethod gActivityThreadMethods[] = { // ------------ Regular JNI ------------------ { "nPurgePendingResources", "()V", (void*) android_app_ActivityThread_purgePendingResources }, { "nDumpGraphicsInfo", "(Ljava/io/FileDescriptor;)V", - (void*) android_app_ActivityThread_dumpGraphics } + (void*) android_app_ActivityThread_dumpGraphics }, + { "nInitZygoteChildHeapProfiling", "()V", + (void*) android_app_ActivityThread_initZygoteChildHeapProfiling } }; int register_android_app_ActivityThread(JNIEnv* env) { diff --git a/core/jni/android_graphics_ColorSpace.cpp b/core/jni/android_graphics_ColorSpace.cpp new file mode 100644 index 000000000000..7648fd021d18 --- /dev/null +++ b/core/jni/android_graphics_ColorSpace.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "jni.h" +#include "GraphicsJNI.h" +#include "core_jni_helpers.h" + +#include "SkColor.h" +#include "SkColorSpace.h" + +using namespace android; + +static skcms_Matrix3x3 getNativeXYZMatrix(JNIEnv* env, jfloatArray xyzD50) { + skcms_Matrix3x3 xyzMatrix; + jfloat* array = env->GetFloatArrayElements(xyzD50, NULL); + xyzMatrix.vals[0][0] = array[0]; + xyzMatrix.vals[1][0] = array[1]; + xyzMatrix.vals[2][0] = array[2]; + xyzMatrix.vals[0][1] = array[3]; + xyzMatrix.vals[1][1] = array[4]; + xyzMatrix.vals[2][1] = array[5]; + xyzMatrix.vals[0][2] = array[6]; + xyzMatrix.vals[1][2] = array[7]; + xyzMatrix.vals[2][2] = array[8]; + env->ReleaseFloatArrayElements(xyzD50, array, 0); + return xyzMatrix; +} + +/////////////////////////////////////////////////////////////////////////////// + +static float halfToFloat(uint16_t bits) { + __fp16 h; + memcpy(&h, &bits, 2); + return (float)h; +} + +SkColor4f GraphicsJNI::convertColorLong(jlong color) { + if ((color & 0x3f) == 0) { + // This corresponds to sRGB, which is treated differently than the rest. + uint8_t a = color >> 56 & 0xff; + uint8_t r = color >> 48 & 0xff; + uint8_t g = color >> 40 & 0xff; + uint8_t b = color >> 32 & 0xff; + SkColor c = SkColorSetARGB(a, r, g, b); + return SkColor4f::FromColor(c); + } + + // These match the implementation of android.graphics.Color#red(long) etc. + float r = halfToFloat((uint16_t)(color >> 48 & 0xffff)); + float g = halfToFloat((uint16_t)(color >> 32 & 0xffff)); + float b = halfToFloat((uint16_t)(color >> 16 & 0xffff)); + float a = (color >> 6 & 0x3ff) / 1023.0f; + + return SkColor4f{r, g, b, a}; +} + +sk_sp<SkColorSpace> GraphicsJNI::getNativeColorSpace(jlong colorSpaceHandle) { + if (colorSpaceHandle == 0) return nullptr; + return sk_ref_sp(reinterpret_cast<SkColorSpace*>(colorSpaceHandle)); +} + +static void unref_colorSpace(SkColorSpace* cs) { + SkSafeUnref(cs); +} + +static jlong ColorSpace_getNativeFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&unref_colorSpace)); +} + +static jlong ColorSpace_creator(JNIEnv* env, jobject, jfloat a, jfloat b, jfloat c, + jfloat d, jfloat e, jfloat f, jfloat g, jfloatArray xyzD50) { + skcms_TransferFunction p; + p.a = a; + p.b = b; + p.c = c; + p.d = d; + p.e = e; + p.f = f; + p.g = g; + skcms_Matrix3x3 xyzMatrix = getNativeXYZMatrix(env, xyzD50); + + return reinterpret_cast<jlong>(SkColorSpace::MakeRGB(p, xyzMatrix).release()); +} + +static const JNINativeMethod gColorSpaceRgbMethods[] = { + { "nativeGetNativeFinalizer", "()J", (void*)ColorSpace_getNativeFinalizer }, + { "nativeCreate", "(FFFFFFF[F)J", (void*)ColorSpace_creator } +}; + +namespace android { + +int register_android_graphics_ColorSpace(JNIEnv* env) { + return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb", + gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods)); +} + +}; // namespace android diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp new file mode 100644 index 000000000000..1c9ab9403298 --- /dev/null +++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "ActivityRecognitionHardware" + +#include <jni.h> +#include <nativehelper/JNIHelp.h> + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/Log.h> + +// #include <hardware/activity_recognition.h> +// The activity recognition HAL is being deprecated. This means - +// i) Android framework code shall not depend on activity recognition +// being provided through the activity_recognition.h interface. +// ii) activity recognition HAL will not be binderized as the other HALs. +// + +/** + * Initializes the ActivityRecognitionHardware class from the native side. + */ +static void class_init(JNIEnv* /*env*/, jclass /*clazz*/) { + ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op", + __FUNCTION__); +} + +/** + * Initializes and connect the callbacks handlers in the HAL. + */ +static void initialize(JNIEnv* /*env*/, jobject /*obj*/) { + ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op", + __FUNCTION__); +} + +/** + * De-initializes the ActivityRecognitionHardware from the native side. + */ +static void release(JNIEnv* /*env*/, jobject /*obj*/) { + ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op", + __FUNCTION__); +} + +/** + * Returns true if ActivityRecognition HAL is supported, false otherwise. + */ +static jboolean is_supported(JNIEnv* /*env*/, jclass /*clazz*/) { + ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op", + __FUNCTION__); + return JNI_FALSE; +} + +/** + * Gets an array representing the supported activities. + */ +static jobjectArray get_supported_activities(JNIEnv* /*env*/, jobject /*obj*/) { + ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op", + __FUNCTION__); + return NULL; +} + +/** + * Enables a given activity event to be actively monitored. + */ +static int enable_activity_event( + JNIEnv* /*env*/, + jobject /*obj*/, + jint /*activity_handle*/, + jint /*event_type*/, + jlong /*report_latency_ns*/) { + ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op", + __FUNCTION__); + return android::NO_INIT; +} + +/** + * Disables a given activity event from being actively monitored. + */ +static int disable_activity_event( + JNIEnv* /*env*/, + jobject /*obj*/, + jint /*activity_handle*/, + jint /*event_type*/) { + ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op", + __FUNCTION__); + return android::NO_INIT; +} + +/** + * Request flush for al batch buffers. + */ +static int flush(JNIEnv* /*env*/, jobject /*obj*/) { + ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op", + __FUNCTION__); + return android::NO_INIT; +} + + +static const JNINativeMethod sMethods[] = { + // {"name", "signature", (void*) functionPointer }, + { "nativeClassInit", "()V", (void*) class_init }, + { "nativeInitialize", "()V", (void*) initialize }, + { "nativeRelease", "()V", (void*) release }, + { "nativeIsSupported", "()Z", (void*) is_supported }, + { "nativeGetSupportedActivities", "()[Ljava/lang/String;", (void*) get_supported_activities }, + { "nativeEnableActivityEvent", "(IIJ)I", (void*) enable_activity_event }, + { "nativeDisableActivityEvent", "(II)I", (void*) disable_activity_event }, + { "nativeFlush", "()I", (void*) flush }, +}; + +/** + * Registration method invoked in JNI load. + */ +int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env) { + return jniRegisterNativeMethods( + env, + "android/hardware/location/ActivityRecognitionHardware", + sMethods, + NELEM(sMethods)); +} diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 9b138ebb760a..7eddcfe425d3 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -16,8 +16,11 @@ #define LOG_TAG "NetUtils" +#include <vector> + #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include "NetdClient.h" #include <utils/misc.h> #include <android_runtime/AndroidRuntime.h> @@ -55,6 +58,31 @@ static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udp static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest); static const uint16_t kDhcpClientPort = 68; +constexpr int MAXPACKETSIZE = 8 * 1024; +// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this. +constexpr int MAXCMDSIZE = 1024; + +static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { + ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName)); + if (detailMessage.get() == NULL) { + // Not really much we can do here. We're probably dead in the water, + // but let's try to stumble on... + env->ExceptionClear(); + } + static jclass errnoExceptionClass = + MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException")); + + static jmethodID errnoExceptionCtor = + GetMethodIDOrDie(env, errnoExceptionClass, + "<init>", "(Ljava/lang/String;I)V"); + + jobject exception = env->NewObject(errnoExceptionClass, + errnoExceptionCtor, + detailMessage.get(), + error); + env->Throw(reinterpret_cast<jthrowable>(exception)); +} + static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd) { struct sock_filter filter_code[] = { @@ -372,6 +400,63 @@ static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray } } +static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId, + jstring dname, jint ns_class, jint ns_type, jint flags) { + const jsize javaCharsCount = env->GetStringLength(dname); + const jsize byteCountUTF8 = env->GetStringUTFLength(dname); + + // Only allow dname which could be simply formatted to UTF8. + // In native layer, res_mkquery would re-format the input char array to packet. + std::vector<char> queryname(byteCountUTF8 + 1, 0); + + env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data()); + int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkQuery", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId, + jbyteArray msg, jint msgLen, jint flags) { + uint8_t data[MAXCMDSIZE]; + + checkLenAndCopy(env, msg, msgLen, data); + int fd = resNetworkSend(netId, data, msgLen, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkSend", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) { + int fd = jniGetFDFromFileDescriptor(env, javaFd); + int rcode; + std::vector<uint8_t> buf(MAXPACKETSIZE, 0); + + int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + if (res < 0) { + throwErrnoException(env, "resNetworkResult", -res); + return nullptr; + } + + jbyteArray answer = env->NewByteArray(res); + if (answer == nullptr) { + throwErrnoException(env, "resNetworkResult", ENOMEM); + return nullptr; + } else { + env->SetByteArrayRegion(answer, 0, res, + reinterpret_cast<jbyte*>(buf.data())); + } + + return answer; +} // ---------------------------------------------------------------------------- @@ -391,6 +476,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket }, + { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, + { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, + { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 7d63ec9a2ecf..783724853b8a 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -45,6 +45,7 @@ #include <meminfo/sysmeminfo.h> #include <memtrack/memtrack.h> #include <memunreachable/memunreachable.h> +#include <android-base/strings.h> #include "android_os_Debug.h" namespace android @@ -231,244 +232,162 @@ static int read_memtrack_memory(int pid, struct graphics_memory_pss* graphics_me return err; } -static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) +static void load_maps(int pid, stats_t* stats, bool* foundSwapPss) { - char line[1024]; - int len, nameLen; - bool skip, done = false; - - unsigned pss = 0, swappable_pss = 0, rss = 0; - float sharing_proportion = 0.0; - unsigned shared_clean = 0, shared_dirty = 0; - unsigned private_clean = 0, private_dirty = 0; - unsigned swapped_out = 0, swapped_out_pss = 0; - bool is_swappable = false; - unsigned temp; - - uint64_t start; - uint64_t end = 0; - uint64_t prevEnd = 0; - char* name; - int name_pos; - - int whichHeap = HEAP_UNKNOWN; - int subHeap = HEAP_UNKNOWN; - int prevHeap = HEAP_UNKNOWN; - *foundSwapPss = false; + uint64_t prev_end = 0; + int prev_heap = HEAP_UNKNOWN; - if(fgets(line, sizeof(line), fp) == 0) return; - - while (!done) { - prevHeap = whichHeap; - prevEnd = end; - whichHeap = HEAP_UNKNOWN; - subHeap = HEAP_UNKNOWN; - skip = false; - is_swappable = false; - - len = strlen(line); - if (len < 1) return; - line[--len] = 0; - - if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) { - skip = true; + std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid); + auto vma_scan = [&](const meminfo::Vma& vma) { + int which_heap = HEAP_UNKNOWN; + int sub_heap = HEAP_UNKNOWN; + bool is_swappable = false; + std::string name; + if (base::EndsWith(vma.name, " (deleted)")) { + name = vma.name.substr(0, vma.name.size() - strlen(" (deleted)")); } else { - while (isspace(line[name_pos])) { - name_pos += 1; + name = vma.name; + } + + uint32_t namesz = name.size(); + if (base::StartsWith(name, "[heap]")) { + which_heap = HEAP_NATIVE; + } else if (base::StartsWith(name, "[anon:libc_malloc]")) { + which_heap = HEAP_NATIVE; + } else if (base::StartsWith(name, "[stack")) { + which_heap = HEAP_NATIVE; + } else if (base::EndsWith(name, ".so")) { + which_heap = HEAP_SO; + is_swappable = true; + } else if (base::EndsWith(name, ".jar")) { + which_heap = HEAP_JAR; + is_swappable = true; + } else if (base::EndsWith(name, ".apk")) { + which_heap = HEAP_APK; + is_swappable = true; + } else if (base::EndsWith(name, ".ttf")) { + which_heap = HEAP_TTF; + is_swappable = true; + } else if ((base::EndsWith(name, ".odex")) || + (namesz > 4 && strstr(name.c_str(), ".dex") != nullptr)) { + which_heap = HEAP_DEX; + sub_heap = HEAP_DEX_APP_DEX; + is_swappable = true; + } else if (base::EndsWith(name, ".vdex")) { + which_heap = HEAP_DEX; + // Handle system@framework@boot and system/framework/boot + if ((strstr(name.c_str(), "@boot") != nullptr) || + (strstr(name.c_str(), "/boot"))) { + sub_heap = HEAP_DEX_BOOT_VDEX; + } else { + sub_heap = HEAP_DEX_APP_VDEX; } - name = line + name_pos; - nameLen = strlen(name); - // Trim the end of the line if it is " (deleted)". - const char* deleted_str = " (deleted)"; - if (nameLen > (int)strlen(deleted_str) && - strcmp(name+nameLen-strlen(deleted_str), deleted_str) == 0) { - nameLen -= strlen(deleted_str); - name[nameLen] = '\0'; + is_swappable = true; + } else if (base::EndsWith(name, ".oat")) { + which_heap = HEAP_OAT; + is_swappable = true; + } else if (base::EndsWith(name, ".art")) { + which_heap = HEAP_ART; + // Handle system@framework@boot* and system/framework/boot* + if ((strstr(name.c_str(), "@boot") != nullptr) || + (strstr(name.c_str(), "/boot"))) { + sub_heap = HEAP_DEX_BOOT_VDEX; + } else { + sub_heap = HEAP_DEX_APP_VDEX; } - if ((strstr(name, "[heap]") == name)) { - whichHeap = HEAP_NATIVE; - } else if (strncmp(name, "[anon:libc_malloc]", 18) == 0) { - whichHeap = HEAP_NATIVE; - } else if (strncmp(name, "[stack", 6) == 0) { - whichHeap = HEAP_STACK; - } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) { - whichHeap = HEAP_SO; - is_swappable = true; - } else if (nameLen > 4 && strcmp(name+nameLen-4, ".jar") == 0) { - whichHeap = HEAP_JAR; - is_swappable = true; - } else if (nameLen > 4 && strcmp(name+nameLen-4, ".apk") == 0) { - whichHeap = HEAP_APK; - is_swappable = true; - } else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) { - whichHeap = HEAP_TTF; - is_swappable = true; - } else if ((nameLen > 4 && strstr(name, ".dex") != NULL) || - (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) { - whichHeap = HEAP_DEX; - subHeap = HEAP_DEX_APP_DEX; - is_swappable = true; - } else if (nameLen > 5 && strcmp(name+nameLen-5, ".vdex") == 0) { - whichHeap = HEAP_DEX; - // Handle system@framework@boot* and system/framework/boot* - if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) { - subHeap = HEAP_DEX_BOOT_VDEX; - } else { - subHeap = HEAP_DEX_APP_VDEX; - } - is_swappable = true; - } else if (nameLen > 4 && strcmp(name+nameLen-4, ".oat") == 0) { - whichHeap = HEAP_OAT; - is_swappable = true; - } else if (nameLen > 4 && strcmp(name+nameLen-4, ".art") == 0) { - whichHeap = HEAP_ART; - // Handle system@framework@boot* and system/framework/boot* - if (strstr(name, "@boot") != NULL || strstr(name, "/boot") != NULL) { - subHeap = HEAP_ART_BOOT; + is_swappable = true; + } else if (base::StartsWith(name, "/dev/")) { + which_heap = HEAP_UNKNOWN_DEV; + if (base::StartsWith(name, "/dev/kgsl-3d0")) { + which_heap = HEAP_GL_DEV; + } else if (base::StartsWith(name, "/dev/ashmem/CursorWindow")) { + which_heap = HEAP_CURSOR; + } else if (base::StartsWith(name, "/dev/ashmem")) { + which_heap = HEAP_ASHMEM; + } + } else if (base::StartsWith(name, "[anon:")) { + which_heap = HEAP_UNKNOWN; + if (base::StartsWith(name, "[anon:dalvik-")) { + which_heap = HEAP_DALVIK_OTHER; + if (base::StartsWith(name, "[anon:dalvik-LinearAlloc")) { + sub_heap = HEAP_DALVIK_OTHER_LINEARALLOC; + } else if (base::StartsWith(name, "[anon:dalvik-alloc space") || + base::StartsWith(name, "[anon:dalvik-main space")) { + // This is the regular Dalvik heap. + which_heap = HEAP_DALVIK; + sub_heap = HEAP_DALVIK_NORMAL; + } else if (base::StartsWith(name, + "[anon:dalvik-large object space") || + base::StartsWith( + name, "[anon:dalvik-free list large object space")) { + which_heap = HEAP_DALVIK; + sub_heap = HEAP_DALVIK_LARGE; + } else if (base::StartsWith(name, "[anon:dalvik-non moving space")) { + which_heap = HEAP_DALVIK; + sub_heap = HEAP_DALVIK_NON_MOVING; + } else if (base::StartsWith(name, "[anon:dalvik-zygote space")) { + which_heap = HEAP_DALVIK; + sub_heap = HEAP_DALVIK_ZYGOTE; + } else if (base::StartsWith(name, "[anon:dalvik-indirect ref")) { + sub_heap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE; + } else if (base::StartsWith(name, "[anon:dalvik-jit-code-cache") || + base::StartsWith(name, "[anon:dalvik-data-code-cache")) { + sub_heap = HEAP_DALVIK_OTHER_CODE_CACHE; + } else if (base::StartsWith(name, "[anon:dalvik-CompilerMetadata")) { + sub_heap = HEAP_DALVIK_OTHER_COMPILER_METADATA; } else { - subHeap = HEAP_ART_APP; - } - is_swappable = true; - } else if (strncmp(name, "/dev/", 5) == 0) { - whichHeap = HEAP_UNKNOWN_DEV; - if (strncmp(name, "/dev/kgsl-3d0", 13) == 0) { - whichHeap = HEAP_GL_DEV; - } else if (strncmp(name, "/dev/ashmem/CursorWindow", 24) == 0) { - whichHeap = HEAP_CURSOR; - } else if (strncmp(name, "/dev/ashmem", 11)) { - whichHeap = HEAP_ASHMEM; - } - } else if (strncmp(name, "[anon:", 6) == 0) { - whichHeap = HEAP_UNKNOWN; - if (strncmp(name, "[anon:dalvik-", 13) == 0) { - whichHeap = HEAP_DALVIK_OTHER; - if (strstr(name, "[anon:dalvik-LinearAlloc") == name) { - subHeap = HEAP_DALVIK_OTHER_LINEARALLOC; - } else if ((strstr(name, "[anon:dalvik-alloc space") == name) || - (strstr(name, "[anon:dalvik-main space") == name)) { - // This is the regular Dalvik heap. - whichHeap = HEAP_DALVIK; - subHeap = HEAP_DALVIK_NORMAL; - } else if (strstr(name, "[anon:dalvik-large object space") == name || - strstr(name, "[anon:dalvik-free list large object space") - == name) { - whichHeap = HEAP_DALVIK; - subHeap = HEAP_DALVIK_LARGE; - } else if (strstr(name, "[anon:dalvik-non moving space") == name) { - whichHeap = HEAP_DALVIK; - subHeap = HEAP_DALVIK_NON_MOVING; - } else if (strstr(name, "[anon:dalvik-zygote space") == name) { - whichHeap = HEAP_DALVIK; - subHeap = HEAP_DALVIK_ZYGOTE; - } else if (strstr(name, "[anon:dalvik-indirect ref") == name) { - subHeap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE; - } else if (strstr(name, "[anon:dalvik-jit-code-cache") == name || - strstr(name, "[anon:dalvik-data-code-cache") == name) { - subHeap = HEAP_DALVIK_OTHER_CODE_CACHE; - } else if (strstr(name, "[anon:dalvik-CompilerMetadata") == name) { - subHeap = HEAP_DALVIK_OTHER_COMPILER_METADATA; - } else { - subHeap = HEAP_DALVIK_OTHER_ACCOUNTING; // Default to accounting. - } + sub_heap = HEAP_DALVIK_OTHER_ACCOUNTING; // Default to accounting. } - } else if (nameLen > 0) { - whichHeap = HEAP_UNKNOWN_MAP; - } else if (start == prevEnd && prevHeap == HEAP_SO) { - // bss section of a shared library. - whichHeap = HEAP_SO; } + } else if (namesz > 0) { + which_heap = HEAP_UNKNOWN_MAP; + } else if (vma.start == prev_end && prev_heap == HEAP_SO) { + // bss section of a shared library + which_heap = HEAP_SO; } - //ALOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap, - // isSqliteHeap, line); + prev_end = vma.end; + prev_heap = which_heap; - shared_clean = 0; - shared_dirty = 0; - private_clean = 0; - private_dirty = 0; - swapped_out = 0; - swapped_out_pss = 0; - - while (true) { - if (fgets(line, 1024, fp) == 0) { - done = true; - break; - } - - if (line[0] == 'S' && sscanf(line, "Size: %d kB", &temp) == 1) { - /* size = temp; */ - } else if (line[0] == 'R' && sscanf(line, "Rss: %d kB", &temp) == 1) { - rss = temp; - } else if (line[0] == 'P' && sscanf(line, "Pss: %d kB", &temp) == 1) { - pss = temp; - } else if (line[0] == 'S' && sscanf(line, "Shared_Clean: %d kB", &temp) == 1) { - shared_clean = temp; - } else if (line[0] == 'S' && sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) { - shared_dirty = temp; - } else if (line[0] == 'P' && sscanf(line, "Private_Clean: %d kB", &temp) == 1) { - private_clean = temp; - } else if (line[0] == 'P' && sscanf(line, "Private_Dirty: %d kB", &temp) == 1) { - private_dirty = temp; - } else if (line[0] == 'R' && sscanf(line, "Referenced: %d kB", &temp) == 1) { - /* referenced = temp; */ - } else if (line[0] == 'S' && sscanf(line, "Swap: %d kB", &temp) == 1) { - swapped_out = temp; - } else if (line[0] == 'S' && sscanf(line, "SwapPss: %d kB", &temp) == 1) { - *foundSwapPss = true; - swapped_out_pss = temp; - } else if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %*s %*x %*x:%*x %*d", &start, &end) == 2) { - // looks like a new mapping - // example: "10000000-10001000 ---p 10000000 00:00 0" - break; - } + const meminfo::MemUsage& usage = vma.usage; + if (usage.swap_pss > 0 && *foundSwapPss != true) { + *foundSwapPss = true; } - if (!skip) { - if (is_swappable && (pss > 0)) { - sharing_proportion = 0.0; - if ((shared_clean > 0) || (shared_dirty > 0)) { - sharing_proportion = (pss - private_clean - - private_dirty)/(shared_clean+shared_dirty); - } - swappable_pss = (sharing_proportion*shared_clean) + private_clean; - } else - swappable_pss = 0; - - stats[whichHeap].pss += pss; - stats[whichHeap].swappablePss += swappable_pss; - stats[whichHeap].rss += rss; - stats[whichHeap].privateDirty += private_dirty; - stats[whichHeap].sharedDirty += shared_dirty; - stats[whichHeap].privateClean += private_clean; - stats[whichHeap].sharedClean += shared_clean; - stats[whichHeap].swappedOut += swapped_out; - stats[whichHeap].swappedOutPss += swapped_out_pss; - if (whichHeap == HEAP_DALVIK || whichHeap == HEAP_DALVIK_OTHER || - whichHeap == HEAP_DEX || whichHeap == HEAP_ART) { - stats[subHeap].pss += pss; - stats[subHeap].swappablePss += swappable_pss; - stats[subHeap].rss += rss; - stats[subHeap].privateDirty += private_dirty; - stats[subHeap].sharedDirty += shared_dirty; - stats[subHeap].privateClean += private_clean; - stats[subHeap].sharedClean += shared_clean; - stats[subHeap].swappedOut += swapped_out; - stats[subHeap].swappedOutPss += swapped_out_pss; + uint64_t swapable_pss = 0; + if (is_swappable && (usage.pss > 0)) { + float sharing_proportion = 0.0; + if ((usage.shared_clean > 0) || (usage.shared_dirty > 0)) { + sharing_proportion = (usage.pss - usage.uss) / (usage.shared_clean + usage.shared_dirty); } + swapable_pss = (sharing_proportion * usage.shared_clean) + usage.private_clean; } - } -} -static void load_maps(int pid, stats_t* stats, bool* foundSwapPss) -{ - *foundSwapPss = false; - - std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid); - UniqueFile fp = MakeUniqueFile(smaps_path.c_str(), "re"); - if (fp == nullptr) return; + stats[which_heap].pss += usage.pss; + stats[which_heap].swappablePss += swapable_pss; + stats[which_heap].rss += usage.rss; + stats[which_heap].privateDirty += usage.private_dirty; + stats[which_heap].sharedDirty += usage.shared_dirty; + stats[which_heap].privateClean += usage.private_clean; + stats[which_heap].sharedClean += usage.shared_clean; + stats[which_heap].swappedOut += usage.swap; + stats[which_heap].swappedOutPss += usage.swap_pss; + if (which_heap == HEAP_DALVIK || which_heap == HEAP_DALVIK_OTHER || + which_heap == HEAP_DEX || which_heap == HEAP_ART) { + stats[sub_heap].pss += usage.pss; + stats[sub_heap].swappablePss += swapable_pss; + stats[sub_heap].rss += usage.rss; + stats[sub_heap].privateDirty += usage.private_dirty; + stats[sub_heap].sharedDirty += usage.shared_dirty; + stats[sub_heap].privateClean += usage.private_clean; + stats[sub_heap].sharedClean += usage.shared_clean; + stats[sub_heap].swappedOut += usage.swap; + stats[sub_heap].swappedOutPss += usage.swap_pss; + } + }; - read_mapinfo(fp.get(), stats, foundSwapPss); + meminfo::ForEachVmaFromFile(smaps_path, vma_scan); } static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 897427fd8787..0453195e6a1d 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -126,7 +126,12 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject, jint windowType, jint ownerUid) { ScopedUtfChars name(env, nameStr); - sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj)); + sp<SurfaceComposerClient> client; + if (sessionObj != NULL) { + client = android_view_SurfaceSession_getClient(env, sessionObj); + } else { + client = SurfaceComposerClient::getDefault(); + } SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject); sp<SurfaceControl> surface; status_t err = client->createSurfaceChecked( @@ -277,6 +282,21 @@ static void nativeSetPosition(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setPosition(ctrl, x, y); } +static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, + jobject sourceObj, jobject dstObj, jlong orientation) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); + + Rect source, dst; + if (sourceObj != NULL) { + source = rectFromObj(env, sourceObj); + } + if (dstObj != NULL) { + dst = rectFromObj(env, dstObj); + } + transaction->setGeometry(ctrl, source, dst, orientation); +} + static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject) { @@ -868,13 +888,13 @@ static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong transactionO static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, - jobject newParentObject) { + jlong newParentObject) { auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject); - sp<IBinder> parentHandle = ibinderForJavaObject(env, newParentObject); + auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject); { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - transaction->reparent(ctrl, parentHandle); + transaction->reparent(ctrl, newParent != NULL ? newParent->getHandle() : NULL); } } @@ -1063,7 +1083,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeDeferTransactionUntilSurface }, {"nativeReparentChildren", "(JJLandroid/os/IBinder;)V", (void*)nativeReparentChildren } , - {"nativeReparent", "(JJLandroid/os/IBinder;)V", + {"nativeReparent", "(JJJ)V", (void*)nativeReparent }, {"nativeSeverChildren", "(JJ)V", (void*)nativeSeverChildren } , @@ -1087,6 +1107,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { {"nativeGetDisplayedContentSample", "(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;", (void*)nativeGetDisplayedContentSample }, + {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V", + (void*)nativeSetGeometry } }; int register_android_view_SurfaceControl(JNIEnv* env) diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index e81b6276ef5f..2e7184b4f0fb 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -29,13 +29,17 @@ #include <sys/mount.h> #include <linux/fs.h> +#include <array> +#include <atomic> #include <functional> #include <list> #include <optional> #include <sstream> #include <string> +#include <string_view> #include <android/fdsan.h> +#include <arpa/inet.h> #include <fcntl.h> #include <grp.h> #include <inttypes.h> @@ -46,9 +50,11 @@ #include <stdlib.h> #include <sys/capability.h> #include <sys/cdefs.h> +#include <sys/eventfd.h> #include <sys/personality.h> #include <sys/prctl.h> #include <sys/resource.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/time.h> #include <sys/types.h> @@ -56,10 +62,11 @@ #include <sys/wait.h> #include <unistd.h> -#include "android-base/logging.h" +#include <android-base/logging.h> #include <android-base/properties.h> #include <android-base/file.h> #include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> #include <cutils/fs.h> #include <cutils/multiuser.h> #include <private/android_filesystem_config.h> @@ -81,6 +88,9 @@ namespace { +// TODO (chriswailes): Add a function to initialize native Zygote data. +// TODO (chriswailes): Fix mixed indentation style (2 and 4 spaces). + using namespace std::placeholders; using android::String8; @@ -92,6 +102,9 @@ using android::base::GetBoolProperty; #define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \ append(StringPrintf(__VA_ARGS__)) +// This type is duplicated in fd_utils.h +typedef const std::function<void(std::string)>& fail_fn_t; + static pid_t gSystemServerPid = 0; static const char kIsolatedStorage[] = "persist.sys.isolated_storage"; @@ -103,6 +116,152 @@ static jmethodID gCallPostForkChildHooks; static bool g_is_security_enforced = true; +/** + * The maximum number of characters (not including a null terminator) that a + * process name may contain. + */ +static constexpr size_t MAX_NAME_LENGTH = 15; + +/** + * The prefix string for environmental variables storing socket FDs created by + * init. + */ + +static constexpr std::string_view ANDROID_SOCKET_PREFIX("ANDROID_SOCKET_"); + +/** + * The file descriptor for the Zygote socket opened by init. + */ + +static int gZygoteSocketFD = -1; + +/** + * The file descriptor for the Blastula pool socket opened by init. + */ + +static int gBlastulaPoolSocketFD = -1; + +/** + * The number of Blastulas currently in this Zygote's pool. + */ +static std::atomic_uint32_t gBlastulaPoolCount = 0; + +/** + * Event file descriptor used to communicate reaped blastulas to the + * ZygoteServer. + */ +static int gBlastulaPoolEventFD = -1; + +/** + * The maximum value that the gBlastulaPoolMax variable may take. This value + * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT + */ +static constexpr int BLASTULA_POOL_MAX_LIMIT = 10; + +/** + * A helper class containing accounting information for Blastulas. + */ +class BlastulaTableEntry { + public: + struct EntryStorage { + int32_t pid; + int32_t read_pipe_fd; + + bool operator!=(const EntryStorage& other) { + return pid != other.pid || read_pipe_fd != other.read_pipe_fd; + } + }; + + private: + static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1}; + + std::atomic<EntryStorage> mStorage; + static_assert(decltype(mStorage)::is_always_lock_free); + + public: + constexpr BlastulaTableEntry() : mStorage(INVALID_ENTRY_VALUE) {} + + /** + * If the provided PID matches the one stored in this entry, the entry will + * be invalidated and the associated file descriptor will be closed. If the + * PIDs don't match nothing will happen. + * + * @param pid The ID of the process who's entry we want to clear. + * @return True if the entry was cleared; false otherwise + */ + bool ClearForPID(int32_t pid) { + EntryStorage storage = mStorage.load(); + + if (storage.pid == pid) { + /* + * There are three possible outcomes from this compare-and-exchange: + * 1) It succeeds, in which case we close the FD + * 2) It fails and the new value is INVALID_ENTRY_VALUE, in which case + * the entry has already been cleared. + * 3) It fails and the new value isn't INVALID_ENTRY_VALUE, in which + * case the entry has already been cleared and re-used. + * + * In all three cases the goal of the caller has been met and we can + * return true. + */ + if (mStorage.compare_exchange_strong(storage, INVALID_ENTRY_VALUE)) { + close(storage.read_pipe_fd); + } + + return true; + } else { + return false; + } + } + + /** + * @return A copy of the data stored in this entry. + */ + std::optional<EntryStorage> GetValues() { + EntryStorage storage = mStorage.load(); + + if (storage != INVALID_ENTRY_VALUE) { + return storage; + } else { + return std::nullopt; + } + } + + /** + * Sets the entry to the given values if it is currently invalid. + * + * @param pid The process ID for the new entry. + * @param read_pipe_fd The read end of the blastula control pipe for this + * process. + * @return True if the entry was set; false otherwise. + */ + bool SetIfInvalid(int32_t pid, int32_t read_pipe_fd) { + EntryStorage new_value_storage; + + new_value_storage.pid = pid; + new_value_storage.read_pipe_fd = read_pipe_fd; + + EntryStorage expected = INVALID_ENTRY_VALUE; + + return mStorage.compare_exchange_strong(expected, new_value_storage); + } +}; + +/** + * A table containing information about the Blastulas currently in the pool. + * + * Multiple threads may be attempting to modify the table, either from the + * signal handler or from the ZygoteServer poll loop. Atomic loads/stores in + * the BlastulaTableEntry class prevent data races during these concurrent + * operations. + */ +static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable; + +/** + * The list of open zygote file descriptors. + */ +static FileDescriptorTable* gOpenFdTable = nullptr; + // Must match values in com.android.internal.os.Zygote. enum MountExternalKind { MOUNT_EXTERNAL_NONE = 0, @@ -119,6 +278,9 @@ enum RuntimeFlags : uint32_t { DEBUG_ENABLE_JDWP = 1, }; +// Forward declaration so we don't have to move the signal handler. +static bool RemoveBlastulaTableEntry(pid_t blastula_pid); + static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { std::ostringstream oss; oss << __FILE__ << ":" << line << ": " << msg; @@ -129,6 +291,7 @@ static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { static void SigChldHandler(int /*signal_number*/) { pid_t pid; int status; + int64_t blastulas_removed = 0; // It's necessary to save and restore the errno during this function. // Since errno is stored per thread, changing it here modifies the errno @@ -162,6 +325,11 @@ static void SigChldHandler(int /*signal_number*/) { ALOGE("Exit zygote because system server (%d) has terminated", pid); kill(getpid(), SIGKILL); } + + // Check to see if the PID is in the blastula pool and remove it if it is. + if (RemoveBlastulaTableEntry(pid)) { + ++blastulas_removed; + } } // Note that we shouldn't consider ECHILD an error because @@ -170,6 +338,15 @@ static void SigChldHandler(int /*signal_number*/) { ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno)); } + if (blastulas_removed > 0) { + if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) { + // If this write fails something went terribly wrong. We will now kill + // the zygote and let the system bring it back up. + ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno)); + kill(getpid(), SIGKILL); + } + } + errno = saved_errno; } @@ -194,13 +371,13 @@ static void SetSignalHandlers() { struct sigaction sig_chld = {}; sig_chld.sa_handler = SigChldHandler; - if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) { + if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) { ALOGW("Error setting SIGCHLD handler: %s", strerror(errno)); } struct sigaction sig_hup = {}; sig_hup.sa_handler = SIG_IGN; - if (sigaction(SIGHUP, &sig_hup, NULL) < 0) { + if (sigaction(SIGHUP, &sig_hup, nullptr) < 0) { ALOGW("Error setting SIGHUP handler: %s", strerror(errno)); } } @@ -211,64 +388,57 @@ static void UnsetChldSignalHandler() { memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; - if (sigaction(SIGCHLD, &sa, NULL) < 0) { + if (sigaction(SIGCHLD, &sa, nullptr) < 0) { ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno)); } } // Calls POSIX setgroups() using the int[] object as an argument. -// A NULL argument is tolerated. -static bool SetGids(JNIEnv* env, jintArray javaGids, std::string* error_msg) { - if (javaGids == NULL) { - return true; +// A nullptr argument is tolerated. +static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) { + if (managed_gids == nullptr) { + return; } - ScopedIntArrayRO gids(env, javaGids); - if (gids.get() == NULL) { - *error_msg = CREATE_ERROR("Getting gids int array failed"); - return false; - } - int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])); - if (rc == -1) { - *error_msg = CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()); - return false; + ScopedIntArrayRO gids(env, managed_gids); + if (gids.get() == nullptr) { + fail_fn(CREATE_ERROR("Getting gids int array failed")); } - return true; + if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) { + fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size())); + } } // Sets the resource limits via setrlimit(2) for the values in the // two-dimensional array of integers that's passed in. The second dimension -// contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is +// contains a tuple of length 3: (resource, rlim_cur, rlim_max). nullptr is // treated as an empty array. -static bool SetRLimits(JNIEnv* env, jobjectArray javaRlimits, std::string* error_msg) { - if (javaRlimits == NULL) { - return true; +static void SetRLimits(JNIEnv* env, jobjectArray managed_rlimits, fail_fn_t fail_fn) { + if (managed_rlimits == nullptr) { + return; } rlimit rlim; memset(&rlim, 0, sizeof(rlim)); - for (int i = 0; i < env->GetArrayLength(javaRlimits); ++i) { - ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i)); - ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get())); - if (javaRlimit.size() != 3) { - *error_msg = CREATE_ERROR("rlimits array must have a second dimension of size 3"); - return false; + for (int i = 0; i < env->GetArrayLength(managed_rlimits); ++i) { + ScopedLocalRef<jobject> + managed_rlimit_object(env, env->GetObjectArrayElement(managed_rlimits, i)); + ScopedIntArrayRO rlimit_handle(env, reinterpret_cast<jintArray>(managed_rlimit_object.get())); + + if (rlimit_handle.size() != 3) { + fail_fn(CREATE_ERROR("rlimits array must have a second dimension of size 3")); } - rlim.rlim_cur = javaRlimit[1]; - rlim.rlim_max = javaRlimit[2]; + rlim.rlim_cur = rlimit_handle[1]; + rlim.rlim_max = rlimit_handle[2]; - int rc = setrlimit(javaRlimit[0], &rlim); - if (rc == -1) { - *error_msg = CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur, - rlim.rlim_max); - return false; + if (setrlimit(rlimit_handle[0], &rlim) == -1) { + fail_fn(CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", + rlimit_handle[0], rlim.rlim_cur, rlim.rlim_max)); } } - - return true; } static void EnableDebugger() { @@ -323,10 +493,7 @@ static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) { // Apply system or app filter based on uid. if (uid >= AID_APP_START) { if (is_child_zygote) { - // set_app_zygote_seccomp_filter(); - // TODO(b/111434506) install the filter; for now, install the app filter - // which is more restrictive. - set_app_seccomp_filter(); + set_app_zygote_seccomp_filter(); } else { set_app_seccomp_filter(); } @@ -335,32 +502,26 @@ static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) { } } -static bool EnableKeepCapabilities(std::string* error_msg) { - int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); - if (rc == -1) { - *error_msg = CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)); - return false; +static void EnableKeepCapabilities(fail_fn_t fail_fn) { + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { + fail_fn(CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno))); } - return true; } -static bool DropCapabilitiesBoundingSet(std::string* error_msg) { - for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) { - int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); - if (rc == -1) { +static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) { + for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {; + if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) { if (errno == EINVAL) { ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify " "your kernel is compiled with file capabilities support"); } else { - *error_msg = CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno))); } } } - return true; } -static bool SetInheritable(uint64_t inheritable, std::string* error_msg) { +static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) { __user_cap_header_struct capheader; memset(&capheader, 0, sizeof(capheader)); capheader.version = _LINUX_CAPABILITY_VERSION_3; @@ -368,23 +529,19 @@ static bool SetInheritable(uint64_t inheritable, std::string* error_msg) { __user_cap_data_struct capdata[2]; if (capget(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capget failed: %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capget failed: %s", strerror(errno))); } capdata[0].inheritable = inheritable; capdata[1].inheritable = inheritable >> 32; if (capset(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno))); } - - return true; } -static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable, - std::string* error_msg) { +static void SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable, + fail_fn_t fail_fn) { __user_cap_header_struct capheader; memset(&capheader, 0, sizeof(capheader)); capheader.version = _LINUX_CAPABILITY_VERSION_3; @@ -400,27 +557,23 @@ static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inh capdata[1].inheritable = inheritable >> 32; if (capset(&capheader, &capdata[0]) == -1) { - *error_msg = CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") " - "failed: %s", permitted, effective, inheritable, strerror(errno)); - return false; + fail_fn(CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") " + "failed: %s", permitted, effective, inheritable, strerror(errno))); } - return true; } -static bool SetSchedulerPolicy(std::string* error_msg) { +static void SetSchedulerPolicy(fail_fn_t fail_fn) { errno = -set_sched_policy(0, SP_DEFAULT); if (errno != 0) { - *error_msg = CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno))); } - return true; } static int UnmountTree(const char* path) { size_t path_len = strlen(path); FILE* fp = setmntent("/proc/mounts", "r"); - if (fp == NULL) { + if (fp == nullptr) { ALOGE("Error opening /proc/mounts: %s", strerror(errno)); return -errno; } @@ -429,7 +582,7 @@ static int UnmountTree(const char* path) { // reverse order to give us the best chance of success. std::list<std::string> toUnmount; mntent* mentry; - while ((mentry = getmntent(fp)) != NULL) { + while ((mentry = getmntent(fp)) != nullptr) { if (strncmp(mentry->mnt_dir, path, path_len) == 0) { toUnmount.push_front(std::string(mentry->mnt_dir)); } @@ -444,56 +597,55 @@ static int UnmountTree(const char* path) { return 0; } -static bool createPkgSandbox(uid_t uid, const std::string& package_name, std::string* error_msg) { +static void CreatePkgSandbox(uid_t uid, const std::string& package_name, fail_fn_t fail_fn) { // Create /mnt/user/0/package/<package-name> userid_t user_id = multiuser_get_user_id(uid); std::string pkg_sandbox_dir = StringPrintf("/mnt/user/%d", user_id); if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0751, AID_ROOT, AID_ROOT) != 0) { - *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); - return false; + fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str())); } + StringAppendF(&pkg_sandbox_dir, "/package"); if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0700, AID_ROOT, AID_ROOT) != 0) { - *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); - return false; + fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str())); } + StringAppendF(&pkg_sandbox_dir, "/%s", package_name.c_str()); if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0755, uid, uid) != 0) { - *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); - return false; + fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str())); } - return true; } -static bool bindMount(const std::string& sourceDir, const std::string& targetDir, - std::string* error_msg) { - if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(), - nullptr, MS_BIND | MS_REC, nullptr)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s", - sourceDir.c_str(), targetDir.c_str(), strerror(errno)); - return false; +static void BindMount(const std::string& sourceDir, const std::string& targetDir, + fail_fn_t fail_fn) { + if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(), nullptr, + MS_BIND | MS_REC, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to %s: %s", + sourceDir.c_str(), targetDir.c_str(), strerror(errno))); } - if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(), - nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) { - *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str()); - return false; + + if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(), nullptr, + MS_SLAVE | MS_REC, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str())); } - return true; } -static bool mountPkgSpecificDir(const std::string& mntSourceRoot, - const std::string& mntTargetRoot, const std::string& packageName, - const char* dirName, std::string* error_msg) { +static void MountPkgSpecificDir(const std::string& mntSourceRoot, + const std::string& mntTargetRoot, + const std::string& packageName, + const char* dirName, + fail_fn_t fail_fn) { std::string mntSourceDir = StringPrintf("%s/Android/%s/%s", mntSourceRoot.c_str(), dirName, packageName.c_str()); std::string mntTargetDir = StringPrintf("%s/Android/%s/%s", mntTargetRoot.c_str(), dirName, packageName.c_str()); - return bindMount(mntSourceDir, mntTargetDir, error_msg); + + BindMount(mntSourceDir, mntTargetDir, fail_fn); } -static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames, - const std::vector<std::string>& volumeLabels, bool mountAllObbs, - userid_t userId, std::string* error_msg) { +static void PreparePkgSpecificDirs(const std::vector<std::string>& packageNames, + const std::vector<std::string>& volumeLabels, + bool mountAllObbs, userid_t userId, fail_fn_t fail_fn) { for (auto& label : volumeLabels) { std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str()); std::string mntTarget = StringPrintf("/storage/%s", label.c_str()); @@ -501,28 +653,29 @@ static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames, StringAppendF(&mntSource, "/%d", userId); StringAppendF(&mntTarget, "/%d", userId); } + for (auto& package : packageNames) { - mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg); - mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg); + MountPkgSpecificDir(mntSource, mntTarget, package, "data", fail_fn); + MountPkgSpecificDir(mntSource, mntTarget, package, "media", fail_fn); if (!mountAllObbs) { - mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg); + MountPkgSpecificDir(mntSource, mntTarget, package, "obb", fail_fn); } } + if (mountAllObbs) { StringAppendF(&mntSource, "/Android/obb"); StringAppendF(&mntTarget, "/Android/obb"); - bindMount(mntSource, mntTarget, error_msg); + BindMount(mntSource, mntTarget, fail_fn); } } - return true; } // Create a private mount namespace and bind mount appropriate emulated // storage for the given user. -static bool MountEmulatedStorage(uid_t uid, jint mount_mode, - bool force_mount_namespace, std::string* error_msg, const std::string& package_name, +static void MountEmulatedStorage(uid_t uid, jint mount_mode, + bool force_mount_namespace, const std::string& package_name, const std::vector<std::string>& packages_for_uid, - const std::vector<std::string>& visible_vol_ids) { + const std::vector<std::string>& visible_vol_ids, fail_fn_t fail_fn) { // See storage config details at http://source.android.com/tech/storage/ String8 storageSource; @@ -534,18 +687,17 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, storageSource = "/mnt/runtime/write"; } else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) { // Sane default of no storage visible - return true; + return; } // Create a second private mount namespace for our process if (unshare(CLONE_NEWNS) == -1) { - *error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno)); - return false; + fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno))); } // Handle force_mount_namespace with MOUNT_EXTERNAL_NONE. if (mount_mode == MOUNT_EXTERNAL_NONE) { - return true; + return; } if (GetBoolProperty(kIsolatedStorageSnapshot, GetBoolProperty(kIsolatedStorage, false))) { @@ -553,66 +705,64 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, storageSource = (mount_mode == MOUNT_EXTERNAL_FULL) ? "/mnt/runtime/full" : "/mnt/runtime/write"; if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", - NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", - storageSource.string(), - strerror(errno)); - return false; + NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s", + storageSource.string(), + strerror(errno))); } // Mount user-specific symlink helper into place userid_t user_id = multiuser_get_user_id(uid); const String8 userSource(String8::format("/mnt/user/%d", user_id)); if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { - *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); - return false; + fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s (%s)", + userSource.string(), strerror(errno))); } - if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", - NULL, MS_BIND, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", - userSource.string(), - strerror(errno)); - return false; + + if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", nullptr, MS_BIND, + nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s", + userSource.string(), + strerror(errno))); } } else { if (package_name.empty()) { - return true; + return; } + userid_t user_id = multiuser_get_user_id(uid); - std::string pkgSandboxDir = StringPrintf("/mnt/user/%d/package/%s", - user_id, package_name.c_str()); + std::string pkgSandboxDir = + StringPrintf("/mnt/user/%d/package/%s", user_id, package_name.c_str()); struct stat sb; bool sandboxAlreadyCreated = true; if (TEMP_FAILURE_RETRY(lstat(pkgSandboxDir.c_str(), &sb)) == -1) { if (errno == ENOENT) { ALOGD("Sandbox not yet created for %s", pkgSandboxDir.c_str()); sandboxAlreadyCreated = false; - if (!createPkgSandbox(uid, package_name, error_msg)) { - return false; - } + CreatePkgSandbox(uid, package_name, fail_fn); } else { - ALOGE("Failed to lstat %s", pkgSandboxDir.c_str()); - return false; + fail_fn(CREATE_ERROR("Failed to lstat %s: %s", + pkgSandboxDir.c_str(), strerror(errno))); } } + if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage", - nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", - pkgSandboxDir.c_str(), strerror(errno)); - return false; + nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s", + pkgSandboxDir.c_str(), strerror(errno))); } + if (access("/storage/obb_mount", F_OK) == 0) { if (mount_mode != MOUNT_EXTERNAL_INSTALLER) { remove("/storage/obb_mount"); } } else { if (mount_mode == MOUNT_EXTERNAL_INSTALLER) { - int fd = TEMP_FAILURE_RETRY(open("/storage/obb_mount", - O_RDWR | O_CREAT, 0660)); + int fd = + TEMP_FAILURE_RETRY(open("/storage/obb_mount", O_RDWR | O_CREAT, 0660)); if (fd == -1) { - *error_msg = CREATE_ERROR("Couldn't create /storage/obb_mount: %s", - strerror(errno)); - return false; + fail_fn(CREATE_ERROR("Couldn't create /storage/obb_mount: %s", + strerror(errno))); } close(fd); } @@ -621,38 +771,32 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, // pkg specific directories. Otherwise, leave as is and bind mounts will be taken // care of by vold later. if (sandboxAlreadyCreated) { - if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids, - mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, error_msg)) { - return false; - } + PreparePkgSpecificDirs(packages_for_uid, visible_vol_ids, + mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, fail_fn); } } } else { - if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", - NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", - storageSource.string(), - strerror(errno)); - return false; + if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", nullptr, + MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s", + storageSource.string(), + strerror(errno))); } // Mount user-specific symlink helper into place userid_t user_id = multiuser_get_user_id(uid); const String8 userSource(String8::format("/mnt/user/%d", user_id)); if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { - *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); - return false; + fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", + userSource.string())); } + if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", - NULL, MS_BIND, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", - userSource.string(), - strerror(errno)); - return false; + nullptr, MS_BIND, nullptr)) == -1) { + fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s", + userSource.string(), strerror(errno))); } } - - return true; } static bool NeedsNoRandomizeWorkaround() { @@ -680,55 +824,45 @@ static bool NeedsNoRandomizeWorkaround() { // descriptor (if any) is closed via dup2(), replacing it with a valid // (open) descriptor to /dev/null. -static bool DetachDescriptors(JNIEnv* env, jintArray fdsToClose, std::string* error_msg) { - if (!fdsToClose) { - return true; - } - jsize count = env->GetArrayLength(fdsToClose); - ScopedIntArrayRO ar(env, fdsToClose); - if (ar.get() == NULL) { - *error_msg = "Bad fd array"; - return false; - } - jsize i; - int devnull; - for (i = 0; i < count; i++) { - devnull = open("/dev/null", O_RDWR); - if (devnull < 0) { - *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno)); - return false; +static void DetachDescriptors(JNIEnv* env, + const std::vector<int>& fds_to_close, + fail_fn_t fail_fn) { + + if (fds_to_close.size() > 0) { + android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR)); + if (devnull_fd == -1) { + fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } - ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno)); - if (dup2(devnull, ar[i]) < 0) { - *error_msg = StringPrintf("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno)); - return false; + + for (int fd : fds_to_close) { + ALOGV("Switching descriptor %d to /dev/null", fd); + if (dup2(devnull_fd, fd) == -1) { + fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno))); + } } - close(devnull); } - return true; } -void SetThreadName(const char* thread_name) { +void SetThreadName(const std::string& thread_name) { bool hasAt = false; bool hasDot = false; - const char* s = thread_name; - while (*s) { - if (*s == '.') { + + for (const char str_el : thread_name) { + if (str_el == '.') { hasDot = true; - } else if (*s == '@') { + } else if (str_el == '@') { hasAt = true; } - s++; } - const int len = s - thread_name; - if (len < 15 || hasAt || !hasDot) { - s = thread_name; - } else { - s = thread_name + len - 15; + + const char* name_start_ptr = thread_name.c_str(); + if (thread_name.length() >= MAX_NAME_LENGTH && !hasAt && hasDot) { + name_start_ptr += thread_name.length() - MAX_NAME_LENGTH; } + // pthread_setname_np fails rather than truncating long strings. char buf[16]; // MAX_TASK_COMM_LEN=16 is hard-coded into bionic - strlcpy(buf, s, sizeof(buf)-1); + strlcpy(buf, name_start_ptr, sizeof(buf) - 1); errno = pthread_setname_np(pthread_self(), buf); if (errno != 0) { ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno)); @@ -737,28 +871,16 @@ void SetThreadName(const char* thread_name) { android::base::SetDefaultTag(buf); } -// The list of open zygote file descriptors. -static FileDescriptorTable* gOpenFdTable = NULL; - -static bool FillFileDescriptorVector(JNIEnv* env, - jintArray managed_fds, - std::vector<int>* fds, - std::string* error_msg) { - CHECK(fds != nullptr); - if (managed_fds != nullptr) { - ScopedIntArrayRO ar(env, managed_fds); - if (ar.get() == nullptr) { - *error_msg = "Bad fd array"; - return false; - } - fds->reserve(ar.size()); - for (size_t i = 0; i < ar.size(); ++i) { - fds->push_back(ar[i]); - } - } - return true; -} - +/** + * A failure function used to report fatal errors to the managed runtime. This + * function is often curried with the process name information and then passed + * to called functions. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param msg The error message to be reported + */ [[noreturn]] static void ZygoteFailure(JNIEnv* env, const char* process_name, @@ -779,12 +901,25 @@ static void ZygoteFailure(JNIEnv* env, __builtin_unreachable(); } +/** + * A helper method for converting managed strings to native strings. A fatal + * error is generated if a problem is encountered in extracting a non-null + * string. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param managed_string The managed string to extract + * + * @return An empty option if the managed string is null. A optional-wrapped + * string otherwise. + */ static std::optional<std::string> ExtractJString(JNIEnv* env, const char* process_name, jstring managed_process_name, jstring managed_string) { if (managed_string == nullptr) { - return std::optional<std::string>(); + return std::nullopt; } else { ScopedUtfChars scoped_string_chars(env, managed_string); @@ -796,16 +931,125 @@ static std::optional<std::string> ExtractJString(JNIEnv* env, } } -// Utility routine to fork a zygote. +/** + * A helper method for converting managed string arrays to native vectors. A + * fatal error is generated if a problem is encountered in extracting a non-null array. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param managed_array The managed integer array to extract + * + * @return An empty option if the managed array is null. A optional-wrapped + * vector otherwise. + */ +static std::optional<std::vector<int>> ExtractJIntArray(JNIEnv* env, + const char* process_name, + jstring managed_process_name, + jintArray managed_array) { + if (managed_array == nullptr) { + return std::nullopt; + } else { + ScopedIntArrayRO managed_array_handle(env, managed_array); + + if (managed_array_handle.get() != nullptr) { + std::vector<int> native_array; + native_array.reserve(managed_array_handle.size()); + + for (size_t array_index = 0; array_index < managed_array_handle.size(); ++array_index) { + native_array.push_back(managed_array_handle[array_index]); + } + + return std::move(native_array); + + } else { + ZygoteFailure(env, process_name, managed_process_name, "Failed to extract JIntArray."); + } + } +} + +/** + * A helper method for converting managed string arrays to native vectors. A + * fatal error is generated if a problem is encountered in extracting a non-null array. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param managed_array The managed string array to extract + * + * @return An empty option if the managed array is null. A optional-wrapped + * vector otherwise. + */ +static std::optional<std::vector<std::string>> ExtractJStringArray(JNIEnv* env, + const char* process_name, + jstring managed_process_name, + jobjectArray managed_array) { + if (managed_array == nullptr) { + return std::nullopt; + } else { + jsize element_count = env->GetArrayLength(managed_array); + std::vector<std::string> native_string_vector; + native_string_vector.reserve(element_count); + + for (jsize array_index = 0; array_index < element_count; ++array_index) { + jstring managed_string = (jstring) env->GetObjectArrayElement(managed_array, array_index); + auto native_string = ExtractJString(env, process_name, managed_process_name, managed_string); + + if (LIKELY(native_string.has_value())) { + native_string_vector.emplace_back(std::move(native_string.value())); + } else { + ZygoteFailure(env, process_name, managed_process_name, + "Null string found in managed string array."); + } + } + + return std::move(native_string_vector); + } +} + +/** + * A utility function for blocking signals. + * + * @param signum Signal number to block + * @param fail_fn Fatal error reporting function + * + * @see ZygoteFailure + */ +static void BlockSignal(int signum, fail_fn_t fail_fn) { + sigset_t sigs; + sigemptyset(&sigs); + sigaddset(&sigs, signum); + + if (sigprocmask(SIG_BLOCK, &sigs, nullptr) == -1) { + fail_fn(CREATE_ERROR("Failed to block signal %s: %s", strsignal(signum), strerror(errno))); + } +} + + +/** + * A utility function for unblocking signals. + * + * @param signum Signal number to unblock + * @param fail_fn Fatal error reporting function + * + * @see ZygoteFailure + */ +static void UnblockSignal(int signum, fail_fn_t fail_fn) { + sigset_t sigs; + sigemptyset(&sigs); + sigaddset(&sigs, signum); + + if (sigprocmask(SIG_UNBLOCK, &sigs, nullptr) == -1) { + fail_fn(CREATE_ERROR("Failed to un-block signal %s: %s", strsignal(signum), strerror(errno))); + } +} + +// Utility routine to fork a process from the zygote. static pid_t ForkCommon(JNIEnv* env, bool is_system_server, - jintArray managed_fds_to_close, jintArray managed_fds_to_ignore) { + const std::vector<int>& fds_to_close, + const std::vector<int>& fds_to_ignore) { SetSignalHandlers(); - // Block SIGCHLD prior to fork. - sigset_t sigchld; - sigemptyset(&sigchld); - sigaddset(&sigchld, SIGCHLD); - // Curry a failure function. auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote", nullptr, _1); @@ -815,9 +1059,7 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, // This would cause failures because the FDs are not whitelisted. // // Note that the zygote process is single threaded at this point. - if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) { - fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno))); - } + BlockSignal(SIGCHLD, fail_fn); // Close any logging related FDs before we start evaluating the list of // file descriptors. @@ -827,19 +1069,10 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, // If this is the first fork for this zygote, create the open FD table. If // it isn't, we just need to check whether the list of open files has changed // (and it shouldn't in the normal case). - std::string error_msg; - std::vector<int> fds_to_ignore; - if (!FillFileDescriptorVector(env, managed_fds_to_ignore, &fds_to_ignore, &error_msg)) { - fail_fn(error_msg); - } - if (gOpenFdTable == nullptr) { - gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, &error_msg); - if (gOpenFdTable == nullptr) { - fail_fn(error_msg); - } - } else if (!gOpenFdTable->Restat(fds_to_ignore, &error_msg)) { - fail_fn(error_msg); + gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn); + } else { + gOpenFdTable->Restat(fds_to_ignore, fail_fn); } android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level(); @@ -851,24 +1084,18 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, PreApplicationInit(); // Clean up any descriptors which must be closed immediately - if (!DetachDescriptors(env, managed_fds_to_close, &error_msg)) { - fail_fn(error_msg); - } + DetachDescriptors(env, fds_to_close, fail_fn); // Re-open all remaining open file descriptors so that they aren't shared // with the zygote across a fork. - if (!gOpenFdTable->ReopenOrDetach(&error_msg)) { - fail_fn(error_msg); - } + gOpenFdTable->ReopenOrDetach(fail_fn); // Turn fdsan back on. android_fdsan_set_error_level(fdsan_error_level); } // We blocked SIGCHLD prior to a fork, we unblock it here. - if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) { - fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno))); - } + UnblockSignal(SIGCHLD, fail_fn); return pid; } @@ -883,10 +1110,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jstring managed_app_data_dir, jstring managed_package_name, jobjectArray managed_pacakges_for_uid, jobjectArray managed_visible_vol_ids) { - auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote", - managed_nice_name, _1); - auto extract_fn = std::bind(ExtractJString, env, is_system_server ? "system_server" : "zygote", - managed_nice_name, _1); + const char* process_name = is_system_server ? "system_server" : "zygote"; + auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1); + auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); auto se_info = extract_fn(managed_se_info); auto nice_name = extract_fn(managed_nice_name); @@ -894,22 +1120,14 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, auto app_data_dir = extract_fn(managed_app_data_dir); auto package_name = extract_fn(managed_package_name); - std::string error_msg; - // Keep capabilities across UID change, unless we're staying root. if (uid != 0) { - if (!EnableKeepCapabilities(&error_msg)) { - fail_fn(error_msg); - } + EnableKeepCapabilities(fail_fn); } - if (!SetInheritable(permitted_capabilities, &error_msg)) { - fail_fn(error_msg); - } + SetInheritable(permitted_capabilities, fail_fn); - if (!DropCapabilitiesBoundingSet(&error_msg)) { - fail_fn(error_msg); - } + DropCapabilitiesBoundingSet(fail_fn); bool use_native_bridge = !is_system_server && instruction_set.has_value() && @@ -934,56 +1152,21 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - std::vector<std::string> packages_for_uid; - if (managed_pacakges_for_uid != nullptr) { - jsize count = env->GetArrayLength(managed_pacakges_for_uid); - for (jsize package_index = 0; package_index < count; ++package_index) { - jstring managed_package_for_uid = - (jstring) env->GetObjectArrayElement(managed_pacakges_for_uid, package_index); + std::vector<std::string> packages_for_uid = + ExtractJStringArray(env, process_name, managed_nice_name, managed_pacakges_for_uid). + value_or(std::vector<std::string>()); - auto package_for_uid = extract_fn(managed_package_for_uid); - if (LIKELY(package_for_uid.has_value())) { - packages_for_uid.emplace_back(std::move(package_for_uid.value())); - } else { - fail_fn("Null string found in managed packages_for_uid."); - } - } - } - - std::vector<std::string> visible_vol_ids; - if (managed_visible_vol_ids != nullptr) { - jsize count = env->GetArrayLength(managed_visible_vol_ids); - for (jsize vol_id_index = 0; vol_id_index < count; ++vol_id_index) { - jstring managed_visible_vol_id = - (jstring) env->GetObjectArrayElement(managed_visible_vol_ids, vol_id_index); - - auto visible_vol_id = extract_fn(managed_visible_vol_id); - if (LIKELY(visible_vol_id.has_value())) { - visible_vol_ids.emplace_back(std::move(visible_vol_id.value())); - } else { - fail_fn("Null string found in managed visible_vol_ids."); - } - } - } + std::vector<std::string> visible_vol_ids = + ExtractJStringArray(env, process_name, managed_nice_name, managed_visible_vol_ids). + value_or(std::vector<std::string>()); - if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg, - package_name.value(), packages_for_uid, visible_vol_ids)) { - ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno)); - if (errno == ENOTCONN || errno == EROFS) { - // When device is actively encrypting, we get ENOTCONN here - // since FUSE was mounted before the framework restarted. - // When encrypted device is booting, we get EROFS since - // FUSE hasn't been created yet by init. - // In either case, continue without external storage. - } else { - fail_fn(error_msg); - } - } + MountEmulatedStorage(uid, mount_external, use_native_bridge, package_name.value(), + packages_for_uid, visible_vol_ids, fail_fn); // If this zygote isn't root, it won't be able to create a process group, // since the directory is owned by root. if (!is_system_server && getuid() == 0) { - int rc = createProcessGroup(uid, getpid()); + const int rc = createProcessGroup(uid, getpid()); if (rc == -EROFS) { ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?"); } else if (rc != 0) { @@ -991,13 +1174,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - if (!SetGids(env, gids, &error_msg)) { - fail_fn(error_msg); - } - - if (!SetRLimits(env, rlimits, &error_msg)) { - fail_fn(error_msg); - } + SetGids(env, gids, fail_fn); + SetRLimits(env, rlimits, fail_fn); if (use_native_bridge) { // Due to the logic behind use_native_bridge we know that both app_data_dir @@ -1056,14 +1234,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - if (!SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, - &error_msg)) { - fail_fn(error_msg); - } + SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn); - if (!SetSchedulerPolicy(&error_msg)) { - fail_fn(error_msg); - } + SetSchedulerPolicy(fail_fn); const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr; const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr; @@ -1076,7 +1249,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, // Make it easier to debug audit logs by setting the main thread's name to the // nice name rather than "app_process". if (nice_name.has_value()) { - SetThreadName(nice_name.value().c_str()); + SetThreadName(nice_name.value()); } else if (is_system_server) { SetThreadName("system_server"); } @@ -1089,6 +1262,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, if (env->ExceptionCheck()) { fail_fn("Error calling post fork system server hooks."); } + // TODO(oth): Remove hardcoded label here (b/117874058). static const char* kSystemServerLabel = "u:r:system_server:s0"; if (selinux_android_setcon(kSystemServerLabel) != 0) { @@ -1192,6 +1366,74 @@ static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gi return capabilities & GetEffectiveCapabilityMask(env); } + +/** + * Adds the given information about a newly created blastula to the Zygote's + * blastula table. + * + * @param blastula_pid Process ID of the newly created blastula + * @param read_pipe_fd File descriptor for the read end of the blastula + * reporting pipe. Used in the ZygoteServer poll loop to track blastula + * specialization. + */ +static void AddBlastulaTableEntry(pid_t blastula_pid, int read_pipe_fd) { + static int sBlastulaTableInsertIndex = 0; + + int search_index = sBlastulaTableInsertIndex; + + do { + if (gBlastulaTable[search_index].SetIfInvalid(blastula_pid, read_pipe_fd)) { + // Start our next search right after where we finished this one. + sBlastulaTableInsertIndex = (search_index + 1) % gBlastulaTable.size(); + + return; + } + + search_index = (search_index + 1) % gBlastulaTable.size(); + } while (search_index != sBlastulaTableInsertIndex); + + // Much like money in the banana stand, there should always be an entry + // in the blastula table. + __builtin_unreachable(); +} + +/** + * Invalidates the entry in the BlastulaTable corresponding to the provided + * process ID if it is present. If an entry was removed the blastula pool + * count is decremented. + * + * @param blastula_pid Process ID of the blastula entry to invalidate + * @return True if an entry was invalidated; false otherwise + */ +static bool RemoveBlastulaTableEntry(pid_t blastula_pid) { + for (BlastulaTableEntry& entry : gBlastulaTable) { + if (entry.ClearForPID(blastula_pid)) { + --gBlastulaPoolCount; + return true; + } + } + + return false; +} + +/** + * @return A vector of the read pipe FDs for each of the active blastulas. + */ +std::vector<int> MakeBlastulaPipeReadFDVector() { + std::vector<int> fd_vec; + fd_vec.reserve(gBlastulaTable.size()); + + for (BlastulaTableEntry& entry : gBlastulaTable) { + auto entry_values = entry.GetValues(); + + if (entry_values.has_value()) { + fd_vec.push_back(entry_values.value().read_pipe_fd); + } + } + + return fd_vec; +} + } // anonymous namespace namespace android { @@ -1210,12 +1452,35 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, - jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, + jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jstring package_name, jobjectArray packages_for_uid, jobjectArray visible_vol_ids) { jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); + if (UNLIKELY(managed_fds_to_close == nullptr)) { + ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector."); + } + + std::vector<int> fds_to_close = + ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_close).value(); + std::vector<int> fds_to_ignore = + ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_ignore) + .value_or(std::vector<int>()); + + std::vector<int> blastula_pipes = MakeBlastulaPipeReadFDVector(); + + fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end()); + fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end()); + +// fds_to_close.push_back(gBlastulaPoolSocketFD); + + if (gBlastulaPoolEventFD != -1) { + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + } + pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore); + if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, @@ -1230,9 +1495,19 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { + std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()), + fds_to_ignore(fds_to_close); + +// fds_to_close.push_back(gBlastulaPoolSocketFD); + + if (gBlastulaPoolEventFD != -1) { + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + } + pid_t pid = ForkCommon(env, true, - /* managed_fds_to_close= */ nullptr, - /* managed_fds_to_ignore= */ nullptr); + fds_to_close, + fds_to_ignore); if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities, @@ -1266,6 +1541,52 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( return pid; } +/** + * A JNI function that forks a blastula from the Zygote while ensuring proper + * file descriptor hygiene. + * + * @param env Managed runtime environment + * @param read_pipe_fd The read FD for the blastula reporting pipe. Manually closed by blastlas + * in managed code. + * @param write_pipe_fd The write FD for the blastula reporting pipe. Manually closed by the + * zygote in managed code. + * @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by + * the FD hygiene code and automatically "closed" in the new blastula. + * @return + */ +static jint com_android_internal_os_Zygote_nativeForkBlastula(JNIEnv* env, jclass, + jint read_pipe_fd, jint write_pipe_fd, jintArray managed_session_socket_fds) { + std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()), + fds_to_ignore(fds_to_close); + + std::vector<int> session_socket_fds = + ExtractJIntArray(env, "blastula", nullptr, managed_session_socket_fds) + .value_or(std::vector<int>()); + + // The Blastula Pool Event FD is created during the initialization of the + // blastula pool and should always be valid here. + + fds_to_close.push_back(gZygoteSocketFD); + fds_to_close.push_back(gBlastulaPoolEventFD); + fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end()); + + fds_to_ignore.push_back(gZygoteSocketFD); + fds_to_ignore.push_back(gBlastulaPoolSocketFD); + fds_to_ignore.push_back(gBlastulaPoolEventFD); + fds_to_ignore.push_back(read_pipe_fd); + fds_to_ignore.push_back(write_pipe_fd); + fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end()); + + pid_t blastula_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore); + + if (blastula_pid != 0) { + ++gBlastulaPoolCount; + AddBlastulaTableEntry(blastula_pid, read_pipe_fd); + } + + return blastula_pid; +} + static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( JNIEnv* env, jclass, jstring path) { ScopedUtfChars path_native(env, path); @@ -1321,14 +1642,125 @@ static void com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter( return; } - // TODO(b/111434506) install the filter - - /* bool installed = install_setuidgid_seccomp_filter(uidGidMin, uidGidMax); if (!installed) { RuntimeAbort(env, __LINE__, "Could not install setuid/setgid seccomp filter."); } - */ +} + +/** + * Called from a blastula to specialize the process for a specific application. + * + * @param env Managed runtime environment + * @param uid User ID of the new application + * @param gid Group ID of the new application + * @param gids Extra groups that the process belongs to + * @param runtime_flags Flags for changing the behavior of the managed runtime + * @param rlimits Resource limits + * @param mount_external The mode (read/write/normal) that external storage will be mounted with + * @param se_info SELinux policy information + * @param nice_name New name for this process + * @param is_child_zygote If the process is to become a WebViewZygote + * @param instruction_set The instruction set expected/requested by the new application + * @param app_data_dir Path to the application's data directory + */ +static void com_android_internal_os_Zygote_nativeSpecializeBlastula( + JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, + jint runtime_flags, jobjectArray rlimits, + jint mount_external, jstring se_info, jstring nice_name, + jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, + jstring package_name, jobjectArray packages_for_uid, jobjectArray visible_vol_ids) { + jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); + + SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, + capabilities, capabilities, + mount_external, se_info, nice_name, false, + is_child_zygote == JNI_TRUE, instruction_set, app_data_dir, + package_name, packages_for_uid, visible_vol_ids); +} + +/** + * A helper method for fetching socket file descriptors that were opened by init from the + * environment. + * + * @param env Managed runtime environment + * @param is_primary If this process is the primary or secondary Zygote; used to compute the name + * of the environment variable storing the file descriptors. + */ +static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass, + jboolean is_primary) { + std::string android_socket_prefix(ANDROID_SOCKET_PREFIX); + std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary"); + char* env_var_val = getenv(env_var_name.c_str()); + + if (env_var_val != nullptr) { + gZygoteSocketFD = atoi(env_var_val); + ALOGV("Zygote:zygoteSocketFD = %d", gZygoteSocketFD); + } else { + ALOGE("Unable to fetch Zygote socket file descriptor"); + } + + env_var_name = android_socket_prefix + (is_primary ? "blastula_pool" : "blastula_pool_secondary"); + env_var_val = getenv(env_var_name.c_str()); + + if (env_var_val != nullptr) { + gBlastulaPoolSocketFD = atoi(env_var_val); + ALOGV("Zygote:blastulaPoolSocketFD = %d", gBlastulaPoolSocketFD); + } else { + ALOGE("Unable to fetch Blastula pool socket file descriptor"); + } +} + +/** + * @param env Managed runtime environment + * @return A managed array of raw file descriptors for the read ends of the blastula reporting + * pipes. + */ +static jintArray com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs(JNIEnv* env, jclass) { + std::vector<int> blastula_fds = MakeBlastulaPipeReadFDVector(); + + jintArray managed_blastula_fds = env->NewIntArray(blastula_fds.size()); + env->SetIntArrayRegion(managed_blastula_fds, 0, blastula_fds.size(), blastula_fds.data()); + + return managed_blastula_fds; +} + +/** + * A JNI wrapper around RemoveBlastulaTableEntry. + * + * @param env Managed runtime environment + * @param blastula_pid Process ID of the blastula entry to invalidate + * @return True if an entry was invalidated; false otherwise. + */ +static jboolean com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry(JNIEnv* env, jclass, + jint blastula_pid) { + return RemoveBlastulaTableEntry(blastula_pid); +} + +/** + * Creates the blastula pool event FD if it doesn't exist and returns it. This is used by the + * ZygoteServer poll loop to know when to re-fill the blastula pool. + * + * @param env Managed runtime environment + * @return A raw event file descriptor used to communicate (from the signal handler) when the + * Zygote receives a SIGCHLD for a blastula + */ +static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD(JNIEnv* env, jclass) { + if (gBlastulaPoolEventFD == -1) { + if ((gBlastulaPoolEventFD = eventfd(0, 0)) == -1) { + ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno))); + } + } + + return gBlastulaPoolEventFD; +} + +/** + * @param env Managed runtime environment + * @return The number of blastulas currently in the blastula pool + */ +static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* env, jclass) { + return gBlastulaPoolCount; } static const JNINativeMethod gMethods[] = { @@ -1346,7 +1778,22 @@ static const JNINativeMethod gMethods[] = { { "nativePreApplicationInit", "()V", (void *) com_android_internal_os_Zygote_nativePreApplicationInit }, { "nativeInstallSeccompUidGidFilter", "(II)V", - (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter } + (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter }, + { "nativeForkBlastula", "(II[I)I", + (void *) com_android_internal_os_Zygote_nativeForkBlastula }, + { "nativeSpecializeBlastula", + "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V", + (void *) com_android_internal_os_Zygote_nativeSpecializeBlastula }, + { "nativeGetSocketFDs", "(Z)V", + (void *) com_android_internal_os_Zygote_nativeGetSocketFDs }, + { "nativeGetBlastulaPipeFDs", "()[I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs }, + { "nativeRemoveBlastulaTableEntry", "(I)Z", + (void *) com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry }, + { "nativeGetBlastulaPoolEventFD", "()I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD }, + { "nativeGetBlastulaPoolCount", "()I", + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount } }; int register_com_android_internal_os_Zygote(JNIEnv* env) { diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index d60d1a637962..53dde80edd89 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -38,6 +38,8 @@ static const char* kPathWhitelist[] = { "/dev/null", "/dev/socket/zygote", "/dev/socket/zygote_secondary", + "/dev/socket/blastula_pool", + "/dev/socket/blastula_pool_secondary", "/dev/socket/webview_zygote", "/dev/socket/heapprofd", "/sys/kernel/debug/tracing/trace_marker", @@ -134,15 +136,14 @@ FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr; // open zygote file descriptor. class FileDescriptorInfo { public: - // Create a FileDescriptorInfo for a given file descriptor. Returns - // |NULL| if an error occurred. - static FileDescriptorInfo* CreateFromFd(int fd, std::string* error_msg); + // Create a FileDescriptorInfo for a given file descriptor. + static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn); // Checks whether the file descriptor associated with this object // refers to the same description. - bool Restat() const; + bool RefersToSameFile() const; - bool ReopenOrDetach(std::string* error_msg) const; + void ReopenOrDetach(fail_fn_t fail_fn) const; const int fd; const struct stat stat; @@ -168,19 +169,18 @@ class FileDescriptorInfo { // address). static bool GetSocketName(const int fd, std::string* result); - bool DetachSocket(std::string* error_msg) const; + void DetachSocket(fail_fn_t fail_fn) const; DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); }; // static -FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_msg) { +FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) { struct stat f_stat; // This should never happen; the zygote should always have the right set // of permissions required to stat all its open files. if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { - *error_msg = android::base::StringPrintf("Unable to stat %d", fd); - return nullptr; + fail_fn(android::base::StringPrintf("Unable to stat %d", fd)); } const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get(); @@ -188,15 +188,13 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ if (S_ISSOCK(f_stat.st_mode)) { std::string socket_name; if (!GetSocketName(fd, &socket_name)) { - *error_msg = "Unable to get socket name"; - return nullptr; + fail_fn("Unable to get socket name"); } if (!whitelist->IsAllowed(socket_name)) { - *error_msg = android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)", - socket_name.c_str(), - fd); - return nullptr; + fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)", + socket_name.c_str(), + fd)); } return new FileDescriptorInfo(fd); @@ -209,26 +207,35 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). // S_ISLINK : Not supported. // S_ISBLK : Not supported. - // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate - // with the child process across forks but those should have been closed - // before we got to this point. + // S_ISFIFO : Not supported. Note that the Zygote and blastulas use pipes to + // communicate with the child processes across forks but those should have been + // added to the redirection exemption list. if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { - *error_msg = android::base::StringPrintf("Unsupported st_mode %u", f_stat.st_mode); - return nullptr; + std::string mode = "Unknown"; + + if (S_ISDIR(f_stat.st_mode)) { + mode = "DIR"; + } else if (S_ISLNK(f_stat.st_mode)) { + mode = "LINK"; + } else if (S_ISBLK(f_stat.st_mode)) { + mode = "BLOCK"; + } else if (S_ISFIFO(f_stat.st_mode)) { + mode = "FIFO"; + } + + fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d: %s", fd, mode.c_str())); } std::string file_path; const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd); if (!android::base::Readlink(fd_path, &file_path)) { - *error_msg = android::base::StringPrintf("Could not read fd link %s: %s", - fd_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Could not read fd link %s: %s", + fd_path.c_str(), + strerror(errno))); } if (!whitelist->IsAllowed(file_path)) { - *error_msg = std::string("Not whitelisted : ").append(file_path); - return nullptr; + fail_fn(std::string("Not whitelisted : ").append(file_path)); } // File descriptor flags : currently on FD_CLOEXEC. We can set these @@ -236,11 +243,10 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // there won't be any races. const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); if (fd_flags == -1) { - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s", - fd, - file_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s", + fd, + file_path.c_str(), + strerror(errno))); } // File status flags : @@ -257,11 +263,10 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ // their presence and pass them in to open(). int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); if (fs_flags == -1) { - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s", - fd, - file_path.c_str(), - strerror(errno)); - return nullptr; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s", + fd, + file_path.c_str(), + strerror(errno))); } // File offset : Ignore the offset for non seekable files. @@ -276,7 +281,7 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_ return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); } -bool FileDescriptorInfo::Restat() const { +bool FileDescriptorInfo::RefersToSameFile() const { struct stat f_stat; if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { PLOG(ERROR) << "Unable to restat fd " << fd; @@ -286,9 +291,9 @@ bool FileDescriptorInfo::Restat() const { return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev; } -bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const { +void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const { if (is_sock) { - return DetachSocket(error_msg); + return DetachSocket(fail_fn); } // NOTE: This might happen if the file was unlinked after being opened. @@ -297,57 +302,50 @@ bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const { const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); if (new_fd == -1) { - *error_msg = android::base::StringPrintf("Failed open(%s, %i): %s", - file_path.c_str(), - open_flags, - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s", + file_path.c_str(), + open_flags, + strerror(errno))); } if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s", - new_fd, - fd_flags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s", + new_fd, + fd_flags, + file_path.c_str(), + strerror(errno))); } if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s", - new_fd, - fs_flags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s", + new_fd, + fs_flags, + file_path.c_str(), + strerror(errno))); } if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s", - new_fd, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s", + new_fd, + file_path.c_str(), + strerror(errno))); } - int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0; - if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) { + int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0; + if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) { close(new_fd); - *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s", - fd, - new_fd, - dupFlags, - file_path.c_str(), - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s", + fd, + new_fd, + dup_flags, + file_path.c_str(), + strerror(errno))); } close(new_fd); - - return true; } FileDescriptorInfo::FileDescriptorInfo(int fd) : @@ -373,7 +371,6 @@ FileDescriptorInfo::FileDescriptorInfo(struct stat stat, const std::string& file is_sock(false) { } -// static bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { sockaddr_storage ss; sockaddr* addr = reinterpret_cast<sockaddr*>(&ss); @@ -417,86 +414,75 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { return true; } -bool FileDescriptorInfo::DetachSocket(std::string* error_msg) const { +void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const { const int dev_null_fd = open("/dev/null", O_RDWR); if (dev_null_fd < 0) { - *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno)); - return false; + fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno))); } if (dup2(dev_null_fd, fd) == -1) { - *error_msg = android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s", - fd, - strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s", + fd, + strerror(errno))); } if (close(dev_null_fd) == -1) { - *error_msg = android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)); - return false; + fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno))); } - - return true; } // static FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore, - std::string* error_msg) { - DIR* d = opendir(kFdPath); - if (d == nullptr) { - *error_msg = std::string("Unable to open directory ").append(kFdPath); - return nullptr; + fail_fn_t fail_fn) { + DIR* proc_fd_dir = opendir(kFdPath); + if (proc_fd_dir == nullptr) { + fail_fn(std::string("Unable to open directory ").append(kFdPath)); } - int dir_fd = dirfd(d); - dirent* e; + + int dir_fd = dirfd(proc_fd_dir); + dirent* dir_entry; std::unordered_map<int, FileDescriptorInfo*> open_fd_map; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); + while ((dir_entry = readdir(proc_fd_dir)) != nullptr) { + const int fd = ParseFd(dir_entry, dir_fd); if (fd == -1) { continue; } + if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) { LOG(INFO) << "Ignoring open file descriptor " << fd; continue; } - FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg); - if (info == NULL) { - if (closedir(d) == -1) { - PLOG(ERROR) << "Unable to close directory"; - } - return NULL; - } - open_fd_map[fd] = info; + open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn); } - if (closedir(d) == -1) { - *error_msg = "Unable to close directory"; - return nullptr; + if (closedir(proc_fd_dir) == -1) { + fail_fn("Unable to close directory"); } + return new FileDescriptorTable(open_fd_map); } -bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg) { +void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) { std::set<int> open_fds; // First get the list of open descriptors. - DIR* d = opendir(kFdPath); - if (d == NULL) { - *error_msg = android::base::StringPrintf("Unable to open directory %s: %s", - kFdPath, - strerror(errno)); - return false; + DIR* proc_fd_dir = opendir(kFdPath); + if (proc_fd_dir == nullptr) { + fail_fn(android::base::StringPrintf("Unable to open directory %s: %s", + kFdPath, + strerror(errno))); } - int dir_fd = dirfd(d); - dirent* e; - while ((e = readdir(d)) != NULL) { - const int fd = ParseFd(e, dir_fd); + int dir_fd = dirfd(proc_fd_dir); + dirent* dir_entry; + while ((dir_entry = readdir(proc_fd_dir)) != nullptr) { + const int fd = ParseFd(dir_entry, dir_fd); if (fd == -1) { continue; } + if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) { LOG(INFO) << "Ignoring open file descriptor " << fd; continue; @@ -505,27 +491,24 @@ bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::str open_fds.insert(fd); } - if (closedir(d) == -1) { - *error_msg = android::base::StringPrintf("Unable to close directory: %s", strerror(errno)); - return false; + if (closedir(proc_fd_dir) == -1) { + fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno))); } - return RestatInternal(open_fds, error_msg); + RestatInternal(open_fds, fail_fn); } -// Reopens all file descriptors that are contained in the table. Returns true -// if all descriptors were successfully re-opened or detached, and false if an -// error occurred. -bool FileDescriptorTable::ReopenOrDetach(std::string* error_msg) { +// Reopens all file descriptors that are contained in the table. +void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) { std::unordered_map<int, FileDescriptorInfo*>::const_iterator it; for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { const FileDescriptorInfo* info = it->second; - if (info == NULL || !info->ReopenOrDetach(error_msg)) { - return false; + if (info == nullptr) { + return; + } else { + info->ReopenOrDetach(fail_fn); } } - - return true; } FileDescriptorTable::FileDescriptorTable( @@ -533,9 +516,7 @@ FileDescriptorTable::FileDescriptorTable( : open_fd_map_(map) { } -bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* error_msg) { - bool error = false; - +void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) { // Iterate through the list of file descriptors we've already recorded // and check whether : // @@ -558,28 +539,18 @@ bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* e } else { // The entry from the file descriptor table is still open. Restat // it and check whether it refers to the same file. - const bool same_file = it->second->Restat(); - if (!same_file) { + if (!it->second->RefersToSameFile()) { // The file descriptor refers to a different description. We must // update our entry in the table. delete it->second; - it->second = FileDescriptorInfo::CreateFromFd(*element, error_msg); - if (it->second == NULL) { - // The descriptor no longer no longer refers to a whitelisted file. - // We flag an error and remove it from the list of files we're - // tracking. - error = true; - it = open_fd_map_.erase(it); - } else { - // Successfully restatted the file, move on to the next open FD. - ++it; - } + it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn); } else { // It's the same file. Nothing to do here. Move on to the next open // FD. - ++it; } + ++it; + // Finally, remove the FD from the set of open_fds. We do this last because // |element| will not remain valid after a call to erase. open_fds.erase(element); @@ -598,25 +569,15 @@ bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* e std::set<int>::const_iterator it; for (it = open_fds.begin(); it != open_fds.end(); ++it) { const int fd = (*it); - FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg); - if (info == NULL) { - // A newly opened file is not on the whitelist. Flag an error and - // continue. - error = true; - } else { - // Track the newly opened file. - open_fd_map_[fd] = info; - } + open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn); } } - - return !error; } // static -int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) { +int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) { char* end; - const int fd = strtol(e->d_name, &end, 10); + const int fd = strtol(dir_entry->d_name, &end, 10); if ((*end) != '\0') { return -1; } diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h index 09022a2e2408..2caf1575981a 100644 --- a/core/jni/fd_utils.h +++ b/core/jni/fd_utils.h @@ -30,6 +30,9 @@ class FileDescriptorInfo; +// This type is duplicated in com_android_internal_os_Zygote.cpp +typedef const std::function<void(std::string)>& fail_fn_t; + // Whitelist of open paths that the zygote is allowed to keep open. // // In addition to the paths listed in kPathWhitelist in file_utils.cpp, and @@ -76,19 +79,19 @@ class FileDescriptorTable { // /proc/self/fd for the list of open file descriptors and collects // information about them. Returns NULL if an error occurs. static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore, - std::string* error_msg); + fail_fn_t fail_fn); - bool Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg); + void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn); // Reopens all file descriptors that are contained in the table. Returns true // if all descriptors were successfully re-opened or detached, and false if an // error occurred. - bool ReopenOrDetach(std::string* error_msg); + void ReopenOrDetach(fail_fn_t fail_fn); private: explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map); - bool RestatInternal(std::set<int>& open_fds, std::string* error_msg); + void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn); static int ParseFd(dirent* e, int dir_fd); diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 7f3ea7a249ba..e68f9dbbc9b7 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -221,6 +221,8 @@ message ConstantsProto { optional bool use_heartbeats = 23; message TimeController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't // ready now. optional bool skip_not_ready_jobs = 1; @@ -228,6 +230,8 @@ message ConstantsProto { optional TimeController time_controller = 25; message QuotaController { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + // How much time each app will have to run jobs within their standby bucket window. optional int64 allowed_time_per_period_ms = 1; // How much time the package should have before transitioning from out-of-quota to in-quota. @@ -251,6 +255,21 @@ message ConstantsProto { optional int64 rare_window_size_ms = 6; // The maximum amount of time an app can have its jobs running within a 24 hour window. optional int64 max_execution_time_ms = 7; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_active = 8; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_working = 9; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_frequent = 10; + // The maximum number of jobs an app can run within this particular standby bucket's + // window size. + optional int32 max_job_count_rare = 11; + // The maximum number of jobs that should be allowed to run in the past + // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}. + optional int32 max_job_count_per_allowed_time = 12; } optional QuotaController quota_controller = 24; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ea0c8e250fe2..96b8dc222e7e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1748,6 +1748,10 @@ <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" android:protectionLevel="signature" /> + <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. --> + <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" + android:protectionLevel="signature" /> + <!-- ================================== --> <!-- Permissions for accessing accounts --> <!-- ================================== --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5995640c7659..16d1d1a9b90a 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -381,7 +381,7 @@ <!-- Configuration of Ethernet interfaces in the following format: - <interface name|mac address>;[Network Capabilities];[IP config] + <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport] Where [Network Capabilities] Optional. A comma seprated list of network capabilities. Values must be from NetworkCapabilities#NET_CAPABILITIES_* constants. @@ -389,11 +389,16 @@ use the following format to specify static IP configuration: ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses> domains=<comma-sep-domains> + [Override Transport] Optional. An override network transport type to allow + the propagation of an interface type on the other end of a local Ethernet + interface. Value must be from NetworkCapabilities#TRANSPORT_* constants. If + left out, this will default to TRANSPORT_ETHERNET. --> <string-array translatable="false" name="config_ethernet_interfaces"> <!-- <item>eth1;12,13,14,15;ip=192.168.0.10/24 gateway=192.168.0.1 dns=4.4.4.4,8.8.8.8</item> <item>eth2;;ip=192.168.0.11/24</item> + <item>eth3;12,13,14,15;ip=192.168.0.12/24;1</item> --> </string-array> @@ -705,6 +710,9 @@ Software implementation will be used if config_hardware_auto_brightness_available is not set --> <bool name="config_automatic_brightness_available">false</bool> + <!-- Flag indicating whether we should enable the adaptive sleep.--> + <bool name="config_adaptive_sleep_available">false</bool> + <!-- Flag indicating whether we should enable smart battery. --> <bool name="config_smart_battery_available">false</bool> @@ -931,6 +939,10 @@ in hardware. --> <bool name="config_setColorTransformAccelerated">false</bool> + <!-- Boolean indicating whether the HWC setColorTransform function can be performed efficiently + in hardware for individual layers. --> + <bool name="config_setColorTransformAcceleratedPerLayer">false</bool> + <!-- Control whether Night display is available. This should only be enabled on devices that have a HWC implementation that can apply the matrix passed to setColorTransform without impacting power, performance, and app compatibility (e.g. protected content). --> @@ -1743,6 +1755,19 @@ config_enableGeofenceOverlay is false. --> <string name="config_geofenceProviderPackageName" translatable="false">@null</string> + <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware + Activity-Recognition to be replaced by an app at run-time. When disabled, only the + config_activityRecognitionHardwarePackageName package will be searched for + its implementation, otherwise packages whose signature matches the + signatures of config_locationProviderPackageNames will be searched, and + the service with the highest version number will be picked. Anyone who + wants to disable the overlay mechanism can set it to false. + --> + <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool> + <!-- Package name providing Hardware Activity-Recognition API support. Used only when + config_enableActivityRecognitionHardwareOverlay is false. --> + <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string> + <!-- Package name(s) containing location provider support. These packages can contain services implementing location providers, such as the Geocode Provider, Network Location Provider, and @@ -3728,4 +3753,7 @@ <!-- Enable Zram writeback feature to allow unused pages in zram be written to flash. --> <bool name="config_zramWriteback">false</bool> + + <!-- Whether cbrs is supported on the device or not --> + <bool translatable="false" name="config_cbrs_supported">false</bool> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 0878562e00fb..f2b4b9ccfce8 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3353,9 +3353,9 @@ <string name="wifi_available_action_all_networks">All networks</string> <!-- Notification title for a connection to a app suggested wireless network.--> - <string name="wifi_suggestion_title">Connected to Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g></string> + <string name="wifi_suggestion_title">A Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g> is available</string> <!-- Notification content for a connection to a app suggested wireless network.--> - <string name="wifi_suggestion_content">Do you want to let <xliff:g id="name" example="App123">%s</xliff:g> propose networks for you?</string> + <string name="wifi_suggestion_content">Do you want to connect to networks proposed by <xliff:g id="name" example="App123">%s</xliff:g>?</string> <!-- Notification action for allowing app specified in the notification body.--> <string name="wifi_suggestion_action_allow_app">Yes</string> <!-- Notification action for disallowing app specified in the notification body.--> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 53c33a3855dd..751721860441 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1845,6 +1845,7 @@ <java-symbol type="array" name="config_defaultNotificationVibePattern" /> <java-symbol type="array" name="config_notificationFallbackVibePattern" /> <java-symbol type="bool" name="config_useAttentionLight" /> + <java-symbol type="bool" name="config_adaptive_sleep_available" /> <java-symbol type="bool" name="config_animateScreenLights" /> <java-symbol type="bool" name="config_automatic_brightness_available" /> <java-symbol type="bool" name="config_smart_battery_available" /> @@ -1853,6 +1854,7 @@ <java-symbol type="bool" name="config_enableNightMode" /> <java-symbol type="bool" name="config_tintNotificationActionButtons" /> <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" /> + <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> <java-symbol type="bool" name="config_enableHardwareFlpOverlay" /> <java-symbol type="bool" name="config_enableGeocoderOverlay" /> @@ -2021,6 +2023,7 @@ <java-symbol type="string" name="car_mode_disable_notification_title" /> <java-symbol type="string" name="chooser_wallpaper" /> <java-symbol type="string" name="config_datause_iface" /> + <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" /> <java-symbol type="string" name="config_fusedLocationProviderPackageName" /> <java-symbol type="string" name="config_hardwareFlpPackageName" /> <java-symbol type="string" name="config_geocoderProviderPackageName" /> @@ -3031,6 +3034,7 @@ <java-symbol type="drawable" name="ic_doc_generic" /> <java-symbol type="bool" name="config_setColorTransformAccelerated" /> + <java-symbol type="bool" name="config_setColorTransformAcceleratedPerLayer" /> <java-symbol type="bool" name="config_displayWhiteBalanceAvailable" /> <java-symbol type="bool" name="config_nightDisplayAvailable" /> <java-symbol type="bool" name="config_allowDisablingAssistDisclosure" /> @@ -3540,4 +3544,7 @@ <java-symbol type="bool" name="config_silenceSensorAvailable" /> <java-symbol type="bool" name="config_zramWriteback" /> + + <!-- For CBRS --> + <java-symbol type="bool" name="config_cbrs_supported" /> </resources> diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java index 8d42c74be7b0..5731daa0b2a9 100644 --- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java @@ -20,12 +20,23 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; +import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality; +import static android.app.admin.PasswordMetrics.getActualRequiredQuality; +import static android.app.admin.PasswordMetrics.getMinimumMetrics; +import static android.app.admin.PasswordMetrics.getTargetQualityMetrics; +import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; -import android.app.admin.PasswordMetrics.PasswordComplexityBucket; import android.os.Parcel; import androidx.test.filters.SmallTest; @@ -109,7 +120,7 @@ public class PasswordMetricsTest { assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, PasswordMetrics.computeForPassword("1").quality); // contains a long sequence so isn't complex - assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, + assertEquals(PASSWORD_QUALITY_NUMERIC, PasswordMetrics.computeForPassword("1234").quality); assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, PasswordMetrics.computeForPassword("").quality); @@ -145,7 +156,7 @@ public class PasswordMetricsTest { new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5)); assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4), - new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 4)); + new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4)); metrics0 = PasswordMetrics.computeForPassword("1234abcd,./"); metrics1 = PasswordMetrics.computeForPassword("1234abcd,./"); @@ -176,9 +187,9 @@ public class PasswordMetricsTest { @Test public void testConstructQuality() { PasswordMetrics expected = new PasswordMetrics(); - expected.quality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; + expected.quality = PASSWORD_QUALITY_COMPLEX; - PasswordMetrics actual = new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX); + PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX); assertEquals(expected, actual); } @@ -256,42 +267,178 @@ public class PasswordMetricsTest { } @Test - public void testComplexityLevelToBucket_none() { - PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( - PASSWORD_COMPLEXITY_NONE).getMetrics(); + public void testSanitizeComplexityLevel_none() { + assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_NONE)); - for (PasswordMetrics metrics : bucket) { - assertEquals(PASSWORD_COMPLEXITY_NONE, metrics.determineComplexity()); - } } @Test - public void testComplexityLevelToBucket_low() { - PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( - PASSWORD_COMPLEXITY_LOW).getMetrics(); + public void testSanitizeComplexityLevel_low() { + assertEquals(PASSWORD_COMPLEXITY_LOW, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_LOW)); + } - for (PasswordMetrics metrics : bucket) { - assertEquals(PASSWORD_COMPLEXITY_LOW, metrics.determineComplexity()); - } + @Test + public void testSanitizeComplexityLevel_medium() { + assertEquals( + PASSWORD_COMPLEXITY_MEDIUM, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_MEDIUM)); } @Test - public void testComplexityLevelToBucket_medium() { - PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( - PASSWORD_COMPLEXITY_MEDIUM).getMetrics(); + public void testSanitizeComplexityLevel_high() { + assertEquals(PASSWORD_COMPLEXITY_HIGH, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_HIGH)); + } - for (PasswordMetrics metrics : bucket) { - assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metrics.determineComplexity()); - } + @Test + public void testSanitizeComplexityLevel_invalid() { + assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(-1)); } @Test - public void testComplexityLevelToBucket_high() { - PasswordMetrics[] bucket = PasswordComplexityBucket.complexityLevelToBucket( - PASSWORD_COMPLEXITY_HIGH).getMetrics(); + public void testComplexityLevelToMinQuality_none() { + assertEquals(PASSWORD_QUALITY_UNSPECIFIED, + complexityLevelToMinQuality(PASSWORD_COMPLEXITY_NONE)); + } - for (PasswordMetrics metrics : bucket) { - assertEquals(PASSWORD_COMPLEXITY_HIGH, metrics.determineComplexity()); - } + @Test + public void testComplexityLevelToMinQuality_low() { + assertEquals(PASSWORD_QUALITY_SOMETHING, + complexityLevelToMinQuality(PASSWORD_COMPLEXITY_LOW)); + } + + @Test + public void testComplexityLevelToMinQuality_medium() { + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, + complexityLevelToMinQuality(PASSWORD_COMPLEXITY_MEDIUM)); + } + + @Test + public void testComplexityLevelToMinQuality_high() { + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, + complexityLevelToMinQuality(PASSWORD_COMPLEXITY_HIGH)); + } + + @Test + public void testComplexityLevelToMinQuality_invalid() { + assertEquals(PASSWORD_QUALITY_UNSPECIFIED, complexityLevelToMinQuality(-1)); + } + + @Test + public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() { + PasswordMetrics metrics = + getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC); + + assertTrue(metrics.isDefault()); + } + + @Test + public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() { + PasswordMetrics metrics = + getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC); + + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality); + assertEquals(/* expected= */ 4, metrics.length); + } + + @Test + public void testGetTargetQualityMetrics_highComplexityNumericComplex() { + PasswordMetrics metrics = getTargetQualityMetrics( + PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX); + + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality); + assertEquals(/* expected= */ 8, metrics.length); + } + + @Test + public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() { + PasswordMetrics metrics = getTargetQualityMetrics( + PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC); + + assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality); + assertEquals(/* expected= */ 4, metrics.length); + } + + @Test + public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() { + PasswordMetrics metrics = getTargetQualityMetrics( + PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC); + + assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality); + assertEquals(/* expected= */ 4, metrics.length); + } + + @Test + public void testGetActualRequiredQuality_nonComplex() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_NUMERIC_COMPLEX, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual); + } + + @Test + public void testGetActualRequiredQuality_complexRequiresNone() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_COMPLEX, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual); + } + + @Test + public void testGetActualRequiredQuality_complexRequiresNumeric() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_COMPLEX, + /* requiresNumeric= */ true, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_NUMERIC, actual); + } + + @Test + public void testGetActualRequiredQuality_complexRequiresLetters() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_COMPLEX, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ true); + + assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual); + } + + @Test + public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() { + int actual = getActualRequiredQuality( + PASSWORD_QUALITY_COMPLEX, + /* requiresNumeric= */ true, + /* requiresLettersOrSymbols= */ true); + + assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual); + } + + @Test + public void testGetMinimumMetrics_userInputStricter() { + PasswordMetrics metrics = getMinimumMetrics( + PASSWORD_COMPLEXITY_HIGH, + PASSWORD_QUALITY_ALPHANUMERIC, + PASSWORD_QUALITY_NUMERIC, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality); + assertEquals(/* expected= */ 6, metrics.length); + } + + @Test + public void testGetMinimumMetrics_actualRequiredQualityStricter() { + PasswordMetrics metrics = getMinimumMetrics( + PASSWORD_COMPLEXITY_HIGH, + PASSWORD_QUALITY_UNSPECIFIED, + PASSWORD_QUALITY_NUMERIC, + /* requiresNumeric= */ false, + /* requiresLettersOrSymbols= */ false); + + assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality); + assertEquals(/* expected= */ 8, metrics.length); } } diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java index 425ab8962307..e94d60c32bfe 100644 --- a/core/tests/coretests/src/android/os/WorkSourceTest.java +++ b/core/tests/coretests/src/android/os/WorkSourceTest.java @@ -341,12 +341,37 @@ public class WorkSourceTest extends TestCase { } public void testGetAttributionId() { - WorkSource ws1 = new WorkSource(); - WorkChain wc = ws1.createWorkChain(); - wc.addNode(100, "tag"); - assertEquals(100, wc.getAttributionUid()); - wc.addNode(200, "tag2"); - assertEquals(100, wc.getAttributionUid()); + WorkSource ws = new WorkSource(); + WorkChain wc1 = ws.createWorkChain(); + wc1.addNode(100, "tag"); + assertEquals(100, wc1.getAttributionUid()); + assertEquals(100, ws.getAttributionUid()); + wc1.addNode(200, "tag2"); + assertEquals(100, wc1.getAttributionUid()); + assertEquals(100, ws.getAttributionUid()); + WorkChain wc2 = ws.createWorkChain(); + wc2.addNode(300, "tag3"); + assertEquals(300, wc2.getAttributionUid()); + assertEquals(100, ws.getAttributionUid()); + } + + public void testGetAttributionIdWithoutWorkChain() { + WorkSource ws1 = new WorkSource(100); + ws1.add(200); + WorkSource ws2 = new WorkSource(); + ws2.add(100); + ws2.add(200); + assertEquals(100, ws1.getAttributionUid()); + assertEquals(100, ws2.getAttributionUid()); + } + + public void testGetAttributionWhenEmpty() { + WorkSource ws = new WorkSource(); + assertEquals(-1, ws.getAttributionUid()); + WorkChain wc = ws.createWorkChain(); + assertEquals(-1, ws.getAttributionUid()); + assertEquals(-1, wc.getAttributionUid()); + assertNull(wc.getAttributionTag()); } public void testGetAttributionTag() { diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 9857b7a1e035..2db2f5f2a4fd 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -60,7 +60,7 @@ public class InsetsStateTest { mState.getSource(TYPE_IME).setVisible(true); SparseIntArray typeSideMap = new SparseIntArray(); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, typeSideMap); + DisplayCutout.NO_CUTOUT, null, null, typeSideMap); assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all())); assertEquals(INSET_SIDE_TOP, typeSideMap.get(TYPE_TOP_BAR)); @@ -76,7 +76,7 @@ public class InsetsStateTest { mState.getSource(TYPE_IME).setFrame(new Rect(0, 100, 100, 300)); mState.getSource(TYPE_IME).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, null); + DisplayCutout.NO_CUTOUT, null, null, null); assertEquals(100, insets.getStableInsetBottom()); assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.all())); assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets()); @@ -92,7 +92,7 @@ public class InsetsStateTest { mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300)); mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, null); + DisplayCutout.NO_CUTOUT, null, null, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.sideBars())); @@ -106,7 +106,7 @@ public class InsetsStateTest { mState.getSource(TYPE_IME).setVisible(true); mState.removeSource(TYPE_IME); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, null); + DisplayCutout.NO_CUTOUT, null, null, null); assertEquals(0, insets.getSystemWindowInsetBottom()); } diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index 15a96a144dd5..d57fa8f9f612 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -18,7 +18,7 @@ package android.view; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.sideBars; - +import static android.view.WindowInsets.Type.topBar; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -54,6 +54,7 @@ public class WindowInsetsTest { assertTrue(new WindowInsets((Rect) null).isConsumed()); } + // TODO: Move this to CTS once API made public @Test public void typeMap() { Builder b = new WindowInsets.Builder(); @@ -62,4 +63,14 @@ public class WindowInsetsTest { WindowInsets insets = b.build(); assertEquals(300, insets.getSystemWindowInsets().bottom); } + + // TODO: Move this to CTS once API made public + @Test + public void compatInsets() { + Builder b = new WindowInsets.Builder(); + b.setSystemWindowInsets(Insets.of(0, 50, 30, 10)); + WindowInsets insets = b.build(); + assertEquals(Insets.of(0, 50, 0, 0), insets.getInsets(topBar())); + assertEquals(Insets.of(0, 0, 30, 10), insets.getInsets(sideBars())); + } } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 0dd768530603..683d16b1dea1 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -88,32 +88,32 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public void setOnKeyEventResult(boolean handled, int sequence) {} - public float getMagnificationScale() { + public float getMagnificationScale(int displayId) { return 0.0f; } - public float getMagnificationCenterX() { + public float getMagnificationCenterX(int displayId) { return 0.0f; } - public float getMagnificationCenterY() { + public float getMagnificationCenterY(int displayId) { return 0.0f; } - public Region getMagnificationRegion() { + public Region getMagnificationRegion(int displayId) { return null; } - public boolean resetMagnification(boolean animate) { + public boolean resetMagnification(int displayId, boolean animate) { return false; } - public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY, - boolean animate) { + public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, + float centerY, boolean animate) { return false; } - public void setMagnificationCallbackEnabled(boolean enabled) {} + public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {} public boolean setSoftKeyboardShowMode(int showMode) { return false; diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java index 0179eadae6a1..9cac7e794965 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java @@ -43,6 +43,10 @@ import android.view.Display; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; import com.android.internal.util.ArrayUtils; import org.junit.Before; @@ -76,13 +80,13 @@ import java.util.Arrays; @RunWith(AndroidJUnit4.class) public class BatteryStatsCpuTimesTest { @Mock - KernelUidCpuTimeReader mKernelUidCpuTimeReader; + KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader; @Mock - KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader; + KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader; @Mock - KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader; + KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader; @Mock - KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader; + KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader; @Mock BatteryStatsImpl.UserInfoProvider mUserInfoProvider; @Mock @@ -98,10 +102,10 @@ public class BatteryStatsCpuTimesTest { mClocks = new MockClocks(); mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks) - .setKernelUidCpuTimeReader(mKernelUidCpuTimeReader) - .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader) - .setKernelUidCpuActiveTimeReader(mKernelUidCpuActiveTimeReader) - .setKernelUidCpuClusterTimeReader(mKernelUidCpuClusterTimeReader) + .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader) + .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader) + .setKernelCpuUidActiveTimeReader(mCpuUidActiveTimeReader) + .setKernelCpuUidClusterTimeReader(mCpuUidClusterTimeReader) .setUserInfoProvider(mUserInfoProvider); } @@ -113,21 +117,21 @@ public class BatteryStatsCpuTimesTest { final int numClusters = 3; initKernelCpuSpeedReaders(numClusters); final long[] freqs = {1, 12, 123, 12, 1234}; - when(mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs); + when(mCpuUidFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs); // RUN mBatteryStatsImpl.updateCpuTimeLocked(false, false); // VERIFY assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs()); - verify(mKernelUidCpuTimeReader).readDelta(null); - verify(mKernelUidCpuFreqTimeReader).readDelta(null); + verify(mCpuUidUserSysTimeReader).readDelta(null); + verify(mCpuUidFreqTimeReader).readDelta(null); for (int i = 0; i < numClusters; ++i) { verify(mKernelCpuSpeedReaders[i]).readDelta(); } // Prepare for next test - Mockito.reset(mUserInfoProvider, mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader); + Mockito.reset(mUserInfoProvider, mCpuUidFreqTimeReader, mCpuUidUserSysTimeReader); for (int i = 0; i < numClusters; ++i) { Mockito.reset(mKernelCpuSpeedReaders[i]); } @@ -140,17 +144,18 @@ public class BatteryStatsCpuTimesTest { // VERIFY verify(mUserInfoProvider).refreshUserIds(); - verify(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class)); + verify(mCpuUidUserSysTimeReader).readDelta( + any(KernelCpuUidUserSysTimeReader.Callback.class)); // perClusterTimesAvailable is called twice, once in updateCpuTimeLocked() and the other // in readKernelUidCpuFreqTimesLocked. - verify(mKernelUidCpuFreqTimeReader, times(2)).perClusterTimesAvailable(); - verify(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); - verify(mKernelUidCpuActiveTimeReader).readDelta( - any(KernelUidCpuActiveTimeReader.Callback.class)); - verify(mKernelUidCpuClusterTimeReader).readDelta( - any(KernelUidCpuClusterTimeReader.Callback.class)); - verifyNoMoreInteractions(mKernelUidCpuFreqTimeReader); + verify(mCpuUidFreqTimeReader, times(2)).perClusterTimesAvailable(); + verify(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); + verify(mCpuUidActiveTimeReader).readDelta( + any(KernelCpuUidActiveTimeReader.Callback.class)); + verify(mCpuUidClusterTimeReader).readDelta( + any(KernelCpuUidClusterTimeReader.Callback.class)); + verifyNoMoreInteractions(mCpuUidFreqTimeReader); for (int i = 0; i < numClusters; ++i) { verify(mKernelCpuSpeedReaders[i]).readDelta(); } @@ -253,13 +258,14 @@ public class BatteryStatsCpuTimesTest { {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l} }; doAnswer(invocation -> { - final KernelUidCpuTimeReader.Callback callback = - (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidUserSysTimeReader.Callback<long[]> callback = + (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]); + callback.onUidCpuTime(testUids[i], uidTimesUs[i]); } return null; - }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class)); + }).when(mCpuUidUserSysTimeReader).readDelta( + any(KernelCpuUidUserSysTimeReader.Callback.class)); // RUN final SparseLongArray updatedUids = new SparseLongArray(); @@ -287,13 +293,14 @@ public class BatteryStatsCpuTimesTest { {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348} }; doAnswer(invocation -> { - final KernelUidCpuTimeReader.Callback callback = - (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidUserSysTimeReader.Callback<long[]> callback = + (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]); + callback.onUidCpuTime(testUids[i], deltasUs[i]); } return null; - }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class)); + }).when(mCpuUidUserSysTimeReader).readDelta( + any(KernelCpuUidUserSysTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true); @@ -326,13 +333,14 @@ public class BatteryStatsCpuTimesTest { {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l} }; doAnswer(invocation -> { - final KernelUidCpuTimeReader.Callback callback = - (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidUserSysTimeReader.Callback<long[]> callback = + (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]); + callback.onUidCpuTime(testUids[i], uidTimesUs[i]); } return null; - }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class)); + }).when(mCpuUidUserSysTimeReader).readDelta( + any(KernelCpuUidUserSysTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true); @@ -350,7 +358,7 @@ public class BatteryStatsCpuTimesTest { assertEquals("Unexpected system cpu time for uid=" + testUids[i], uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED)); } - verify(mKernelUidCpuTimeReader).removeUid(isolatedUid); + verify(mCpuUidUserSysTimeReader).removeUid(isolatedUid); // Add an isolated uid mapping and repeat the test. @@ -361,13 +369,14 @@ public class BatteryStatsCpuTimesTest { {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348} }; doAnswer(invocation -> { - final KernelUidCpuTimeReader.Callback callback = - (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidUserSysTimeReader.Callback<long[]> callback = + (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]); + callback.onUidCpuTime(testUids[i], deltasUs[i]); } return null; - }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class)); + }).when(mCpuUidUserSysTimeReader).readDelta( + any(KernelCpuUidUserSysTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true); @@ -414,15 +423,16 @@ public class BatteryStatsCpuTimesTest { {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l} }; doAnswer(invocation -> { - final KernelUidCpuTimeReader.Callback callback = - (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidUserSysTimeReader.Callback<long[]> callback = + (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]); + callback.onUidCpuTime(testUids[i], uidTimesUs[i]); } // And one for the invalid uid - callback.onUidCpuTime(invalidUid, 3879, 239); + callback.onUidCpuTime(invalidUid, new long[]{3879, 239}); return null; - }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class)); + }).when(mCpuUidUserSysTimeReader).readDelta( + any(KernelCpuUidUserSysTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null, true); @@ -438,7 +448,7 @@ public class BatteryStatsCpuTimesTest { } assertNull("There shouldn't be an entry for invalid uid=" + invalidUid, mBatteryStatsImpl.getUidStats().get(invalidUid)); - verify(mKernelUidCpuTimeReader).removeUid(invalidUid); + verify(mCpuUidUserSysTimeReader).removeUid(invalidUid); } @Test @@ -462,13 +472,14 @@ public class BatteryStatsCpuTimesTest { {12, 34}, {3394, 3123}, {7977, 80434} }; doAnswer(invocation -> { - final KernelUidCpuTimeReader.Callback callback = - (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidUserSysTimeReader.Callback<long[]> callback = + (KernelCpuUidUserSysTimeReader.Callback<long[]>) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]); + callback.onUidCpuTime(testUids[i], uidTimesUs[i]); } return null; - }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class)); + }).when(mCpuUidUserSysTimeReader).readDelta( + any(KernelCpuUidUserSysTimeReader.Callback.class)); // RUN final SparseLongArray updatedUids = new SparseLongArray(); @@ -541,14 +552,14 @@ public class BatteryStatsCpuTimesTest { {8, 25, 3, 0, 42} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false); @@ -574,14 +585,14 @@ public class BatteryStatsCpuTimesTest { {43, 3345, 2143, 123, 4554} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], deltasMs[i]); + callback.onUidCpuTime(testUids[i], deltasMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true); @@ -624,15 +635,15 @@ public class BatteryStatsCpuTimesTest { {8, 25, 3, 0, 42} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); - when(mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()).thenReturn(true); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); + when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false); @@ -668,14 +679,14 @@ public class BatteryStatsCpuTimesTest { {43, 3345, 2143, 123, 4554} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], deltasMs[i]); + callback.onUidCpuTime(testUids[i], deltasMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true); @@ -734,15 +745,15 @@ public class BatteryStatsCpuTimesTest { {8, 25, 3, 0, 42} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); - when(mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()).thenReturn(true); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); + when(mCpuUidFreqTimeReader.perClusterTimesAvailable()).thenReturn(true); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(partialTimers, true, false); @@ -824,14 +835,14 @@ public class BatteryStatsCpuTimesTest { {8, 25, 3, 0, 42} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false); @@ -857,14 +868,14 @@ public class BatteryStatsCpuTimesTest { {43, 3345, 2143, 123, 4554, 9374983794839l, 979875} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], deltasMs[i]); + callback.onUidCpuTime(testUids[i], deltasMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, true); @@ -901,14 +912,14 @@ public class BatteryStatsCpuTimesTest { {8, 25, 3, 0, 42} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false); @@ -927,7 +938,7 @@ public class BatteryStatsCpuTimesTest { assertNull("Unexpected screen-off cpu times for uid=" + testUids[i], u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED)); } - verify(mKernelUidCpuFreqTimeReader).removeUid(isolatedUid); + verify(mCpuUidFreqTimeReader).removeUid(isolatedUid); // Add an isolated uid mapping and repeat the test. @@ -941,14 +952,14 @@ public class BatteryStatsCpuTimesTest { {43, 3345, 2143, 123, 4554} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], deltasMs[i]); + callback.onUidCpuTime(testUids[i], deltasMs[i]); } return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false); @@ -996,16 +1007,16 @@ public class BatteryStatsCpuTimesTest { {8, 25, 3, 0, 42} }; doAnswer(invocation -> { - final KernelUidCpuFreqTimeReader.Callback callback = - (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidFreqTimeReader.Callback callback = + (KernelCpuUidFreqTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } // And one for the invalid uid - callback.onUidCpuFreqTime(invalidUid, new long[]{12, 839, 32, 34, 21}); + callback.onUidCpuTime(invalidUid, new long[]{12, 839, 32, 34, 21}); return null; - }).when(mKernelUidCpuFreqTimeReader).readDelta( - any(KernelUidCpuFreqTimeReader.Callback.class)); + }).when(mCpuUidFreqTimeReader).readDelta( + any(KernelCpuUidFreqTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(null, true, false); @@ -1022,7 +1033,7 @@ public class BatteryStatsCpuTimesTest { } assertNull("There shouldn't be an entry for invalid uid=" + invalidUid, mBatteryStatsImpl.getUidStats().get(invalidUid)); - verify(mKernelUidCpuFreqTimeReader).removeUid(invalidUid); + verify(mCpuUidFreqTimeReader).removeUid(invalidUid); } @Test @@ -1039,14 +1050,14 @@ public class BatteryStatsCpuTimesTest { }); final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000}; doAnswer(invocation -> { - final KernelUidCpuActiveTimeReader.Callback callback = - (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidActiveTimeReader.Callback callback = + (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuActiveTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } return null; - }).when(mKernelUidCpuActiveTimeReader).readDelta( - any(KernelUidCpuActiveTimeReader.Callback.class)); + }).when(mCpuUidActiveTimeReader).readDelta( + any(KernelCpuUidActiveTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true); @@ -1065,14 +1076,14 @@ public class BatteryStatsCpuTimesTest { updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); final long[] deltasMs = {43000, 3345000, 2143000, 123000, 4554000}; doAnswer(invocation -> { - final KernelUidCpuActiveTimeReader.Callback callback = - (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidActiveTimeReader.Callback callback = + (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuActiveTime(testUids[i], deltasMs[i]); + callback.onUidCpuTime(testUids[i], deltasMs[i]); } return null; - }).when(mKernelUidCpuActiveTimeReader).readDelta( - any(KernelUidCpuActiveTimeReader.Callback.class)); + }).when(mCpuUidActiveTimeReader).readDelta( + any(KernelCpuUidActiveTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true); @@ -1103,16 +1114,16 @@ public class BatteryStatsCpuTimesTest { }); final long[] uidTimesMs = {8000, 25000, 3000, 0, 42000}; doAnswer(invocation -> { - final KernelUidCpuActiveTimeReader.Callback callback = - (KernelUidCpuActiveTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidActiveTimeReader.Callback callback = + (KernelCpuUidActiveTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuActiveTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } // And one for the invalid uid - callback.onUidCpuActiveTime(invalidUid, 1200L); + callback.onUidCpuTime(invalidUid, 1200L); return null; - }).when(mKernelUidCpuActiveTimeReader).readDelta( - any(KernelUidCpuActiveTimeReader.Callback.class)); + }).when(mCpuUidActiveTimeReader).readDelta( + any(KernelCpuUidActiveTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuActiveTimesLocked(true); @@ -1126,7 +1137,7 @@ public class BatteryStatsCpuTimesTest { } assertNull("There shouldn't be an entry for invalid uid=" + invalidUid, mBatteryStatsImpl.getUidStats().get(invalidUid)); - verify(mKernelUidCpuActiveTimeReader).removeUid(invalidUid); + verify(mCpuUidActiveTimeReader).removeUid(invalidUid); } @Test @@ -1147,14 +1158,14 @@ public class BatteryStatsCpuTimesTest { {8000, 0} }; doAnswer(invocation -> { - final KernelUidCpuClusterTimeReader.Callback callback = - (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidClusterTimeReader.Callback callback = + (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuPolicyTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } return null; - }).when(mKernelUidCpuClusterTimeReader).readDelta( - any(KernelUidCpuClusterTimeReader.Callback.class)); + }).when(mCpuUidClusterTimeReader).readDelta( + any(KernelCpuUidClusterTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true); @@ -1177,14 +1188,14 @@ public class BatteryStatsCpuTimesTest { {43000, 3345000} }; doAnswer(invocation -> { - final KernelUidCpuClusterTimeReader.Callback callback = - (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidClusterTimeReader.Callback callback = + (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuPolicyTime(testUids[i], deltasMs[i]); + callback.onUidCpuTime(testUids[i], deltasMs[i]); } return null; - }).when(mKernelUidCpuClusterTimeReader).readDelta( - any(KernelUidCpuClusterTimeReader.Callback.class)); + }).when(mCpuUidClusterTimeReader).readDelta( + any(KernelCpuUidClusterTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true); @@ -1193,7 +1204,8 @@ public class BatteryStatsCpuTimesTest { for (int i = 0; i < testUids.length; ++i) { final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]); assertNotNull("No entry for uid=" + testUids[i], u); - assertArrayEquals("Unexpected cpu cluster time for uid=" + testUids[i], sum(uidTimesMs[i], deltasMs[i]), + assertArrayEquals("Unexpected cpu cluster time for uid=" + testUids[i], + sum(uidTimesMs[i], deltasMs[i]), u.getCpuClusterTimes()); } } @@ -1219,16 +1231,16 @@ public class BatteryStatsCpuTimesTest { {8000, 0} }; doAnswer(invocation -> { - final KernelUidCpuClusterTimeReader.Callback callback = - (KernelUidCpuClusterTimeReader.Callback) invocation.getArguments()[0]; + final KernelCpuUidClusterTimeReader.Callback callback = + (KernelCpuUidClusterTimeReader.Callback) invocation.getArguments()[0]; for (int i = 0; i < testUids.length; ++i) { - callback.onUidCpuPolicyTime(testUids[i], uidTimesMs[i]); + callback.onUidCpuTime(testUids[i], uidTimesMs[i]); } // And one for the invalid uid - callback.onUidCpuPolicyTime(invalidUid, new long[] {400, 1000}); + callback.onUidCpuTime(invalidUid, new long[]{400, 1000}); return null; - }).when(mKernelUidCpuClusterTimeReader).readDelta( - any(KernelUidCpuClusterTimeReader.Callback.class)); + }).when(mCpuUidClusterTimeReader).readDelta( + any(KernelCpuUidClusterTimeReader.Callback.class)); // RUN mBatteryStatsImpl.readKernelUidCpuClusterTimesLocked(true); @@ -1242,7 +1254,7 @@ public class BatteryStatsCpuTimesTest { } assertNull("There shouldn't be an entry for invalid uid=" + invalidUid, mBatteryStatsImpl.getUidStats().get(invalidUid)); - verify(mKernelUidCpuClusterTimeReader).removeUid(invalidUid); + verify(mCpuUidClusterTimeReader).removeUid(invalidUid); } @Test @@ -1268,25 +1280,25 @@ public class BatteryStatsCpuTimesTest { mClocks.realtime = mClocks.uptime = 400_000; mBatteryStatsImpl.clearPendingRemovedUids(); assertEquals(1, mBatteryStatsImpl.getPendingRemovedUids().size()); - verify(mKernelUidCpuActiveTimeReader).removeUid(1); - verify(mKernelUidCpuActiveTimeReader).removeUidsInRange(5, 10); - verify(mKernelUidCpuClusterTimeReader).removeUid(1); - verify(mKernelUidCpuClusterTimeReader).removeUidsInRange(5, 10); - verify(mKernelUidCpuFreqTimeReader).removeUid(1); - verify(mKernelUidCpuFreqTimeReader).removeUidsInRange(5, 10); - verify(mKernelUidCpuTimeReader).removeUid(1); - verify(mKernelUidCpuTimeReader).removeUidsInRange(5, 10); + verify(mCpuUidActiveTimeReader).removeUid(1); + verify(mCpuUidActiveTimeReader).removeUidsInRange(5, 10); + verify(mCpuUidClusterTimeReader).removeUid(1); + verify(mCpuUidClusterTimeReader).removeUidsInRange(5, 10); + verify(mCpuUidFreqTimeReader).removeUid(1); + verify(mCpuUidFreqTimeReader).removeUidsInRange(5, 10); + verify(mCpuUidUserSysTimeReader).removeUid(1); + verify(mCpuUidUserSysTimeReader).removeUidsInRange(5, 10); mClocks.realtime = mClocks.uptime = 800_000; mBatteryStatsImpl.clearPendingRemovedUids(); assertEquals(0, mBatteryStatsImpl.getPendingRemovedUids().size()); - verify(mKernelUidCpuActiveTimeReader).removeUid(100); - verify(mKernelUidCpuClusterTimeReader).removeUid(100); - verify(mKernelUidCpuFreqTimeReader).removeUid(100); - verify(mKernelUidCpuTimeReader).removeUid(100); + verify(mCpuUidActiveTimeReader).removeUid(100); + verify(mCpuUidClusterTimeReader).removeUid(100); + verify(mCpuUidFreqTimeReader).removeUid(100); + verify(mCpuUidUserSysTimeReader).removeUid(100); - verifyNoMoreInteractions(mKernelUidCpuActiveTimeReader, mKernelUidCpuClusterTimeReader, - mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader); + verifyNoMoreInteractions(mCpuUidActiveTimeReader, mCpuUidClusterTimeReader, + mCpuUidFreqTimeReader, mCpuUidUserSysTimeReader); } private void updateTimeBasesLocked(boolean unplugged, int screenState, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index dc9367557f9f..077182921118 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -39,6 +39,7 @@ import android.view.Display; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.util.ArrayUtils; import org.junit.Before; @@ -54,7 +55,7 @@ import java.util.Arrays; @RunWith(AndroidJUnit4.class) public class BatteryStatsImplTest { @Mock - private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader; + private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader; @Mock private KernelSingleUidTimeReader mKernelSingleUidTimeReader; @@ -67,7 +68,7 @@ public class BatteryStatsImplTest { when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true); when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true); mBatteryStatsImpl = new MockBatteryStatsImpl() - .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader) + .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader) .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader); } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index d69e1d131731..a6329298b0f9 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -38,7 +38,6 @@ import org.junit.runners.Suite; BatteryStatsTimerTest.class, BatteryStatsUidTest.class, BatteryStatsUserLifecycleTests.class, - KernelCpuProcReaderTest.class, KernelCpuProcStringReaderTest.class, KernelCpuUidActiveTimeReaderTest.class, KernelCpuUidClusterTimeReaderTest.class, @@ -46,9 +45,6 @@ import org.junit.runners.Suite; KernelCpuUidUserSysTimeReaderTest.class, KernelMemoryBandwidthStatsTest.class, KernelSingleUidTimeReaderTest.class, - KernelUidCpuFreqTimeReaderTest.class, - KernelUidCpuActiveTimeReaderTest.class, - KernelUidCpuClusterTimeReaderTest.class, KernelWakelockReaderTest.class, LongSamplingCounterTest.class, LongSamplingCounterArrayTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java deleted file mode 100644 index a25a74890029..000000000000 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java +++ /dev/null @@ -1,199 +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.internal.os; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.os.FileUtils; -import android.os.SystemClock; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.util.Arrays; -import java.util.Random; - -/** - * Test class for {@link KernelCpuProcReader}. - * - * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReaderTest - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class KernelCpuProcReaderTest { - - private File mRoot; - private File mTestDir; - private File mTestFile; - private Random mRand = new Random(); - - private KernelCpuProcReader mKernelCpuProcReader; - - private Context getContext() { - return InstrumentationRegistry.getContext(); - } - - @Before - public void setUp() { - mTestDir = getContext().getDir("test", Context.MODE_PRIVATE); - mRoot = getContext().getFilesDir(); - mTestFile = new File(mTestDir, "test.file"); - mKernelCpuProcReader = new KernelCpuProcReader(mTestFile.getAbsolutePath()); - } - - @After - public void tearDown() throws Exception { - FileUtils.deleteContents(mTestDir); - FileUtils.deleteContents(mRoot); - } - - - /** - * Tests that reading will return null if the file does not exist. - */ - @Test - public void testReadInvalidFile() throws Exception { - assertEquals(null, mKernelCpuProcReader.readBytes()); - } - - /** - * Tests that reading will always return null after 5 failures. - */ - @Test - public void testReadErrorsLimit() throws Exception { - mKernelCpuProcReader.setThrottleInterval(0); - for (int i = 0; i < 3; i++) { - assertNull(mKernelCpuProcReader.readBytes()); - SystemClock.sleep(50); - } - - final byte[] data = new byte[1024]; - mRand.nextBytes(data); - try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) { - os.write(data); - } - assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes()))); - - assertTrue(mTestFile.delete()); - for (int i = 0; i < 3; i++) { - assertNull(mKernelCpuProcReader.readBytes()); - SystemClock.sleep(50); - } - try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) { - os.write(data); - } - assertNull(mKernelCpuProcReader.readBytes()); - } - - /** - * Tests reading functionality. - */ - @Test - public void testSimpleRead() throws Exception { - final byte[] data = new byte[1024]; - mRand.nextBytes(data); - try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) { - os.write(data); - } - assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes()))); - } - - /** - * Tests multiple reading functionality. - */ - @Test - public void testMultipleRead() throws Exception { - mKernelCpuProcReader.setThrottleInterval(0); - for (int i = 0; i < 100; i++) { - final byte[] data = new byte[mRand.nextInt(102400) + 4]; - mRand.nextBytes(data); - try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) { - os.write(data); - } - assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes()))); - assertTrue(mTestFile.delete()); - } - } - - /** - * Tests reading with resizing. - */ - @Test - public void testReadWithResize() throws Exception { - final byte[] data = new byte[128001]; - mRand.nextBytes(data); - try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) { - os.write(data); - } - assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes()))); - } - - /** - * Tests that reading a file over the limit (1MB) will return null. - */ - @Test - public void testReadOverLimit() throws Exception { - final byte[] data = new byte[1228800]; - mRand.nextBytes(data); - try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) { - os.write(data); - } - assertNull(mKernelCpuProcReader.readBytes()); - } - - /** - * Tests throttling. Deleting underlying file should not affect cache. - */ - @Test - public void testThrottle() throws Exception { - mKernelCpuProcReader.setThrottleInterval(3000); - final byte[] data = new byte[20001]; - mRand.nextBytes(data); - try (OutputStream os = Files.newOutputStream(mTestFile.toPath())) { - os.write(data); - } - assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes()))); - assertTrue(mTestFile.delete()); - for (int i = 0; i < 5; i++) { - assertTrue(Arrays.equals(data, toArray(mKernelCpuProcReader.readBytes()))); - SystemClock.sleep(10); - } - SystemClock.sleep(5000); - assertNull(mKernelCpuProcReader.readBytes()); - } - - private byte[] toArray(ByteBuffer buffer) { - assertNotNull(buffer); - byte[] arr = new byte[buffer.remaining()]; - buffer.get(arr); - return arr; - } -} diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java index 7a316056429a..cbd2ba4eeabc 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java @@ -299,9 +299,10 @@ public class KernelCpuProcStringReaderTest { assertTrue(mTestFile.delete()); try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) { w.write(data1); - modify.countDown(); } catch (Throwable e) { errs.add(e); + } finally { + modify.countDown(); } }, 600, TimeUnit.MILLISECONDS); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java deleted file mode 100644 index 12f6c188f18f..000000000000 --- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuActiveTimeReaderTest.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Random; - -/** - * Test class for {@link KernelUidCpuActiveTimeReader}. - * - * To run it: - * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuActiveTimeReaderTest - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class KernelUidCpuActiveTimeReaderTest { - @Mock - private KernelCpuProcReader mProcReader; - @Mock - private KernelUidCpuActiveTimeReader.Callback mCallback; - private KernelUidCpuActiveTimeReader mReader; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mReader = new KernelUidCpuActiveTimeReader(mProcReader); - mReader.setThrottleInterval(0); - } - - @Test - public void testReadDelta() { - final int cores = 8; - final int[] uids = {1, 22, 333, 4444, 5555}; - - final long[][] times = increaseTime(new long[uids.length][cores]); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length; i++) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i])); - } - verifyNoMoreInteractions(mCallback); - - // Verify that a second call will only return deltas. - Mockito.reset(mCallback); - final long[][] times1 = increaseTime(times); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length; i++) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times1[i], times[i]))); - } - verifyNoMoreInteractions(mCallback); - - // Verify that there won't be a callback if the proc file values didn't change. - Mockito.reset(mCallback); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1)); - mReader.readDelta(mCallback); - verifyNoMoreInteractions(mCallback); - - // Verify that calling with a null callback doesn't result in any crashes - Mockito.reset(mCallback); - final long[][] times2 = increaseTime(times1); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2)); - mReader.readDelta(null); - - // Verify that the readDelta call will only return deltas when - // the previous call had null callback. - Mockito.reset(mCallback); - final long[][] times3 = increaseTime(times2); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length; ++i) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i]))); - } - verifyNoMoreInteractions(mCallback); - } - - @Test - public void testReadAbsolute() { - final int cores = 8; - final int[] uids = {1, 22, 333, 4444, 5555}; - - final long[][] times = increaseTime(new long[uids.length][cores]); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times)); - mReader.readAbsolute(mCallback); - for (int i = 0; i < uids.length; i++) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i])); - } - verifyNoMoreInteractions(mCallback); - - // Verify that a second call still returns absolute values - Mockito.reset(mCallback); - final long[][] times1 = increaseTime(times); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1)); - mReader.readAbsolute(mCallback); - for (int i = 0; i < uids.length; i++) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times1[i])); - } - verifyNoMoreInteractions(mCallback); - } - - @Test - public void testReadDelta_malformedData() { - final int cores = 8; - final int[] uids = {1, 22, 333, 4444, 5555}; - final long[][] times = increaseTime(new long[uids.length][cores]); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length; i++) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(times[i])); - } - verifyNoMoreInteractions(mCallback); - - // Verify that there is no callback if subsequent call is in wrong format. - Mockito.reset(mCallback); - final long[][] times1 = increaseTime(times); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times1).putInt(0, 5)); - mReader.readDelta(mCallback); - verifyNoMoreInteractions(mCallback); - - // Verify that the internal state was not modified if the given core count does not match - // the following # of entries. - Mockito.reset(mCallback); - final long[][] times2 = increaseTime(times); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times2)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length; i++) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times2[i], times[i]))); - } - verifyNoMoreInteractions(mCallback); - - // Verify that there is no callback if any value in the proc file is -ve. - Mockito.reset(mCallback); - final long[][] times3 = increaseTime(times2); - times3[uids.length - 1][cores - 1] *= -1; - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length - 1; ++i) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times3[i], times2[i]))); - } - verifyNoMoreInteractions(mCallback); - - // Verify that the internal state was not modified when the proc file had -ve value. - Mockito.reset(mCallback); - for (int i = 0; i < cores; i++) { - times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520; - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times3)); - mReader.readDelta(mCallback); - verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1], - getTotal(subtract(times3[uids.length - 1], times2[uids.length - 1]))); - verifyNoMoreInteractions(mCallback); - - // Verify that there is no callback if the values in the proc file are decreased. - Mockito.reset(mCallback); - final long[][] times4 = increaseTime(times3); - System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores); - times4[uids.length - 1][cores - 1] -= 100; - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length - 1; ++i) { - verify(mCallback).onUidCpuActiveTime(uids[i], getTotal(subtract(times4[i], times3[i]))); - } - verifyNoMoreInteractions(mCallback); - - // Verify that the internal state was not modified when the proc file had decreased values. - Mockito.reset(mCallback); - for (int i = 0; i < cores; i++) { - times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520; - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times4)); - mReader.readDelta(mCallback); - verify(mCallback).onUidCpuActiveTime(uids[uids.length - 1], - getTotal(subtract(times4[uids.length - 1], times3[uids.length - 1]))); - verifyNoMoreInteractions(mCallback); - } - - private long[] subtract(long[] a1, long[] a2) { - long[] val = new long[a1.length]; - for (int i = 0; i < val.length; ++i) { - val[i] = a1[i] - a2[i]; - } - return val; - } - - /** - * Unit of original and return value is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, - * ..., 10. So that when wedivide shared cpu time by concurrent thread count, we always get a - * nice integer, avoiding rounding errors. - */ - private long[][] increaseTime(long[][] original) { - long[][] newTime = new long[original.length][original[0].length]; - Random rand = new Random(); - for (int i = 0; i < original.length; i++) { - for (int j = 0; j < original[0].length; j++) { - newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520; - } - } - return newTime; - } - - // Unit of times is 10ms - private long getTotal(long[] times) { - long sum = 0; - for (int i = 0; i < times.length; i++) { - sum += times[i] * 10 / (i + 1); - } - return sum; - } - - /** - * Format uids and times (in 10ms) into the following format: - * [n, uid0, time0a, time0b, ..., time0n, - * uid1, time1a, time1b, ..., time1n, - * uid2, time2a, time2b, ..., time2n, etc.] - * where n is the total number of cpus (num_possible_cpus) - */ - private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) { - int size = (1 + uids.length * (times[0].length + 1)) * 4; - ByteBuffer buf = ByteBuffer.allocate(size); - buf.order(ByteOrder.nativeOrder()); - buf.putInt(times[0].length); - for (int i = 0; i < uids.length; i++) { - buf.putInt(uids[i]); - for (int j = 0; j < times[i].length; j++) { - buf.putInt((int) times[i][j]); - } - } - buf.flip(); - return buf.order(ByteOrder.nativeOrder()); - } -} diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java deleted file mode 100644 index 532f337fe2e1..000000000000 --- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuClusterTimeReaderTest.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.when; - -import android.util.SparseArray; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Random; - -/** - * Test class for {@link KernelUidCpuClusterTimeReader}. - * - * To run it: - * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuClusterTimeReaderTest - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class KernelUidCpuClusterTimeReaderTest { - @Mock - private KernelCpuProcReader mProcReader; - private KernelUidCpuClusterTimeReader mReader; - private VerifiableCallback mCallback; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mReader = new KernelUidCpuClusterTimeReader(mProcReader); - mCallback = new VerifiableCallback(); - mReader.setThrottleInterval(0); - } - - @Test - public void testReadDelta() throws Exception { - VerifiableCallback cb = new VerifiableCallback(); - final int cores = 6; - final int[] clusters = {2, 4}; - final int[] uids = {1, 22, 333, 4444, 5555}; - - // Verify initial call - final long[][] times = increaseTime(new long[uids.length][cores]); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times)); - mReader.readDelta(cb); - for (int i = 0; i < uids.length; i++) { - cb.verify(uids[i], getTotal(clusters, times[i])); - } - cb.verifyNoMoreInteractions(); - - // Verify that a second call will only return deltas. - cb.clear(); - Mockito.reset(mProcReader); - final long[][] times1 = increaseTime(times); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1)); - mReader.readDelta(cb); - for (int i = 0; i < uids.length; i++) { - cb.verify(uids[i], getTotal(clusters, subtract(times1[i], times[i]))); - } - cb.verifyNoMoreInteractions(); - - // Verify that there won't be a callback if the proc file values didn't change. - cb.clear(); - Mockito.reset(mProcReader); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1)); - mReader.readDelta(cb); - cb.verifyNoMoreInteractions(); - - // Verify that calling with a null callback doesn't result in any crashes - Mockito.reset(mProcReader); - final long[][] times2 = increaseTime(times1); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2)); - mReader.readDelta(null); - - // Verify that the readDelta call will only return deltas when - // the previous call had null callback. - cb.clear(); - Mockito.reset(mProcReader); - final long[][] times3 = increaseTime(times2); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3)); - mReader.readDelta(cb); - for (int i = 0; i < uids.length; i++) { - cb.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i]))); - } - cb.verifyNoMoreInteractions(); - - } - - @Test - public void testReadAbsolute() throws Exception { - VerifiableCallback cb = new VerifiableCallback(); - final int cores = 6; - final int[] clusters = {2, 4}; - final int[] uids = {1, 22, 333, 4444, 5555}; - - // Verify return absolute value - final long[][] times = increaseTime(new long[uids.length][cores]); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times)); - mReader.readAbsolute(cb); - for (int i = 0; i < uids.length; i++) { - cb.verify(uids[i], getTotal(clusters, times[i])); - } - cb.verifyNoMoreInteractions(); - - // Verify that a second call should return the same absolute value - cb.clear(); - Mockito.reset(mProcReader); - final long[][] times1 = increaseTime(times); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1)); - mReader.readAbsolute(cb); - for (int i = 0; i < uids.length; i++) { - cb.verify(uids[i], getTotal(clusters, times1[i])); - } - cb.verifyNoMoreInteractions(); - } - - @Test - public void testReadDelta_malformedData() throws Exception { - final int cores = 6; - final int[] clusters = {2, 4}; - final int[] uids = {1, 22, 333, 4444, 5555}; - - // Verify initial call - final long[][] times = increaseTime(new long[uids.length][cores]); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length; i++) { - mCallback.verify(uids[i], getTotal(clusters, times[i])); - } - mCallback.verifyNoMoreInteractions(); - - // Verify that there is no callback if a call has wrong format - mCallback.clear(); - Mockito.reset(mProcReader); - final long[][] temp = increaseTime(times); - final long[][] times1 = new long[uids.length][]; - for (int i = 0; i < temp.length; i++) { - times1[i] = Arrays.copyOfRange(temp[i], 0, 4); - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1)); - mReader.readDelta(mCallback); - mCallback.verifyNoMoreInteractions(); - - // Verify that the internal state was not modified if the given core count does not match - // the following # of entries. - mCallback.clear(); - Mockito.reset(mProcReader); - final long[][] times2 = increaseTime(times); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length; i++) { - mCallback.verify(uids[i], getTotal(clusters, subtract(times2[i], times[i]))); - } - mCallback.verifyNoMoreInteractions(); - - // Verify that there is no callback if any value in the proc file is -ve. - mCallback.clear(); - Mockito.reset(mProcReader); - final long[][] times3 = increaseTime(times2); - times3[uids.length - 1][cores - 1] *= -1; - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length - 1; i++) { - mCallback.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i]))); - } - mCallback.verifyNoMoreInteractions(); - - // Verify that the internal state was not modified when the proc file had -ve value. - mCallback.clear(); - Mockito.reset(mProcReader); - for (int i = 0; i < cores; i++) { - times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520; - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3)); - mReader.readDelta(mCallback); - mCallback.verify(uids[uids.length - 1], - getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1]))); - - // Verify that there is no callback if the values in the proc file are decreased. - mCallback.clear(); - Mockito.reset(mProcReader); - final long[][] times4 = increaseTime(times3); - System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores); - times4[uids.length - 1][cores - 1] -= 100; - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4)); - mReader.readDelta(mCallback); - for (int i = 0; i < uids.length - 1; i++) { - mCallback.verify(uids[i], getTotal(clusters, subtract(times4[i], times3[i]))); - } - mCallback.verifyNoMoreInteractions(); - - // Verify that the internal state was not modified when the proc file had decreased values. - mCallback.clear(); - Mockito.reset(mProcReader); - for (int i = 0; i < cores; i++) { - times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520; - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4)); - mReader.readDelta(mCallback); - mCallback.verify(uids[uids.length - 1], - getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1]))); - mCallback.verifyNoMoreInteractions(); - } - - - private long[] subtract(long[] a1, long[] a2) { - long[] val = new long[a1.length]; - for (int i = 0; i < val.length; ++i) { - val[i] = a1[i] - a2[i]; - } - return val; - } - - /** - * Unit is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, ..., 10. So that when we - * divide shared cpu time by concurrent thread count, we always get a nice integer, avoiding - * rounding errors. - */ - private long[][] increaseTime(long[][] original) { - long[][] newTime = new long[original.length][original[0].length]; - Random rand = new Random(); - for (int i = 0; i < original.length; i++) { - for (int j = 0; j < original[0].length; j++) { - newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520; - } - } - return newTime; - } - - // Format an array of cluster times according to the algorithm in KernelUidCpuClusterTimeReader - private long[] getTotal(int[] cluster, long[] times) { - int core = 0; - long[] sumTimes = new long[cluster.length]; - for (int i = 0; i < cluster.length; i++) { - double sum = 0; - for (int j = 0; j < cluster[i]; j++) { - sum += (double) times[core++] * 10 / (j + 1); - } - sumTimes[i] = (long) sum; - } - return sumTimes; - } - - private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback { - - SparseArray<long[]> mData = new SparseArray<>(); - int count = 0; - - public void verify(int uid, long[] cpuClusterTimeMs) { - long[] array = mData.get(uid); - assertNotNull(array); - assertArrayEquals(cpuClusterTimeMs, array); - count++; - } - - public void clear() { - mData.clear(); - count = 0; - } - - @Override - public void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs) { - long[] array = new long[cpuClusterTimeMs.length]; - System.arraycopy(cpuClusterTimeMs, 0, array, 0, array.length); - mData.put(uid, array); - } - - public void verifyNoMoreInteractions() { - assertEquals(mData.size(), count); - } - } - - /** - * Format uids and times (in 10ms) into the following format: - * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n, - * uid1, time1a, time1b, ..., time1n, - * uid2, time2a, time2b, ..., time2n, etc.] - * where n is the number of policies - * xi is the number cpus on a particular policy - */ - private ByteBuffer getUidTimesBytes(int[] uids, int[] clusters, long[][] times) { - int size = (1 + clusters.length + uids.length * (times[0].length + 1)) * 4; - ByteBuffer buf = ByteBuffer.allocate(size); - buf.order(ByteOrder.nativeOrder()); - buf.putInt(clusters.length); - for (int i = 0; i < clusters.length; i++) { - buf.putInt(clusters[i]); - } - for (int i = 0; i < uids.length; i++) { - buf.putInt(uids[i]); - for (int j = 0; j < times[i].length; j++) { - buf.putInt((int) (times[i][j])); - } - } - buf.flip(); - return buf.order(ByteOrder.nativeOrder()); - } -} diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java deleted file mode 100644 index 6d2980b8bed0..000000000000 --- a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.os; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import android.util.SparseArray; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.io.BufferedReader; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -/** - * Test class for {@link KernelUidCpuFreqTimeReader}. - * - * To run the tests, use - * - * runtest -c com.android.internal.os.KernelUidCpuFreqTimeReaderTest frameworks-core - * - * or the following steps: - * - * Build: m FrameworksCoreTests - * Install: adb install -r \ - * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk - * Run: adb shell am instrument -e class com.android.internal.os.KernelUidCpuFreqTimeReaderTest -w \ - * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner - * - * or - * - * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuFreqTimeReaderTest - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class KernelUidCpuFreqTimeReaderTest { - @Mock - private BufferedReader mBufferedReader; - @Mock - private KernelUidCpuFreqTimeReader.Callback mCallback; - @Mock - private PowerProfile mPowerProfile; - @Mock - private KernelCpuProcReader mProcReader; - - private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader(mProcReader); - mKernelUidCpuFreqTimeReader.setThrottleInterval(0); - } - - @Test - public void testReadFreqs_perClusterTimesNotAvailable() throws Exception { - final long[][] freqs = { - {1, 12, 123, 1234}, - {1, 12, 123, 23, 123, 1234, 12345, 123456}, - {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345}, - {1, 12, 123, 23, 2345, 234567} - }; - final int[] numClusters = {2, 2, 3, 1}; - final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}}; - for (int i = 0; i < freqs.length; ++i) { - setCpuClusterFreqs(numClusters[i], numFreqs[i]); - when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs[i])); - long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs( - mBufferedReader, mPowerProfile); - assertArrayEquals(freqs[i], actualFreqs); - verifyZeroInteractions(mCallback); - final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s", - Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i])); - assertFalse(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()); - - // Verify that a second call won't read the proc file again - Mockito.reset(mBufferedReader); - actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile); - assertArrayEquals(freqs[i], actualFreqs); - assertFalse(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()); - - // Prepare for next iteration - Mockito.reset(mBufferedReader, mPowerProfile); - } - } - - @Test - public void testReadFreqs_perClusterTimesAvailable() throws Exception { - final long[][] freqs = { - {1, 12, 123, 1234}, - {1, 12, 123, 23, 123, 1234, 12345, 123456}, - {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345, 1234567} - }; - final int[] numClusters = {1, 2, 3}; - final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}}; - for (int i = 0; i < freqs.length; ++i) { - setCpuClusterFreqs(numClusters[i], numFreqs[i]); - when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs[i])); - long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs( - mBufferedReader, mPowerProfile); - assertArrayEquals(freqs[i], actualFreqs); - verifyZeroInteractions(mCallback); - final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s", - Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i])); - assertTrue(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()); - - // Verify that a second call won't read the proc file again - Mockito.reset(mBufferedReader); - actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile); - assertArrayEquals(freqs[i], actualFreqs); - assertTrue(errMsg, mKernelUidCpuFreqTimeReader.perClusterTimesAvailable()); - - // Prepare for next iteration - Mockito.reset(mBufferedReader, mPowerProfile); - } - } - - @Test - public void testReadDelta_Binary() throws Exception { - VerifiableCallback cb = new VerifiableCallback(); - final long[] freqs = {110, 123, 145, 167, 289, 997}; - final int[] uids = {1, 22, 333, 444, 555}; - final long[][] times = new long[uids.length][freqs.length]; - for (int i = 0; i < uids.length; ++i) { - for (int j = 0; j < freqs.length; ++j) { - times[i][j] = uids[i] * freqs[j] * 10; - } - } - when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs)); - long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile); - - assertArrayEquals(freqs, actualFreqs); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times)); - mKernelUidCpuFreqTimeReader.readDeltaImpl(cb); - for (int i = 0; i < uids.length; ++i) { - cb.verify(uids[i], times[i]); - } - cb.verifyNoMoreInteractions(); - - // Verify that a second call will only return deltas. - cb.clear(); - Mockito.reset(mProcReader); - final long[][] newTimes1 = new long[uids.length][freqs.length]; - for (int i = 0; i < uids.length; ++i) { - for (int j = 0; j < freqs.length; ++j) { - newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50; - } - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1)); - mKernelUidCpuFreqTimeReader.readDeltaImpl(cb); - for (int i = 0; i < uids.length; ++i) { - cb.verify(uids[i], subtract(newTimes1[i], times[i])); - } - cb.verifyNoMoreInteractions(); - - // Verify that there won't be a callback if the proc file values didn't change. - cb.clear(); - Mockito.reset(mProcReader); - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1)); - mKernelUidCpuFreqTimeReader.readDeltaImpl(cb); - cb.verifyNoMoreInteractions(); - - // Verify that calling with a null callback doesn't result in any crashes - cb.clear(); - Mockito.reset(mProcReader); - final long[][] newTimes2 = new long[uids.length][freqs.length]; - for (int i = 0; i < uids.length; ++i) { - for (int j = 0; j < freqs.length; ++j) { - newTimes2[i][j] = newTimes1[i][j] + (uids[i] * freqs[j]) * 30; - } - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes2)); - mKernelUidCpuFreqTimeReader.readDeltaImpl(null); - cb.verifyNoMoreInteractions(); - - // Verify that the readDelta call will only return deltas when - // the previous call had null callback. - cb.clear(); - Mockito.reset(mProcReader); - final long[][] newTimes3 = new long[uids.length][freqs.length]; - for (int i = 0; i < uids.length; ++i) { - for (int j = 0; j < freqs.length; ++j) { - newTimes3[i][j] = newTimes2[i][j] + (uids[i] + freqs[j]) * 40; - } - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes3)); - mKernelUidCpuFreqTimeReader.readDeltaImpl(cb); - for (int i = 0; i < uids.length; ++i) { - cb.verify(uids[i], subtract(newTimes3[i], newTimes2[i])); - } - cb.verifyNoMoreInteractions(); - } - - @Test - public void testReadAbsolute() throws Exception { - VerifiableCallback cb = new VerifiableCallback(); - final long[] freqs = {110, 123, 145, 167, 289, 997}; - final int[] uids = {1, 22, 333, 444, 555}; - final long[][] times = new long[uids.length][freqs.length]; - for (int i = 0; i < uids.length; ++i) { - for (int j = 0; j < freqs.length; ++j) { - times[i][j] = uids[i] * freqs[j] * 10; - } - } - when(mBufferedReader.readLine()).thenReturn(getFreqsLine(freqs)); - long[] actualFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mBufferedReader, mPowerProfile); - - assertArrayEquals(freqs, actualFreqs); - // Verify that the absolute values are returned - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, times)); - mKernelUidCpuFreqTimeReader.readAbsolute(cb); - for (int i = 0; i < uids.length; ++i) { - cb.verify(uids[i], times[i]); - } - cb.verifyNoMoreInteractions(); - - // Verify that a second call should still return absolute values - cb.clear(); - Mockito.reset(mProcReader); - final long[][] newTimes1 = new long[uids.length][freqs.length]; - for (int i = 0; i < uids.length; ++i) { - for (int j = 0; j < freqs.length; ++j) { - newTimes1[i][j] = times[i][j] + (uids[i] + freqs[j]) * 50; - } - } - when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, newTimes1)); - mKernelUidCpuFreqTimeReader.readAbsolute(cb); - for (int i = 0; i < uids.length; ++i) { - cb.verify(uids[i], newTimes1[i]); - } - cb.verifyNoMoreInteractions(); - } - - private long[] subtract(long[] a1, long[] a2) { - long[] val = new long[a1.length]; - for (int i = 0; i < val.length; ++i) { - val[i] = a1[i] - a2[i]; - } - return val; - } - - private String getFreqsLine(long[] freqs) { - final StringBuilder sb = new StringBuilder(); - sb.append("uid:"); - for (int i = 0; i < freqs.length; ++i) { - sb.append(" " + freqs[i]); - } - return sb.toString(); - } - - private ByteBuffer getUidTimesBytes(int[] uids, long[][] times) { - int size = (1 + uids.length + uids.length * times[0].length) * 4; - ByteBuffer buf = ByteBuffer.allocate(size); - buf.order(ByteOrder.nativeOrder()); - buf.putInt(times[0].length); - for (int i = 0; i < uids.length; i++) { - buf.putInt(uids[i]); - for (int j = 0; j < times[i].length; j++) { - buf.putInt((int) (times[i][j] / 10)); - } - } - buf.flip(); - return buf.asReadOnlyBuffer().order(ByteOrder.nativeOrder()); - } - - private void setCpuClusterFreqs(int numClusters, int... clusterFreqs) { - assertEquals(numClusters, clusterFreqs.length); - when(mPowerProfile.getNumCpuClusters()).thenReturn(numClusters); - for (int i = 0; i < numClusters; ++i) { - when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]); - } - } - - private class VerifiableCallback implements KernelUidCpuFreqTimeReader.Callback { - - SparseArray<long[]> mData = new SparseArray<>(); - int count = 0; - - public void verify(int uid, long[] cpuFreqTimeMs) { - long[] array = mData.get(uid); - assertNotNull(array); - assertArrayEquals(cpuFreqTimeMs, array); - count++; - } - - public void clear() { - mData.clear(); - count = 0; - } - - @Override - public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) { - long[] array = new long[cpuFreqTimeMs.length]; - System.arraycopy(cpuFreqTimeMs, 0, array, 0, array.length); - mData.put(uid, array); - } - - public void verifyNoMoreInteractions() { - assertEquals(mData.size(), count); - } - } -} diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index c18445e26173..bc0e0a496d80 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -21,6 +21,10 @@ import android.os.Looper; import android.util.SparseIntArray; import com.android.internal.location.gnssmetrics.GnssMetrics; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; import java.util.ArrayList; import java.util.Queue; @@ -43,13 +47,14 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase); setExternalStatsSyncLocked(new DummyExternalStatsSync()); - for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { - mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000-i, null, - mOnBatteryTimeBase); + for (int i = 0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { + mGpsSignalQualityTimer[i] = new StopwatchTimer(clocks, null, -1000 - i, null, + mOnBatteryTimeBase); } // A no-op handler. - mHandler = new Handler(Looper.getMainLooper()) {}; + mHandler = new Handler(Looper.getMainLooper()) { + }; } MockBatteryStatsImpl() { @@ -95,23 +100,26 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return this; } - public MockBatteryStatsImpl setKernelUidCpuFreqTimeReader(KernelUidCpuFreqTimeReader reader) { - mKernelUidCpuFreqTimeReader = reader; + public MockBatteryStatsImpl setKernelCpuUidFreqTimeReader(KernelCpuUidFreqTimeReader reader) { + mCpuUidFreqTimeReader = reader; return this; } - public MockBatteryStatsImpl setKernelUidCpuActiveTimeReader(KernelUidCpuActiveTimeReader reader) { - mKernelUidCpuActiveTimeReader = reader; + public MockBatteryStatsImpl setKernelCpuUidActiveTimeReader( + KernelCpuUidActiveTimeReader reader) { + mCpuUidActiveTimeReader = reader; return this; } - public MockBatteryStatsImpl setKernelUidCpuClusterTimeReader(KernelUidCpuClusterTimeReader reader) { - mKernelUidCpuClusterTimeReader = reader; + public MockBatteryStatsImpl setKernelCpuUidClusterTimeReader( + KernelCpuUidClusterTimeReader reader) { + mCpuUidClusterTimeReader = reader; return this; } - public MockBatteryStatsImpl setKernelUidCpuTimeReader(KernelUidCpuTimeReader reader) { - mKernelUidCpuTimeReader = reader; + public MockBatteryStatsImpl setKernelCpuUidUserSysTimeReader( + KernelCpuUidUserSysTimeReader reader) { + mCpuUidUserSysTimeReader = reader; return this; } diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java new file mode 100644 index 000000000000..1a81c2c9fd41 --- /dev/null +++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.test.filters; + +import android.os.Bundle; + +import com.android.test.filters.SelectTest; + +/** + * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests. + * + * <p>Use this filter when running FrameworksCoreTests as + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.server.wm.test.filters.CoreTestsFilter \ + * -e selectTest_verbose true \ + * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner + * </pre> + */ +public final class CoreTestsFilter extends SelectTest { + + private static final String[] SELECTED_CORE_TESTS = { + "android.app.servertransaction.", // all tests under the package. + "android.view.DisplayCutoutTest", + "android.view.InsetsControllerTest", + "android.view.InsetsSourceTest", + "android.view.InsetsSourceConsumerTest", + "android.view.InsetsStateTest", + }; + + public CoreTestsFilter(Bundle testArgs) { + super(addSelectTest(testArgs, SELECTED_CORE_TESTS)); + } +} diff --git a/core/tests/hdmitests/Android.mk b/core/tests/hdmitests/Android.mk index 2ca31a6a240a..f155feba0588 100644 --- a/core/tests/hdmitests/Android.mk +++ b/core/tests/hdmitests/Android.mk @@ -20,7 +20,7 @@ LOCAL_MODULE_TAGS := tests # Include all test java files LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils +LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils truth-prebuilt LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := HdmiCecTests diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java new file mode 100644 index 000000000000..fdc6b847d055 --- /dev/null +++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java @@ -0,0 +1,145 @@ +/* + * 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.hardware.hdmi; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +/** + * Tests for {@link HdmiUtils}. + */ +@RunWith(JUnit4.class) +@SmallTest +public class HdmiUtilsTest { + @Test + public void testInvalidAddress() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0, -1)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN); + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFF, 0xFFFF)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN); + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFFF, 0)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN); + } + + @Test + public void testSameAddress() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1000)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SAME); + } + + @Test + public void testDirectlyAbove() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1200)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE); + } + + @Test + public void testDirectlyAbove_rootDevice() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x2000)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE); + } + + @Test + public void testDirectlyAbove_leafDevice() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1240, 0x1245)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE); + } + + @Test + public void testAbove() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1210)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE); + } + + @Test + public void testAbove_rootDevice() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x1200)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE); + } + + @Test + public void testDirectlyBelow() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x2250, 0x2200)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW); + } + + @Test + public void testDirectlyBelow_rootDevice() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5000, 0x0000)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW); + } + + @Test + public void testDirectlyBelow_leafDevice() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3249, 0x3240)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW); + } + + @Test + public void testBelow() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5143, 0x5100)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW); + } + + @Test + public void testBelow_rootDevice() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3420, 0x0000)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW); + } + + @Test + public void testSibling() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x4000, 0x5000)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING); + } + + @Test + public void testSibling_leafDevice() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x798F)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING); + } + + @Test + public void testDifferentBranch() { + assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x7970)) + .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH); + } + + @Test + public void isValidPysicalAddress_true() { + assertThat(HdmiUtils.isValidPhysicalAddress(0)).isTrue(); + assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFE)).isTrue(); + assertThat(HdmiUtils.isValidPhysicalAddress(0x1200)).isTrue(); + } + + @Test + public void isValidPysicalAddress_outOfRange() { + assertThat(HdmiUtils.isValidPhysicalAddress(-1)).isFalse(); + assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFF)).isFalse(); + assertThat(HdmiUtils.isValidPhysicalAddress(0x10000)).isFalse(); + } + + @Test + public void isValidPysicalAddress_nonTrailingZeros() { + assertThat(HdmiUtils.isValidPhysicalAddress(0x0001)).isFalse(); + assertThat(HdmiUtils.isValidPhysicalAddress(0x0213)).isFalse(); + } +} diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java index 03cf3eb6a2b9..9913531cdf13 100644 --- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java @@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -67,6 +68,7 @@ public class LockPatternUtilsTest { // TODO(b/63758238): stop spying the class under test mLockPatternUtils = spy(new LockPatternUtils(context)); when(mLockPatternUtils.getLockSettings()).thenReturn(ils); + doReturn(true).when(mLockPatternUtils).hasSecureLockScreen(); final UserInfo userInfo = Mockito.mock(UserInfo.class); when(userInfo.isDemo()).thenReturn(isDemoUser); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 597d14ac286e..904c3fb6d549 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -180,6 +180,7 @@ applications that come with the platform <permission name="android.permission.WRITE_APN_SETTINGS"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/> + <permission name="android.permission.READ_PRECISE_PHONE_STATE"/> <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> <permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/> </privapp-permissions> diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 30f0bfa68b4a..bfbdbc585e08 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -21,7 +21,6 @@ import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.Size; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.annotation.WorkerThread; @@ -748,22 +747,10 @@ public final class Bitmap implements Parcelable { throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE."); } int format = hardwareBuffer.getFormat(); - ColorSpace.Rgb rgb = null; - if (colorSpace != null) { - if (!(colorSpace instanceof ColorSpace.Rgb)) { - throw new IllegalArgumentException("colorSpace must be an RGB color space"); - } - rgb = (ColorSpace.Rgb) colorSpace; - } else { - rgb = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB); - } - ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters(); - if (parameters == null) { - throw new IllegalArgumentException("colorSpace must use an ICC " - + "parametric transfer function"); + if (colorSpace == null) { + colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); } - ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50); - return nativeWrapHardwareBufferBitmap(hardwareBuffer, d50.getTransform(), parameters); + return nativeWrapHardwareBufferBitmap(hardwareBuffer, colorSpace.getNativeInstance()); } /** @@ -1103,26 +1090,18 @@ public final class Bitmap implements Parcelable { throw new IllegalArgumentException("can't create bitmap without a color space"); } - Bitmap bm; - // nullptr color spaces have a particular meaning in native and are interpreted as sRGB - // (we also avoid the unnecessary extra work of the else branch) - if (config != Config.ARGB_8888 || colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) { - bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, null, null); - } else { - if (!(colorSpace instanceof ColorSpace.Rgb)) { - throw new IllegalArgumentException("colorSpace must be an RGB color space"); - } - ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace; - ColorSpace.Rgb.TransferParameters parameters = rgb.getTransferParameters(); - if (parameters == null) { - throw new IllegalArgumentException("colorSpace must use an ICC " - + "parametric transfer function"); + if (config != Config.ARGB_8888) { + if (config == Config.RGBA_F16) { + // FIXME: This should be LINEAR_EXTENDED_SRGB, but that would fail a CTS test. See + // b/120960866. SRGB matches the old (incorrect) behavior. + //colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); + colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + } else { + colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); } - - ColorSpace.Rgb d50 = (ColorSpace.Rgb) ColorSpace.adapt(rgb, ColorSpace.ILLUMINANT_D50); - bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, - d50.getTransform(), parameters); } + Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true, + colorSpace.getNativeInstance()); if (display != null) { bm.mDensity = display.densityDpi; @@ -1200,8 +1179,9 @@ public final class Bitmap implements Parcelable { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("width and height must be > 0"); } + ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB); Bitmap bm = nativeCreate(colors, offset, stride, width, height, - config.nativeInt, false, null, null); + config.nativeInt, false, sRGB.getNativeInstance()); if (display != null) { bm.mDensity = display.densityDpi; } @@ -1798,11 +1778,7 @@ public final class Bitmap implements Parcelable { } ColorSpace cs = Color.colorSpace(c); - float r = Color.red(c); - float g = Color.green(c); - float b = Color.blue(c); - float a = Color.alpha(c); - nativeErase(mNativePtr, cs, r, g, b, a); + nativeErase(mNativePtr, cs.getNativeInstance(), c); } /** @@ -2133,8 +2109,7 @@ public final class Bitmap implements Parcelable { private static native Bitmap nativeCreate(int[] colors, int offset, int stride, int width, int height, int nativeConfig, boolean mutable, - @Nullable @Size(9) float[] xyzD50, - @Nullable ColorSpace.Rgb.TransferParameters p); + long nativeColorSpace); private static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig, boolean isMutable); private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap); @@ -2149,8 +2124,7 @@ public final class Bitmap implements Parcelable { int quality, OutputStream stream, byte[] tempStorage); private static native void nativeErase(long nativeBitmap, int color); - private static native void nativeErase(long nativeBitmap, ColorSpace cs, - float r, float g, float b, float a); + private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color); private static native int nativeRowBytes(long nativeBitmap); private static native int nativeConfig(long nativeBitmap); @@ -2194,8 +2168,7 @@ public final class Bitmap implements Parcelable { private static native Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap); private static native Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer); private static native Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer, - @Size(9) float[] xyzD50, - ColorSpace.Rgb.TransferParameters p); + long nativeColorSpace); private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap); private static native boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params); private static native boolean nativeIsSRGB(long nativePtr); diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 022fbdc9df74..7aff0414106a 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -460,6 +460,21 @@ public class BitmapFactory { } } } + + /** + * Helper for passing SkColorSpace pointer to native. + * + * @throws IllegalArgumentException if the ColorSpace is not Rgb or does + * not have TransferParameters. + */ + static long nativeColorSpace(Options opts) { + if (opts == null || opts.inPreferredColorSpace == null) { + return 0; + } + + return opts.inPreferredColorSpace.getNativeInstance(); + } + } /** @@ -633,7 +648,8 @@ public class BitmapFactory { Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); try { - bm = nativeDecodeByteArray(data, offset, length, opts); + bm = nativeDecodeByteArray(data, offset, length, opts, + Options.nativeColorSpace(opts)); if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); @@ -728,7 +744,7 @@ public class BitmapFactory { try { if (is instanceof AssetManager.AssetInputStream) { final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); - bm = nativeDecodeAsset(asset, outPadding, opts); + bm = nativeDecodeAsset(asset, outPadding, opts, Options.nativeColorSpace(opts)); } else { bm = decodeStreamInternal(is, outPadding, opts); } @@ -755,7 +771,8 @@ public class BitmapFactory { byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE]; - return nativeDecodeStream(is, tempStorage, outPadding, opts); + return nativeDecodeStream(is, tempStorage, outPadding, opts, + Options.nativeColorSpace(opts)); } /** @@ -798,7 +815,8 @@ public class BitmapFactory { Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor"); try { if (nativeIsSeekable(fd)) { - bm = nativeDecodeFileDescriptor(fd, outPadding, opts); + bm = nativeDecodeFileDescriptor(fd, outPadding, opts, + Options.nativeColorSpace(opts)); } else { FileInputStream fis = new FileInputStream(fd); try { @@ -835,14 +853,15 @@ public class BitmapFactory { @UnsupportedAppUsage private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, - Rect padding, Options opts); + Rect padding, Options opts, long colorSpaceHandle); @UnsupportedAppUsage private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, - Rect padding, Options opts); + Rect padding, Options opts, long colorSpaceHandle); @UnsupportedAppUsage - private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts); + private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts, + long colorSpaceHandle); @UnsupportedAppUsage private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, - int length, Options opts); + int length, Options opts, long colorSpaceHandle); private static native boolean nativeIsSeekable(FileDescriptor fd); } diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java index 43282d39ed47..1410423eafac 100644 --- a/graphics/java/android/graphics/BitmapRegionDecoder.java +++ b/graphics/java/android/graphics/BitmapRegionDecoder.java @@ -195,7 +195,8 @@ public final class BitmapRegionDecoder { || rect.top >= getHeight()) throw new IllegalArgumentException("rectangle is outside the image"); return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, options); + rect.right - rect.left, rect.bottom - rect.top, options, + BitmapFactory.Options.nativeColorSpace(options)); } } @@ -265,7 +266,7 @@ public final class BitmapRegionDecoder { private static native Bitmap nativeDecodeRegion(long lbm, int start_x, int start_y, int width, int height, - BitmapFactory.Options options); + BitmapFactory.Options options, long colorSpaceHandle); private static native int nativeGetWidth(long lbm); private static native int nativeGetHeight(long lbm); private static native void nativeClean(long lbm); diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 9fa70a5ab19c..4755d45cd434 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -25,6 +25,8 @@ import android.annotation.Size; import android.annotation.SuppressAutoDoc; import android.util.Pair; +import libcore.util.NativeAllocationRegistry; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -199,6 +201,9 @@ public abstract class ColorSpace { private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f }; private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f }; + private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS = + new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4); + // See static initialization block next to #get(Named) private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length]; @@ -1341,6 +1346,26 @@ public abstract class ColorSpace { } /** + * Helper method for creating native SkColorSpace. + * + * This essentially calls adapt on a ColorSpace that has not been fully + * created. It also does not fully create the adapted ColorSpace, but + * just returns the transform. + */ + @NonNull @Size(9) + private static float[] adaptToIlluminantD50( + @NonNull @Size(2) float[] origWhitePoint, + @NonNull @Size(9) float[] origTransform) { + float[] desired = ILLUMINANT_D50; + if (compare(origWhitePoint, desired)) return origTransform; + + float[] xyz = xyYToXyz(desired); + float[] adaptationTransform = chromaticAdaptation(Adaptation.BRADFORD.mTransform, + xyYToXyz(origWhitePoint), xyz); + return mul3x3(adaptationTransform, origTransform); + } + + /** * <p>Returns an instance of {@link ColorSpace} whose ID matches the * specified ID.</p> * @@ -1431,7 +1456,7 @@ public abstract class ColorSpace { "sRGB IEC61966-2.1", SRGB_PRIMARIES, ILLUMINANT_D65, - new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), + SRGB_TRANSFER_PARAMETERS, Named.SRGB.ordinal() ); sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb( @@ -1446,9 +1471,11 @@ public abstract class ColorSpace { "scRGB-nl IEC 61966-2-2:2003", SRGB_PRIMARIES, ILLUMINANT_D65, + null, x -> absRcpResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), x -> absResponse(x, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), -0.799f, 2.399f, + null, // FIXME: Use SRGB_TRANSFER_PARAMETERS Named.EXTENDED_SRGB.ordinal() ); sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( @@ -1485,7 +1512,7 @@ public abstract class ColorSpace { "Display P3", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, ILLUMINANT_D65, - new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), + SRGB_TRANSFER_PARAMETERS, Named.DISPLAY_P3.ordinal() ); sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( @@ -1967,6 +1994,15 @@ public abstract class ColorSpace { } /** + * Retrieve the native SkColorSpace object for passing to native. + * + * Only valid on ColorSpace.Rgb. + */ + long getNativeInstance() { + throw new IllegalArgumentException("colorSpace must be an RGB color space"); + } + + /** * {@usesMathJax} * * <p>An RGB color space is an additive color space using the @@ -2269,7 +2305,22 @@ public abstract class ColorSpace { private final boolean mIsWideGamut; private final boolean mIsSrgb; - @Nullable private TransferParameters mTransferParameters; + @Nullable private final TransferParameters mTransferParameters; + private final long mNativePtr; + + @Override + long getNativeInstance() { + if (mNativePtr == 0) { + // If this object has TransferParameters, it must have a native object. + throw new IllegalArgumentException("ColorSpace must use an ICC " + + "parametric transfer function! used " + this); + } + return mNativePtr; + } + + private static native long nativeGetNativeFinalizer(); + private static native long nativeCreate(float a, float b, float c, float d, + float e, float f, float g, float[] xyz); /** * <p>Creates a new RGB color space using a 3x3 column-major transform matrix. @@ -2298,8 +2349,8 @@ public abstract class ColorSpace { @NonNull @Size(9) float[] toXYZ, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf) { - this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), - oetf, eotf, 0.0f, 1.0f, MIN_ID); + this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), null, + oetf, eotf, 0.0f, 1.0f, null, MIN_ID); } /** @@ -2349,7 +2400,7 @@ public abstract class ColorSpace { @NonNull DoubleUnaryOperator eotf, float min, float max) { - this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID); + this(name, primaries, whitePoint, null, oetf, eotf, min, max, null, MIN_ID); } /** @@ -2459,7 +2510,7 @@ public abstract class ColorSpace { @NonNull @Size(min = 2, max = 3) float[] whitePoint, @NonNull TransferParameters function, @IntRange(from = MIN_ID, to = MAX_ID) int id) { - this(name, primaries, whitePoint, + this(name, primaries, whitePoint, null, function.e == 0.0 && function.f == 0.0 ? x -> rcpResponse(x, function.a, function.b, function.c, function.d, function.g) : @@ -2470,8 +2521,7 @@ public abstract class ColorSpace { function.c, function.d, function.g) : x -> response(x, function.a, function.b, function.c, function.d, function.e, function.f, function.g), - 0.0f, 1.0f, id); - mTransferParameters = function; + 0.0f, 1.0f, function, id); } /** @@ -2586,13 +2636,12 @@ public abstract class ColorSpace { float min, float max, @IntRange(from = MIN_ID, to = MAX_ID) int id) { - this(name, primaries, whitePoint, + this(name, primaries, whitePoint, null, gamma == 1.0 ? DoubleUnaryOperator.identity() : x -> Math.pow(x < 0.0 ? 0.0 : x, 1 / gamma), gamma == 1.0 ? DoubleUnaryOperator.identity() : x -> Math.pow(x < 0.0 ? 0.0 : x, gamma), - min, max, id); - mTransferParameters = new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma); + min, max, new TransferParameters(1.0, 0.0, 0.0, 0.0, gamma), id); } /** @@ -2615,10 +2664,13 @@ public abstract class ColorSpace { * @param name Name of the color space, cannot be null, its length must be >= 1 * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats + * @param transform Computed transform matrix that converts from RGB to XYZ, or + * {@code null} to compute it from {@code primaries} and {@code whitePoint}. * @param oetf Opto-electronic transfer function, cannot be null * @param eotf Electro-optical transfer function, cannot be null * @param min The minimum valid value in this color space's RGB range * @param max The maximum valid value in this color space's RGB range + * @param transferParameters Parameters for the transfer functions * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID} * * @throws IllegalArgumentException If any of the following conditions is met: @@ -2637,10 +2689,12 @@ public abstract class ColorSpace { @NonNull @Size(min = 1) String name, @NonNull @Size(min = 6, max = 9) float[] primaries, @NonNull @Size(min = 2, max = 3) float[] whitePoint, + @Nullable @Size(9) float[] transform, @NonNull DoubleUnaryOperator oetf, @NonNull DoubleUnaryOperator eotf, float min, float max, + @Nullable TransferParameters transferParameters, @IntRange(from = MIN_ID, to = MAX_ID) int id) { super(name, Model.RGB, id); @@ -2668,7 +2722,15 @@ public abstract class ColorSpace { mWhitePoint = xyWhitePoint(whitePoint); mPrimaries = xyPrimaries(primaries); - mTransform = computeXYZMatrix(mPrimaries, mWhitePoint); + if (transform == null) { + mTransform = computeXYZMatrix(mPrimaries, mWhitePoint); + } else { + if (transform.length != 9) { + throw new IllegalArgumentException("Transform must have 9 entries! Has " + + transform.length); + } + mTransform = transform; + } mInverseTransform = inverse3x3(mTransform); mOetf = oetf; @@ -2681,10 +2743,39 @@ public abstract class ColorSpace { mClampedOetf = oetf.andThen(clamp); mClampedEotf = clamp.andThen(eotf); + mTransferParameters = transferParameters; + // A color space is wide-gamut if its area is >90% of NTSC 1953 and // if it entirely contains the Color space definition in xyY mIsWideGamut = isWideGamut(mPrimaries, min, max); mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id); + + if (mTransferParameters != null) { + if (mWhitePoint == null || mTransform == null) { + throw new IllegalStateException( + "ColorSpace (" + this + ") cannot create native object! mWhitePoint: " + + mWhitePoint + " mTransform: " + mTransform); + } + + // This mimics the old code that was in native. + float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform); + mNativePtr = nativeCreate((float) mTransferParameters.a, + (float) mTransferParameters.b, + (float) mTransferParameters.c, + (float) mTransferParameters.d, + (float) mTransferParameters.e, + (float) mTransferParameters.f, + (float) mTransferParameters.g, + nativeTransform); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr); + } else { + mNativePtr = 0; + } + } + + private static class NoImagePreloadHolder { + public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0); } /** @@ -2695,27 +2786,9 @@ public abstract class ColorSpace { private Rgb(Rgb colorSpace, @NonNull @Size(9) float[] transform, @NonNull @Size(min = 2, max = 3) float[] whitePoint) { - super(colorSpace.getName(), Model.RGB, -1); - - mWhitePoint = xyWhitePoint(whitePoint); - mPrimaries = colorSpace.mPrimaries; - - mTransform = transform; - mInverseTransform = inverse3x3(transform); - - mMin = colorSpace.mMin; - mMax = colorSpace.mMax; - - mOetf = colorSpace.mOetf; - mEotf = colorSpace.mEotf; - - mClampedOetf = colorSpace.mClampedOetf; - mClampedEotf = colorSpace.mClampedEotf; - - mIsWideGamut = colorSpace.mIsWideGamut; - mIsSrgb = colorSpace.mIsSrgb; - - mTransferParameters = colorSpace.mTransferParameters; + this(colorSpace.getName(), colorSpace.mPrimaries, whitePoint, transform, + colorSpace.mOetf, colorSpace.mEotf, colorSpace.mMin, colorSpace.mMax, + colorSpace.mTransferParameters, MIN_ID); } /** diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index e3b165ccaf34..466a5fc2a770 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -1628,17 +1628,6 @@ public final class ImageDecoder implements AutoCloseable { if (mPostProcessor != null && mUnpremultipliedRequired) { throw new IllegalStateException("Cannot draw to unpremultiplied pixels!"); } - - if (mDesiredColorSpace != null) { - if (!(mDesiredColorSpace instanceof ColorSpace.Rgb)) { - throw new IllegalArgumentException("The target color space must use the " - + "RGB color model - provided: " + mDesiredColorSpace); - } - if (((ColorSpace.Rgb) mDesiredColorSpace).getTransferParameters() == null) { - throw new IllegalArgumentException("The target color space must use an " - + "ICC parametric transfer function - provided: " + mDesiredColorSpace); - } - } } private static void checkSubset(int width, int height, Rect r) { @@ -1655,10 +1644,12 @@ public final class ImageDecoder implements AutoCloseable { @NonNull private Bitmap decodeBitmapInternal() throws IOException { checkState(); + long colorSpacePtr = mDesiredColorSpace == null ? 0 : + mDesiredColorSpace.getNativeInstance(); return nDecodeBitmap(mNativePtr, this, mPostProcessor != null, mDesiredWidth, mDesiredHeight, mCropRect, mMutable, mAllocator, mUnpremultipliedRequired, - mConserveMemory, mDecodeAsAlphaMask, mDesiredColorSpace); + mConserveMemory, mDecodeAsAlphaMask, colorSpacePtr); } private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener, @@ -1946,7 +1937,7 @@ public final class ImageDecoder implements AutoCloseable { @Nullable Rect cropRect, boolean mutable, int allocator, boolean unpremulRequired, boolean conserveMemory, boolean decodeAsAlphaMask, - @Nullable ColorSpace desiredColorSpace) + long desiredColorSpace) throws IOException; private static native Size nGetSampledSize(long nativePtr, int sampleSize); diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 54e1abcaf1b7..7eee6f4bf37d 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1017,7 +1017,7 @@ public class Paint { float b = Color.blue(color); float a = Color.alpha(color); - nSetColor(mNativePaint, cs, r, g, b, a); + nSetColor(mNativePaint, cs.getNativeInstance(), r, g, b, a); mColor = color; } @@ -1455,7 +1455,7 @@ public class Paint { float g = Color.green(shadowColor); float b = Color.blue(shadowColor); float a = Color.alpha(shadowColor); - nSetShadowLayer(mNativePaint, radius, dx, dy, cs, r, g, b, a); + nSetShadowLayer(mNativePaint, radius, dx, dy, cs.getNativeInstance(), r, g, b, a); mShadowLayerRadius = radius; mShadowLayerDx = dx; @@ -3003,11 +3003,6 @@ public class Paint { int contextStart, int contextEnd, boolean isRtl, int offset); private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance); - private static native void nSetColor(long paintPtr, ColorSpace cs, - float r, float g, float b, float a); - private static native void nSetShadowLayer(long paintPtr, - float radius, float dx, float dy, ColorSpace cs, - float r, float g, float b, float a); // ---------------- @FastNative ------------------------ @@ -3063,7 +3058,8 @@ public class Paint { int mMinikinLocaleListId); @CriticalNative private static native void nSetShadowLayer(long paintPtr, - float radius, float dx, float dy, @ColorInt int color); + float radius, float dx, float dy, long colorSpaceHandle, + float r, float g, float b, float a); @CriticalNative private static native boolean nHasShadowLayer(long paintPtr); @CriticalNative @@ -3111,6 +3107,9 @@ public class Paint { @CriticalNative private static native void nSetFilterBitmap(long paintPtr, boolean filter); @CriticalNative + private static native void nSetColor(long paintPtr, long colorSpaceHandle, + float r, float g, float b, float a); + @CriticalNative private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText); @CriticalNative private static native boolean nIsElegantTextHeight(long paintPtr); diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 154bd563fb0f..3d0afb098697 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -16,6 +16,8 @@ package android.location; +import android.Manifest; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.os.Build; @@ -161,6 +163,7 @@ public final class LocationRequest implements Parcelable { private WorkSource mWorkSource = null; @UnsupportedAppUsage private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps + private boolean mLocationSettingsIgnored = false; @UnsupportedAppUsage private String mProvider = LocationManager.FUSED_PROVIDER; @@ -261,6 +264,7 @@ public final class LocationRequest implements Parcelable { mWorkSource = src.mWorkSource; mHideFromAppOps = src.mHideFromAppOps; mLowPowerMode = src.mLowPowerMode; + mLocationSettingsIgnored = src.mLocationSettingsIgnored; } /** @@ -375,6 +379,32 @@ public final class LocationRequest implements Parcelable { } /** + * Requests that user location settings be ignored in order to satisfy this request. This API + * is only for use in extremely rare scenarios where it is appropriate to ignore user location + * settings, such as a user initiated emergency (dialing 911 for instance). + * + * @param locationSettingsIgnored Whether to ignore location settings + * @return the same object, so that setters can be chained + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + @SystemApi + public LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) { + mLocationSettingsIgnored = locationSettingsIgnored; + return this; + } + + /** + * Returns true if location settings will be ignored in order to satisfy this request. + * + * @hide + */ + @SystemApi + public boolean isLocationSettingsIgnored() { + return mLocationSettingsIgnored; + } + + /** * Explicitly set the fastest interval for location updates, in * milliseconds. * diff --git a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java new file mode 100644 index 000000000000..843dd670315a --- /dev/null +++ b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.location.provider; + +import android.annotation.NonNull; + +import java.security.InvalidParameterException; +import java.util.List; + +/** + * A class representing an event for Activity changes. + * @hide + */ +public class ActivityChangedEvent { + private final List<ActivityRecognitionEvent> mActivityRecognitionEvents; + + public ActivityChangedEvent(List<ActivityRecognitionEvent> activityRecognitionEvents) { + if (activityRecognitionEvents == null) { + throw new InvalidParameterException( + "Parameter 'activityRecognitionEvents' must not be null."); + } + + mActivityRecognitionEvents = activityRecognitionEvents; + } + + @NonNull + public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() { + return mActivityRecognitionEvents; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:"); + + for (ActivityRecognitionEvent event : mActivityRecognitionEvents) { + builder.append("\n "); + builder.append(event.toString()); + } + builder.append("\n]"); + + return builder.toString(); + } +} diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java new file mode 100644 index 000000000000..e54dea40d690 --- /dev/null +++ b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.location.provider; + +/** + * A class that represents an Activity Recognition Event. + * @hide + */ +public class ActivityRecognitionEvent { + private final String mActivity; + private final int mEventType; + private final long mTimestampNs; + + public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) { + mActivity = activity; + mEventType = eventType; + mTimestampNs = timestampNs; + } + + public String getActivity() { + return mActivity; + } + + public int getEventType() { + return mEventType; + } + + public long getTimestampNs() { + return mTimestampNs; + } + + @Override + public String toString() { + String eventString; + switch (mEventType) { + case ActivityRecognitionProvider.EVENT_TYPE_ENTER: + eventString = "Enter"; + break; + case ActivityRecognitionProvider.EVENT_TYPE_EXIT: + eventString = "Exit"; + break; + case ActivityRecognitionProvider.EVENT_TYPE_FLUSH_COMPLETE: + eventString = "FlushComplete"; + break; + default: + eventString = "<Invalid>"; + break; + } + + return String.format( + "Activity='%s', EventType=%s(%s), TimestampNs=%s", + mActivity, + eventString, + mEventType, + mTimestampNs); + } +} diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java new file mode 100644 index 000000000000..0eff7d3f2014 --- /dev/null +++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.location.provider; + +import com.android.internal.util.Preconditions; + +import android.hardware.location.IActivityRecognitionHardware; +import android.hardware.location.IActivityRecognitionHardwareSink; +import android.os.RemoteException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +/** + * A class that exposes {@link IActivityRecognitionHardware} functionality to unbundled services. + * @hide + */ +public final class ActivityRecognitionProvider { + private final IActivityRecognitionHardware mService; + private final HashSet<Sink> mSinkSet = new HashSet<>(); + + // the following constants must remain in sync with activity_recognition.h + + public static final String ACTIVITY_IN_VEHICLE = "android.activity_recognition.in_vehicle"; + public static final String ACTIVITY_ON_BICYCLE = "android.activity_recognition.on_bicycle"; + public static final String ACTIVITY_WALKING = "android.activity_recognition.walking"; + public static final String ACTIVITY_RUNNING = "android.activity_recognition.running"; + public static final String ACTIVITY_STILL = "android.activity_recognition.still"; + public static final String ACTIVITY_TILTING = "android.activity_recognition.tilting"; + + // NOTE: when adding an additional EVENT_TYPE_, EVENT_TYPE_COUNT needs to be updated in + // android.hardware.location.ActivityRecognitionHardware + public static final int EVENT_TYPE_FLUSH_COMPLETE = 0; + public static final int EVENT_TYPE_ENTER = 1; + public static final int EVENT_TYPE_EXIT = 2; + + // end constants activity_recognition.h + + /** + * Used to receive Activity-Recognition events. + */ + public interface Sink { + void onActivityChanged(ActivityChangedEvent event); + } + + public ActivityRecognitionProvider(IActivityRecognitionHardware service) + throws RemoteException { + Preconditions.checkNotNull(service); + mService = service; + mService.registerSink(new SinkTransport()); + } + + public String[] getSupportedActivities() throws RemoteException { + return mService.getSupportedActivities(); + } + + public boolean isActivitySupported(String activity) throws RemoteException { + return mService.isActivitySupported(activity); + } + + public void registerSink(Sink sink) { + Preconditions.checkNotNull(sink); + synchronized (mSinkSet) { + mSinkSet.add(sink); + } + } + + // TODO: if this functionality is exposed to 3rd party developers, handle unregistration (here + // and in the service) of all sinks while failing to disable all events + public void unregisterSink(Sink sink) { + Preconditions.checkNotNull(sink); + synchronized (mSinkSet) { + mSinkSet.remove(sink); + } + } + + public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) + throws RemoteException { + return mService.enableActivityEvent(activity, eventType, reportLatencyNs); + } + + public boolean disableActivityEvent(String activity, int eventType) throws RemoteException { + return mService.disableActivityEvent(activity, eventType); + } + + public boolean flush() throws RemoteException { + return mService.flush(); + } + + private final class SinkTransport extends IActivityRecognitionHardwareSink.Stub { + @Override + public void onActivityChanged(android.hardware.location.ActivityChangedEvent event) { + Collection<Sink> sinks; + synchronized (mSinkSet) { + if (mSinkSet.isEmpty()) { + return; + } + sinks = new ArrayList<>(mSinkSet); + } + + // translate the event from platform internal and GmsCore types + ArrayList<ActivityRecognitionEvent> gmsEvents = new ArrayList<>(); + for (android.hardware.location.ActivityRecognitionEvent reportingEvent + : event.getActivityRecognitionEvents()) { + ActivityRecognitionEvent gmsEvent = new ActivityRecognitionEvent( + reportingEvent.getActivity(), + reportingEvent.getEventType(), + reportingEvent.getTimestampNs()); + gmsEvents.add(gmsEvent); + } + ActivityChangedEvent gmsEvent = new ActivityChangedEvent(gmsEvents); + + for (Sink sink : sinks) { + sink.onActivityChanged(gmsEvent); + } + } + } +} diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java new file mode 100644 index 000000000000..326d901b9daa --- /dev/null +++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.location.provider; + +import android.annotation.NonNull; +import android.hardware.location.IActivityRecognitionHardware; +import android.hardware.location.IActivityRecognitionHardwareClient; +import android.os.Binder; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; + +/** + * A client class for interaction with an Activity-Recognition provider. + * @hide + */ +public abstract class ActivityRecognitionProviderClient { + private static final String TAG = "ArProviderClient"; + + protected ActivityRecognitionProviderClient() {} + + private IActivityRecognitionHardwareClient.Stub mClient = + new IActivityRecognitionHardwareClient.Stub() { + @Override + public void onAvailabilityChanged( + boolean isSupported, + IActivityRecognitionHardware instance) { + int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID) { + Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid); + return; + } + ActivityRecognitionProvider provider; + try { + provider = isSupported ? new ActivityRecognitionProvider(instance) : null; + } catch (RemoteException e) { + Log.e(TAG, "Error creating Hardware Activity-Recognition Provider.", e); + return; + } + onProviderChanged(isSupported, provider); + } + }; + + /** + * Gets the binder needed to interact with proxy provider in the platform. + */ + @NonNull + public IBinder getBinder() { + return mClient; + } + + /** + * Called when a change in the availability of {@link ActivityRecognitionProvider} is detected. + * + * @param isSupported whether the platform supports the provider natively + * @param instance the available provider's instance + */ + public abstract void onProviderChanged( + boolean isSupported, + ActivityRecognitionProvider instance); +} diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java new file mode 100644 index 000000000000..42f77b42766f --- /dev/null +++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.location.provider; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.location.IActivityRecognitionHardware; +import android.hardware.location.IActivityRecognitionHardwareWatcher; +import android.os.Binder; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; + +/** + * A watcher class for Activity-Recognition instances. + * + * @deprecated use {@link ActivityRecognitionProviderClient} instead. + * @hide + */ +@Deprecated +public class ActivityRecognitionProviderWatcher { + private static final String TAG = "ActivityRecognitionProviderWatcher"; + + private static ActivityRecognitionProviderWatcher sWatcher; + private static final Object sWatcherLock = new Object(); + + private ActivityRecognitionProvider mActivityRecognitionProvider; + + private ActivityRecognitionProviderWatcher() {} + + public static ActivityRecognitionProviderWatcher getInstance() { + synchronized (sWatcherLock) { + if (sWatcher == null) { + sWatcher = new ActivityRecognitionProviderWatcher(); + } + return sWatcher; + } + } + + private IActivityRecognitionHardwareWatcher.Stub mWatcherStub = + new IActivityRecognitionHardwareWatcher.Stub() { + @Override + public void onInstanceChanged(IActivityRecognitionHardware instance) { + int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID) { + Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid); + return; + } + + try { + mActivityRecognitionProvider = new ActivityRecognitionProvider(instance); + } catch (RemoteException e) { + Log.e(TAG, "Error creating Hardware Activity-Recognition", e); + } + } + }; + + /** + * Gets the binder needed to interact with proxy provider in the platform. + */ + @NonNull + public IBinder getBinder() { + return mWatcherStub; + } + + /** + * Gets an object that supports the functionality of {@link ActivityRecognitionProvider}. + * + * @return Non-null value if the functionality is supported by the platform, false otherwise. + */ + @Nullable + public ActivityRecognitionProvider getActivityRecognitionProvider() { + return mActivityRecognitionProvider; + } +} diff --git a/lowpan/tests/Android.mk b/lowpan/tests/Android.mk index 67727a7b1baa..832ed2f53f7b 100644 --- a/lowpan/tests/Android.mk +++ b/lowpan/tests/Android.mk @@ -45,7 +45,7 @@ LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include) LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude) LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ + androidx.test.rules \ guava \ mockito-target-minus-junit4 \ frameworks-base-testutils \ diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml index a21621423e41..4225613b4cc8 100644 --- a/lowpan/tests/AndroidManifest.xml +++ b/lowpan/tests/AndroidManifest.xml @@ -30,7 +30,7 @@ </activity> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="android.net.lowpan.test" android:label="Frameworks LoWPAN API Tests"> </instrumentation> diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml index 72ad050efaeb..978cc02d2f57 100644 --- a/lowpan/tests/AndroidTest.xml +++ b/lowpan/tests/AndroidTest.xml @@ -22,6 +22,6 @@ <option name="test-tag" value="FrameworksLowpanApiTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="android.net.lowpan.test" /> - <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> </test> </configuration> diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md index d0eed95739a3..cb5772ee48ab 100644 --- a/lowpan/tests/README.md +++ b/lowpan/tests/README.md @@ -37,7 +37,7 @@ runtests.sh -e class android.net.lowpan.LowpanManagerTest If you manually build and push the test APK to the device you can run tests using ``` -adb shell am instrument -w 'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner' +adb shell am instrument -w 'android.net.wifi.test/androidx.test.runner.AndroidJUnitRunner' ``` ## Adding Tests diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh index 040f4f0492e9..8267a7975c42 100755 --- a/lowpan/tests/runtests.sh +++ b/lowpan/tests/runtests.sh @@ -21,4 +21,4 @@ adb wait-for-device adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk" -adb shell am instrument -w "$@" 'android.net.lowpan.test/android.support.test.runner.AndroidJUnitRunner' +adb shell am instrument -w "$@" 'android.net.lowpan.test/androidx.test.runner.AndroidJUnitRunner' diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java index a495d3d7a784..86f9d0e3ca69 100644 --- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java +++ b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java @@ -23,15 +23,18 @@ import android.content.pm.ApplicationInfo; import android.os.Handler; import android.os.IBinder; import android.os.test.TestLooper; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import java.util.Map; + +import androidx.test.runner.AndroidJUnit4; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Map; + /** Unit tests for android.net.lowpan.LowpanInterface. */ @RunWith(AndroidJUnit4.class) @SmallTest diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java index 3dd7504d8332..998e8a549540 100644 --- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java +++ b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java @@ -26,8 +26,10 @@ import android.content.pm.ApplicationInfo; import android.os.Handler; import android.os.IBinder; import android.os.test.TestLooper; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.runner.AndroidJUnit4; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java index 52771e4199d8..1d763ced3ca0 100644 --- a/media/java/android/media/AudioRecordingConfiguration.java +++ b/media/java/android/media/AudioRecordingConfiguration.java @@ -356,11 +356,11 @@ public final class AudioRecordingConfiguration implements Parcelable { dest.writeInt(mDeviceSource); dest.writeInt(mClientEffects.length); for (int i = 0; i < mClientEffects.length; i++) { - mClientEffects[i].writeToParcel(dest, 0); + mClientEffects[i].writeToParcel(dest); } dest.writeInt(mDeviceEffects.length); for (int i = 0; i < mDeviceEffects.length; i++) { - mDeviceEffects[i].writeToParcel(dest, 0); + mDeviceEffects[i].writeToParcel(dest); } } @@ -375,13 +375,13 @@ public final class AudioRecordingConfiguration implements Parcelable { mClientPortId = in.readInt(); mClientSilenced = in.readBoolean(); mDeviceSource = in.readInt(); - mClientEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt()); + mClientEffects = new AudioEffect.Descriptor[in.readInt()]; for (int i = 0; i < mClientEffects.length; i++) { - mClientEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in); + mClientEffects[i] = new AudioEffect.Descriptor(in); } - mDeviceEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt()); - for (int i = 0; i < mClientEffects.length; i++) { - mDeviceEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in); + mDeviceEffects = new AudioEffect.Descriptor[in.readInt()]; + for (int i = 0; i < mDeviceEffects.length; i++) { + mDeviceEffects[i] = new AudioEffect.Descriptor(in); } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 2848b89a45ca..af016d5d4be9 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1100,7 +1100,8 @@ public class AudioSystem (1 << STREAM_RING) | (1 << STREAM_NOTIFICATION) | (1 << STREAM_SYSTEM) | - (1 << STREAM_VOICE_CALL); + (1 << STREAM_VOICE_CALL) | + (1 << STREAM_BLUETOOTH_SCO); /** * Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes. diff --git a/media/java/android/media/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl index a591c1119aeb..74c05c407571 100644 --- a/media/java/android/media/IRemoteVolumeController.aidl +++ b/media/java/android/media/IRemoteVolumeController.aidl @@ -9,6 +9,7 @@ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. @@ -16,7 +17,7 @@ package android.media; -import android.media.session.ISessionController; +import android.media.session.MediaSession; /** * AIDL for the MediaSessionService to report interesting events on remote playback @@ -25,8 +26,8 @@ import android.media.session.ISessionController; * @hide */ oneway interface IRemoteVolumeController { - void remoteVolumeChanged(in ISessionController session, int flags); + void remoteVolumeChanged(in MediaSession.Token sessionToken, int flags); // sets the default session to use with the slider, replaces remoteSliderVisibility // on IVolumeController - void updateRemoteController(in ISessionController session); + void updateRemoteController(in MediaSession.Token sessionToken); } diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index dd971959dd25..887b4475a4d1 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -45,7 +45,7 @@ import java.util.concurrent.Executor; /** * Allows an app to interact with an active {@link MediaSession2} or a - * MediaSession2Service which would provide {@link MediaSession2}. Media buttons and other + * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other * commands can be sent to the session. * <p> * This API is not generally intended for third party application developers. @@ -53,7 +53,6 @@ import java.util.concurrent.Executor; * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. */ -// TODO: use @link for MediaSession2Service public class MediaController2 implements AutoCloseable { static final String TAG = "MediaController2"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -71,6 +70,8 @@ public class MediaController2 implements AutoCloseable { private final Object mLock = new Object(); //@GuardedBy("mLock") + private boolean mClosed; + //@GuardedBy("mLock") private int mNextSeqNumber; //@GuardedBy("mLock") private Session2Link mSessionBinder; @@ -141,7 +142,14 @@ public class MediaController2 implements AutoCloseable { @Override public void close() { synchronized (mLock) { + if (mClosed) { + // Already closed. Ignore rest of clean up code. + // Note: unbindService() throws IllegalArgumentException when it's called twice. + return; + } + mClosed = true; if (mServiceConnection != null) { + // Note: This should be called even when the bindService() has returned false. mContext.unbindService(mServiceConnection); } if (mSessionBinder != null) { @@ -167,7 +175,7 @@ public class MediaController2 implements AutoCloseable { * If it is not connected yet, it returns {@code null}. * <p> * This may differ with the {@link Session2Token} from the constructor. For example, if the - * controller is created with the token for MediaSession2Service, this would return + * controller is created with the token for {@link MediaSession2Service}, this would return * token for the {@link MediaSession2} in the service. * * @return Session2Token of the connected session, or {@code null} if not connected @@ -316,7 +324,7 @@ public class MediaController2 implements AutoCloseable { MediaController2.this, command, args); if (resultReceiver != null) { if (result == null) { - throw new RuntimeException("onSessionCommand shouldn't return null"); + resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null); } else { resultReceiver.send(result.getResultCode(), result.getResultData()); } @@ -433,8 +441,8 @@ public class MediaController2 implements AutoCloseable { * @param controller the controller for this event * @param command the session command * @param args optional arguments - * @return the result for the session command. A runtime exception will be thrown if null - * is returned. + * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED + * will be sent to the session. */ @Nullable public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller, diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 3adac7295fff..fdd07fdd52e3 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -90,6 +90,8 @@ public class MediaSession2 implements AutoCloseable { private boolean mClosed; //@GuardedBy("mLock") private boolean mPlaybackActive; + //@GuardedBy("mLock") + private ForegroundServiceEventCallback mForegroundServiceEventCallback; MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity, @NonNull Executor callbackExecutor, @NonNull SessionCallback callback) { @@ -119,6 +121,7 @@ public class MediaSession2 implements AutoCloseable { public void close() { try { List<ControllerInfo> controllerInfos; + ForegroundServiceEventCallback callback; synchronized (mLock) { if (mClosed) { return; @@ -126,11 +129,15 @@ public class MediaSession2 implements AutoCloseable { mClosed = true; controllerInfos = getConnectedControllers(); mConnectedControllers.clear(); - mCallback.onSessionClosed(this); + callback = mForegroundServiceEventCallback; + mForegroundServiceEventCallback = null; } synchronized (MediaSession2.class) { SESSION_ID_LIST.remove(mSessionId); } + if (callback != null) { + callback.onSessionClosed(this); + } for (ControllerInfo info : controllerInfos) { info.notifyDisconnected(); } @@ -224,11 +231,16 @@ public class MediaSession2 implements AutoCloseable { * @param playbackActive {@code true} if the playback active, {@code false} otherwise. **/ public void setPlaybackActive(boolean playbackActive) { + final ForegroundServiceEventCallback serviceCallback; synchronized (mLock) { if (mPlaybackActive == playbackActive) { return; } mPlaybackActive = playbackActive; + serviceCallback = mForegroundServiceEventCallback; + } + if (serviceCallback != null) { + serviceCallback.onPlaybackActiveChanged(this, playbackActive); } List<ControllerInfo> controllerInfos = getConnectedControllers(); for (ControllerInfo controller : controllerInfos) { @@ -257,6 +269,18 @@ public class MediaSession2 implements AutoCloseable { return mCallback; } + void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { + synchronized (mLock) { + if (mForegroundServiceEventCallback == callback) { + return; + } + if (mForegroundServiceEventCallback != null && callback != null) { + throw new IllegalStateException("A session cannot be added to multiple services"); + } + mForegroundServiceEventCallback = callback; + } + } + // Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq, Bundle connectionRequest) { @@ -373,7 +397,7 @@ public class MediaSession2 implements AutoCloseable { MediaSession2.this, controllerInfo, command, args); if (resultReceiver != null) { if (result == null) { - throw new RuntimeException("onSessionCommand shouldn't return null"); + resultReceiver.send(Session2Command.RESULT_INFO_SKIPPED, null); } else { resultReceiver.send(result.getResultCode(), result.getResultData()); } @@ -695,8 +719,6 @@ public class MediaSession2 implements AutoCloseable { * This API is not generally intended for third party application developers. */ public abstract static class SessionCallback { - ForegroundServiceEventCallback mForegroundServiceEventCallback; - /** * Called when a controller is created for this session. Return allowed commands for * controller. By default it returns {@code null}. @@ -731,8 +753,8 @@ public class MediaSession2 implements AutoCloseable { * @param controller controller information * @param command the session command * @param args optional arguments - * @return the result for the session command. A runtime exception will be thrown if null - * is returned. + * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED + * will be sent to the session. */ @Nullable public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session, @@ -753,19 +775,10 @@ public class MediaSession2 implements AutoCloseable { public void onCommandResult(@NonNull MediaSession2 session, @NonNull ControllerInfo controller, @NonNull Object token, @NonNull Session2Command command, @NonNull Session2Command.Result result) {} + } - final void onSessionClosed(MediaSession2 session) { - if (mForegroundServiceEventCallback != null) { - mForegroundServiceEventCallback.onSessionClosed(session); - } - } - - void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) { - mForegroundServiceEventCallback = callback; - } - - abstract static class ForegroundServiceEventCallback { - public void onSessionClosed(MediaSession2 session) {} - } + abstract static class ForegroundServiceEventCallback { + public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {} + public void onSessionClosed(MediaSession2 session) {} } } diff --git a/media/java/android/media/MediaSession2Service.java b/media/java/android/media/MediaSession2Service.java index 8fb00fe487e8..5bb746a7f9e3 100644 --- a/media/java/android/media/MediaSession2Service.java +++ b/media/java/android/media/MediaSession2Service.java @@ -19,7 +19,10 @@ package android.media; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Notification; +import android.app.NotificationManager; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Bundle; @@ -28,8 +31,6 @@ import android.os.IBinder; import android.util.ArrayMap; import android.util.Log; -import com.android.internal.annotations.GuardedBy; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -42,11 +43,7 @@ import java.util.Map; * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. - * @hide */ -// TODO: Unhide -// TODO: Add onUpdateNotification(), and calls it to get Notification for startForegroundService() -// when a session's player state becomes playing. public abstract class MediaSession2Service extends Service { /** * The {@link Intent} that must be declared as handled by the service. @@ -56,10 +53,29 @@ public abstract class MediaSession2Service extends Service { private static final String TAG = "MediaSession2Service"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback = + new MediaSession2.ForegroundServiceEventCallback() { + @Override + public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { + MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive); + } + + @Override + public void onSessionClosed(MediaSession2 session) { + removeSession(session); + } + }; + private final Object mLock = new Object(); - @GuardedBy("mLock") + //@GuardedBy("mLock") + private NotificationManager mNotificationManager; + //@GuardedBy("mLock") + private Intent mStartSelfIntent; + //@GuardedBy("mLock") private Map<String, MediaSession2> mSessions = new ArrayMap<>(); - + //@GuardedBy("mLock") + private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>(); + //@GuardedBy("mLock") private MediaSession2ServiceStub mStub; /** @@ -72,7 +88,12 @@ public abstract class MediaSession2Service extends Service { @Override public void onCreate() { super.onCreate(); - mStub = new MediaSession2ServiceStub(this); + synchronized (mLock) { + mStub = new MediaSession2ServiceStub(this); + mStartSelfIntent = new Intent(this, this.getClass()); + mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } } @CallSuper @@ -80,18 +101,13 @@ public abstract class MediaSession2Service extends Service { @Nullable public IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mStub; + synchronized (mLock) { + return mStub; + } } return null; } - @CallSuper - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // TODO: Dispatch media key events to the primary session. - return START_STICKY; - } - /** * Called by the system to notify that it is no longer used and is being removed. Do not call * this method directly. @@ -104,10 +120,12 @@ public abstract class MediaSession2Service extends Service { public void onDestroy() { super.onDestroy(); synchronized (mLock) { - for (MediaSession2 session : mSessions.values()) { - session.getCallback().setForegroundServiceEventCallback(null); + List<MediaSession2> sessions = getSessions(); + for (MediaSession2 session : sessions) { + removeSession(session); } mSessions.clear(); + mNotifications.clear(); } mStub.close(); } @@ -144,6 +162,24 @@ public abstract class MediaSession2Service extends Service { public abstract MediaSession2 onGetPrimarySession(); /** + * Called when notification UI needs update. Override this method to show or cancel your own + * notification UI. + * <p> + * This would be called on {@link MediaSession2}'s callback executor when playback state is + * changed. + * <p> + * With the notification returned here, the service becomes foreground service when the playback + * is started. Apps must request the permission + * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes + * background service after the playback is stopped. + * + * @param session a session that needs notification update. + * @return a {@link MediaNotification}. Can be {@code null}. + */ + @Nullable + public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session); + + /** * Adds a session to this service. * <p> * Added session will be removed automatically when it's closed, or removed when @@ -161,21 +197,15 @@ public abstract class MediaSession2Service extends Service { } synchronized (mLock) { MediaSession2 previousSession = mSessions.get(session.getSessionId()); - if (previousSession != session) { - if (previousSession != null) { + if (previousSession != null) { + if (previousSession != session) { Log.w(TAG, "Session ID should be unique, ID=" + session.getSessionId() + ", previous=" + previousSession + ", session=" + session); } return; } mSessions.put(session.getSessionId(), session); - session.getCallback().setForegroundServiceEventCallback( - new MediaSession2.SessionCallback.ForegroundServiceEventCallback() { - @Override - public void onSessionClosed(MediaSession2 session) { - removeSession(session); - } - }); + session.setForegroundServiceEventCallback(mForegroundServiceEventCallback); } } @@ -189,8 +219,21 @@ public abstract class MediaSession2Service extends Service { if (session == null) { throw new IllegalArgumentException("session shouldn't be null"); } + MediaNotification notification; synchronized (mLock) { + if (mSessions.get(session.getSessionId()) != session) { + // Session isn't added or removed already. + return; + } mSessions.remove(session.getSessionId()); + notification = mNotifications.remove(session); + } + session.setForegroundServiceEventCallback(null); + if (notification != null) { + mNotificationManager.cancel(notification.getNotificationId()); + } + if (getSessions().isEmpty()) { + stopForeground(false); } } @@ -207,6 +250,78 @@ public abstract class MediaSession2Service extends Service { return list; } + /** + * Called by registered {@link MediaSession2.ForegroundServiceEventCallback} + * + * @param session session with change + * @param playbackActive {@code true} if playback is active. + */ + void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) { + MediaNotification mediaNotification = onUpdateNotification(session); + if (mediaNotification == null) { + // The service implementation doesn't want to use the automatic start/stopForeground + // feature. + return; + } + synchronized (mLock) { + mNotifications.put(session, mediaNotification); + } + int id = mediaNotification.getNotificationId(); + Notification notification = mediaNotification.getNotification(); + if (!playbackActive) { + mNotificationManager.notify(id, notification); + return; + } + // playbackActive == true + startForegroundService(mStartSelfIntent); + startForeground(id, notification); + } + + /** + * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service + * foreground service to keep playback running in the background. It's highly recommended to + * show media style notification here. + */ + public static class MediaNotification { + private final int mNotificationId; + private final Notification mNotification; + + /** + * Default constructor + * + * @param notificationId notification id to be used for + * {@link NotificationManager#notify(int, Notification)}. + * @param notification a notification to make session service run in the foreground. Media + * style notification is recommended here. + */ + public MediaNotification(int notificationId, @NonNull Notification notification) { + if (notification == null) { + throw new IllegalArgumentException("notification shouldn't be null"); + } + mNotificationId = notificationId; + mNotification = notification; + } + + /** + * Gets the id of the notification. + * + * @return the notification id + */ + public int getNotificationId() { + return mNotificationId; + } + + /** + * Gets the notification. + * + * @return the notification + */ + @NonNull + public Notification getNotification() { + return mNotification; + } + } + private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub implements AutoCloseable { final WeakReference<MediaSession2Service> mService; diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 3b51c82e06c9..325420b06122 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -21,18 +21,14 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; +import android.media.session.MediaSession; import android.media.session.MediaSessionLegacyHelper; import android.media.session.PlaybackState; -import android.media.session.MediaSession; import android.os.Bundle; -import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.os.SystemClock; import android.util.Log; -import java.lang.IllegalArgumentException; - /** * RemoteControlClient enables exposing information meant to be consumed by remote controls * capable of displaying metadata, artwork and media transport control buttons. @@ -682,7 +678,7 @@ import java.lang.IllegalArgumentException; // USE_SESSIONS if (mSession != null) { - int pbState = PlaybackState.getStateFromRccState(state); + int pbState = getStateFromRccState(state); long position = hasPosition ? mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN; @@ -718,8 +714,7 @@ import java.lang.IllegalArgumentException; // USE_SESSIONS if (mSession != null) { PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState); - bob.setActions( - PlaybackState.getActionsFromRccControlFlags(transportControlFlags)); + bob.setActions(getActionsFromRccControlFlags(transportControlFlags)); mSessionPlaybackState = bob.build(); mSession.setPlaybackState(mSessionPlaybackState); } @@ -1001,16 +996,19 @@ import java.lang.IllegalArgumentException; * Period for playback position drift checks, 15s when playing at 1x or slower. */ private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000; + /** * Minimum period for playback position drift checks, never more often when every 2s, when * fast forwarding or rewinding. */ private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000; + /** * The value above which the difference between client-reported playback position and * estimated position is considered a drift. */ private final static long POSITION_DRIFT_MAX_MS = 500; + /** * Compute the period at which the estimated playback position should be compared against the * actual playback position. Is a funciton of playback speed. @@ -1025,4 +1023,151 @@ import java.lang.IllegalArgumentException; POSITION_REFRESH_PERIOD_MIN_MS); } } + + /** + * Get the {@link PlaybackState} state for the given + * {@link RemoteControlClient} state. + * + * @param rccState The state used by {@link RemoteControlClient}. + * @return The equivalent state used by {@link PlaybackState}. + */ + private static int getStateFromRccState(int rccState) { + switch (rccState) { + case PLAYSTATE_BUFFERING: + return PlaybackState.STATE_BUFFERING; + case PLAYSTATE_ERROR: + return PlaybackState.STATE_ERROR; + case PLAYSTATE_FAST_FORWARDING: + return PlaybackState.STATE_FAST_FORWARDING; + case PLAYSTATE_NONE: + return PlaybackState.STATE_NONE; + case PLAYSTATE_PAUSED: + return PlaybackState.STATE_PAUSED; + case PLAYSTATE_PLAYING: + return PlaybackState.STATE_PLAYING; + case PLAYSTATE_REWINDING: + return PlaybackState.STATE_REWINDING; + case PLAYSTATE_SKIPPING_BACKWARDS: + return PlaybackState.STATE_SKIPPING_TO_PREVIOUS; + case PLAYSTATE_SKIPPING_FORWARDS: + return PlaybackState.STATE_SKIPPING_TO_NEXT; + case PLAYSTATE_STOPPED: + return PlaybackState.STATE_STOPPED; + default: + return -1; + } + } + + /** + * Get the {@link RemoteControlClient} state for the given + * {@link PlaybackState} state. + * + * @param state The state used by {@link PlaybackState}. + * @return The equivalent state used by {@link RemoteControlClient}. + */ + static int getRccStateFromState(int state) { + switch (state) { + case PlaybackState.STATE_BUFFERING: + return PLAYSTATE_BUFFERING; + case PlaybackState.STATE_ERROR: + return PLAYSTATE_ERROR; + case PlaybackState.STATE_FAST_FORWARDING: + return PLAYSTATE_FAST_FORWARDING; + case PlaybackState.STATE_NONE: + return PLAYSTATE_NONE; + case PlaybackState.STATE_PAUSED: + return PLAYSTATE_PAUSED; + case PlaybackState.STATE_PLAYING: + return PLAYSTATE_PLAYING; + case PlaybackState.STATE_REWINDING: + return PLAYSTATE_REWINDING; + case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: + return PLAYSTATE_SKIPPING_BACKWARDS; + case PlaybackState.STATE_SKIPPING_TO_NEXT: + return PLAYSTATE_SKIPPING_FORWARDS; + case PlaybackState.STATE_STOPPED: + return PLAYSTATE_STOPPED; + default: + return -1; + } + } + + private static long getActionsFromRccControlFlags(int rccFlags) { + long actions = 0; + long flag = 1; + while (flag <= rccFlags) { + if ((flag & rccFlags) != 0) { + actions |= getActionForRccFlag((int) flag); + } + flag = flag << 1; + } + return actions; + } + + static int getRccControlFlagsFromActions(long actions) { + int rccFlags = 0; + long action = 1; + while (action <= actions && action < Integer.MAX_VALUE) { + if ((action & actions) != 0) { + rccFlags |= getRccFlagForAction(action); + } + action = action << 1; + } + return rccFlags; + } + + private static long getActionForRccFlag(int flag) { + switch (flag) { + case FLAG_KEY_MEDIA_PREVIOUS: + return PlaybackState.ACTION_SKIP_TO_PREVIOUS; + case FLAG_KEY_MEDIA_REWIND: + return PlaybackState.ACTION_REWIND; + case FLAG_KEY_MEDIA_PLAY: + return PlaybackState.ACTION_PLAY; + case FLAG_KEY_MEDIA_PLAY_PAUSE: + return PlaybackState.ACTION_PLAY_PAUSE; + case FLAG_KEY_MEDIA_PAUSE: + return PlaybackState.ACTION_PAUSE; + case FLAG_KEY_MEDIA_STOP: + return PlaybackState.ACTION_STOP; + case FLAG_KEY_MEDIA_FAST_FORWARD: + return PlaybackState.ACTION_FAST_FORWARD; + case FLAG_KEY_MEDIA_NEXT: + return PlaybackState.ACTION_SKIP_TO_NEXT; + case FLAG_KEY_MEDIA_POSITION_UPDATE: + return PlaybackState.ACTION_SEEK_TO; + case FLAG_KEY_MEDIA_RATING: + return PlaybackState.ACTION_SET_RATING; + } + return 0; + } + + private static int getRccFlagForAction(long action) { + // We only care about the lower set of actions that can map to rcc + // flags. + int testAction = action < Integer.MAX_VALUE ? (int) action : 0; + switch (testAction) { + case (int) PlaybackState.ACTION_SKIP_TO_PREVIOUS: + return FLAG_KEY_MEDIA_PREVIOUS; + case (int) PlaybackState.ACTION_REWIND: + return FLAG_KEY_MEDIA_REWIND; + case (int) PlaybackState.ACTION_PLAY: + return FLAG_KEY_MEDIA_PLAY; + case (int) PlaybackState.ACTION_PLAY_PAUSE: + return FLAG_KEY_MEDIA_PLAY_PAUSE; + case (int) PlaybackState.ACTION_PAUSE: + return FLAG_KEY_MEDIA_PAUSE; + case (int) PlaybackState.ACTION_STOP: + return FLAG_KEY_MEDIA_STOP; + case (int) PlaybackState.ACTION_FAST_FORWARD: + return FLAG_KEY_MEDIA_FAST_FORWARD; + case (int) PlaybackState.ACTION_SKIP_TO_NEXT: + return FLAG_KEY_MEDIA_NEXT; + case (int) PlaybackState.ACTION_SEEK_TO: + return FLAG_KEY_MEDIA_POSITION_UPDATE; + case (int) PlaybackState.ACTION_SET_RATING: + return FLAG_KEY_MEDIA_RATING; + } + return 0; + } } diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java index 5e9eed737256..f70963a982e4 100644 --- a/media/java/android/media/RemoteController.java +++ b/media/java/android/media/RemoteController.java @@ -632,8 +632,8 @@ import java.util.List; l = this.mOnClientUpdateListener; } if (l != null) { - int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE : PlaybackState - .getRccStateFromState(state.getState()); + int playstate = state == null ? RemoteControlClient.PLAYSTATE_NONE + : RemoteControlClient.getRccStateFromState(state.getState()); if (state == null || state.getPosition() == PlaybackState.PLAYBACK_POSITION_UNKNOWN) { l.onClientPlaybackStateUpdate(playstate); } else { @@ -642,7 +642,7 @@ import java.util.List; } if (state != null) { l.onClientTransportControlUpdate( - PlaybackState.getRccControlFlagsFromActions(state.getActions())); + RemoteControlClient.getRccControlFlagsFromActions(state.getActions())); } } } diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java index d8f74c518bf4..238cc2b8ee7d 100644 --- a/media/java/android/media/Session2Token.java +++ b/media/java/android/media/Session2Token.java @@ -35,7 +35,7 @@ import java.util.List; import java.util.Objects; /** - * Represents an ongoing {@link MediaSession2} or a MediaSession2Service. + * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}. * If it's representing a session service, it may not be ongoing. * <p> * This API is not generally intended for third party application developers. @@ -48,14 +48,6 @@ import java.util.Objects; * <p> * It can be also obtained by {@link android.media.session.MediaSessionManager}. */ -// New version of MediaSession2.Token for following reasons -// - Stop implementing Parcelable for updatable support -// - Represent session and library service (formerly browser service) in one class. -// Previously MediaSession2.Token was for session and ComponentName was for service. -// This helps controller apps to keep target of dispatching media key events in uniform way. -// For details about the reason, see following. (Android O+) -// android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged -// TODO: use @link for MediaSession2Service public final class Session2Token implements Parcelable { private static final String TAG = "Session2Token"; @@ -85,12 +77,13 @@ public final class Session2Token implements Parcelable { public static final int TYPE_SESSION = 0; /** - * Type for MediaSession2Service. + * Type for {@link MediaSession2Service}. */ public static final int TYPE_SESSION_SERVICE = 1; private final int mUid; - private final @TokenType int mType; + @TokenType + private final int mType; private final String mPackageName; private final String mServiceName; private final Session2Link mSessionLink; @@ -206,14 +199,6 @@ public final class Session2Token implements Parcelable { } /** - * @hide - * @return component name of the session. Can be {@code null} for {@link #TYPE_SESSION}. - */ - public ComponentName getComponentName() { - return mComponentName; - } - - /** * @return type of the token * @see #TYPE_SESSION * @see #TYPE_SESSION_SERVICE @@ -222,10 +207,7 @@ public final class Session2Token implements Parcelable { return mType; } - /** - * @hide - */ - public Session2Link getSessionLink() { + Session2Link getSessionLink() { return mSessionLink; } diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 52e9ae191f0c..5b4bbce91784 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -26,7 +26,6 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Parcel; -import android.os.Parcelable; import android.util.Log; import java.lang.ref.WeakReference; @@ -229,7 +228,7 @@ public class AudioEffect { * The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects * enumeration. */ - public static final class Descriptor implements Parcelable { + public static class Descriptor { public Descriptor() { } @@ -294,7 +293,9 @@ public class AudioEffect { this.implementor = implementor; } - private Descriptor(Parcel in) { + /** @hide */ + @TestApi + public Descriptor(Parcel in) { type = UUID.fromString(in.readString()); uuid = UUID.fromString(in.readString()); connectMode = in.readString(); @@ -302,33 +303,14 @@ public class AudioEffect { implementor = in.readString(); } - public static final Parcelable.Creator<Descriptor> CREATOR = - new Parcelable.Creator<Descriptor>() { - /** - * Rebuilds a Descriptor previously stored with writeToParcel(). - * @param p Parcel object to read the Descriptor from - * @return a new Descriptor created from the data in the parcel - */ - public Descriptor createFromParcel(Parcel p) { - return new Descriptor(p); - } - public Descriptor[] newArray(int size) { - return new Descriptor[size]; - } - }; - @Override public int hashCode() { return Objects.hash(type, uuid, connectMode, name, implementor); } - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { + /** @hide */ + @TestApi + public void writeToParcel(Parcel dest) { dest.writeString(type.toString()); dest.writeString(uuid.toString()); dest.writeString(connectMode); diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java index 95e19d20e57d..2d59e442abf0 100644 --- a/media/java/android/media/session/ControllerCallbackLink.java +++ b/media/java/android/media/session/ControllerCallbackLink.java @@ -24,6 +24,7 @@ import android.annotation.SystemApi; import android.content.Context; import android.content.pm.PackageManager; import android.media.MediaMetadata; +import android.media.MediaParceledListSlice; import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession.QueueItem; import android.os.Binder; @@ -127,7 +128,8 @@ public final class ControllerCallbackLink implements Parcelable { @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) public void notifyQueueChanged(@Nullable List<QueueItem> queue) { try { - mIControllerCallback.notifyQueueChanged(queue); + mIControllerCallback.notifyQueueChanged(queue == null ? null : + new MediaParceledListSlice(queue)); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -254,7 +256,7 @@ public final class ControllerCallbackLink implements Parcelable { @Override public void notifyPlaybackStateChanged(PlaybackState state) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPlaybackStateChanged(state); @@ -265,7 +267,7 @@ public final class ControllerCallbackLink implements Parcelable { @Override public void notifyMetadataChanged(MediaMetadata metadata) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onMetadataChanged(metadata); @@ -275,11 +277,11 @@ public final class ControllerCallbackLink implements Parcelable { } @Override - public void notifyQueueChanged(List<QueueItem> queue) { - ensureMediasControlPermission(); + public void notifyQueueChanged(MediaParceledListSlice queue) { + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { - mCallbackStub.onQueueChanged(queue); + mCallbackStub.onQueueChanged(queue == null ? null : queue.getList()); } finally { Binder.restoreCallingIdentity(token); } @@ -287,7 +289,7 @@ public final class ControllerCallbackLink implements Parcelable { @Override public void notifyQueueTitleChanged(CharSequence title) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onQueueTitleChanged(title); @@ -303,7 +305,7 @@ public final class ControllerCallbackLink implements Parcelable { @Override public void notifyVolumeInfoChanged(PlaybackInfo info) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onVolumeInfoChanged(info); @@ -312,7 +314,7 @@ public final class ControllerCallbackLink implements Parcelable { } } - private void ensureMediasControlPermission() { + private void ensureMediaControlPermission() { // Allow API calls from the System UI if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE) == PackageManager.PERMISSION_GRANTED) { diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl index 5c02e7c2f6e3..56ae852d6f50 100644 --- a/media/java/android/media/session/ISessionControllerCallback.aidl +++ b/media/java/android/media/session/ISessionControllerCallback.aidl @@ -16,8 +16,8 @@ package android.media.session; import android.media.MediaMetadata; +import android.media.MediaParceledListSlice; import android.media.session.MediaController; -import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.os.Bundle; @@ -31,7 +31,7 @@ oneway interface ISessionControllerCallback { // These callbacks are for the TransportController void notifyPlaybackStateChanged(in PlaybackState state); void notifyMetadataChanged(in MediaMetadata metadata); - void notifyQueueChanged(in List<MediaSession.QueueItem> queue); + void notifyQueueChanged(in MediaParceledListSlice queue); void notifyQueueTitleChanged(CharSequence title); void notifyExtrasChanged(in Bundle extras); void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 9d537c8c2fcb..057c9cb028c1 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -117,7 +117,7 @@ public final class MediaController { * @param token The token for the session. */ public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) { - this(context, token.getBinder()); + this(context, token.getControllerLink()); } /** diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index f02d9ba1b7df..4896d0803e42 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -19,6 +19,7 @@ package android.media.session; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.Activity; import android.app.PendingIntent; @@ -33,23 +34,15 @@ import android.media.session.MediaSessionManager.RemoteUserInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.os.Looper; -import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.ResultReceiver; import android.service.media.MediaBrowserService; import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; -import android.view.KeyEvent; -import android.view.ViewConfiguration; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; import java.util.List; -import java.util.Objects; /** * Allows interaction with media controllers, volume keys, media buttons, and @@ -74,7 +67,7 @@ import java.util.Objects; * MediaSession objects are thread safe. */ public final class MediaSession { - private static final String TAG = "MediaSession"; + static final String TAG = "MediaSession"; /** * Set this flag on the session to indicate that it can handle media button @@ -121,21 +114,11 @@ public final class MediaSession { FLAG_EXCLUSIVE_GLOBAL_PRIORITY }) public @interface SessionFlags { } - private final Object mLock = new Object(); - private final int mMaxBitmapSize; - - private final MediaSession.Token mSessionToken; - private final MediaController mController; - private final SessionLink mSessionLink; - private final SessionCallbackLink mCbStub; + private final MediaSessionEngine mImpl; // Do not change the name of mCallback. Support lib accesses this by using reflection. @UnsupportedAppUsage - private CallbackMessageHandler mCallback; - private VolumeProvider mVolumeProvider; - private PlaybackState mPlaybackState; - - private boolean mActive = false; + private Object mCallback; /** * Creates a new session. The session will automatically be registered with @@ -153,15 +136,15 @@ public final class MediaSession { if (TextUtils.isEmpty(tag)) { throw new IllegalArgumentException("tag cannot be null or empty"); } - mMaxBitmapSize = context.getResources().getDimensionPixelSize( - android.R.dimen.config_mediaMetadataBitmapMaxSize); - mCbStub = new SessionCallbackLink(context, new CallbackStub(this)); MediaSessionManager manager = (MediaSessionManager) context .getSystemService(Context.MEDIA_SESSION_SERVICE); try { - mSessionLink = manager.createSession(mCbStub, tag); - mSessionToken = new Token(mSessionLink.getController()); - mController = new MediaController(context, mSessionToken); + MediaSessionEngine.CallbackStub cbStub = new MediaSessionEngine.CallbackStub(); + SessionCallbackLink cbLink = new SessionCallbackLink(context, cbStub); + SessionLink sessionLink = manager.createSession(cbLink, tag); + mImpl = new MediaSessionEngine(context, sessionLink, cbLink, cbStub, + context.getResources().getDimensionPixelSize( + android.R.dimen.config_mediaMetadataBitmapMaxSize)); } catch (RuntimeException e) { throw new RuntimeException("Remote error creating session.", e); } @@ -177,7 +160,8 @@ public final class MediaSession { * @param callback The callback object */ public void setCallback(@Nullable Callback callback) { - setCallback(callback, null); + mCallback = callback == null ? null : new Object(); + mImpl.setCallback(callback); } /** @@ -190,24 +174,8 @@ public final class MediaSession { * @param handler The handler that events should be posted on. */ public void setCallback(@Nullable Callback callback, @Nullable Handler handler) { - synchronized (mLock) { - if (mCallback != null) { - // We're updating the callback, clear the session from the old one. - mCallback.mCallback.mSession = null; - mCallback.removeCallbacksAndMessages(null); - } - if (callback == null) { - mCallback = null; - return; - } - if (handler == null) { - handler = new Handler(); - } - callback.mSession = this; - CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(), - callback); - mCallback = msgHandler; - } + mCallback = callback == null ? null : new Object(); + mImpl.setCallback(callback, handler); } /** @@ -218,11 +186,7 @@ public final class MediaSession { * @param pi The intent to launch to show UI for this Session. */ public void setSessionActivity(@Nullable PendingIntent pi) { - try { - mSessionLink.setLaunchPendingIntent(pi); - } catch (RuntimeException e) { - Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e); - } + mImpl.setSessionActivity(pi); } /** @@ -234,11 +198,7 @@ public final class MediaSession { * @param mbr The {@link PendingIntent} to send the media button event to. */ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { - try { - mSessionLink.setMediaButtonReceiver(mbr); - } catch (RuntimeException e) { - Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e); - } + mImpl.setMediaButtonReceiver(mbr); } /** @@ -247,11 +207,7 @@ public final class MediaSession { * @param flags The flags to set for this session. */ public void setFlags(@SessionFlags int flags) { - try { - mSessionLink.setFlags(flags); - } catch (RuntimeException e) { - Log.wtf(TAG, "Failure in setFlags.", e); - } + mImpl.setFlags(flags); } /** @@ -266,14 +222,7 @@ public final class MediaSession { * @param attributes The {@link AudioAttributes} for this session's audio. */ public void setPlaybackToLocal(AudioAttributes attributes) { - if (attributes == null) { - throw new IllegalArgumentException("Attributes cannot be null for local playback."); - } - try { - mSessionLink.setPlaybackToLocal(attributes); - } catch (RuntimeException e) { - Log.wtf(TAG, "Failure in setPlaybackToLocal.", e); - } + mImpl.setPlaybackToLocal(attributes); } /** @@ -288,26 +237,7 @@ public final class MediaSession { * not be null. */ public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) { - if (volumeProvider == null) { - throw new IllegalArgumentException("volumeProvider may not be null!"); - } - synchronized (mLock) { - mVolumeProvider = volumeProvider; - } - volumeProvider.setCallback(new VolumeProvider.Callback() { - @Override - public void onVolumeChanged(VolumeProvider volumeProvider) { - notifyRemoteVolumeChanged(volumeProvider); - } - }); - - try { - mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(), - volumeProvider.getMaxVolume()); - mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume()); - } catch (RuntimeException e) { - Log.wtf(TAG, "Failure in setPlaybackToRemote.", e); - } + mImpl.setPlaybackToRemote(volumeProvider); } /** @@ -319,15 +249,7 @@ public final class MediaSession { * @param active Whether this session is active or not. */ public void setActive(boolean active) { - if (mActive == active) { - return; - } - try { - mSessionLink.setActive(active); - mActive = active; - } catch (RuntimeException e) { - Log.wtf(TAG, "Failure in setActive.", e); - } + mImpl.setActive(active); } /** @@ -336,7 +258,7 @@ public final class MediaSession { * @return True if the session is active, false otherwise. */ public boolean isActive() { - return mActive; + return mImpl.isActive(); } /** @@ -348,14 +270,7 @@ public final class MediaSession { * @param extras Any extras included with the event */ public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) { - if (TextUtils.isEmpty(event)) { - throw new IllegalArgumentException("event cannot be null or empty"); - } - try { - mSessionLink.sendEvent(event, extras); - } catch (RuntimeException e) { - Log.wtf(TAG, "Error sending event", e); - } + mImpl.sendSessionEvent(event, extras); } /** @@ -364,11 +279,7 @@ public final class MediaSession { * but it must be released if your activity or service is being destroyed. */ public void release() { - try { - mSessionLink.destroySession(); - } catch (RuntimeException e) { - Log.wtf(TAG, "Error releasing session: ", e); - } + mImpl.close(); } /** @@ -380,7 +291,7 @@ public final class MediaSession { * session */ public @NonNull Token getSessionToken() { - return mSessionToken; + return mImpl.getSessionToken(); } /** @@ -390,7 +301,7 @@ public final class MediaSession { * @return A controller for this session. */ public @NonNull MediaController getController() { - return mController; + return mImpl.getController(); } /** @@ -399,12 +310,7 @@ public final class MediaSession { * @param state The current state of playback */ public void setPlaybackState(@Nullable PlaybackState state) { - mPlaybackState = state; - try { - mSessionLink.setPlaybackState(state); - } catch (RuntimeException e) { - Log.wtf(TAG, "Dead object in setPlaybackState.", e); - } + mImpl.setPlaybackState(state); } /** @@ -416,24 +322,7 @@ public final class MediaSession { * @see android.media.MediaMetadata.Builder#putBitmap */ public void setMetadata(@Nullable MediaMetadata metadata) { - long duration = -1; - int fields = 0; - MediaDescription description = null; - if (metadata != null) { - metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build(); - if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { - duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION); - } - fields = metadata.size(); - description = metadata.getDescription(); - } - String metadataDescription = "size=" + fields + ", description=" + description; - - try { - mSessionLink.setMetadata(metadata, duration, metadataDescription); - } catch (RuntimeException e) { - Log.wtf(TAG, "Dead object in setPlaybackState.", e); - } + mImpl.setMetadata(metadata); } /** @@ -448,11 +337,7 @@ public final class MediaSession { * @param queue A list of items in the play queue. */ public void setQueue(@Nullable List<QueueItem> queue) { - try { - mSessionLink.setQueue(queue); - } catch (RuntimeException e) { - Log.wtf("Dead object in setQueue.", e); - } + mImpl.setQueue(queue); } /** @@ -463,11 +348,7 @@ public final class MediaSession { * @param title The title of the play queue. */ public void setQueueTitle(@Nullable CharSequence title) { - try { - mSessionLink.setQueueTitle(title); - } catch (RuntimeException e) { - Log.wtf("Dead object in setQueueTitle.", e); - } + mImpl.setQueueTitle(title); } /** @@ -484,11 +365,7 @@ public final class MediaSession { * </ul> */ public void setRatingType(@Rating.Style int type) { - try { - mSessionLink.setRatingType(type); - } catch (RuntimeException e) { - Log.e(TAG, "Error in setRatingType.", e); - } + mImpl.setRatingType(type); } /** @@ -499,11 +376,7 @@ public final class MediaSession { * @param extras The extras associated with the {@link MediaSession}. */ public void setExtras(@Nullable Bundle extras) { - try { - mSessionLink.setExtras(extras); - } catch (RuntimeException e) { - Log.wtf("Dead object in setExtras.", e); - } + mImpl.setExtras(extras); } /** @@ -515,11 +388,7 @@ public final class MediaSession { * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo) */ public final @NonNull RemoteUserInfo getCurrentControllerInfo() { - if (mCallback == null || mCallback.mCurrentControllerInfo == null) { - throw new IllegalStateException( - "This should be called inside of MediaSession.Callback methods"); - } - return mCallback.mCurrentControllerInfo; + return mImpl.getCurrentControllerInfo(); } /** @@ -529,17 +398,7 @@ public final class MediaSession { * @hide */ public void notifyRemoteVolumeChanged(VolumeProvider provider) { - synchronized (mLock) { - if (provider == null || provider != mVolumeProvider) { - Log.w(TAG, "Received update from stale volume provider"); - return; - } - } - try { - mSessionLink.setCurrentVolume(provider.getCurrentVolume()); - } catch (RuntimeException e) { - Log.e(TAG, "Error in notifyVolumeChanged", e); - } + mImpl.notifyRemoteVolumeChanged(provider); } /** @@ -551,119 +410,7 @@ public final class MediaSession { */ @UnsupportedAppUsage public String getCallingPackage() { - if (mCallback != null && mCallback.mCurrentControllerInfo != null) { - return mCallback.mCurrentControllerInfo.getPackageName(); - } - return null; - } - - private void dispatchPrepare(RemoteUserInfo caller) { - postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null); - } - - private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) { - postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras); - } - - private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) { - postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras); - } - - private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) { - postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras); - } - - private void dispatchPlay(RemoteUserInfo caller) { - postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null); - } - - private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) { - postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras); - } - - private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) { - postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras); - } - - private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) { - postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras); - } - - private void dispatchSkipToItem(RemoteUserInfo caller, long id) { - postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null); - } - - private void dispatchPause(RemoteUserInfo caller) { - postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null); - } - - private void dispatchStop(RemoteUserInfo caller) { - postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null); - } - - private void dispatchNext(RemoteUserInfo caller) { - postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null); - } - - private void dispatchPrevious(RemoteUserInfo caller) { - postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null); - } - - private void dispatchFastForward(RemoteUserInfo caller) { - postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null); - } - - private void dispatchRewind(RemoteUserInfo caller) { - postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null); - } - - private void dispatchSeekTo(RemoteUserInfo caller, long pos) { - postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null); - } - - private void dispatchRate(RemoteUserInfo caller, Rating rating) { - postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null); - } - - private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) { - postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args); - } - - private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) { - postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null); - } - - private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent, - long delay) { - postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT, - mediaButtonIntent, null, delay); - } - - private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) { - postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null); - } - - private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) { - postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null); - } - - private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args, - ResultReceiver resultCb) { - Command cmd = new Command(command, args, resultCb); - postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null); - } - - private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) { - postToCallbackDelayed(caller, what, obj, data, 0); - } - - private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data, - long delay) { - synchronized (mLock) { - if (mCallback != null) { - mCallback.post(caller, what, obj, data, delay); - } - } + return mImpl.getCallingPackage(); } /** @@ -672,17 +419,7 @@ public final class MediaSession { * @hide */ public static boolean isActiveState(int state) { - switch (state) { - case PlaybackState.STATE_FAST_FORWARDING: - case PlaybackState.STATE_REWINDING: - case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: - case PlaybackState.STATE_SKIPPING_TO_NEXT: - case PlaybackState.STATE_BUFFERING: - case PlaybackState.STATE_CONNECTING: - case PlaybackState.STATE_PLAYING: - return true; - } - return false; + return MediaSessionEngine.isActiveState(state); } /** @@ -692,13 +429,13 @@ public final class MediaSession { */ public static final class Token implements Parcelable { - private ControllerLink mBinder; + private ControllerLink mControllerLink; /** * @hide */ - public Token(ControllerLink binder) { - mBinder = binder; + public Token(ControllerLink controllerLink) { + mControllerLink = controllerLink; } @Override @@ -708,14 +445,15 @@ public final class MediaSession { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(mBinder, flags); + dest.writeParcelable(mControllerLink, flags); } @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((mBinder == null) ? 0 : mBinder.getBinder().hashCode()); + result = prime * result + ((mControllerLink == null) + ? 0 : mControllerLink.getBinder().hashCode()); return result; } @@ -728,17 +466,23 @@ public final class MediaSession { if (getClass() != obj.getClass()) return false; Token other = (Token) obj; - if (mBinder == null) { - if (other.mBinder != null) + if (mControllerLink == null) { + if (other.mControllerLink != null) { return false; - } else if (!mBinder.getBinder().equals(other.mBinder.getBinder())) { + } + } else if (!mControllerLink.getBinder().equals(other.mControllerLink.getBinder())) { return false; } return true; } - ControllerLink getBinder() { - return mBinder; + /** + * Gets the controller link in this token. + * @hide + */ + @SystemApi + ControllerLink getControllerLink() { + return mControllerLink; } public static final Parcelable.Creator<Token> CREATOR = @@ -762,9 +506,7 @@ public final class MediaSession { */ public abstract static class Callback { - private MediaSession mSession; - private CallbackMessageHandler mHandler; - private boolean mMediaPlayPauseKeyPending; + MediaSessionEngine.MediaButtonEventDelegate mMediaButtonEventDelegate; public Callback() { } @@ -796,110 +538,12 @@ public final class MediaSession { * @return True if the event was handled, false otherwise. */ public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { - if (mSession != null && mHandler != null - && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) { - KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); - if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) { - PlaybackState state = mSession.mPlaybackState; - long validActions = state == null ? 0 : state.getActions(); - switch (ke.getKeyCode()) { - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_HEADSETHOOK: - if (ke.getRepeatCount() > 0) { - // Consider long-press as a single tap. - handleMediaPlayPauseKeySingleTapIfPending(); - } else if (mMediaPlayPauseKeyPending) { - // Consider double tap as the next. - mHandler.removeMessages(CallbackMessageHandler - .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT); - mMediaPlayPauseKeyPending = false; - if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { - onSkipToNext(); - } - } else { - mMediaPlayPauseKeyPending = true; - mSession.dispatchMediaButtonDelayed( - mSession.getCurrentControllerInfo(), - mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout()); - } - return true; - default: - // If another key is pressed within double tap timeout, consider the - // pending play/pause as a single tap to handle media keys in order. - handleMediaPlayPauseKeySingleTapIfPending(); - break; - } - - switch (ke.getKeyCode()) { - case KeyEvent.KEYCODE_MEDIA_PLAY: - if ((validActions & PlaybackState.ACTION_PLAY) != 0) { - onPlay(); - return true; - } - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - if ((validActions & PlaybackState.ACTION_PAUSE) != 0) { - onPause(); - return true; - } - break; - case KeyEvent.KEYCODE_MEDIA_NEXT: - if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { - onSkipToNext(); - return true; - } - break; - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) { - onSkipToPrevious(); - return true; - } - break; - case KeyEvent.KEYCODE_MEDIA_STOP: - if ((validActions & PlaybackState.ACTION_STOP) != 0) { - onStop(); - return true; - } - break; - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) { - onFastForward(); - return true; - } - break; - case KeyEvent.KEYCODE_MEDIA_REWIND: - if ((validActions & PlaybackState.ACTION_REWIND) != 0) { - onRewind(); - return true; - } - break; - } - } + if (mMediaButtonEventDelegate != null) { + return mMediaButtonEventDelegate.onMediaButtonIntent(mediaButtonIntent); } return false; } - private void handleMediaPlayPauseKeySingleTapIfPending() { - if (!mMediaPlayPauseKeyPending) { - return; - } - mMediaPlayPauseKeyPending = false; - mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT); - PlaybackState state = mSession.mPlaybackState; - long validActions = state == null ? 0 : state.getActions(); - boolean isPlaying = state != null - && state.getState() == PlaybackState.STATE_PLAYING; - boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE - | PlaybackState.ACTION_PLAY)) != 0; - boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE - | PlaybackState.ACTION_PAUSE)) != 0; - if (isPlaying && canPause) { - onPause(); - } else if (!isPlaying && canPlay) { - onPlay(); - } - } - /** * Override to handle requests to prepare playback. During the preparation, a session should * not hold audio focus in order to allow other sessions play seamlessly. The state of @@ -1042,251 +686,14 @@ public final class MediaSession { */ public void onCustomAction(@NonNull String action, @Nullable Bundle extras) { } - } - - /** - * @hide - */ - public static final class CallbackStub extends SessionCallbackLink.CallbackStub { - private WeakReference<MediaSession> mMediaSession; - - public CallbackStub(MediaSession session) { - mMediaSession = new WeakReference<>(session); - } - - private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - return new RemoteUserInfo(packageName, pid, uid, - caller != null ? caller.getBinder() : null); - } - - @Override - public void onCommand(String packageName, int pid, int uid, - ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller), - command, args, cb); - } - } - - @Override - public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent, - int sequenceNumber, ResultReceiver cb) { - MediaSession session = mMediaSession.get(); - try { - if (session != null) { - session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, null), - mediaButtonIntent); - } - } finally { - if (cb != null) { - cb.send(sequenceNumber, null); - } - } - } - - @Override - public void onMediaButtonFromController(String packageName, int pid, int uid, - ControllerCallbackLink caller, Intent mediaButtonIntent) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller), - mediaButtonIntent); - } - } - - @Override - public void onPrepare(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller)); - } - } - - @Override - public void onPrepareFromMediaId(String packageName, int pid, int uid, - ControllerCallbackLink caller, String mediaId, - Bundle extras) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPrepareFromMediaId( - createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras); - } - } - - @Override - public void onPrepareFromSearch(String packageName, int pid, int uid, - ControllerCallbackLink caller, String query, - Bundle extras) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPrepareFromSearch( - createRemoteUserInfo(packageName, pid, uid, caller), query, extras); - } - } - - @Override - public void onPrepareFromUri(String packageName, int pid, int uid, - ControllerCallbackLink caller, Uri uri, Bundle extras) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller), - uri, extras); - } - } - - @Override - public void onPlay(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller)); - } - } - - @Override - public void onPlayFromMediaId(String packageName, int pid, int uid, - ControllerCallbackLink caller, String mediaId, - Bundle extras) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPlayFromMediaId(createRemoteUserInfo(packageName, pid, uid, caller), - mediaId, extras); - } - } - - @Override - public void onPlayFromSearch(String packageName, int pid, int uid, - ControllerCallbackLink caller, String query, - Bundle extras) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPlayFromSearch(createRemoteUserInfo(packageName, pid, uid, caller), - query, extras); - } - } - - @Override - public void onPlayFromUri(String packageName, int pid, int uid, - ControllerCallbackLink caller, Uri uri, Bundle extras) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller), - uri, extras); - } - } - - @Override - public void onSkipToTrack(String packageName, int pid, int uid, - ControllerCallbackLink caller, long id) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id); - } - } - - @Override - public void onPause(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller)); - } - } - @Override - public void onStop(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller)); - } - } - - @Override - public void onNext(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller)); - } - } - - @Override - public void onPrevious(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller)); - } - } - - @Override - public void onFastForward(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller)); - } - } - - @Override - public void onRewind(String packageName, int pid, int uid, - ControllerCallbackLink caller) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller)); - } - } - - @Override - public void onSeekTo(String packageName, int pid, int uid, - ControllerCallbackLink caller, long pos) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos); - } - } - - @Override - public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller, - Rating rating) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchRate(createRemoteUserInfo(packageName, pid, uid, caller), rating); - } - } - - @Override - public void onCustomAction(String packageName, int pid, int uid, - ControllerCallbackLink caller, String action, Bundle args) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller), - action, args); - } - } - - @Override - public void onAdjustVolume(String packageName, int pid, int uid, - ControllerCallbackLink caller, int direction) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller), - direction); - } - } - - @Override - public void onSetVolumeTo(String packageName, int pid, int uid, - ControllerCallbackLink caller, int value) { - MediaSession session = mMediaSession.get(); - if (session != null) { - session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller), - value); - } + /** + * @hide + */ + @SystemApi + public void onSetMediaButtonEventDelegate( + @NonNull MediaSessionEngine.MediaButtonEventDelegate delegate) { + mMediaButtonEventDelegate = delegate; } } @@ -1300,7 +707,7 @@ public final class MediaSession { */ public static final int UNKNOWN_ID = -1; - private final MediaDescription mDescription; + private final MediaSessionEngine.QueueItem mImpl; @UnsupportedAppUsage private final long mId; @@ -1312,39 +719,32 @@ public final class MediaSession { * play queue and cannot be {@link #UNKNOWN_ID}. */ public QueueItem(MediaDescription description, long id) { - if (description == null) { - throw new IllegalArgumentException("Description cannot be null."); - } - if (id == UNKNOWN_ID) { - throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); - } - mDescription = description; + mImpl = new MediaSessionEngine.QueueItem(description, id); mId = id; } private QueueItem(Parcel in) { - mDescription = MediaDescription.CREATOR.createFromParcel(in); - mId = in.readLong(); + mImpl = new MediaSessionEngine.QueueItem(in); + mId = mImpl.getQueueId(); } /** * Get the description for this item. */ public MediaDescription getDescription() { - return mDescription; + return mImpl.getDescription(); } /** * Get the queue id for this item. */ public long getQueueId() { - return mId; + return mImpl.getQueueId(); } @Override public void writeToParcel(Parcel dest, int flags) { - mDescription.writeToParcel(dest, flags); - dest.writeLong(mId); + mImpl.writeToParcel(dest, flags); } @Override @@ -1368,9 +768,7 @@ public final class MediaSession { @Override public String toString() { - return "MediaSession.QueueItem {" + - "Description=" + mDescription + - ", Id=" + mId + " }"; + return mImpl.toString(); } @Override @@ -1383,167 +781,7 @@ public final class MediaSession { return false; } - final QueueItem item = (QueueItem) o; - if (mId != item.mId) { - return false; - } - - if (!Objects.equals(mDescription, item.mDescription)) { - return false; - } - - return true; - } - } - - private static final class Command { - public final String command; - public final Bundle extras; - public final ResultReceiver stub; - - public Command(String command, Bundle extras, ResultReceiver stub) { - this.command = command; - this.extras = extras; - this.stub = stub; - } - } - - private class CallbackMessageHandler extends Handler { - private static final int MSG_COMMAND = 1; - private static final int MSG_MEDIA_BUTTON = 2; - private static final int MSG_PREPARE = 3; - private static final int MSG_PREPARE_MEDIA_ID = 4; - private static final int MSG_PREPARE_SEARCH = 5; - private static final int MSG_PREPARE_URI = 6; - private static final int MSG_PLAY = 7; - private static final int MSG_PLAY_MEDIA_ID = 8; - private static final int MSG_PLAY_SEARCH = 9; - private static final int MSG_PLAY_URI = 10; - private static final int MSG_SKIP_TO_ITEM = 11; - private static final int MSG_PAUSE = 12; - private static final int MSG_STOP = 13; - private static final int MSG_NEXT = 14; - private static final int MSG_PREVIOUS = 15; - private static final int MSG_FAST_FORWARD = 16; - private static final int MSG_REWIND = 17; - private static final int MSG_SEEK_TO = 18; - private static final int MSG_RATE = 19; - private static final int MSG_CUSTOM_ACTION = 20; - private static final int MSG_ADJUST_VOLUME = 21; - private static final int MSG_SET_VOLUME = 22; - private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23; - - private MediaSession.Callback mCallback; - private RemoteUserInfo mCurrentControllerInfo; - - public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) { - super(looper); - mCallback = callback; - mCallback.mHandler = this; - } - - public void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) { - Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj); - Message msg = obtainMessage(what, objWithCaller); - msg.setAsynchronous(true); - msg.setData(data); - if (delayMs > 0) { - sendMessageDelayed(msg, delayMs); - } else { - sendMessage(msg); - } - } - - @Override - public void handleMessage(Message msg) { - mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first; - - VolumeProvider vp; - Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second; - - switch (msg.what) { - case MSG_COMMAND: - Command cmd = (Command) obj; - mCallback.onCommand(cmd.command, cmd.extras, cmd.stub); - break; - case MSG_MEDIA_BUTTON: - mCallback.onMediaButtonEvent((Intent) obj); - break; - case MSG_PREPARE: - mCallback.onPrepare(); - break; - case MSG_PREPARE_MEDIA_ID: - mCallback.onPrepareFromMediaId((String) obj, msg.getData()); - break; - case MSG_PREPARE_SEARCH: - mCallback.onPrepareFromSearch((String) obj, msg.getData()); - break; - case MSG_PREPARE_URI: - mCallback.onPrepareFromUri((Uri) obj, msg.getData()); - break; - case MSG_PLAY: - mCallback.onPlay(); - break; - case MSG_PLAY_MEDIA_ID: - mCallback.onPlayFromMediaId((String) obj, msg.getData()); - break; - case MSG_PLAY_SEARCH: - mCallback.onPlayFromSearch((String) obj, msg.getData()); - break; - case MSG_PLAY_URI: - mCallback.onPlayFromUri((Uri) obj, msg.getData()); - break; - case MSG_SKIP_TO_ITEM: - mCallback.onSkipToQueueItem((Long) obj); - break; - case MSG_PAUSE: - mCallback.onPause(); - break; - case MSG_STOP: - mCallback.onStop(); - break; - case MSG_NEXT: - mCallback.onSkipToNext(); - break; - case MSG_PREVIOUS: - mCallback.onSkipToPrevious(); - break; - case MSG_FAST_FORWARD: - mCallback.onFastForward(); - break; - case MSG_REWIND: - mCallback.onRewind(); - break; - case MSG_SEEK_TO: - mCallback.onSeekTo((Long) obj); - break; - case MSG_RATE: - mCallback.onSetRating((Rating) obj); - break; - case MSG_CUSTOM_ACTION: - mCallback.onCustomAction((String) obj, msg.getData()); - break; - case MSG_ADJUST_VOLUME: - synchronized (mLock) { - vp = mVolumeProvider; - } - if (vp != null) { - vp.onAdjustVolume((int) obj); - } - break; - case MSG_SET_VOLUME: - synchronized (mLock) { - vp = mVolumeProvider; - } - if (vp != null) { - vp.onSetVolumeTo((int) obj); - } - break; - case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT: - mCallback.handleMediaPlayPauseKeySingleTapIfPending(); - break; - } - mCurrentControllerInfo = null; + return mImpl.equals(((QueueItem) o).mImpl); } } } diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/java/android/media/session/MediaSessionEngine.java new file mode 100644 index 000000000000..f159a9538835 --- /dev/null +++ b/media/java/android/media/session/MediaSessionEngine.java @@ -0,0 +1,1479 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.session; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.UnsupportedAppUsage; +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.media.AudioAttributes; +import android.media.MediaDescription; +import android.media.MediaMetadata; +import android.media.Rating; +import android.media.VolumeProvider; +import android.media.session.MediaSessionManager.RemoteUserInfo; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; +import android.os.ResultReceiver; +import android.service.media.MediaBrowserService; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Objects; + +/** + * @hide + */ +@SystemApi +public final class MediaSessionEngine implements AutoCloseable { + private static final String TAG = MediaSession.TAG; + + private final Object mLock = new Object(); + private final int mMaxBitmapSize; + + private final MediaSession.Token mSessionToken; + private final MediaController mController; + private final SessionLink mSessionLink; + private final SessionCallbackLink mCbLink; + + // Do not change the name of mCallbackWrapper. Support lib accesses this by using reflection. + @UnsupportedAppUsage + private CallbackMessageHandler mCallbackHandler; + private VolumeProvider mVolumeProvider; + private PlaybackState mPlaybackState; + + private boolean mActive = false; + + /** + * Creates a new session. The session will automatically be registered with + * the system but will not be published until {@link #setActive(boolean) + * setActive(true)} is called. You must call {@link #close()} when + * finished with the session. + * + * @param context The context to use to create the session. + * @param sessionLink A session link for the binder of MediaSessionRecord + * @param cbStub A callback link that handles incoming command to {@link MediaSession.Callback}. + */ + public MediaSessionEngine(@NonNull Context context, @NonNull SessionLink sessionLink, + @NonNull SessionCallbackLink cbLink, @NonNull CallbackStub cbStub, int maxBitmapSize) { + mSessionLink = sessionLink; + mCbLink = cbLink; + mMaxBitmapSize = maxBitmapSize; + + cbStub.setSessionImpl(this); + mSessionToken = new MediaSession.Token(mSessionLink.getController()); + mController = new MediaController(context, mSessionToken); + } + + /** + * Set the callback to receive updates for the MediaSession. This includes + * media button events and transport controls. The caller's thread will be + * used to post updates. + * <p> + * Set the callback to null to stop receiving updates. + * + * @param callback The callback object + */ + public void setCallback(@Nullable MediaSession.Callback callback) { + setCallback(callback, new Handler()); + } + + /** + * Set the callback to receive updates for the MediaSession. This includes + * media button events and transport controls. + * <p> + * Set the callback to null to stop receiving updates. + * + * @param callback The callback to receive updates on. + * @param handler The handler that events should be posted on. + */ + public void setCallback(@Nullable MediaSession.Callback callback, @NonNull Handler handler) { + setCallbackInternal(callback == null ? null : new CallbackWrapper(callback), handler); + } + + private void setCallbackInternal(CallbackWrapper callback, Handler handler) { + synchronized (mLock) { + if (mCallbackHandler != null) { + // We're updating the callback, clear the session from the old one. + mCallbackHandler.mCallbackWrapper.mSessionImpl = null; + mCallbackHandler.removeCallbacksAndMessages(null); + } + if (callback == null) { + mCallbackHandler = null; + return; + } + callback.mSessionImpl = this; + CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(), + callback); + mCallbackHandler = msgHandler; + } + } + + /** + * Set an intent for launching UI for this Session. This can be used as a + * quick link to an ongoing media screen. The intent should be for an + * activity that may be started using {@link Activity#startActivity(Intent)}. + * + * @param pi The intent to launch to show UI for this Session. + */ + public void setSessionActivity(@Nullable PendingIntent pi) { + try { + mSessionLink.setLaunchPendingIntent(pi); + } catch (RuntimeException e) { + Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e); + } + } + + /** + * Set a pending intent for your media button receiver to allow restarting + * playback after the session has been stopped. If your app is started in + * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via + * the pending intent. + * + * @param mbr The {@link PendingIntent} to send the media button event to. + */ + public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { + try { + mSessionLink.setMediaButtonReceiver(mbr); + } catch (RuntimeException e) { + Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e); + } + } + + /** + * Set any flags for the session. + * + * @param flags The flags to set for this session. + */ + public void setFlags(@MediaSession.SessionFlags int flags) { + try { + mSessionLink.setFlags(flags); + } catch (RuntimeException e) { + Log.wtf(TAG, "Failure in setFlags.", e); + } + } + + /** + * Set the attributes for this session's audio. This will affect the + * system's volume handling for this session. If + * {@link #setPlaybackToRemote} was previously called it will stop receiving + * volume commands and the system will begin sending volume changes to the + * appropriate stream. + * <p> + * By default sessions use attributes for media. + * + * @param attributes The {@link AudioAttributes} for this session's audio. + */ + public void setPlaybackToLocal(AudioAttributes attributes) { + if (attributes == null) { + throw new IllegalArgumentException("Attributes cannot be null for local playback."); + } + try { + mSessionLink.setPlaybackToLocal(attributes); + } catch (RuntimeException e) { + Log.wtf(TAG, "Failure in setPlaybackToLocal.", e); + } + } + + /** + * Configure this session to use remote volume handling. This must be called + * to receive volume button events, otherwise the system will adjust the + * appropriate stream volume for this session. If + * {@link #setPlaybackToLocal} was previously called the system will stop + * handling volume changes for this session and pass them to the volume + * provider instead. + * + * @param volumeProvider The provider that will handle volume changes. May + * not be null. + */ + public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) { + if (volumeProvider == null) { + throw new IllegalArgumentException("volumeProvider may not be null!"); + } + synchronized (mLock) { + mVolumeProvider = volumeProvider; + } + volumeProvider.setCallback(new VolumeProvider.Callback() { + @Override + public void onVolumeChanged(VolumeProvider volumeProvider) { + notifyRemoteVolumeChanged(volumeProvider); + } + }); + + try { + mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(), + volumeProvider.getMaxVolume()); + mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume()); + } catch (RuntimeException e) { + Log.wtf(TAG, "Failure in setPlaybackToRemote.", e); + } + } + + /** + * Set if this session is currently active and ready to receive commands. If + * set to false your session's controller may not be discoverable. You must + * set the session to active before it can start receiving media button + * events or transport commands. + * + * @param active Whether this session is active or not. + */ + public void setActive(boolean active) { + if (mActive == active) { + return; + } + try { + mSessionLink.setActive(active); + mActive = active; + } catch (RuntimeException e) { + Log.wtf(TAG, "Failure in setActive.", e); + } + } + + /** + * Get the current active state of this session. + * + * @return True if the session is active, false otherwise. + */ + public boolean isActive() { + return mActive; + } + + /** + * Send a proprietary event to all MediaControllers listening to this + * Session. It's up to the Controller/Session owner to determine the meaning + * of any events. + * + * @param event The name of the event to send + * @param extras Any extras included with the event + */ + public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) { + if (TextUtils.isEmpty(event)) { + throw new IllegalArgumentException("event cannot be null or empty"); + } + try { + mSessionLink.sendEvent(event, extras); + } catch (RuntimeException e) { + Log.wtf(TAG, "Error sending event", e); + } + } + + /** + * This must be called when an app has finished performing playback. If + * playback is expected to start again shortly the session can be left open, + * but it must be released if your activity or service is being destroyed. + */ + public void close() { + try { + mSessionLink.destroySession(); + } catch (RuntimeException e) { + Log.wtf(TAG, "Error releasing session: ", e); + } + } + + /** + * Retrieve a token object that can be used by apps to create a + * {@link MediaController} for interacting with this session. The owner of + * the session is responsible for deciding how to distribute these tokens. + * + * @return A token that can be used to create a MediaController for this + * session + */ + public @NonNull MediaSession.Token getSessionToken() { + return mSessionToken; + } + + /** + * Get a controller for this session. This is a convenience method to avoid + * having to cache your own controller in process. + * + * @return A controller for this session. + */ + public @NonNull MediaController getController() { + return mController; + } + + /** + * Update the current playback state. + * + * @param state The current state of playback + */ + public void setPlaybackState(@Nullable PlaybackState state) { + mPlaybackState = state; + try { + mSessionLink.setPlaybackState(state); + } catch (RuntimeException e) { + Log.wtf(TAG, "Dead object in setPlaybackState.", e); + } + } + + /** + * Update the current metadata. New metadata can be created using + * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to + * the size of the bitmap to replace large bitmaps with a scaled down copy. + * + * @param metadata The new metadata + * @see android.media.MediaMetadata.Builder#putBitmap + */ + public void setMetadata(@Nullable MediaMetadata metadata) { + long duration = -1; + int fields = 0; + MediaDescription description = null; + if (metadata != null) { + metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build(); + if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) { + duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION); + } + fields = metadata.size(); + description = metadata.getDescription(); + } + String metadataDescription = "size=" + fields + ", description=" + description; + + try { + mSessionLink.setMetadata(metadata, duration, metadataDescription); + } catch (RuntimeException e) { + Log.wtf(TAG, "Dead object in setPlaybackState.", e); + } + } + + /** + * Update the list of items in the play queue. It is an ordered list and + * should contain the current item, and previous or upcoming items if they + * exist. Specify null if there is no current play queue. + * <p> + * The queue should be of reasonable size. If the play queue is unbounded + * within your app, it is better to send a reasonable amount in a sliding + * window instead. + * + * @param queue A list of items in the play queue. + */ + public void setQueue(@Nullable List<MediaSession.QueueItem> queue) { + try { + mSessionLink.setQueue(queue); + } catch (RuntimeException e) { + Log.wtf("Dead object in setQueue.", e); + } + } + + /** + * Set the title of the play queue. The UI should display this title along + * with the play queue itself. + * e.g. "Play Queue", "Now Playing", or an album name. + * + * @param title The title of the play queue. + */ + public void setQueueTitle(@Nullable CharSequence title) { + try { + mSessionLink.setQueueTitle(title); + } catch (RuntimeException e) { + Log.wtf("Dead object in setQueueTitle.", e); + } + } + + /** + * Set the style of rating used by this session. Apps trying to set the + * rating should use this style. Must be one of the following: + * <ul> + * <li>{@link Rating#RATING_NONE}</li> + * <li>{@link Rating#RATING_3_STARS}</li> + * <li>{@link Rating#RATING_4_STARS}</li> + * <li>{@link Rating#RATING_5_STARS}</li> + * <li>{@link Rating#RATING_HEART}</li> + * <li>{@link Rating#RATING_PERCENTAGE}</li> + * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li> + * </ul> + */ + public void setRatingType(@Rating.Style int type) { + try { + mSessionLink.setRatingType(type); + } catch (RuntimeException e) { + Log.e(TAG, "Error in setRatingType.", e); + } + } + + /** + * Set some extras that can be associated with the {@link MediaSession}. No assumptions should + * be made as to how a {@link MediaController} will handle these extras. + * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts. + * + * @param extras The extras associated with the {@link MediaSession}. + */ + public void setExtras(@Nullable Bundle extras) { + try { + mSessionLink.setExtras(extras); + } catch (RuntimeException e) { + Log.wtf("Dead object in setExtras.", e); + } + } + + /** + * Gets the controller information who sent the current request. + * <p> + * Note: This is only valid while in a request callback, such as + * {@link MediaSession.Callback#onPlay}. + * + * @throws IllegalStateException If this method is called outside of + * {@link MediaSession.Callback} methods. + * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo) + */ + public @NonNull RemoteUserInfo getCurrentControllerInfo() { + if (mCallbackHandler == null || mCallbackHandler.mCurrentControllerInfo == null) { + throw new IllegalStateException( + "This should be called inside of MediaSession.Callback methods"); + } + return mCallbackHandler.mCurrentControllerInfo; + } + + /** + * Notify the system that the remote volume changed. + * + * @param provider The provider that is handling volume changes. + * @hide + */ + public void notifyRemoteVolumeChanged(VolumeProvider provider) { + synchronized (mLock) { + if (provider == null || provider != mVolumeProvider) { + Log.w(TAG, "Received update from stale volume provider"); + return; + } + } + try { + mSessionLink.setCurrentVolume(provider.getCurrentVolume()); + } catch (RuntimeException e) { + Log.e(TAG, "Error in notifyVolumeChanged", e); + } + } + + /** + * Returns the name of the package that sent the last media button, transport control, or + * command from controllers and the system. This is only valid while in a request callback, such + * as {@link MediaSession.Callback#onPlay}. + */ + public String getCallingPackage() { + if (mCallbackHandler != null && mCallbackHandler.mCurrentControllerInfo != null) { + return mCallbackHandler.mCurrentControllerInfo.getPackageName(); + } + return null; + } + + private void dispatchPrepare(RemoteUserInfo caller) { + postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null); + } + + private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) { + postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras); + } + + private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) { + postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras); + } + + private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) { + postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras); + } + + private void dispatchPlay(RemoteUserInfo caller) { + postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null); + } + + private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) { + postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras); + } + + private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) { + postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras); + } + + private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) { + postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras); + } + + private void dispatchSkipToItem(RemoteUserInfo caller, long id) { + postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null); + } + + private void dispatchPause(RemoteUserInfo caller) { + postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null); + } + + private void dispatchStop(RemoteUserInfo caller) { + postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null); + } + + private void dispatchNext(RemoteUserInfo caller) { + postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null); + } + + private void dispatchPrevious(RemoteUserInfo caller) { + postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null); + } + + private void dispatchFastForward(RemoteUserInfo caller) { + postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null); + } + + private void dispatchRewind(RemoteUserInfo caller) { + postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null); + } + + private void dispatchSeekTo(RemoteUserInfo caller, long pos) { + postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null); + } + + private void dispatchRate(RemoteUserInfo caller, Rating rating) { + postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null); + } + + private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) { + postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args); + } + + private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) { + postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null); + } + + private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent, + long delay) { + postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT, + mediaButtonIntent, null, delay); + } + + private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) { + postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null); + } + + private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) { + postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null); + } + + private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args, + ResultReceiver resultCb) { + Command cmd = new Command(command, args, resultCb); + postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null); + } + + private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) { + postToCallbackDelayed(caller, what, obj, data, 0); + } + + private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data, + long delay) { + synchronized (mLock) { + if (mCallbackHandler != null) { + mCallbackHandler.post(caller, what, obj, data, delay); + } + } + } + + /** + * Return true if this is considered an active playback state. + */ + public static boolean isActiveState(int state) { + switch (state) { + case PlaybackState.STATE_FAST_FORWARDING: + case PlaybackState.STATE_REWINDING: + case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: + case PlaybackState.STATE_SKIPPING_TO_NEXT: + case PlaybackState.STATE_BUFFERING: + case PlaybackState.STATE_CONNECTING: + case PlaybackState.STATE_PLAYING: + return true; + } + return false; + } + + /** + * Interface for handling MediaButtoneEvent + */ + public interface MediaButtonEventDelegate { + /** + * Called when a media button is pressed and this session has the + * highest priority or a controller sends a media button event to the + * session. + * + * @param mediaButtonIntent an intent containing the KeyEvent as an extra + * @return True if the event was handled, false otherwise. + */ + boolean onMediaButtonIntent(Intent mediaButtonIntent); + } + + /** + * Receives media buttons, transport controls, and commands from controllers + * and the system. A callback may be set using {@link #setCallback}. + * @hide + */ + public static class CallbackWrapper implements MediaButtonEventDelegate { + + private final MediaSession.Callback mCallback; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + MediaSessionEngine mSessionImpl; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + CallbackMessageHandler mHandler; + private boolean mMediaPlayPauseKeyPending; + + public CallbackWrapper(MediaSession.Callback callback) { + mCallback = callback; + if (mCallback != null) { + mCallback.onSetMediaButtonEventDelegate(this); + } + } + + /** + * Called when a controller has sent a command to this session. + * The owner of the session may handle custom commands but is not + * required to. + * + * @param command The command name. + * @param args Optional parameters for the command, may be null. + * @param cb A result receiver to which a result may be sent by the command, may be null. + */ + public void onCommand(@NonNull String command, @Nullable Bundle args, + @Nullable ResultReceiver cb) { + if (mCallback != null) { + mCallback.onCommand(command, args, cb); + } + } + + /** + * Called when a media button is pressed and this session has the + * highest priority or a controller sends a media button event to the + * session. The default behavior will call the relevant method if the + * action for it was set. + * <p> + * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a + * KeyEvent in {@link Intent#EXTRA_KEY_EVENT} + * + * @param mediaButtonIntent an intent containing the KeyEvent as an + * extra + * @return True if the event was handled, false otherwise. + */ + public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { + return mCallback == null ? false : mCallback.onMediaButtonEvent(mediaButtonIntent); + } + + private void handleMediaPlayPauseKeySingleTapIfPending() { + if (!mMediaPlayPauseKeyPending) { + return; + } + mMediaPlayPauseKeyPending = false; + mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT); + PlaybackState state = mSessionImpl.mPlaybackState; + long validActions = state == null ? 0 : state.getActions(); + boolean isPlaying = state != null + && state.getState() == PlaybackState.STATE_PLAYING; + boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE + | PlaybackState.ACTION_PLAY)) != 0; + boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE + | PlaybackState.ACTION_PAUSE)) != 0; + if (isPlaying && canPause) { + onPause(); + } else if (!isPlaying && canPlay) { + onPlay(); + } + } + + /** + * Override to handle requests to prepare playback. During the preparation, a session should + * not hold audio focus in order to allow other sessions play seamlessly. The state of + * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is + * done. + */ + public void onPrepare() { + if (mCallback != null) { + mCallback.onPrepare(); + } + } + + /** + * Override to handle requests to prepare for playing a specific mediaId that was provided + * by your app's {@link MediaBrowserService}. During the preparation, a session should not + * hold audio focus in order to allow other sessions play seamlessly. The state of playback + * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. + * The playback of the prepared content should start in the implementation of + * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting + * playback without preparation. + */ + public void onPrepareFromMediaId(String mediaId, Bundle extras) { + if (mCallback != null) { + mCallback.onPrepareFromMediaId(mediaId, extras); + } + } + + /** + * Override to handle requests to prepare playback from a search query. An empty query + * indicates that the app may prepare any music. The implementation should attempt to make a + * smart choice about what to play. During the preparation, a session should not hold audio + * focus in order to allow other sessions play seamlessly. The state of playback should be + * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback + * of the prepared content should start in the implementation of {@link #onPlay}. Override + * {@link #onPlayFromSearch} to handle requests for starting playback without preparation. + */ + public void onPrepareFromSearch(String query, Bundle extras) { + if (mCallback != null) { + mCallback.onPrepareFromSearch(query, extras); + } + } + + /** + * Override to handle requests to prepare a specific media item represented by a URI. + * During the preparation, a session should not hold audio focus in order to allow + * other sessions play seamlessly. The state of playback should be updated to + * {@link PlaybackState#STATE_PAUSED} after the preparation is done. + * The playback of the prepared content should start in the implementation of + * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests + * for starting playback without preparation. + */ + public void onPrepareFromUri(Uri uri, Bundle extras) { + if (mCallback != null) { + mCallback.onPrepareFromUri(uri, extras); + } + } + + /** + * Override to handle requests to begin playback. + */ + public void onPlay() { + if (mCallback != null) { + mCallback.onPlay(); + } + } + + /** + * Override to handle requests to begin playback from a search query. An + * empty query indicates that the app may play any music. The + * implementation should attempt to make a smart choice about what to + * play. + */ + public void onPlayFromSearch(String query, Bundle extras) { + if (mCallback != null) { + mCallback.onPlayFromSearch(query, extras); + } + } + + /** + * Override to handle requests to play a specific mediaId that was + * provided by your app's {@link MediaBrowserService}. + */ + public void onPlayFromMediaId(String mediaId, Bundle extras) { + if (mCallback != null) { + mCallback.onPlayFromMediaId(mediaId, extras); + } + } + + /** + * Override to handle requests to play a specific media item represented by a URI. + */ + public void onPlayFromUri(Uri uri, Bundle extras) { + if (mCallback != null) { + mCallback.onPlayFromUri(uri, extras); + } + } + + /** + * Override to handle requests to play an item with a given id from the + * play queue. + */ + public void onSkipToQueueItem(long id) { + if (mCallback != null) { + mCallback.onSkipToQueueItem(id); + } + } + + /** + * Override to handle requests to pause playback. + */ + public void onPause() { + if (mCallback != null) { + mCallback.onPause(); + } + } + + /** + * Override to handle requests to skip to the next media item. + */ + public void onSkipToNext() { + if (mCallback != null) { + mCallback.onSkipToNext(); + } + } + + /** + * Override to handle requests to skip to the previous media item. + */ + public void onSkipToPrevious() { + if (mCallback != null) { + mCallback.onSkipToPrevious(); + } + } + + /** + * Override to handle requests to fast forward. + */ + public void onFastForward() { + if (mCallback != null) { + mCallback.onFastForward(); + } + } + + /** + * Override to handle requests to rewind. + */ + public void onRewind() { + if (mCallback != null) { + mCallback.onRewind(); + } + } + + /** + * Override to handle requests to stop playback. + */ + public void onStop() { + if (mCallback != null) { + mCallback.onStop(); + } + } + + /** + * Override to handle requests to seek to a specific position in ms. + * + * @param pos New position to move to, in milliseconds. + */ + public void onSeekTo(long pos) { + if (mCallback != null) { + mCallback.onSeekTo(pos); + } + } + + /** + * Override to handle the item being rated. + * + * @param rating + */ + public void onSetRating(@NonNull Rating rating) { + if (mCallback != null) { + mCallback.onSetRating(rating); + } + } + + /** + * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be + * performed. + * + * @param action The action that was originally sent in the + * {@link PlaybackState.CustomAction}. + * @param extras Optional extras specified by the {@link MediaController}. + */ + public void onCustomAction(@NonNull String action, @Nullable Bundle extras) { + if (mCallback != null) { + mCallback.onCustomAction(action, extras); + } + } + + @Override + public boolean onMediaButtonIntent(Intent mediaButtonIntent) { + if (mSessionImpl != null && mHandler != null + && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) { + KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) { + PlaybackState state = mSessionImpl.mPlaybackState; + long validActions = state == null ? 0 : state.getActions(); + switch (ke.getKeyCode()) { + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_HEADSETHOOK: + if (ke.getRepeatCount() > 0) { + // Consider long-press as a single tap. + handleMediaPlayPauseKeySingleTapIfPending(); + } else if (mMediaPlayPauseKeyPending) { + // Consider double tap as the next. + mHandler.removeMessages(CallbackMessageHandler + .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT); + mMediaPlayPauseKeyPending = false; + if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { + onSkipToNext(); + } + } else { + mMediaPlayPauseKeyPending = true; + mSessionImpl.dispatchMediaButtonDelayed( + mSessionImpl.getCurrentControllerInfo(), + mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout()); + } + return true; + default: + // If another key is pressed within double tap timeout, consider the + // pending play/pause as a single tap to handle media keys in order. + handleMediaPlayPauseKeySingleTapIfPending(); + break; + } + + switch (ke.getKeyCode()) { + case KeyEvent.KEYCODE_MEDIA_PLAY: + if ((validActions & PlaybackState.ACTION_PLAY) != 0) { + onPlay(); + return true; + } + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + if ((validActions & PlaybackState.ACTION_PAUSE) != 0) { + onPause(); + return true; + } + break; + case KeyEvent.KEYCODE_MEDIA_NEXT: + if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { + onSkipToNext(); + return true; + } + break; + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) { + onSkipToPrevious(); + return true; + } + break; + case KeyEvent.KEYCODE_MEDIA_STOP: + if ((validActions & PlaybackState.ACTION_STOP) != 0) { + onStop(); + return true; + } + break; + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) { + onFastForward(); + return true; + } + break; + case KeyEvent.KEYCODE_MEDIA_REWIND: + if ((validActions & PlaybackState.ACTION_REWIND) != 0) { + onRewind(); + return true; + } + break; + } + } + } + return false; + } + } + + /** + * @hide + */ + @SystemApi + public static final class CallbackStub extends SessionCallbackLink.CallbackStub { + private WeakReference<MediaSessionEngine> mSessionImpl; + + private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid) { + return new RemoteUserInfo(packageName, pid, uid); + } + + public CallbackStub() { + } + + @Override + public void onCommand(String packageName, int pid, int uid, + ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid), + command, args, cb); + } + } + + @Override + public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent, + int sequenceNumber, ResultReceiver cb) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + try { + if (sessionImpl != null) { + sessionImpl.dispatchMediaButton( + createRemoteUserInfo(packageName, pid, uid), mediaButtonIntent); + } + } finally { + if (cb != null) { + cb.send(sequenceNumber, null); + } + } + } + + @Override + public void onMediaButtonFromController(String packageName, int pid, int uid, + ControllerCallbackLink caller, Intent mediaButtonIntent) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid), + mediaButtonIntent); + } + } + + @Override + public void onPrepare(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid)); + } + } + + @Override + public void onPrepareFromMediaId(String packageName, int pid, int uid, + ControllerCallbackLink caller, String mediaId, + Bundle extras) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPrepareFromMediaId( + createRemoteUserInfo(packageName, pid, uid), mediaId, extras); + } + } + + @Override + public void onPrepareFromSearch(String packageName, int pid, int uid, + ControllerCallbackLink caller, String query, + Bundle extras) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPrepareFromSearch( + createRemoteUserInfo(packageName, pid, uid), query, extras); + } + } + + @Override + public void onPrepareFromUri(String packageName, int pid, int uid, + ControllerCallbackLink caller, Uri uri, Bundle extras) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPrepareFromUri( + createRemoteUserInfo(packageName, pid, uid), uri, extras); + } + } + + @Override + public void onPlay(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid)); + } + } + + @Override + public void onPlayFromMediaId(String packageName, int pid, int uid, + ControllerCallbackLink caller, String mediaId, + Bundle extras) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPlayFromMediaId( + createRemoteUserInfo(packageName, pid, uid), mediaId, extras); + } + } + + @Override + public void onPlayFromSearch(String packageName, int pid, int uid, + ControllerCallbackLink caller, String query, + Bundle extras) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPlayFromSearch( + createRemoteUserInfo(packageName, pid, uid), query, extras); + } + } + + @Override + public void onPlayFromUri(String packageName, int pid, int uid, + ControllerCallbackLink caller, Uri uri, Bundle extras) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPlayFromUri( + createRemoteUserInfo(packageName, pid, uid), uri, extras); + } + } + + @Override + public void onSkipToTrack(String packageName, int pid, int uid, + ControllerCallbackLink caller, long id) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchSkipToItem( + createRemoteUserInfo(packageName, pid, uid), id); + } + } + + @Override + public void onPause(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid)); + } + } + + @Override + public void onStop(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid)); + } + } + + @Override + public void onNext(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid)); + } + } + + @Override + public void onPrevious(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid)); + } + } + + @Override + public void onFastForward(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchFastForward( + createRemoteUserInfo(packageName, pid, uid)); + } + } + + @Override + public void onRewind(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid)); + } + } + + @Override + public void onSeekTo(String packageName, int pid, int uid, + ControllerCallbackLink caller, long pos) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchSeekTo( + createRemoteUserInfo(packageName, pid, uid), pos); + } + } + + @Override + public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller, + Rating rating) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchRate( + createRemoteUserInfo(packageName, pid, uid), rating); + } + } + + @Override + public void onCustomAction(String packageName, int pid, int uid, + ControllerCallbackLink caller, String action, Bundle args) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchCustomAction( + createRemoteUserInfo(packageName, pid, uid), action, args); + } + } + + @Override + public void onAdjustVolume(String packageName, int pid, int uid, + ControllerCallbackLink caller, int direction) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchAdjustVolume( + createRemoteUserInfo(packageName, pid, uid), direction); + } + } + + @Override + public void onSetVolumeTo(String packageName, int pid, int uid, + ControllerCallbackLink caller, int value) { + MediaSessionEngine sessionImpl = mSessionImpl.get(); + if (sessionImpl != null) { + sessionImpl.dispatchSetVolumeTo( + createRemoteUserInfo(packageName, pid, uid), value); + } + } + + void setSessionImpl(MediaSessionEngine sessionImpl) { + mSessionImpl = new WeakReference<>(sessionImpl); + } + } + + /** + * A single item that is part of the play queue. It contains a description + * of the item and its id in the queue. + */ + public static final class QueueItem { + /** + * This id is reserved. No items can be explicitly assigned this id. + */ + public static final int UNKNOWN_ID = -1; + + private final MediaDescription mDescription; + private final long mId; + + /** + * Create a new {@link MediaSession.QueueItem}. + * + * @param description The {@link MediaDescription} for this item. + * @param id An identifier for this item. It must be unique within the + * play queue and cannot be {@link #UNKNOWN_ID}. + */ + public QueueItem(MediaDescription description, long id) { + if (description == null) { + throw new IllegalArgumentException("Description cannot be null."); + } + if (id == UNKNOWN_ID) { + throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); + } + mDescription = description; + mId = id; + } + + public QueueItem(Parcel in) { + mDescription = MediaDescription.CREATOR.createFromParcel(in); + mId = in.readLong(); + } + + /** + * Get the description for this item. + */ + public MediaDescription getDescription() { + return mDescription; + } + + /** + * Get the queue id for this item. + */ + public long getQueueId() { + return mId; + } + + /** + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + */ + public void writeToParcel(Parcel dest, int flags) { + mDescription.writeToParcel(dest, flags); + dest.writeLong(mId); + } + + @Override + public String toString() { + return "MediaSession.QueueItem {" + "Description=" + mDescription + ", Id=" + mId + + " }"; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + + if (!(o instanceof QueueItem)) { + return false; + } + + final QueueItem item = (QueueItem) o; + if (mId != item.mId) { + return false; + } + + if (!Objects.equals(mDescription, item.mDescription)) { + return false; + } + + return true; + } + } + + private static final class Command { + public final String command; + public final Bundle extras; + public final ResultReceiver stub; + + Command(String command, Bundle extras, ResultReceiver stub) { + this.command = command; + this.extras = extras; + this.stub = stub; + } + } + + private class CallbackMessageHandler extends Handler { + private static final int MSG_COMMAND = 1; + private static final int MSG_MEDIA_BUTTON = 2; + private static final int MSG_PREPARE = 3; + private static final int MSG_PREPARE_MEDIA_ID = 4; + private static final int MSG_PREPARE_SEARCH = 5; + private static final int MSG_PREPARE_URI = 6; + private static final int MSG_PLAY = 7; + private static final int MSG_PLAY_MEDIA_ID = 8; + private static final int MSG_PLAY_SEARCH = 9; + private static final int MSG_PLAY_URI = 10; + private static final int MSG_SKIP_TO_ITEM = 11; + private static final int MSG_PAUSE = 12; + private static final int MSG_STOP = 13; + private static final int MSG_NEXT = 14; + private static final int MSG_PREVIOUS = 15; + private static final int MSG_FAST_FORWARD = 16; + private static final int MSG_REWIND = 17; + private static final int MSG_SEEK_TO = 18; + private static final int MSG_RATE = 19; + private static final int MSG_CUSTOM_ACTION = 20; + private static final int MSG_ADJUST_VOLUME = 21; + private static final int MSG_SET_VOLUME = 22; + private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23; + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + CallbackWrapper mCallbackWrapper; + @SuppressWarnings("WeakerAccess") /* synthetic access */ + RemoteUserInfo mCurrentControllerInfo; + + CallbackMessageHandler(Looper looper, CallbackWrapper callbackWrapper) { + super(looper); + mCallbackWrapper = callbackWrapper; + mCallbackWrapper.mHandler = this; + } + + void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) { + Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj); + Message msg = obtainMessage(what, objWithCaller); + msg.setAsynchronous(true); + msg.setData(data); + if (delayMs > 0) { + sendMessageDelayed(msg, delayMs); + } else { + sendMessage(msg); + } + } + + @Override + public void handleMessage(Message msg) { + mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first; + + VolumeProvider vp; + Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second; + + switch (msg.what) { + case MSG_COMMAND: + Command cmd = (Command) obj; + mCallbackWrapper.onCommand(cmd.command, cmd.extras, cmd.stub); + break; + case MSG_MEDIA_BUTTON: + mCallbackWrapper.onMediaButtonEvent((Intent) obj); + break; + case MSG_PREPARE: + mCallbackWrapper.onPrepare(); + break; + case MSG_PREPARE_MEDIA_ID: + mCallbackWrapper.onPrepareFromMediaId((String) obj, msg.getData()); + break; + case MSG_PREPARE_SEARCH: + mCallbackWrapper.onPrepareFromSearch((String) obj, msg.getData()); + break; + case MSG_PREPARE_URI: + mCallbackWrapper.onPrepareFromUri((Uri) obj, msg.getData()); + break; + case MSG_PLAY: + mCallbackWrapper.onPlay(); + break; + case MSG_PLAY_MEDIA_ID: + mCallbackWrapper.onPlayFromMediaId((String) obj, msg.getData()); + break; + case MSG_PLAY_SEARCH: + mCallbackWrapper.onPlayFromSearch((String) obj, msg.getData()); + break; + case MSG_PLAY_URI: + mCallbackWrapper.onPlayFromUri((Uri) obj, msg.getData()); + break; + case MSG_SKIP_TO_ITEM: + mCallbackWrapper.onSkipToQueueItem((Long) obj); + break; + case MSG_PAUSE: + mCallbackWrapper.onPause(); + break; + case MSG_STOP: + mCallbackWrapper.onStop(); + break; + case MSG_NEXT: + mCallbackWrapper.onSkipToNext(); + break; + case MSG_PREVIOUS: + mCallbackWrapper.onSkipToPrevious(); + break; + case MSG_FAST_FORWARD: + mCallbackWrapper.onFastForward(); + break; + case MSG_REWIND: + mCallbackWrapper.onRewind(); + break; + case MSG_SEEK_TO: + mCallbackWrapper.onSeekTo((Long) obj); + break; + case MSG_RATE: + mCallbackWrapper.onSetRating((Rating) obj); + break; + case MSG_CUSTOM_ACTION: + mCallbackWrapper.onCustomAction((String) obj, msg.getData()); + break; + case MSG_ADJUST_VOLUME: + synchronized (mLock) { + vp = mVolumeProvider; + } + if (vp != null) { + vp.onAdjustVolume((int) obj); + } + break; + case MSG_SET_VOLUME: + synchronized (mLock) { + vp = mVolumeProvider; + } + if (vp != null) { + vp.onSetVolumeTo((int) obj); + } + break; + case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT: + mCallbackWrapper.handleMediaPlayPauseKeySingleTapIfPending(); + break; + } + mCurrentControllerInfo = null; + } + } +} diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 77e758fcf2dd..c64c452be3ef 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -28,7 +28,6 @@ import android.media.AudioManager; import android.media.IRemoteVolumeController; import android.media.MediaSession2; import android.media.Session2Token; -import android.media.browse.MediaBrowser; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -37,6 +36,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.service.media.MediaBrowserService; import android.service.notification.NotificationListenerService; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.view.KeyEvent; @@ -122,7 +122,6 @@ public final class MediaSessionManager { * {@link MediaSession2.Builder} instead. * * @param token newly created session2 token - * @hide */ public void notifySession2Created(@NonNull Session2Token token) { if (token == null) { @@ -196,9 +195,7 @@ public final class MediaSessionManager { * reject your uses of {@link MediaSession2}. * * @return A list of {@link Session2Token}. - * @hide */ - // TODO: unhide @NonNull public List<Session2Token> getSession2Tokens() { return getSession2Tokens(UserHandle.myUserId()); @@ -209,13 +206,12 @@ public final class MediaSessionManager { * given user. * <p> * If you want to get tokens for another user, you must hold the - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission. + * android.Manifest.permission#INTERACT_ACROSS_USERS_FULL permission. * * @param userId The user id to fetch sessions for. * @return A list of {@link Session2Token} * @hide */ - // TODO: unhide @NonNull public List<Session2Token> getSession2Tokens(int userId) { try { @@ -342,13 +338,25 @@ public final class MediaSessionManager { * for consistent behavior across all devices. * * @param listener The listener to add - * @param handler The handler to call listener on. If {@code null}, calling thread's looper will - * be used. - * @hide */ - // TODO(jaewan): Unhide public void addOnSession2TokensChangedListener( - @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) { + @NonNull OnSession2TokensChangedListener listener) { + addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, new Handler()); + } + + /** + * Adds a listener to be notified when the {@link #getSession2Tokens()} changes. + * <p> + * This API is not generally intended for third party application developers. + * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> + * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> + * for consistent behavior across all devices. + * + * @param listener The listener to add + * @param handler The handler to call listener on. + */ + public void addOnSession2TokensChangedListener( + @NonNull OnSession2TokensChangedListener listener, @NonNull Handler handler) { addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, handler); } @@ -392,9 +400,7 @@ public final class MediaSessionManager { * Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates. * * @param listener The listener to remove. - * @hide */ - // TODO(jaewan): Unhide public void removeOnSession2TokensChangedListener( @NonNull OnSession2TokensChangedListener listener) { if (listener == null) { @@ -688,8 +694,6 @@ public final class MediaSessionManager { * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a> * for consistent behavior across all devices. - * - * @hide */ public interface OnSession2TokensChangedListener { /** @@ -793,7 +797,6 @@ public final class MediaSessionManager { private final String mPackageName; private final int mPid; private final int mUid; - private final IBinder mCallerBinder; /** * Create a new remote user information. @@ -803,22 +806,9 @@ public final class MediaSessionManager { * @param uid The uid of the remote user */ public RemoteUserInfo(@NonNull String packageName, int pid, int uid) { - this(packageName, pid, uid, null); - } - - /** - * Create a new remote user information. - * - * @param packageName The package name of the remote user - * @param pid The pid of the remote user - * @param uid The uid of the remote user - * @param callerBinder The binder of the remote user. Can be {@code null}. - */ - public RemoteUserInfo(String packageName, int pid, int uid, IBinder callerBinder) { mPackageName = packageName; mPid = pid; mUid = uid; - mCallerBinder = callerBinder; } /** @@ -843,13 +833,8 @@ public final class MediaSessionManager { } /** - * Returns equality of two RemoteUserInfo. Two RemoteUserInfos are the same only if they're - * sent to the same controller (either {@link MediaController} or - * {@link MediaBrowser}. If it's not nor one of them is triggered by the key presses, they - * would be considered as different one. - * <p> - * If you only want to compare the caller's package, compare them with the - * {@link #getPackageName()}, {@link #getPid()}, and/or {@link #getUid()} directly. + * Returns equality of two RemoteUserInfo. Two RemoteUserInfo objects are equal + * if and only if they have the same package name, same pid, and same uid. * * @param obj the reference object with which to compare. * @return {@code true} if equals, {@code false} otherwise @@ -863,8 +848,9 @@ public final class MediaSessionManager { return true; } RemoteUserInfo otherUserInfo = (RemoteUserInfo) obj; - return (mCallerBinder == null || otherUserInfo.mCallerBinder == null) ? false - : mCallerBinder.equals(otherUserInfo.mCallerBinder); + return TextUtils.equals(mPackageName, otherUserInfo.mPackageName) + && mPid == otherUserInfo.mPid + && mUid == otherUserInfo.mUid; } @Override diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 2c57d1f676ed..0d0ec4c78394 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -19,7 +19,6 @@ import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.Nullable; -import android.media.RemoteControlClient; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -481,161 +480,6 @@ public final class PlaybackState implements Parcelable { return mExtras; } - /** - * Get the {@link PlaybackState} state for the given - * {@link RemoteControlClient} state. - * - * @param rccState The state used by {@link RemoteControlClient}. - * @return The equivalent state used by {@link PlaybackState}. - * @hide - */ - public static int getStateFromRccState(int rccState) { - switch (rccState) { - case RemoteControlClient.PLAYSTATE_BUFFERING: - return STATE_BUFFERING; - case RemoteControlClient.PLAYSTATE_ERROR: - return STATE_ERROR; - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - return STATE_FAST_FORWARDING; - case RemoteControlClient.PLAYSTATE_NONE: - return STATE_NONE; - case RemoteControlClient.PLAYSTATE_PAUSED: - return STATE_PAUSED; - case RemoteControlClient.PLAYSTATE_PLAYING: - return STATE_PLAYING; - case RemoteControlClient.PLAYSTATE_REWINDING: - return STATE_REWINDING; - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - return STATE_SKIPPING_TO_PREVIOUS; - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - return STATE_SKIPPING_TO_NEXT; - case RemoteControlClient.PLAYSTATE_STOPPED: - return STATE_STOPPED; - default: - return -1; - } - } - - /** - * Get the {@link RemoteControlClient} state for the given - * {@link PlaybackState} state. - * - * @param state The state used by {@link PlaybackState}. - * @return The equivalent state used by {@link RemoteControlClient}. - * @hide - */ - public static int getRccStateFromState(int state) { - switch (state) { - case STATE_BUFFERING: - return RemoteControlClient.PLAYSTATE_BUFFERING; - case STATE_ERROR: - return RemoteControlClient.PLAYSTATE_ERROR; - case STATE_FAST_FORWARDING: - return RemoteControlClient.PLAYSTATE_FAST_FORWARDING; - case STATE_NONE: - return RemoteControlClient.PLAYSTATE_NONE; - case STATE_PAUSED: - return RemoteControlClient.PLAYSTATE_PAUSED; - case STATE_PLAYING: - return RemoteControlClient.PLAYSTATE_PLAYING; - case STATE_REWINDING: - return RemoteControlClient.PLAYSTATE_REWINDING; - case STATE_SKIPPING_TO_PREVIOUS: - return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS; - case STATE_SKIPPING_TO_NEXT: - return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS; - case STATE_STOPPED: - return RemoteControlClient.PLAYSTATE_STOPPED; - default: - return -1; - } - } - - /** - * @hide - */ - public static long getActionsFromRccControlFlags(int rccFlags) { - long actions = 0; - long flag = 1; - while (flag <= rccFlags) { - if ((flag & rccFlags) != 0) { - actions |= getActionForRccFlag((int) flag); - } - flag = flag << 1; - } - return actions; - } - - /** - * @hide - */ - public static int getRccControlFlagsFromActions(long actions) { - int rccFlags = 0; - long action = 1; - while (action <= actions && action < Integer.MAX_VALUE) { - if ((action & actions) != 0) { - rccFlags |= getRccFlagForAction(action); - } - action = action << 1; - } - return rccFlags; - } - - private static long getActionForRccFlag(int flag) { - switch (flag) { - case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS: - return ACTION_SKIP_TO_PREVIOUS; - case RemoteControlClient.FLAG_KEY_MEDIA_REWIND: - return ACTION_REWIND; - case RemoteControlClient.FLAG_KEY_MEDIA_PLAY: - return ACTION_PLAY; - case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE: - return ACTION_PLAY_PAUSE; - case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE: - return ACTION_PAUSE; - case RemoteControlClient.FLAG_KEY_MEDIA_STOP: - return ACTION_STOP; - case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD: - return ACTION_FAST_FORWARD; - case RemoteControlClient.FLAG_KEY_MEDIA_NEXT: - return ACTION_SKIP_TO_NEXT; - case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE: - return ACTION_SEEK_TO; - case RemoteControlClient.FLAG_KEY_MEDIA_RATING: - return ACTION_SET_RATING; - } - return 0; - } - - private static int getRccFlagForAction(long action) { - // We only care about the lower set of actions that can map to rcc - // flags. - int testAction = action < Integer.MAX_VALUE ? (int) action : 0; - switch (testAction) { - case (int) ACTION_SKIP_TO_PREVIOUS: - return RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS; - case (int) ACTION_REWIND: - return RemoteControlClient.FLAG_KEY_MEDIA_REWIND; - case (int) ACTION_PLAY: - return RemoteControlClient.FLAG_KEY_MEDIA_PLAY; - case (int) ACTION_PLAY_PAUSE: - return RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; - case (int) ACTION_PAUSE: - return RemoteControlClient.FLAG_KEY_MEDIA_PAUSE; - case (int) ACTION_STOP: - return RemoteControlClient.FLAG_KEY_MEDIA_STOP; - case (int) ACTION_FAST_FORWARD: - return RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD; - case (int) ACTION_SKIP_TO_NEXT: - return RemoteControlClient.FLAG_KEY_MEDIA_NEXT; - case (int) ACTION_SEEK_TO: - return RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE; - case (int) ACTION_SET_RATING: - return RemoteControlClient.FLAG_KEY_MEDIA_RATING; - } - return 0; - } - public static final Parcelable.Creator<PlaybackState> CREATOR = new Parcelable.Creator<PlaybackState>() { @Override diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java index 0265687bcd54..0dbf427b048d 100644 --- a/media/java/android/media/session/SessionCallbackLink.java +++ b/media/java/android/media/session/SessionCallbackLink.java @@ -669,7 +669,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyCommand(String packageName, int pid, int uid, ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb); @@ -681,7 +681,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent, @@ -694,7 +694,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyMediaButtonFromController(String packageName, int pid, int uid, ControllerCallbackLink caller, Intent mediaButtonIntent) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller, @@ -707,7 +707,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPrepare(String packageName, int pid, int uid, ControllerCallbackLink caller) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPrepare(packageName, pid, uid, caller); @@ -719,7 +719,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPrepareFromMediaId(String packageName, int pid, int uid, ControllerCallbackLink caller, String mediaId, Bundle extras) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras); @@ -731,7 +731,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPrepareFromSearch(String packageName, int pid, int uid, ControllerCallbackLink caller, String query, Bundle extras) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras); @@ -743,7 +743,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPrepareFromUri(String packageName, int pid, int uid, ControllerCallbackLink caller, Uri uri, Bundle extras) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras); @@ -755,7 +755,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPlay(String packageName, int pid, int uid, ControllerCallbackLink caller) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPlay(packageName, pid, uid, caller); @@ -767,7 +767,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPlayFromMediaId(String packageName, int pid, int uid, ControllerCallbackLink caller, String mediaId, Bundle extras) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras); @@ -779,7 +779,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPlayFromSearch(String packageName, int pid, int uid, ControllerCallbackLink caller, String query, Bundle extras) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras); @@ -791,7 +791,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPlayFromUri(String packageName, int pid, int uid, ControllerCallbackLink caller, Uri uri, Bundle extras) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras); @@ -803,7 +803,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifySkipToTrack(String packageName, int pid, int uid, ControllerCallbackLink caller, long id) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id); @@ -815,7 +815,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPause(String packageName, int pid, int uid, ControllerCallbackLink caller) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPause(packageName, pid, uid, caller); @@ -827,7 +827,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyStop(String packageName, int pid, int uid, ControllerCallbackLink caller) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onStop(packageName, pid, uid, caller); @@ -839,7 +839,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyNext(String packageName, int pid, int uid, ControllerCallbackLink caller) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onNext(packageName, pid, uid, caller); @@ -851,7 +851,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyPrevious(String packageName, int pid, int uid, ControllerCallbackLink caller) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onPrevious(packageName, pid, uid, caller); @@ -863,7 +863,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyFastForward(String packageName, int pid, int uid, ControllerCallbackLink caller) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onFastForward(packageName, pid, uid, caller); @@ -875,7 +875,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyRewind(String packageName, int pid, int uid, ControllerCallbackLink caller) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onRewind(packageName, pid, uid, caller); @@ -887,7 +887,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifySeekTo(String packageName, int pid, int uid, ControllerCallbackLink caller, long pos) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos); @@ -899,7 +899,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller, Rating rating) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onRate(packageName, pid, uid, caller, rating); @@ -910,7 +910,7 @@ public final class SessionCallbackLink implements Parcelable { public void notifyCustomAction(String packageName, int pid, int uid, ControllerCallbackLink caller, String action, Bundle args) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args); @@ -922,7 +922,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifyAdjustVolume(String packageName, int pid, int uid, ControllerCallbackLink caller, int direction) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction); @@ -934,7 +934,7 @@ public final class SessionCallbackLink implements Parcelable { @Override public void notifySetVolumeTo(String packageName, int pid, int uid, ControllerCallbackLink caller, int value) { - ensureMediasControlPermission(); + ensureMediaControlPermission(); final long token = Binder.clearCallingIdentity(); try { mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value); @@ -943,7 +943,7 @@ public final class SessionCallbackLink implements Parcelable { } } - private void ensureMediasControlPermission() { + private void ensureMediaControlPermission() { // Allow API calls from the System UI if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE) == PackageManager.PERMISSION_GRANTED) { diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 5cb8fb8bee3b..30907a5cf205 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -33,7 +33,10 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.HdmiUtils; +import android.hardware.hdmi.HdmiUtils.HdmiAddressRelativePosition; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; @@ -49,7 +52,6 @@ import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; @@ -145,6 +147,8 @@ public final class TvInputInfo implements Parcelable { // Attributes specific to HDMI private final HdmiDeviceInfo mHdmiDeviceInfo; private final boolean mIsConnectedToHdmiSwitch; + @HdmiAddressRelativePosition + private final int mHdmiConnectionRelativePosition; private final String mParentId; private final Bundle mExtras; @@ -260,7 +264,9 @@ public final class TvInputInfo implements Parcelable { private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, - boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) { + boolean isConnectedToHdmiSwitch, + @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId, + Bundle extras) { mService = service; mId = id; mType = type; @@ -275,6 +281,7 @@ public final class TvInputInfo implements Parcelable { mTunerCount = tunerCount; mHdmiDeviceInfo = hdmiDeviceInfo; mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch; + mHdmiConnectionRelativePosition = hdmiConnectionRelativePosition; mParentId = parentId; mExtras = extras; } @@ -419,6 +426,7 @@ public final class TvInputInfo implements Parcelable { /** * Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e., * the device isn't directly connected to a HDMI port. + * TODO(b/110094868): add @Deprecated for Q * @hide */ @SystemApi @@ -427,6 +435,16 @@ public final class TvInputInfo implements Parcelable { } /** + * Returns the relative position of this HDMI input. + * TODO(b/110094868): unhide for Q + * @hide + */ + @HdmiAddressRelativePosition + public int getHdmiConnectionRelativePosition() { + return mHdmiConnectionRelativePosition; + } + + /** * Checks if this TV input is marked hidden by the user in the settings. * * @param context Supplies a {@link Context} used to check if this TV input is hidden. @@ -555,6 +573,7 @@ public final class TvInputInfo implements Parcelable { && mTunerCount == obj.mTunerCount && Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo) && mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch + && mHdmiConnectionRelativePosition == obj.mHdmiConnectionRelativePosition && TextUtils.equals(mParentId, obj.mParentId) && Objects.equals(mExtras, obj.mExtras); } @@ -589,6 +608,7 @@ public final class TvInputInfo implements Parcelable { dest.writeInt(mTunerCount); dest.writeParcelable(mHdmiDeviceInfo, flags); dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0); + dest.writeInt(mHdmiConnectionRelativePosition); dest.writeString(mParentId); dest.writeBundle(mExtras); } @@ -630,6 +650,7 @@ public final class TvInputInfo implements Parcelable { mTunerCount = in.readInt(); mHdmiDeviceInfo = in.readParcelable(null); mIsConnectedToHdmiSwitch = in.readByte() == 1; + mHdmiConnectionRelativePosition = in.readInt(); mParentId = in.readString(); mExtras = in.readBundle(); } @@ -883,12 +904,17 @@ public final class TvInputInfo implements Parcelable { int type; boolean isHardwareInput = false; boolean isConnectedToHdmiSwitch = false; + @HdmiAddressRelativePosition + int hdmiConnectionRelativePosition = HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN; if (mHdmiDeviceInfo != null) { id = generateInputId(componentName, mHdmiDeviceInfo); type = TYPE_HDMI; isHardwareInput = true; - isConnectedToHdmiSwitch = (mHdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0; + hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo); + isConnectedToHdmiSwitch = + hdmiConnectionRelativePosition + != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW; } else if (mTvInputHardwareInfo != null) { id = generateInputId(componentName, mTvInputHardwareInfo); type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER); @@ -901,7 +927,8 @@ public final class TvInputInfo implements Parcelable { return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId, mIcon, mIconStandby, mIconDisconnected, mSetupActivity, mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount, - mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras); + mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition, + mParentId, mExtras); } private static String generateInputId(ComponentName name) { @@ -923,6 +950,16 @@ public final class TvInputInfo implements Parcelable { + tvInputHardwareInfo.getDeviceId(); } + private static int getRelativePosition(Context context, HdmiDeviceInfo info) { + HdmiControlManager hcm = + (HdmiControlManager) context.getSystemService(Context.HDMI_CONTROL_SERVICE); + if (hcm == null) { + return HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN; + } + return HdmiUtils.getHdmiAddressRelativePosition( + info.getPhysicalAddress(), hcm.getPhysicalAddress()); + } + private void parseServiceMetadata(int inputType) { ServiceInfo si = mResolveInfo.serviceInfo; PackageManager pm = mContext.getPackageManager(); diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index d19d1177cae7..2fbc6998576a 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -541,8 +541,7 @@ public abstract class MediaBrowserService extends Service { throw new IllegalStateException("This should be called inside of onGetRoot or" + " onLoadChildren or onLoadItem methods"); } - return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid, - mCurConnection.callbacks.asBinder()); + return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid); } /** diff --git a/native/android/Android.bp b/native/android/Android.bp index 73d4c4522010..7c1af4a81f9d 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -56,6 +56,7 @@ cc_library_shared { shared_libs: [ "liblog", + "libhidlbase", "libcutils", "libandroidfw", "libinput", @@ -70,6 +71,8 @@ cc_library_shared { "libnetd_client", "libhwui", "libxml2", + "android.hardware.configstore@1.0", + "android.hardware.configstore-utils", ], static_libs: [ diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 8be8eda06a59..8b45af0c3450 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -172,6 +172,7 @@ LIBANDROID { ASensorEventQueue_hasEvents; ASensorEventQueue_registerSensor; # introduced=26 ASensorEventQueue_setEventRate; + ASensorEventQueue_requestAdditionalInfoEvents; # introduced=29 ASensorManager_configureDirectReport; # introduced=26 ASensorManager_createEventQueue; ASensorManager_createHardwareBufferDirectChannel; # introduced=26 @@ -185,6 +186,7 @@ LIBANDROID { ASensorManager_getSensorList; ASensor_getFifoMaxEventCount; # introduced=21 ASensor_getFifoReservedEventCount; # introduced=21 + ASensor_getHandle; # introduced=29 ASensor_getHighestDirectReportRateLevel; # introduced=26 ASensor_getMinDelay; ASensor_getName; @@ -207,7 +209,7 @@ LIBANDROID { AStorageManager_unmountObb; ASurfaceControl_create; # introduced=29 ASurfaceControl_createFromWindow; # introduced=29 - ASurfaceControl_destroy; # introduced=29 + ASurfaceControl_release; # introduced=29 ASurfaceTexture_acquireANativeWindow; # introduced=28 ASurfaceTexture_attachToGLContext; # introduced=28 ASurfaceTexture_detachFromGLContext; # introduced=28 @@ -216,13 +218,24 @@ LIBANDROID { ASurfaceTexture_getTransformMatrix; # introduced=28 ASurfaceTexture_release; # introduced=28 ASurfaceTexture_updateTexImage; # introduced=28 + ASurfaceTransactionStats_getAcquireTime; # introduced=29 + ASurfaceTransactionStats_getASurfaceControls; # introduced=29 + ASurfaceTransactionStats_getLatchTime; # introduced=29 + ASurfaceTransactionStats_getPresentFenceFd; # introduced=29 + ASurfaceTransactionStats_getPreviousReleaseFenceFd; # introduced=29 + ASurfaceTransactionStats_releaseASurfaceControls; # introduced=29 ASurfaceTransaction_apply; # introduced=29 ASurfaceTransaction_create; # introduced=29 ASurfaceTransaction_delete; # introduced=29 + ASurfaceTransaction_reparent; # introduced=29 ASurfaceTransaction_setBuffer; # introduced=29 + ASurfaceTransaction_setBufferAlpha; # introduced=29 ASurfaceTransaction_setBufferTransparency; # introduced=29 ASurfaceTransaction_setDamageRegion; # introduced=29 + ASurfaceTransaction_setDesiredPresentTime; # introduced=29 ASurfaceTransaction_setGeometry; # introduced=29 + ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29 + ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29 ASurfaceTransaction_setOnComplete; # introduced=29 ASurfaceTransaction_setVisibility; # introduced=29 ASurfaceTransaction_setZOrder; # introduced=29 diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp index c3b2e2526ea8..63082fd70bc6 100644 --- a/native/android/sensor.cpp +++ b/native/android/sensor.cpp @@ -115,6 +115,7 @@ ASensorEventQueue* ASensorManager_createEventQueue(ASensorManager* manager, if (queue != 0) { ALooper_addFd(looper, queue->getFd(), ident, ALOOPER_EVENT_INPUT, callback, data); queue->looper = looper; + queue->requestAdditionalInfo = false; queue->incStrong(manager); } return static_cast<ASensorEventQueue*>(queue.get()); @@ -274,11 +275,19 @@ ssize_t ASensorEventQueue_getEvents(ASensorEventQueue* queue, ASensorEvent* even return android::BAD_VALUE; } - ssize_t actual = static_cast<SensorEventQueue*>(queue)->read(events, count); + SensorEventQueue* sensorQueue = static_cast<SensorEventQueue*>(queue); + ssize_t actual = sensorQueue->read(events, count); if (actual > 0) { - static_cast<SensorEventQueue*>(queue)->sendAck(events, actual); + sensorQueue->sendAck(events, actual); } - return actual; + + return sensorQueue->filterEvents(events, actual); +} + +int ASensorEventQueue_requestAdditionalInfoEvents(ASensorEventQueue* queue, bool enable) { + RETURN_IF_QUEUE_IS_NULL(android::BAD_VALUE); + queue->requestAdditionalInfo = enable; + return android::OK; } /*****************************************************************************/ diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index ead5b0b37f4b..f0100a9fd4f9 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -14,14 +14,26 @@ * limitations under the License. */ +#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h> #include <android/native_window.h> #include <android/surface_control.h> +#include <configstore/Utils.h> + +#include <gui/HdrMetadata.h> +#include <gui/ISurfaceComposer.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> #include <gui/SurfaceControl.h> +#include <ui/HdrCapabilities.h> + +#include <utils/Timers.h> + +using namespace android::hardware::configstore; +using namespace android::hardware::configstore::V1_0; using namespace android; +using android::hardware::configstore::V1_0::ISurfaceFlingerConfigs; using Transaction = SurfaceComposerClient::Transaction; @@ -95,10 +107,9 @@ ASurfaceControl* ASurfaceControl_create(ASurfaceControl* parent, const char* deb return reinterpret_cast<ASurfaceControl*>(surfaceControl.get()); } -void ASurfaceControl_destroy(ASurfaceControl* aSurfaceControl) { +void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) { sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); - Transaction().reparent(surfaceControl, nullptr).apply(); SurfaceControl_release(surfaceControl.get()); } @@ -120,6 +131,86 @@ void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) { transaction->apply(); } +typedef struct ASurfaceControlStats { + int64_t acquireTime; + sp<Fence> previousReleaseFence; +} ASurfaceControlStats; + +struct ASurfaceTransactionStats { + std::unordered_map<ASurfaceControl*, ASurfaceControlStats> aSurfaceControlStats; + int64_t latchTime; + sp<Fence> presentFence; +}; + +int64_t ASurfaceTransactionStats_getLatchTime(ASurfaceTransactionStats* aSurfaceTransactionStats) { + CHECK_NOT_NULL(aSurfaceTransactionStats); + return aSurfaceTransactionStats->latchTime; +} + +int ASurfaceTransactionStats_getPresentFenceFd(ASurfaceTransactionStats* aSurfaceTransactionStats) { + CHECK_NOT_NULL(aSurfaceTransactionStats); + auto& presentFence = aSurfaceTransactionStats->presentFence; + return (presentFence) ? presentFence->dup() : -1; +} + +void ASurfaceTransactionStats_getASurfaceControls(ASurfaceTransactionStats* aSurfaceTransactionStats, + ASurfaceControl*** outASurfaceControls, + size_t* outASurfaceControlsSize) { + CHECK_NOT_NULL(aSurfaceTransactionStats); + CHECK_NOT_NULL(outASurfaceControls); + CHECK_NOT_NULL(outASurfaceControlsSize); + + size_t size = aSurfaceTransactionStats->aSurfaceControlStats.size(); + + SurfaceControl** surfaceControls = new SurfaceControl*[size]; + ASurfaceControl** aSurfaceControls = reinterpret_cast<ASurfaceControl**>(surfaceControls); + + size_t i = 0; + for (auto& [aSurfaceControl, aSurfaceControlStats] : aSurfaceTransactionStats->aSurfaceControlStats) { + aSurfaceControls[i] = aSurfaceControl; + i++; + } + + *outASurfaceControls = aSurfaceControls; + *outASurfaceControlsSize = size; +} + +int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* aSurfaceTransactionStats, + ASurfaceControl* aSurfaceControl) { + CHECK_NOT_NULL(aSurfaceTransactionStats); + CHECK_NOT_NULL(aSurfaceControl); + + const auto& aSurfaceControlStats = + aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl); + LOG_ALWAYS_FATAL_IF( + aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(), + "ASurfaceControl not found"); + + return aSurfaceControlStats->second.acquireTime; +} + +int ASurfaceTransactionStats_getPreviousReleaseFenceFd( + ASurfaceTransactionStats* aSurfaceTransactionStats, ASurfaceControl* aSurfaceControl) { + CHECK_NOT_NULL(aSurfaceTransactionStats); + CHECK_NOT_NULL(aSurfaceControl); + + const auto& aSurfaceControlStats = + aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl); + LOG_ALWAYS_FATAL_IF( + aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(), + "ASurfaceControl not found"); + + auto& previousReleaseFence = aSurfaceControlStats->second.previousReleaseFence; + return (previousReleaseFence) ? previousReleaseFence->dup() : -1; +} + +void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** aSurfaceControls) { + CHECK_NOT_NULL(aSurfaceControls); + + SurfaceControl** surfaceControls = reinterpret_cast<SurfaceControl**>(aSurfaceControls); + delete[] surfaceControls; +} + void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, void* context, ASurfaceTransaction_OnComplete func) { CHECK_NOT_NULL(aSurfaceTransaction); @@ -127,9 +218,23 @@ void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, CHECK_NOT_NULL(func); TransactionCompletedCallbackTakesContext callback = [func](void* callback_context, - const TransactionStats& stats) { - int fence = (stats.presentFence) ? stats.presentFence->dup() : -1; - (*func)(callback_context, fence); + nsecs_t latchTime, + const sp<Fence>& presentFence, + const std::vector<SurfaceControlStats>& surfaceControlStats) { + ASurfaceTransactionStats aSurfaceTransactionStats; + + aSurfaceTransactionStats.latchTime = latchTime; + aSurfaceTransactionStats.presentFence = presentFence; + + auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats; + + for (const auto& [surfaceControl, acquireTime, previousReleaseFence] : surfaceControlStats) { + ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get()); + aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime; + aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence; + } + + (*func)(callback_context, &aSurfaceTransactionStats); }; Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); @@ -137,7 +242,23 @@ void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, transaction->addTransactionCompletedCallback(callback, context); } -void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, +void ASurfaceTransaction_reparent(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, + ASurfaceControl* newParentASurfaceControl) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + sp<SurfaceControl> newParentSurfaceControl = ASurfaceControl_to_SurfaceControl( + newParentASurfaceControl); + sp<IBinder> newParentHandle = (newParentSurfaceControl)? newParentSurfaceControl->getHandle() : nullptr; + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + transaction->reparent(surfaceControl, newParentHandle); +} + +void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, int8_t visibility) { CHECK_NOT_NULL(aSurfaceTransaction); CHECK_NOT_NULL(aSurfaceControl); @@ -157,7 +278,8 @@ void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction, } } -void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, +void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, int32_t z_order) { CHECK_NOT_NULL(aSurfaceTransaction); CHECK_NOT_NULL(aSurfaceControl); @@ -168,8 +290,9 @@ void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction, ASu transaction->setLayer(surfaceControl, z_order); } -void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, - AHardwareBuffer* buffer, int fence_fd) { +void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, + AHardwareBuffer* buffer, int acquire_fence_fd) { CHECK_NOT_NULL(aSurfaceTransaction); CHECK_NOT_NULL(aSurfaceControl); @@ -179,8 +302,8 @@ void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, ASu sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer)); transaction->setBuffer(surfaceControl, graphic_buffer); - if (fence_fd != -1) { - sp<Fence> fence = new Fence(fence_fd); + if (acquire_fence_fd != -1) { + sp<Fence> fence = new Fence(acquire_fence_fd); transaction->setAcquireFence(surfaceControl, fence); } } @@ -215,7 +338,8 @@ void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* aSurfaceTran transaction->setFlags(surfaceControl, flags, layer_state_t::eLayerOpaque); } -void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl, +void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, const ARect rects[], uint32_t count) { CHECK_NOT_NULL(aSurfaceTransaction); CHECK_NOT_NULL(aSurfaceControl); @@ -230,3 +354,80 @@ void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransactio transaction->setSurfaceDamageRegion(surfaceControl, region); } + +void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* aSurfaceTransaction, + int64_t desiredPresentTime) { + CHECK_NOT_NULL(aSurfaceTransaction); + + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + transaction->setDesiredPresentTime(static_cast<nsecs_t>(desiredPresentTime)); +} + +void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, + float alpha) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + + LOG_ALWAYS_FATAL_IF(alpha < 0.0 || alpha > 1.0, "invalid alpha"); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + transaction->setAlpha(surfaceControl, alpha); +} + +void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, + struct AHdrMetadata_smpte2086* metadata) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + HdrMetadata hdrMetadata; + + if (metadata) { + hdrMetadata.smpte2086.displayPrimaryRed.x = metadata->displayPrimaryRed.x; + hdrMetadata.smpte2086.displayPrimaryRed.y = metadata->displayPrimaryRed.y; + hdrMetadata.smpte2086.displayPrimaryGreen.x = metadata->displayPrimaryGreen.x; + hdrMetadata.smpte2086.displayPrimaryGreen.y = metadata->displayPrimaryGreen.y; + hdrMetadata.smpte2086.displayPrimaryBlue.x = metadata->displayPrimaryBlue.x; + hdrMetadata.smpte2086.displayPrimaryBlue.y = metadata->displayPrimaryBlue.y; + hdrMetadata.smpte2086.whitePoint.x = metadata->whitePoint.x; + hdrMetadata.smpte2086.whitePoint.y = metadata->whitePoint.y; + hdrMetadata.smpte2086.minLuminance = metadata->minLuminance; + hdrMetadata.smpte2086.maxLuminance = metadata->maxLuminance; + + hdrMetadata.validTypes |= HdrMetadata::SMPTE2086; + } else { + hdrMetadata.validTypes &= ~HdrMetadata::SMPTE2086; + } + + transaction->setHdrMetadata(surfaceControl, hdrMetadata); +} + +void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* aSurfaceTransaction, + ASurfaceControl* aSurfaceControl, + struct AHdrMetadata_cta861_3* metadata) { + CHECK_NOT_NULL(aSurfaceTransaction); + CHECK_NOT_NULL(aSurfaceControl); + + sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl); + Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction); + + HdrMetadata hdrMetadata; + + if (metadata) { + hdrMetadata.cta8613.maxContentLightLevel = metadata->maxContentLightLevel; + hdrMetadata.cta8613.maxFrameAverageLightLevel = metadata->maxFrameAverageLightLevel; + + hdrMetadata.validTypes |= HdrMetadata::CTA861_3; + } else { + hdrMetadata.validTypes &= ~HdrMetadata::CTA861_3; + } + + transaction->setHdrMetadata(surfaceControl, hdrMetadata); +} diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 8d04702ea5f6..da3416b886ad 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -76,7 +76,7 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, - Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, + Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_QUERY_ARGS }; private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { @@ -444,6 +444,7 @@ public class ExternalStorageProvider extends FileSystemProvider { row.add(Root.COLUMN_FLAGS, root.flags); row.add(Root.COLUMN_TITLE, root.title); row.add(Root.COLUMN_DOCUMENT_ID, root.docId); + row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS); long availableBytes = -1; if (root.reportAvailableBytes) { diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java index 14e293694ebd..7b112dfc125c 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java @@ -29,7 +29,6 @@ import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_DGRAM; import static android.system.OsConstants.SOL_SOCKET; -import static android.system.OsConstants.SO_BINDTODEVICE; import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_REUSEADDR; @@ -45,6 +44,7 @@ import android.net.MacAddress; import android.net.NetworkUtils; import android.net.TrafficStats; import android.net.util.SharedLog; +import android.net.util.SocketUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -629,14 +629,10 @@ public class DhcpServer extends IDhcpServer.Stub { final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_DHCP_SERVER); try { mSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SocketUtils.bindSocketToInterface(mSocket, mIfName); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1); - // SO_BINDTODEVICE actually takes a string. This works because the first member - // of struct ifreq is a NULL-terminated interface name. - // TODO: add a setsockoptString() - Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIfName); Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1); Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER); - NetworkUtils.protectFromVpn(mSocket); return mSocket; } catch (IOException | ErrnoException e) { diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java index f38888aafbd6..868f3bed4eed 100644 --- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java @@ -30,10 +30,10 @@ import android.annotation.Nullable; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.NetworkUtils; - -import com.google.android.collect.Sets; +import android.util.ArraySet; import java.net.Inet4Address; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -208,7 +208,7 @@ public class DhcpServingParams { * but it must always be set explicitly before building the {@link DhcpServingParams}. */ public Builder setDefaultRouters(@NonNull Inet4Address... defaultRouters) { - return setDefaultRouters(Sets.newArraySet(defaultRouters)); + return setDefaultRouters(new ArraySet<>(Arrays.asList(defaultRouters))); } /** @@ -238,7 +238,7 @@ public class DhcpServingParams { * building the {@link DhcpServingParams}. */ public Builder setDnsServers(@NonNull Inet4Address... dnsServers) { - return setDnsServers(Sets.newArraySet(dnsServers)); + return setDnsServers(new ArraySet<>(Arrays.asList(dnsServers))); } /** @@ -268,7 +268,7 @@ public class DhcpServingParams { * and do not need to be set here. */ public Builder setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) { - return setExcludedAddrs(Sets.newArraySet(excludedAddrs)); + return setExcludedAddrs(new ArraySet<>(Arrays.asList(excludedAddrs))); } /** diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index c8a8e1f8e3db..a3d7852c9f4a 100644 --- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -24,6 +24,7 @@ import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE; @@ -80,7 +81,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Protocol; import com.android.internal.util.RingBufferIndices; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -150,29 +150,28 @@ public class NetworkMonitor extends StateMachine { } } - private static final int BASE = Protocol.BASE_NETWORK_MONITOR; /** * ConnectivityService has sent a notification to indicate that network has connected. * Initiates Network Validation. */ - private static final int CMD_NETWORK_CONNECTED = BASE + 1; + private static final int CMD_NETWORK_CONNECTED = 1; /** * Message to self indicating it's time to evaluate a network's connectivity. * arg1 = Token to ignore old messages. */ - private static final int CMD_REEVALUATE = BASE + 6; + private static final int CMD_REEVALUATE = 6; /** * ConnectivityService has sent a notification to indicate that network has disconnected. */ - private static final int CMD_NETWORK_DISCONNECTED = BASE + 7; + private static final int CMD_NETWORK_DISCONNECTED = 7; /** * Force evaluation even if it has succeeded in the past. * arg1 = UID responsible for requesting this reeval. Will be billed for data. */ - private static final int CMD_FORCE_REEVALUATION = BASE + 8; + private static final int CMD_FORCE_REEVALUATION = 8; /** * Message to self indicating captive portal app finished. @@ -181,7 +180,7 @@ public class NetworkMonitor extends StateMachine { * APP_RETURN_WANTED_AS_IS * obj = mCaptivePortalLoggedInResponseToken as String */ - private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9; + private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = 9; /** * Message indicating sign-in app should be launched. @@ -190,14 +189,14 @@ public class NetworkMonitor extends StateMachine { * ConnectivityService when the user touches the "sign into * network" button in the wifi access point detail page. */ - private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; + private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = 11; /** * Retest network to see if captive portal is still in place. * arg1 = UID responsible for requesting this reeval. Will be billed for data. * 0 indicates self-initiated, so nobody to blame. */ - private static final int CMD_CAPTIVE_PORTAL_RECHECK = BASE + 12; + private static final int CMD_CAPTIVE_PORTAL_RECHECK = 12; /** * ConnectivityService notifies NetworkMonitor of settings changes to @@ -210,20 +209,20 @@ public class NetworkMonitor extends StateMachine { * states, including being ignored until after an ongoing captive portal * validation phase is completed. */ - private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13; - private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15; + private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = 13; + private static final int CMD_EVALUATE_PRIVATE_DNS = 15; /** * Message to self indicating captive portal detection is completed. * obj = CaptivePortalProbeResult for detection result; */ - public static final int CMD_PROBE_COMPLETE = BASE + 16; + public static final int CMD_PROBE_COMPLETE = 16; /** * ConnectivityService notifies NetworkMonitor of DNS query responses event. * arg1 = returncode in OnDnsEvent which indicates the response code for the DNS query. */ - public static final int EVENT_DNS_NOTIFICATION = BASE + 17; + public static final int EVENT_DNS_NOTIFICATION = 17; // Start mReevaluateDelayMs at this value and double. private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; @@ -245,7 +244,6 @@ public class NetworkMonitor extends StateMachine { private final INetworkMonitorCallbacks mCallback; private final Network mNetwork; private final Network mNonPrivateDnsBypassNetwork; - private final int mNetId; private final TelephonyManager mTelephonyManager; private final WifiManager mWifiManager; private final ConnectivityManager mCm; @@ -322,7 +320,7 @@ public class NetworkMonitor extends StateMachine { NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs, Dependencies deps) { // Add suffix indicating which NetworkMonitor we're talking about. - super(TAG + "/" + network.netId); + super(TAG + "/" + network.toString()); // Logs with a tag of the form given just above, e.g. // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ... @@ -335,7 +333,6 @@ public class NetworkMonitor extends StateMachine { mDependencies = deps; mNonPrivateDnsBypassNetwork = network; mNetwork = deps.getPrivateDnsBypassNetwork(network); - mNetId = mNetwork.netId; mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -471,7 +468,7 @@ public class NetworkMonitor extends StateMachine { @Override protected void log(String s) { - if (DBG) Log.d(TAG + "/" + mNetwork.netId, s); + if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s); } private void validationLog(int probeType, Object url, String msg) { @@ -795,7 +792,7 @@ public class NetworkMonitor extends StateMachine { CustomIntentReceiver(String action, int token, int what) { mToken = token; mWhat = what; - mAction = action + "_" + mNetId + "_" + token; + mAction = action + "_" + mNetwork.getNetworkHandle() + "_" + token; mContext.registerReceiver(this, new IntentFilter(mAction)); } public PendingIntent getPendingIntent() { @@ -1088,11 +1085,6 @@ public class NetworkMonitor extends StateMachine { return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; } - private boolean getWifiScansAlwaysAvailableDisabled() { - return mDependencies.getSetting( - mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0; - } - private String getCaptivePortalServerHttpsUrl() { return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL); @@ -1484,7 +1476,7 @@ public class NetworkMonitor extends StateMachine { */ private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal, long requestTimestampMs, long responseTimestampMs) { - if (getWifiScansAlwaysAvailableDisabled()) { + if (!mWifiManager.isScanAlwaysAvailable()) { return; } @@ -1568,7 +1560,7 @@ public class NetworkMonitor extends StateMachine { private void logNetworkEvent(int evtype) { int[] transports = mNetworkCapabilities.getTransportTypes(); - mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype)); + mMetricsLog.log(mNetwork, transports, new NetworkEvent(evtype)); } private int networkEventType(ValidationStage s, EvaluationResult r) { @@ -1590,7 +1582,8 @@ public class NetworkMonitor extends StateMachine { private void maybeLogEvaluationResult(int evtype) { if (mEvaluationTimer.isRunning()) { int[] transports = mNetworkCapabilities.getTransportTypes(); - mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop())); + mMetricsLog.log(mNetwork, transports, + new NetworkEvent(evtype, mEvaluationTimer.stop())); mEvaluationTimer.reset(); } } @@ -1603,7 +1596,7 @@ public class NetworkMonitor extends StateMachine { .setReturnCode(probeResult) .setDurationMs(durationMs) .build(); - mMetricsLog.log(mNetId, transports, ev); + mMetricsLog.log(mNetwork, transports, ev); } @VisibleForTesting @@ -1743,7 +1736,7 @@ public class NetworkMonitor extends StateMachine { boolean result = false; // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the // possible traffic cost in metered network. - if (mNetworkCapabilities.isMetered() + if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) && (SystemClock.elapsedRealtime() - getLastProbeTime() < mDataStallMinEvaluateTime)) { return false; diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml index 9f30eda242f6..716fc8ded734 100644 --- a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml +++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml @@ -33,7 +33,7 @@ android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/> <LinearLayout - android:id="@+id/all_apps_view" + android:id="@+id/app_views_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="16dp" @@ -61,4 +61,12 @@ android:layout_height="48dp" android:gravity="center"/> + <TextView + android:id="@+id/empty_view" + android:layout_width="match_parent" + android:layout_height="106dp" + android:gravity="center" + android:visibility="gone" + android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/> + </LinearLayout> diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java index 73cb8db136b5..330049fc6673 100644 --- a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java +++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java @@ -77,9 +77,11 @@ public class AppEntitiesHeaderController { private final Context mContext; private final TextView mHeaderTitleView; + private final TextView mHeaderEmptyView; private final Button mHeaderDetailsView; private final AppEntityInfo[] mAppEntityInfos; + private final View mAppViewsContainer; private final View[] mAppEntityViews; private final ImageView[] mAppIconViews; private final TextView[] mAppTitleViews; @@ -87,6 +89,7 @@ public class AppEntitiesHeaderController { private int mHeaderTitleRes; private int mHeaderDetailsRes; + private int mHeaderEmptyRes; private View.OnClickListener mDetailsOnClickListener; /** @@ -104,6 +107,8 @@ public class AppEntitiesHeaderController { mContext = context; mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title); mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details); + mHeaderEmptyView = appEntitiesHeaderView.findViewById(R.id.empty_view); + mAppViewsContainer = appEntitiesHeaderView.findViewById(R.id.app_views_container); mAppEntityInfos = new AppEntityInfo[MAXIMUM_APPS]; mAppIconViews = new ImageView[MAXIMUM_APPS]; @@ -152,6 +157,14 @@ public class AppEntitiesHeaderController { } /** + * Sets the string resource id for the empty text. + */ + public AppEntitiesHeaderController setHeaderEmptyRes(@StringRes int emptyRes) { + mHeaderEmptyRes = emptyRes; + return this; + } + + /** * Set an app entity at a specified position view. * * @param index the index at which the specified view is to be inserted @@ -192,6 +205,12 @@ public class AppEntitiesHeaderController { */ public void apply() { bindHeaderTitleView(); + + if (isAppEntityInfosEmpty()) { + setEmptyViewVisible(true); + return; + } + setEmptyViewVisible(false); bindHeaderDetailsView(); // Rebind all apps view @@ -245,4 +264,22 @@ public class AppEntitiesHeaderController { mAppSummaryViews[index].setText(summary); } } + + private void setEmptyViewVisible(boolean visible) { + if (mHeaderEmptyRes != 0) { + mHeaderEmptyView.setText(mHeaderEmptyRes); + } + mHeaderEmptyView.setVisibility(visible ? View.VISIBLE : View.GONE); + mHeaderDetailsView.setVisibility(visible ? View.GONE : View.VISIBLE); + mAppViewsContainer.setVisibility(visible ? View.GONE : View.VISIBLE); + } + + private boolean isAppEntityInfosEmpty() { + for (AppEntityInfo info : mAppEntityInfos) { + if (info != null) { + return false; + } + } + return true; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 27dc628ac094..9b12a31dc149 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -622,7 +622,7 @@ public class AccessPoint implements Comparable<AccessPoint> { .append(KEY_PREFIX_FQDN) .append(config.FQDN).toString(); } else { - return getKey(config.SSID, config.BSSID, getSecurity(config)); + return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config)); } } @@ -1555,7 +1555,7 @@ public class AccessPoint implements Comparable<AccessPoint> { mOsuFailure = mContext.getString( R.string.osu_failure_provisioning_not_available); break; - case OSU_FAILURE_INVALID_SERVER_URL: + case OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU: mOsuFailure = mContext.getString(R.string.osu_failure_invalid_server_url); break; case OSU_FAILURE_UNEXPECTED_COMMAND_TYPE: 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 index 8c18c356159e..4c68c1476cff 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java @@ -60,6 +60,7 @@ public class AppEntitiesHeaderControllerTest { .setOnClickListener(v -> { }) .build(); + mController.setAppEntity(0, mAppEntityInfo); } @Test @@ -172,6 +173,8 @@ public class AppEntitiesHeaderControllerTest { mController.setAppEntity(0, mAppEntityInfo) .setAppEntity(1, mAppEntityInfo) .setAppEntity(2, mAppEntityInfo).apply(); + final View appViewsContainer = mAppEntitiesHeaderView.findViewById( + R.id.app_views_container); 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); @@ -181,8 +184,28 @@ public class AppEntitiesHeaderControllerTest { 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); + + assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void apply_noAppEntitySet_shouldOnlyShowTitleAndEmptyView() { + mController.setHeaderTitleRes(R.string.expand_button_title) + .setAppEntity(0, mAppEntityInfo) + .setAppEntity(1, mAppEntityInfo) + .setAppEntity(2, mAppEntityInfo).apply(); + final View titleView = mAppEntitiesHeaderView.findViewById(R.id.header_title); + final View detailsView = mAppEntitiesHeaderView.findViewById(R.id.header_details); + final View emptyView = mAppEntitiesHeaderView.findViewById(R.id.empty_view); + final View appViewsContainer = mAppEntitiesHeaderView.findViewById( + R.id.app_views_container); + + mController.clearAllAppEntities().apply(); + + assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(emptyView.getVisibility()).isEqualTo(View.VISIBLE); + + assertThat(detailsView.getVisibility()).isEqualTo(View.GONE); + assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE); } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index b903142c44c6..c3c3f25a9f03 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -166,6 +166,8 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> <uses-permission android:name="android.permission.SUSPEND_APPS" /> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> + <!-- Permission needed to wipe the device for Test Harness Mode --> + <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" /> <uses-permission android:name="android.permission.MANAGE_APPOPS" /> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 98f0cbe29110..f2be2e7d8f28 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -458,6 +458,11 @@ heads-up notifications. --> <bool name="config_smart_replies_in_notifications_show_in_heads_up">true</bool> + <!-- Smart replies in notifications: Minimum number of system generated smart replies that + should be shown in a notification. If we cannot show at least this many replies we instead + show none. --> + <integer name="config_smart_replies_in_notifications_min_num_system_generated_replies">0</integer> + <!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents. Blank sends the user to the Chooser first. This name is in the ComponentName flattened format (package/class) --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f384d8f5b357..d64e2f9d21ce 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -150,6 +150,9 @@ <!-- Option to always allow USB debugging from the attached computer --> <string name="usb_debugging_always">Always allow from this computer</string> + <!-- Button label for confirming acceptance of enabling USB debugging [CHAR LIMIT=15] --> + <string name="usb_debugging_allow">Allow</string> + <!-- Title of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. --> <string name="usb_debugging_secondary_user_title">USB debugging not allowed</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 1aff3949a74b..7218acf614d4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -1,15 +1,9 @@ package com.android.keyguard; -import android.content.ContentResolver; import android.content.Context; -import android.database.ContentObserver; import android.graphics.Paint; import android.graphics.Paint.Style; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; import android.util.AttributeSet; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -18,29 +12,19 @@ import android.widget.TextClock; import androidx.annotation.VisibleForTesting; -import com.android.keyguard.clock.BubbleClockController; -import com.android.keyguard.clock.StretchAnalogClockController; -import com.android.keyguard.clock.TypeClockController; +import com.android.keyguard.clock.ClockManager; import com.android.systemui.Dependency; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.ExtensionController.Extension; -import java.util.Objects; import java.util.TimeZone; -import java.util.function.Consumer; -import java.util.function.Supplier; /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. */ public class KeyguardClockSwitch extends RelativeLayout { - private LayoutInflater mLayoutInflater; - - private final ContentResolver mContentResolver; /** * Optional/alternative clock injected via plugin. */ @@ -63,14 +47,6 @@ public class KeyguardClockSwitch extends RelativeLayout { */ private View mKeyguardStatusArea; /** - * Used to select between plugin or default implementations of ClockPlugin interface. - */ - private Extension<ClockPlugin> mClockExtension; - /** - * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads. - */ - private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin); - /** * Maintain state so that a newly connected plugin can be initialized. */ private float mDarkAmount; @@ -94,16 +70,7 @@ public class KeyguardClockSwitch extends RelativeLayout { } }; - private final ContentObserver mContentObserver = - new ContentObserver(new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - if (mClockExtension != null) { - mClockExtension.reload(); - } - } - }; + private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; public KeyguardClockSwitch(Context context) { this(context, null); @@ -111,8 +78,6 @@ public class KeyguardClockSwitch extends RelativeLayout { public KeyguardClockSwitch(Context context, AttributeSet attrs) { super(context, attrs); - mLayoutInflater = LayoutInflater.from(context); - mContentResolver = context.getContentResolver(); } /** @@ -133,45 +98,14 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class) - .withPlugin(ClockPlugin.class) - .withCallback(mClockPluginConsumer) - // Using withDefault even though this isn't the default as a workaround. - // ExtensionBulider doesn't provide the ability to supply a ClockPlugin - // instance based off of the value of a setting. Since multiple "default" - // can be provided, using a supplier that changes the settings value. - // A null return will cause Extension#reload to look at the next "default" - // supplier. - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - BubbleClockController.class.getName(), - () -> BubbleClockController.build(mLayoutInflater))) - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - StretchAnalogClockController.class.getName(), - () -> StretchAnalogClockController.build(mLayoutInflater))) - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - TypeClockController.class.getName(), - () -> TypeClockController.build(mLayoutInflater))) - .build(); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), - false, mContentObserver); + Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener); Dependency.get(StatusBarStateController.class).addCallback(mStateListener); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mClockExtension.destroy(); - mContentResolver.unregisterContentObserver(mContentObserver); + Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener); Dependency.get(StatusBarStateController.class).removeCallback(mStateListener); } @@ -313,52 +247,12 @@ public class KeyguardClockSwitch extends RelativeLayout { } @VisibleForTesting (otherwise = VisibleForTesting.NONE) - Consumer<ClockPlugin> getClockPluginConsumer() { - return mClockPluginConsumer; + ClockManager.ClockChangedListener getClockChangedListener() { + return mClockChangedListener; } @VisibleForTesting (otherwise = VisibleForTesting.NONE) StatusBarStateController.StateListener getStateListener() { return mStateListener; } - - /** - * Supplier that only gets an instance when a settings value matches expected value. - */ - private static class SettingsGattedSupplier implements Supplier<ClockPlugin> { - - private final ContentResolver mContentResolver; - private final String mKey; - private final String mValue; - private final Supplier<ClockPlugin> mSupplier; - - /** - * Constructs a supplier that changes secure setting key against value. - * - * @param contentResolver Used to look up settings value. - * @param key Settings key. - * @param value If the setting matches this values that get supplies a ClockPlugin - * instance. - * @param supplier Supplier of ClockPlugin instance, only used if the setting - * matches value. - */ - SettingsGattedSupplier(ContentResolver contentResolver, String key, String value, - Supplier<ClockPlugin> supplier) { - mContentResolver = contentResolver; - mKey = key; - mValue = value; - mSupplier = supplier; - } - - /** - * Returns null if the settings value doesn't match the expected value. - * - * A null return causes Extension#reload to skip this supplier and move to the next. - */ - @Override - public ClockPlugin get() { - final String currentValue = Settings.Secure.getString(mContentResolver, mKey); - return Objects.equals(currentValue, mValue) ? mSupplier.get() : null; - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java new file mode 100644 index 000000000000..3217ca6f489c --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.keyguard.clock; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.view.LayoutInflater; + +import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.ExtensionController.Extension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Manages custom clock faces. + */ +@Singleton +public final class ClockManager { + + private final LayoutInflater mLayoutInflater; + private final ContentResolver mContentResolver; + + /** + * Observe settings changes to know when to switch the clock face. + */ + private final ContentObserver mContentObserver = + new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + if (mClockExtension != null) { + mClockExtension.reload(); + } + } + }; + + private final ExtensionController mExtensionController; + /** + * Used to select between plugin or default implementations of ClockPlugin interface. + */ + private Extension<ClockPlugin> mClockExtension; + /** + * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads. + */ + private final Consumer<ClockPlugin> mClockPluginConsumer = this::setClockPlugin; + + private final List<ClockChangedListener> mListeners = new ArrayList<>(); + + @Inject + public ClockManager(Context context, ExtensionController extensionController) { + mExtensionController = extensionController; + mLayoutInflater = LayoutInflater.from(context); + mContentResolver = context.getContentResolver(); + } + + /** + * Add listener to be notified when clock implementation should change. + */ + public void addOnClockChangedListener(ClockChangedListener listener) { + if (mListeners.isEmpty()) { + register(); + } + mListeners.add(listener); + if (mClockExtension != null) { + mClockExtension.reload(); + } + } + + /** + * Remove listener added with {@link addOnClockChangedListener}. + */ + public void removeOnClockChangedListener(ClockChangedListener listener) { + mListeners.remove(listener); + if (mListeners.isEmpty()) { + unregister(); + } + } + + private void setClockPlugin(ClockPlugin plugin) { + for (int i = 0; i < mListeners.size(); i++) { + // It probably doesn't make sense to supply the same plugin instances to multiple + // listeners. This should be fine for now since there is only a single listener. + mListeners.get(i).onClockChanged(plugin); + } + } + + private void register() { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), + false, mContentObserver); + mClockExtension = mExtensionController.newExtension(ClockPlugin.class) + .withPlugin(ClockPlugin.class) + .withCallback(mClockPluginConsumer) + // Using withDefault even though this isn't the default as a workaround. + // ExtensionBuilder doesn't provide the ability to supply a ClockPlugin + // instance based off of the value of a setting. Since multiple "default" + // can be provided, using a supplier that changes the settings value. + // A null return will cause Extension#reload to look at the next "default" + // supplier. + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + BubbleClockController.class.getName(), + () -> BubbleClockController.build(mLayoutInflater))) + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + StretchAnalogClockController.class.getName(), + () -> StretchAnalogClockController.build(mLayoutInflater))) + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + TypeClockController.class.getName(), + () -> TypeClockController.build(mLayoutInflater))) + .build(); + } + + private void unregister() { + mContentResolver.unregisterContentObserver(mContentObserver); + mClockExtension.destroy(); + } + + /** + * Listener for events that should cause the custom clock face to change. + */ + public interface ClockChangedListener { + /** + * Called when custom clock should change. + * + * @param clock Custom clock face to use. A null value indicates the default clock face. + */ + void onClockChanged(ClockPlugin clock); + } + + /** + * Supplier that only gets an instance when a settings value matches expected value. + */ + private static class SettingsGattedSupplier implements Supplier<ClockPlugin> { + + private final ContentResolver mContentResolver; + private final String mKey; + private final String mValue; + private final Supplier<ClockPlugin> mSupplier; + + /** + * Constructs a supplier that changes secure setting key against value. + * + * @param contentResolver Used to look up settings value. + * @param key Settings key. + * @param value If the setting matches this values that get supplies a ClockPlugin + * instance. + * @param supplier Supplier of ClockPlugin instance, only used if the setting + * matches value. + */ + SettingsGattedSupplier(ContentResolver contentResolver, String key, String value, + Supplier<ClockPlugin> supplier) { + mContentResolver = contentResolver; + mKey = key; + mValue = value; + mSupplier = supplier; + } + + /** + * Returns null if the settings value doesn't match the expected value. + * + * A null return causes Extension#reload to skip this supplier and move to the next. + */ + @Override + public ClockPlugin get() { + final String currentValue = Settings.Secure.getString(mContentResolver, mKey); + return Objects.equals(currentValue, mValue) ? mSupplier.get() : null; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index ec6ecc64d07e..d99f234c26c8 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -29,6 +29,7 @@ import com.android.internal.app.ColorDisplayController; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.Preconditions; +import com.android.keyguard.clock.ClockManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.appops.AppOpsController; import com.android.systemui.assist.AssistManager; @@ -283,6 +284,7 @@ public class Dependency extends SystemUI { @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; @Nullable @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; + @Inject Lazy<ClockManager> mClockManager; @Inject public Dependency() { @@ -449,6 +451,7 @@ public class Dependency extends SystemUI { mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get); mProviders.put(ForegroundServiceNotificationListener.class, mForegroundServiceNotificationListener::get); + mProviders.put(ClockManager.class, mClockManager::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 1d2d7fafe800..2aecc24e83c0 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -16,14 +16,18 @@ package com.android.systemui; +import static android.view.Display.DEFAULT_DISPLAY; + import android.app.WallpaperManager; import android.content.ComponentCallbacks2; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region.Op; +import android.hardware.display.DisplayManager; import android.os.AsyncTask; import android.os.Handler; import android.os.Trace; @@ -33,7 +37,6 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceHolder; -import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; @@ -94,7 +97,7 @@ public class ImageWallpaper extends WallpaperService { float mYOffset = 0f; float mScale = 1f; - private Display mDefaultDisplay; + private Display mDisplay; private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); boolean mVisible = true; @@ -138,10 +141,20 @@ public class ImageWallpaper extends WallpaperService { super.onCreate(surfaceHolder); //noinspection ConstantConditions - mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay(); + final Context displayContext = getDisplayContext(); + final int displayId = displayContext == null ? DEFAULT_DISPLAY : + displayContext.getDisplayId(); + DisplayManager dm = getSystemService(DisplayManager.class); + if (dm != null) { + mDisplay = dm.getDisplay(displayId); + if (mDisplay == null) { + Log.e(TAG, "Cannot find display! Fallback to default."); + mDisplay = dm.getDisplay(DEFAULT_DISPLAY); + } + } setOffsetNotificationsEnabled(false); - updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */); + updateSurfaceSize(surfaceHolder, getDisplayInfo(), false /* forDraw */); } @Override @@ -165,9 +178,26 @@ public class ImageWallpaper extends WallpaperService { hasWallpaper = false; } - // Set surface size equal to bitmap size, prevent memory waste - int surfaceWidth = Math.max(MIN_BACKGROUND_WIDTH, mBackgroundWidth); - int surfaceHeight = Math.max(MIN_BACKGROUND_HEIGHT, mBackgroundHeight); + // Expected surface size. + int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth); + int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight); + + // Calculate the minimum drawing area of the surface, which saves memory and does not + // distort the image. + final float scale = Math.min( + (float) mBackgroundHeight / (float) surfaceHeight, + (float) mBackgroundWidth / (float) surfaceWidth); + surfaceHeight = (int) (scale * surfaceHeight); + surfaceWidth = (int) (scale * surfaceWidth); + + // Set surface size to at least MIN size. + if (surfaceWidth < MIN_BACKGROUND_WIDTH || surfaceHeight < MIN_BACKGROUND_HEIGHT) { + final float scaleUp = Math.max( + (float) MIN_BACKGROUND_WIDTH / (float) surfaceWidth, + (float) MIN_BACKGROUND_HEIGHT / (float) surfaceHeight); + surfaceWidth = (int) ((float) surfaceWidth * scaleUp); + surfaceHeight = (int) ((float) surfaceHeight * scaleUp); + } // Used a fixed size surface, because we are special. We can do // this because we know the current design of window animations doesn't @@ -267,8 +297,8 @@ public class ImageWallpaper extends WallpaperService { } @VisibleForTesting - DisplayInfo getDefaultDisplayInfo() { - mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo); + DisplayInfo getDisplayInfo() { + mDisplay.getDisplayInfo(mTmpDisplayInfo); return mTmpDisplayInfo; } @@ -278,7 +308,7 @@ public class ImageWallpaper extends WallpaperService { } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawWallpaper"); - DisplayInfo displayInfo = getDefaultDisplayInfo(); + DisplayInfo displayInfo = getDisplayInfo(); int newRotation = displayInfo.rotation; // Sometimes a wallpaper is not large enough to cover the screen in one dimension. @@ -445,7 +475,7 @@ public class ImageWallpaper extends WallpaperService { if (DEBUG) { Log.d(TAG, "Wallpaper loaded: " + mBackground); } - updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(), + updateSurfaceSize(getSurfaceHolder(), getDisplayInfo(), false /* forDraw */); } diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 36664004a4a6..9efa656b3ed3 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -604,8 +604,14 @@ public class SwipeHelper implements Gefingerpoken { if (absDelta >= size) { delta = delta > 0 ? maxScrollDistance : -maxScrollDistance; } else { - delta = maxScrollDistance * (float) Math.sin( - (delta / size) * (Math.PI / 2)); + int startPosition = mCallback.getConstrainSwipeStartPosition(); + if (absDelta > startPosition) { + int signedStartPosition = + (int) (startPosition * Math.signum(delta)); + delta = signedStartPosition + + maxScrollDistance * (float) Math.sin( + ((delta - signedStartPosition) / size) * (Math.PI / 2)); + } } } @@ -742,6 +748,14 @@ public class SwipeHelper implements Gefingerpoken { float getFalsingThresholdFactor(); /** + * @return The position, in pixels, at which a constrained swipe should start being + * constrained. + */ + default int getConstrainSwipeStartPosition() { + return 0; + } + + /** * @return If true, the given view is draggable. */ default boolean canChildBeDragged(@NonNull View animView) { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 957d772be730..a457deed7ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -267,8 +267,9 @@ public class BubbleController { BubbleView bubble = (BubbleView) mInflater.inflate( R.layout.bubble_view, mStackView, false /* attachToRoot */); bubble.setNotif(notif); - if (shouldUseActivityView(mContext)) { - bubble.setAppOverlayIntent(getAppOverlayIntent(notif)); + PendingIntent bubbleIntent = getValidBubbleIntent(notif); + if (shouldUseActivityView(mContext) || bubbleIntent != null) { + bubble.setBubbleIntent(getValidBubbleIntent(notif)); } mBubbles.put(bubble.getKey(), bubble); mStackView.addBubble(bubble); @@ -282,7 +283,7 @@ public class BubbleController { } @Nullable - private PendingIntent getAppOverlayIntent(NotificationEntry notif) { + private PendingIntent getValidBubbleIntent(NotificationEntry notif) { Notification notification = notif.notification.getNotification(); if (canLaunchInActivityView(notification.getBubbleMetadata() != null ? notification.getBubbleMetadata().getIntent() : null)) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 9a11b965b319..dcd121bdb239 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -38,9 +38,11 @@ import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; +import android.widget.LinearLayout; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -226,6 +228,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } /** + * Sets the entry that should be expanded and expands if needed. + */ + @VisibleForTesting + public void setExpandedBubble(NotificationEntry entry) { + for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { + BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i); + if (entry.equals(bv.getEntry())) { + setExpandedBubble(bv); + } + } + } + + /** * Adds a bubble to the top of the stack. * * @param bubbleView the view to add to the stack. @@ -456,7 +471,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F if (mExpandedBubble.hasAppOverlayIntent()) { // Bubble with activity view expanded state ActivityView expandedView = mExpandedBubble.getActivityView(); - expandedView.setLayoutParams(new ViewGroup.LayoutParams( + // XXX: gets added to linear layout + expandedView.setLayoutParams(new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight)); final PendingIntent intent = mExpandedBubble.getAppOverlayIntent(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 91893ef3db00..7b6e79be64db 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -298,7 +298,7 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati } - public void setAppOverlayIntent(PendingIntent intent) { + public void setBubbleIntent(PendingIntent intent) { mAppOverlayIntent = intent; } } diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java index fa5a114c7187..d332f59a4500 100644 --- a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java +++ b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java @@ -48,6 +48,11 @@ public interface DockManager { */ void removeListener(DockEventListener callback); + /** + * Returns true if the device is in docking state. + */ + boolean isDocked(); + /** Callback for receiving dock events */ interface DockEventListener { /** diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java index 9fc22340ea22..5353ee6c8ab3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java @@ -22,7 +22,6 @@ import android.os.UserHandle; import android.util.Log; import com.android.internal.hardware.AmbientDisplayConfiguration; -import com.android.systemui.SysUiServiceProvider; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.DozeMachine.State; @@ -46,12 +45,12 @@ public class DozeDockHandler implements DozeMachine.Part { private int mDockState = DockManager.STATE_NONE; public DozeDockHandler(Context context, DozeMachine machine, DozeHost dozeHost, - AmbientDisplayConfiguration config, Handler handler) { + AmbientDisplayConfiguration config, Handler handler, DockManager dockManager) { mMachine = machine; mDozeHost = dozeHost; mConfig = config; mHandler = handler; - mDockManager = SysUiServiceProvider.getComponent(context, DockManager.class); + mDockManager = dockManager; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 58ae555dbff1..e338a3406d7f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -27,8 +27,10 @@ import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.SysUiServiceProvider; import com.android.systemui.SystemUIApplication; import com.android.systemui.classifier.FalsingManager; +import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.AsyncSensorManager; import com.android.systemui.util.wakelock.DelayedWakeLock; @@ -44,6 +46,7 @@ public class DozeFactory { Context context = dozeService; SensorManager sensorManager = Dependency.get(AsyncSensorManager.class); AlarmManager alarmManager = context.getSystemService(AlarmManager.class); + DockManager dockManager = SysUiServiceProvider.getComponent(context, DockManager.class); DozeHost host = getHost(dozeService); AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context); @@ -63,13 +66,13 @@ public class DozeFactory { new DozePauser(handler, machine, alarmManager, params.getPolicy()), new DozeFalsingManagerAdapter(FalsingManager.getInstance(context)), createDozeTriggers(context, sensorManager, host, alarmManager, config, params, - handler, wakeLock, machine), + handler, wakeLock, machine, dockManager), createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params), new DozeScreenState(wrappedService, handler, params, wakeLock), createDozeScreenBrightness(context, wrappedService, sensorManager, host, params, handler), new DozeWallpaperState(context), - new DozeDockHandler(context, machine, host, config, handler) + new DozeDockHandler(context, machine, host, config, handler, dockManager) }); return machine; @@ -86,10 +89,11 @@ public class DozeFactory { private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager, DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config, - DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine) { + DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine, + DockManager dockManager) { boolean allowPulseTriggers = true; return new DozeTriggers(context, machine, host, alarmManager, config, params, - sensorManager, handler, wakeLock, allowPulseTriggers); + sensorManager, handler, wakeLock, allowPulseTriggers, dockManager); } private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 78374a0b1621..562edd61c867 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -214,6 +214,15 @@ public class DozeSensors { mPickupSensor.setDisabled(disable); } + /** Ignore the setting value of only the sensors that require the touchscreen. */ + public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) { + for (TriggerSensor sensor : mSensors) { + if (sensor.mRequiresTouchscreen) { + sensor.ignoreSetting(ignore); + } + } + } + /** Dump current state */ public void dump(PrintWriter pw) { for (TriggerSensor s : mSensors) { @@ -323,6 +332,7 @@ public class DozeSensors { protected boolean mRequested; protected boolean mRegistered; protected boolean mDisabled; + protected boolean mIgnoresSetting; public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen) { @@ -333,6 +343,13 @@ public class DozeSensors { public TriggerSensor(Sensor sensor, String setting, boolean settingDef, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen) { + this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates, + requiresTouchscreen, false /* ignoresSetting */); + } + + private TriggerSensor(Sensor sensor, String setting, boolean settingDef, + boolean configured, int pulseReason, boolean reportsTouchCoordinates, + boolean requiresTouchscreen, boolean ignoresSetting) { mSensor = sensor; mSetting = setting; mSettingDefault = settingDef; @@ -340,6 +357,7 @@ public class DozeSensors { mPulseReason = pulseReason; mReportsTouchCoordinates = reportsTouchCoordinates; mRequiresTouchscreen = requiresTouchscreen; + mIgnoresSetting = ignoresSetting; } public void setListening(boolean listen) { @@ -354,9 +372,16 @@ public class DozeSensors { updateListener(); } + public void ignoreSetting(boolean ignored) { + if (mIgnoresSetting == ignored) return; + mIgnoresSetting = ignored; + updateListener(); + } + public void updateListener() { if (!mConfigured || mSensor == null) return; - if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) { + if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting) + && !mRegistered) { mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered); } else if (mRegistered) { @@ -382,6 +407,7 @@ public class DozeSensors { .append(", mRequested=").append(mRequested) .append(", mDisabled=").append(mDisabled) .append(", mConfigured=").append(mConfigured) + .append(", mIgnoresSetting=").append(mIgnoresSetting) .append(", mSensor=").append(mSensor).append("}").toString(); } @@ -464,7 +490,8 @@ public class DozeSensors { public void updateListener() { if (!mConfigured) return; AsyncSensorManager asyncSensorManager = (AsyncSensorManager) mSensorManager; - if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) { + if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting) + && !mRegistered) { asyncSensorManager.registerPluginListener(mPluginSensor, mTriggerEventListener); mRegistered = true; if (DEBUG) Log.d(TAG, "registerPluginListener"); @@ -481,6 +508,7 @@ public class DozeSensors { .append(", mRequested=").append(mRequested) .append(", mDisabled=").append(mDisabled) .append(", mConfigured=").append(mConfigured) + .append(", mIgnoresSetting=").append(mIgnoresSetting) .append(", mSensor=").append(mPluginSensor).append("}").toString(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index e2e448bb0d0c..dc505b57eb6b 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -33,8 +33,10 @@ import android.os.UserHandle; import android.text.format.Formatter; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.util.Preconditions; +import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; import com.android.systemui.util.wakelock.WakeLock; @@ -71,6 +73,8 @@ public class DozeTriggers implements DozeMachine.Part { private final boolean mAllowPulseTriggers; private final UiModeManager mUiModeManager; private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver(); + private final DockEventListener mDockEventListener = new DockEventListener(); + private final DockManager mDockManager; private long mNotificationPulseTime; private boolean mPulsePending; @@ -79,7 +83,7 @@ public class DozeTriggers implements DozeMachine.Part { public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost, AlarmManager alarmManager, AmbientDisplayConfiguration config, DozeParameters dozeParameters, SensorManager sensorManager, Handler handler, - WakeLock wakeLock, boolean allowPulseTriggers) { + WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) { mContext = context; mMachine = machine; mDozeHost = dozeHost; @@ -93,6 +97,7 @@ public class DozeTriggers implements DozeMachine.Part { config, wakeLock, this::onSensor, this::onProximityFar, dozeParameters.getPolicy()); mUiModeManager = mContext.getSystemService(UiModeManager.class); + mDockManager = dockManager; } private void onNotification() { @@ -129,7 +134,8 @@ public class DozeTriggers implements DozeMachine.Part { } } - private void onSensor(int pulseReason, boolean sensorPerformedProxCheck, + @VisibleForTesting + void onSensor(int pulseReason, boolean sensorPerformedProxCheck, float screenX, float screenY, float[] rawValues) { boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; boolean isTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_TAP; @@ -159,7 +165,7 @@ public class DozeTriggers implements DozeMachine.Part { } else { mDozeHost.extendPulse(); } - }, sensorPerformedProxCheck, pulseReason); + }, sensorPerformedProxCheck || mDockManager.isDocked(), pulseReason); } if (isPickup) { @@ -223,6 +229,7 @@ public class DozeTriggers implements DozeMachine.Part { case INITIALIZED: mBroadcastReceiver.register(mContext); mDozeHost.addCallback(mHostCallback); + mDockManager.addListener(mDockEventListener); checkTriggersAtInit(); break; case DOZE: @@ -248,6 +255,7 @@ public class DozeTriggers implements DozeMachine.Part { case FINISH: mBroadcastReceiver.unregister(mContext); mDozeHost.removeCallback(mHostCallback); + mDockManager.removeListener(mDockEventListener); mDozeSensors.setListening(false); mDozeSensors.setProxListening(false); break; @@ -423,6 +431,24 @@ public class DozeTriggers implements DozeMachine.Part { } } + private class DockEventListener implements DockManager.DockEventListener { + @Override + public void onEvent(int event) { + if (DEBUG) Log.d(TAG, "dock event = " + event); + switch (event) { + case DockManager.STATE_DOCKED: + case DockManager.STATE_DOCKED_HIDE: + mDozeSensors.ignoreTouchScreenSensorsSettingInterferingWithDocking(true); + break; + case DockManager.STATE_NONE: + mDozeSensors.ignoreTouchScreenSensorsSettingInterferingWithDocking(false); + break; + default: + // no-op + } + } + } + private DozeHost.Callback mHostCallback = new DozeHost.Callback() { @Override public void onNotificationAlerted() { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt index 77e25e324915..26c6d501f2cb 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt @@ -30,6 +30,7 @@ import android.widget.TextView import com.android.systemui.Dependency import com.android.systemui.R import com.android.systemui.plugins.ActivityStarter +import java.util.concurrent.TimeUnit class OngoingPrivacyDialog constructor( val context: Context, @@ -60,7 +61,8 @@ class OngoingPrivacyDialog constructor( setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null) setPositiveButton(R.string.ongoing_privacy_dialog_open_settings, object : DialogInterface.OnClickListener { - val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE) + val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).putExtra( + Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1)) @Suppress("DEPRECATION") override fun onClick(dialog: DialogInterface?, which: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 5ba9b4b34042..76d394d197eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -32,6 +32,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.UiOffloadThread; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.StatusBarStateController; @@ -460,7 +461,9 @@ public class NotificationLogger implements StateListener { mUiOffloadThread.submit(() -> { try { mBarService.onNotificationExpansionChanged( - key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded); + key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded, + // TODO (b/120767764): fill in location + ExpandableViewState.LOCATION_UNKNOWN /* notificationLocation */); } catch (RemoteException e) { Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index d0665670bf8f..0c37666a79d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -5818,6 +5818,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @Override + public int getConstrainSwipeStartPosition() { + NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); + if (menuRow != null) { + return Math.abs(menuRow.getMenuSnapTarget()); + } + return 0; + } + + @Override public boolean canChildBeDismissed(View v) { return NotificationStackScrollLayout.this.canChildBeDismissed(v); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java index 2a11c2676b0c..d02280836fb5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java @@ -97,10 +97,11 @@ public class NearestTouchFrame extends FrameLayout { } return mClickableChildren .stream() - .filter(v -> v.isAttachedToWindow()) + .filter(View::isAttachedToWindow) .map(v -> new Pair<>(distance(v, event), v)) .min(Comparator.comparingInt(f -> f.first)) - .get().second; + .map(data -> data.second) + .orElse(null); } private int distance(View v, MotionEvent event) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index 88f904882c8f..ffaa236218fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -69,13 +69,13 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat private final WindowManager mWindowManager; private final IActivityManager mActivityManager; private final DozeParameters mDozeParameters; + private final WindowManager.LayoutParams mLpChanged; + private final boolean mKeyguardScreenRotation; private ViewGroup mStatusBarView; private WindowManager.LayoutParams mLp; - private WindowManager.LayoutParams mLpChanged; private boolean mHasTopUi; private boolean mHasTopUiChanged; private int mBarHeight; - private final boolean mKeyguardScreenRotation; private float mScreenBrightnessDoze; private final State mCurrentState = new State(); private OtherwisedCollapsedListener mListener; @@ -97,6 +97,7 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation(); mDozeParameters = dozeParameters; mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze(); + mLpChanged = new WindowManager.LayoutParams(); Dependency.get(StatusBarStateController.class).addCallback( mStateListener, StatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER); Dependency.get(ConfigurationController.class).addCallback(this); @@ -138,7 +139,6 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat mStatusBarView = statusBarView; mBarHeight = barHeight; mWindowManager.addView(mStatusBarView, mLp); - mLpChanged = new WindowManager.LayoutParams(); mLpChanged.copyFrom(mLp); onThemeChanged(); } @@ -228,7 +228,9 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat private void applyHeight(State state) { boolean expanded = isExpanded(state); if (state.forcePluginOpen) { - mListener.setWouldOtherwiseCollapse(expanded); + if (mListener != null) { + mListener.setWouldOtherwiseCollapse(expanded); + } expanded = true; } if (expanded) { @@ -247,7 +249,7 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat private void applyFitsSystemWindows(State state) { boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded(); - if (mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) { + if (mStatusBarView != null && mStatusBarView.getFitsSystemWindows() != fitsSystemWindows) { mStatusBarView.setFitsSystemWindows(fitsSystemWindows); mStatusBarView.requestApplyInsets(); } @@ -289,7 +291,7 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat applyBrightness(state); applyHasTopUi(state); applyNotTouchable(state); - if (mLp.copyFrom(mLpChanged) != 0) { + if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { mWindowManager.updateViewLayout(mStatusBarView, mLp); } if (mHasTopUi != mHasTopUiChanged) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java index 3bd0d456dbd3..db0462095daf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java @@ -46,18 +46,21 @@ public final class SmartReplyConstants extends ContentObserver { private static final String KEY_EDIT_CHOICES_BEFORE_SENDING = "edit_choices_before_sending"; private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up"; + private static final String KEY_MIN_NUM_REPLIES = "min_num_system_generated_replies"; private final boolean mDefaultEnabled; private final boolean mDefaultRequiresP; private final int mDefaultMaxSqueezeRemeasureAttempts; private final boolean mDefaultEditChoicesBeforeSending; private final boolean mDefaultShowInHeadsUp; + private final int mDefaultMinNumSystemGeneratedReplies; private boolean mEnabled; private boolean mRequiresTargetingP; private int mMaxSqueezeRemeasureAttempts; private boolean mEditChoicesBeforeSending; private boolean mShowInHeadsUp; + private int mMinNumSystemGeneratedReplies; private final Context mContext; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -78,6 +81,8 @@ public final class SmartReplyConstants extends ContentObserver { R.bool.config_smart_replies_in_notifications_edit_choices_before_sending); mDefaultShowInHeadsUp = resources.getBoolean( R.bool.config_smart_replies_in_notifications_show_in_heads_up); + mDefaultMinNumSystemGeneratedReplies = resources.getInteger( + R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS), @@ -105,6 +110,8 @@ public final class SmartReplyConstants extends ContentObserver { mEditChoicesBeforeSending = mParser.getBoolean( KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending); mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp); + mMinNumSystemGeneratedReplies = + mParser.getInt(KEY_MIN_NUM_REPLIES, mDefaultMinNumSystemGeneratedReplies); } } @@ -155,4 +162,12 @@ public final class SmartReplyConstants extends ContentObserver { public boolean getShowInHeadsUp() { return mShowInHeadsUp; } + + /** + * Returns the minimum number of system generated replies to show in a notification. + * If we cannot show at least this many system generated replies we should show none. + */ + public int getMinNumSystemGeneratedReplies() { + return mMinNumSystemGeneratedReplies; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index d6eff941ed70..c4f027f2abea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -88,6 +88,12 @@ public class SmartReplyView extends ViewGroup { private View mSmartReplyContainer; + /** + * Whether the smart replies in this view were generated by the notification assistant. If not + * they're provided by the app. + */ + private boolean mSmartRepliesGeneratedByAssistant = false; + @ColorInt private int mCurrentBackgroundColor; @ColorInt @@ -202,6 +208,7 @@ public class SmartReplyView extends ViewGroup { getContext(), this, i, smartReplies, smartReplyController, entry); addView(replyButton); } + this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant; } } reallocateCandidateButtonQueueForSqueezing(); @@ -344,10 +351,11 @@ public class SmartReplyView extends ViewGroup { mCandidateButtonQueueForSqueezing.clear(); } - int measuredWidth = mPaddingLeft + mPaddingRight; - int maxChildHeight = 0; + SmartSuggestionMeasures accumulatedMeasures = new SmartSuggestionMeasures( + mPaddingLeft + mPaddingRight, + 0 /* maxChildHeight */, + mSingleLineButtonPaddingHorizontal); int displayedChildCount = 0; - int buttonPaddingHorizontal = mSingleLineButtonPaddingHorizontal; // Set up a list of suggestions where actions come before replies. Note that the Buttons // themselves have already been added to the view hierarchy in an order such that Smart @@ -360,11 +368,15 @@ public class SmartReplyView extends ViewGroup { smartSuggestions.addAll(smartReplies); List<View> coveredSuggestions = new ArrayList<>(); + // SmartSuggestionMeasures for all action buttons, this will be filled in when the first + // reply button is added. + SmartSuggestionMeasures actionsMeasures = null; + for (View child : smartSuggestions) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(), - buttonPaddingHorizontal, child.getPaddingBottom()); + child.setPadding(accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingTop(), + accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingBottom()); child.measure(MEASURE_SPEC_ANY_LENGTH, heightMeasureSpec); coveredSuggestions.add(child); @@ -380,45 +392,52 @@ public class SmartReplyView extends ViewGroup { } // Remember the current measurements in case the current button doesn't fit in. - final int originalMaxChildHeight = maxChildHeight; - final int originalMeasuredWidth = measuredWidth; - final int originalButtonPaddingHorizontal = buttonPaddingHorizontal; + SmartSuggestionMeasures originalMeasures = accumulatedMeasures.clone(); + if (actionsMeasures == null && lp.buttonType == SmartButtonType.REPLY) { + // We've added all actions (we go through actions first), now add their + // measurements. + actionsMeasures = accumulatedMeasures.clone(); + } final int spacing = displayedChildCount == 0 ? 0 : mSpacing; final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); - measuredWidth += spacing + childWidth; - maxChildHeight = Math.max(maxChildHeight, childHeight); + accumulatedMeasures.mMeasuredWidth += spacing + childWidth; + accumulatedMeasures.mMaxChildHeight = + Math.max(accumulatedMeasures.mMaxChildHeight, childHeight); // Do we need to increase the number of lines in smart reply buttons to two? final boolean increaseToTwoLines = - buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal - && (lineCount == 2 || measuredWidth > targetWidth); + (accumulatedMeasures.mButtonPaddingHorizontal + == mSingleLineButtonPaddingHorizontal) + && (lineCount == 2 || accumulatedMeasures.mMeasuredWidth > targetWidth); if (increaseToTwoLines) { - measuredWidth += (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease; - buttonPaddingHorizontal = mDoubleLineButtonPaddingHorizontal; + accumulatedMeasures.mMeasuredWidth += + (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease; + accumulatedMeasures.mButtonPaddingHorizontal = + mDoubleLineButtonPaddingHorizontal; } // If the last button doesn't fit into the remaining width, try squeezing preceding // smart reply buttons. - if (measuredWidth > targetWidth) { + if (accumulatedMeasures.mMeasuredWidth > targetWidth) { // Keep squeezing preceding and current smart reply buttons until they all fit. - while (measuredWidth > targetWidth + while (accumulatedMeasures.mMeasuredWidth > targetWidth && !mCandidateButtonQueueForSqueezing.isEmpty()) { final Button candidate = mCandidateButtonQueueForSqueezing.poll(); final int squeezeReduction = squeezeButton(candidate, heightMeasureSpec); if (squeezeReduction != SQUEEZE_FAILED) { - maxChildHeight = Math.max(maxChildHeight, candidate.getMeasuredHeight()); - measuredWidth -= squeezeReduction; + accumulatedMeasures.mMaxChildHeight = + Math.max(accumulatedMeasures.mMaxChildHeight, + candidate.getMeasuredHeight()); + accumulatedMeasures.mMeasuredWidth -= squeezeReduction; } } // If the current button still doesn't fit after squeezing all buttons, undo the // last squeezing round. - if (measuredWidth > targetWidth) { - measuredWidth = originalMeasuredWidth; - maxChildHeight = originalMaxChildHeight; - buttonPaddingHorizontal = originalButtonPaddingHorizontal; + if (accumulatedMeasures.mMeasuredWidth > targetWidth) { + accumulatedMeasures = originalMeasures; // Mark all buttons from the last squeezing round as "failed to squeeze", so // that they're re-measured without squeezing later. @@ -440,16 +459,75 @@ public class SmartReplyView extends ViewGroup { displayedChildCount++; } + if (mSmartRepliesGeneratedByAssistant) { + if (!gotEnoughSmartReplies(smartReplies)) { + // We don't have enough smart replies - hide all of them. + for (View smartReplyButton : smartReplies) { + final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams(); + lp.show = false; + } + // Reset our measures back to when we had only added actions (before adding + // replies). + accumulatedMeasures = actionsMeasures; + } + } + // We're done squeezing buttons, so we can clear the priority queue. mCandidateButtonQueueForSqueezing.clear(); // Finally, we need to re-measure some buttons. - remeasureButtonsIfNecessary(buttonPaddingHorizontal, maxChildHeight); + remeasureButtonsIfNecessary(accumulatedMeasures.mButtonPaddingHorizontal, + accumulatedMeasures.mMaxChildHeight); setMeasuredDimension( - resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec), - resolveSize(Math.max(getSuggestedMinimumHeight(), - mPaddingTop + maxChildHeight + mPaddingBottom), heightMeasureSpec)); + resolveSize(Math.max(getSuggestedMinimumWidth(), + accumulatedMeasures.mMeasuredWidth), + widthMeasureSpec), + resolveSize(Math.max(getSuggestedMinimumHeight(), mPaddingTop + + accumulatedMeasures.mMaxChildHeight + mPaddingBottom), + heightMeasureSpec)); + } + + /** + * Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending + * on which suggestions are added. + */ + private static class SmartSuggestionMeasures { + int mMeasuredWidth = -1; + int mMaxChildHeight = -1; + int mButtonPaddingHorizontal = -1; + + SmartSuggestionMeasures(int measuredWidth, int maxChildHeight, + int buttonPaddingHorizontal) { + this.mMeasuredWidth = measuredWidth; + this.mMaxChildHeight = maxChildHeight; + this.mButtonPaddingHorizontal = buttonPaddingHorizontal; + } + + public SmartSuggestionMeasures clone() { + return new SmartSuggestionMeasures( + mMeasuredWidth, mMaxChildHeight, mButtonPaddingHorizontal); + } + } + + /** + * Returns whether our notification contains at least N smart replies (or 0) where N is + * determined by {@link SmartReplyConstants}. + */ + private boolean gotEnoughSmartReplies(List<View> smartReplies) { + int numShownReplies = 0; + for (View smartReplyButton : smartReplies) { + final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams(); + if (lp.show) { + numShownReplies++; + } + } + if (numShownReplies == 0 + || numShownReplies >= mConstants.getMinNumSystemGeneratedReplies()) { + // We have enough replies, yay! + return true; + } + return false; } private List<View> filterActionsOrReplies(SmartButtonType buttonType) { diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java index ed2ad79bdb50..12006fab2c0b 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java @@ -75,7 +75,7 @@ public class UsbDebuggingActivity extends AlertActivity final AlertController.AlertParams ap = mAlertParams; ap.mTitle = getString(R.string.usb_debugging_title); ap.mMessage = getString(R.string.usb_debugging_message, fingerprints); - ap.mPositiveButtonText = getString(android.R.string.ok); + ap.mPositiveButtonText = getString(R.string.usb_debugging_allow); ap.mNegativeButtonText = getString(android.R.string.cancel); ap.mPositiveButtonListener = this; ap.mNegativeButtonListener = this; diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java index 712ea27982fb..8b00eee29c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java +++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java @@ -25,9 +25,9 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.media.IRemoteVolumeController; import android.media.MediaMetadata; -import android.media.session.ISessionController; import android.media.session.MediaController; import android.media.session.MediaController.PlaybackInfo; +import android.media.session.MediaSession; import android.media.session.MediaSession.QueueItem; import android.media.session.MediaSession.Token; import android.media.session.MediaSessionManager; @@ -113,17 +113,17 @@ public class MediaSessions { r.controller.setVolumeTo(level, 0); } - private void onRemoteVolumeChangedH(ISessionController session, int flags) { - final MediaController controller = new MediaController(mContext, session); + private void onRemoteVolumeChangedH(MediaSession.Token sessionToken, int flags) { + final MediaController controller = new MediaController(mContext, sessionToken); if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " " + Util.audioManagerFlagsToString(flags)); final Token token = controller.getSessionToken(); mCallbacks.onRemoteVolumeChanged(token, flags); } - private void onUpdateRemoteControllerH(ISessionController session) { - final MediaController controller = session != null ? new MediaController(mContext, session) - : null; + private void onUpdateRemoteControllerH(MediaSession.Token sessionToken) { + final MediaController controller = + sessionToken != null ? new MediaController(mContext, sessionToken) : null; final String pkg = controller != null ? controller.getPackageName() : null; if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg); // this may be our only indication that a remote session is changed, refresh @@ -332,15 +332,16 @@ public class MediaSessions { private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() { @Override - public void remoteVolumeChanged(ISessionController session, int flags) + public void remoteVolumeChanged(MediaSession.Token sessionToken, int flags) throws RemoteException { - mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget(); + mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, + sessionToken).sendToTarget(); } @Override - public void updateRemoteController(final ISessionController session) + public void updateRemoteController(final MediaSession.Token sessionToken) throws RemoteException { - mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget(); + mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget(); } }; @@ -360,10 +361,10 @@ public class MediaSessions { onActiveSessionsUpdatedH(mMgr.getActiveSessions(null)); break; case REMOTE_VOLUME_CHANGED: - onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1); + onRemoteVolumeChangedH((MediaSession.Token) msg.obj, msg.arg1); break; case UPDATE_REMOTE_CONTROLLER: - onUpdateRemoteControllerH((ISessionController) msg.obj); + onUpdateRemoteControllerH((MediaSession.Token) msg.obj); break; } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index fbc1c20755a1..d80b444ad64b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -39,6 +39,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextClock; +import com.android.keyguard.clock.ClockManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.statusbar.StatusBarState; @@ -51,8 +52,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.function.Consumer; - @SmallTest @RunWith(AndroidTestingRunner.class) // Need to run on the main thread because KeyguardSliceView$Row init checks for @@ -85,7 +84,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); verify(mClockView).setVisibility(GONE); assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer); @@ -102,7 +101,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getBigClockView()).thenReturn(pluginView); // WHEN the plugin is connected - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); // THEN the big clock container is visible and it is the parent of the // big clock view. assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE); @@ -112,7 +111,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Test public void onPluginConnected_nullView() { ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); verify(mClockView, never()).setVisibility(GONE); } @@ -121,11 +120,11 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // GIVEN a plugin has already connected ClockPlugin plugin1 = mock(ClockPlugin.class); when(plugin1.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin1); // WHEN a second plugin is connected ClockPlugin plugin2 = mock(ClockPlugin.class); when(plugin2.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin2); // THEN only the view from the second plugin should be a child of KeyguardClockSwitch. assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer); assertThat(plugin1.getView().getParent()).isNull(); @@ -137,7 +136,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { mKeyguardClockSwitch.setDarkAmount(0.5f); // WHEN a plugin is connected ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); // THEN dark amount should be initalized on the plugin. verify(plugin).setDarkAmount(0.5f); } @@ -149,8 +148,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { when(plugin.getView()).thenReturn(pluginView); mClockView.setVisibility(GONE); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); - mKeyguardClockSwitch.getClockPluginConsumer().accept(null); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null); verify(mClockView).setVisibility(VISIBLE); assertThat(plugin.getView().getParent()).isNull(); @@ -167,8 +166,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getBigClockView()).thenReturn(pluginView); // WHEN the plugin is connected and then disconnected - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); - mKeyguardClockSwitch.getClockPluginConsumer().accept(null); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null); // THEN the big lock container is GONE and the big clock view doesn't have // a parent. assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE); @@ -178,8 +177,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Test public void onPluginDisconnected_nullView() { ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); - mKeyguardClockSwitch.getClockPluginConsumer().accept(null); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null); verify(mClockView, never()).setVisibility(GONE); } @@ -188,13 +187,13 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // GIVEN two plugins are connected ClockPlugin plugin1 = mock(ClockPlugin.class); when(plugin1.getView()).thenReturn(new TextClock(getContext())); - Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer(); - consumer.accept(plugin1); + ClockManager.ClockChangedListener listener = mKeyguardClockSwitch.getClockChangedListener(); + listener.onClockChanged(plugin1); ClockPlugin plugin2 = mock(ClockPlugin.class); when(plugin2.getView()).thenReturn(new TextClock(getContext())); - consumer.accept(plugin2); + listener.onClockChanged(plugin2); // WHEN the second plugin is disconnected - consumer.accept(null); + listener.onClockChanged(null); // THEN the default clock should be shown. verify(mClockView).setVisibility(VISIBLE); assertThat(plugin1.getView().getParent()).isNull(); @@ -213,7 +212,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); mKeyguardClockSwitch.setTextColor(Color.WHITE); @@ -237,7 +236,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); Style style = mock(Style.class); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); mKeyguardClockSwitch.setStyle(style); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java index 53ad0b5132c0..fc57909f42b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java @@ -68,7 +68,7 @@ public class ImageWallpaperTest extends SysuiTestCase { public Engine onCreateEngine() { return new DrawableEngine() { @Override - DisplayInfo getDefaultDisplayInfo() { + DisplayInfo getDisplayInfo() { return mDisplayInfo; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index fa5cf04d56f4..60a20cf15cc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeastOnce; @@ -160,6 +161,12 @@ public class BubbleControllerTest extends SysuiTestCase { stackView.expandStack(); assertTrue(mBubbleController.isStackExpanded()); + stackView.setExpandedBubble(mRow.getEntry()); + assertEquals(stackView.getExpandedBubble().getEntry(), mRow.getEntry()); + + stackView.setExpandedBubble(mRow2.getEntry()); + assertEquals(stackView.getExpandedBubble().getEntry(), mRow2.getEntry()); + mBubbleController.collapseStack(); assertFalse(mBubbleController.isStackExpanded()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java index b368876d2ae7..839b5e4472c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java @@ -32,6 +32,11 @@ public class DockManagerFake implements DockManager { this.mCallback = null; } + @Override + public boolean isDocked() { + return false; + } + public void setDockEvent(int event) { mCallback.onEvent(event); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java index f45500a87274..e4558df962e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -36,6 +36,8 @@ public class DozeConfigurationUtil { when(params.getPickupVibrationThreshold()).thenReturn(0); when(params.getProxCheckBeforePulse()).thenReturn(true); when(params.getPickupSubtypePerformsProxCheck(anyInt())).thenReturn(true); + when(params.getPolicy()).thenReturn(mock(AlwaysOnDisplayPolicy.class)); + when(params.doubleTapReportsTouchCoordinates()).thenReturn(false); doneHolder[0] = true; return params; @@ -48,9 +50,14 @@ public class DozeConfigurationUtil { when(config.doubleTapGestureEnabled(anyInt())).thenReturn(false); when(config.pickupGestureEnabled(anyInt())).thenReturn(false); when(config.pulseOnNotificationEnabled(anyInt())).thenReturn(true); + when(config.alwaysOnEnabled(anyInt())).thenReturn(false); when(config.doubleTapSensorType()).thenReturn(null); + when(config.tapSensorType()).thenReturn(null); + when(config.longPressSensorType()).thenReturn(null); + when(config.dozePickupSensorAvailable()).thenReturn(false); + when(config.wakeScreenGestureAvailable()).thenReturn(false); doneHolder[0] = true; return config; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java index 926ff69651be..0fc0953f333e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java @@ -75,7 +75,7 @@ public class DozeDockHandlerTest extends SysuiTestCase { mContext.putComponent(DockManager.class, mDockManagerFake); mDockHandler = new DozeDockHandler(mContext, mMachine, mHost, mConfig, - Handler.createAsync(Looper.myLooper())); + Handler.createAsync(Looper.myLooper()), mDockManagerFake); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 31fc625d34dd..7b358b98b459 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -18,8 +18,10 @@ package com.android.systemui.doze; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,6 +36,8 @@ import android.testing.TestableLooper.RunWithLooper; import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dock.DockManager; +import com.android.systemui.dock.DockManagerFake; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLockFake; @@ -59,6 +63,7 @@ public class DozeTriggersTest extends SysuiTestCase { private WakeLock mWakeLock; private Instrumentation mInstrumentation; private AlarmManager mAlarmManager; + private DockManagerFake mDockManagerFake; @BeforeClass public static void setupSuite() { @@ -76,9 +81,12 @@ public class DozeTriggersTest extends SysuiTestCase { mParameters = DozeConfigurationUtil.createMockParameters(); mSensors = new FakeSensorManager(mContext); mWakeLock = new WakeLockFake(); + mDockManagerFake = spy(new DockManagerFake()); + mContext.putComponent(DockManager.class, mDockManagerFake); mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters, - mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true); + mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true, + mDockManagerFake); } @Test @@ -102,4 +110,38 @@ public class DozeTriggersTest extends SysuiTestCase { verify(mMachine).requestPulse(anyInt()); } + @Test + public void testDockEventListener_registerAndUnregister() { + mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED); + + verify(mDockManagerFake).addListener(any()); + + mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH); + + verify(mDockManagerFake).removeListener(any()); + } + + @Test + public void testOnSensor_whenUndockedWithNearAndDoubleTapScreen_shouldNotWakeUp() { + mSensors.getMockProximitySensor().sendProximityResult(false /* far */); + + mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP, + false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */, + null /* rawValues */); + + verify(mMachine, never()).wakeUp(); + } + + @Test + public void testOnSensor_whenDockedWithNearAndDoubleTapScreen_shouldWakeUp() { + doReturn(true).when(mDockManagerFake).isDocked(); + mSensors.getMockProximitySensor().sendProximityResult(false /* far */); + + mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP, + false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */, + null /* rawValues */); + + verify(mMachine).wakeUp(); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java index 4b03399a5e92..2f6b22117a10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.logging; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -27,6 +28,7 @@ import android.testing.TestableLooper; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.UiOffloadThread; @@ -66,7 +68,7 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { waitForUiOffloadThread(); verify(mBarService, Mockito.never()).onNotificationExpansionChanged( - eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean()); + eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt()); } @Test @@ -75,7 +77,7 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { waitForUiOffloadThread(); verify(mBarService, Mockito.never()).onNotificationExpansionChanged( - eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean()); + eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt()); } @Test @@ -87,7 +89,7 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { waitForUiOffloadThread(); verify(mBarService, Mockito.never()).onNotificationExpansionChanged( - eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean()); + eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt()); } @Test @@ -99,7 +101,7 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { waitForUiOffloadThread(); verify(mBarService).onNotificationExpansionChanged( - NOTIFICATION_KEY, true, true); + NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN); } @Test @@ -111,7 +113,7 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { waitForUiOffloadThread(); verify(mBarService).onNotificationExpansionChanged( - NOTIFICATION_KEY, false, true); + NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN); } @Test @@ -123,7 +125,7 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { waitForUiOffloadThread(); verify(mBarService).onNotificationExpansionChanged( - NOTIFICATION_KEY, false, true); + NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN); } @Test @@ -136,7 +138,7 @@ public class ExpansionStateLoggerTest extends SysuiTestCase { waitForUiOffloadThread(); verify(mBarService).onNotificationExpansionChanged( - NOTIFICATION_KEY, false, true); + NOTIFICATION_KEY, false, true, ExpandableViewState.LOCATION_UNKNOWN); } private NotificationVisibility createNotificationVisibility(String key, boolean visibility) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java index 667a5082892d..4dee43857c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.phone; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -171,6 +170,19 @@ public class NearestTouchFrameTest extends SysuiTestCase { ev.recycle(); } + @Test + public void testViewNotAttachedNoCrash() { + View view = mockViewAt(0, 20, 10, 10); + when(view.isAttachedToWindow()).thenReturn(false); + mNearestTouchFrame.addView(view); + mNearestTouchFrame.onMeasure(0, 0); + + MotionEvent ev = MotionEvent.obtain(0, 0, 0, 5 /* x */, 18 /* y */, 0); + mNearestTouchFrame.onTouchEvent(ev); + verify(view, never()).onTouchEvent(eq(ev)); + ev.recycle(); + } + private View mockViewAt(int x, int y, int width, int height) { View v = spy(new View(mContext)); doAnswer(invocation -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java index 98d0c6b4de16..9996a9eddc02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java @@ -95,4 +95,11 @@ public class StatusBarWindowControllerTest extends SysuiTestCase { public void testAdd_updatesVisibilityFlags() { verify(mStatusBarView).setSystemUiVisibility(anyInt()); } + + @Test + public void testSetForcePluginOpen_beforeStatusBarInitialization() { + mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager, + mActivityManager, mDozeParameters); + mStatusBarWindowController.setForcePluginOpen(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java index 3cbf902f1d40..03b7c9507dc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java @@ -55,6 +55,9 @@ public class SmartReplyConstantsTest extends SysuiTestCase { resources.addOverride( R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false); resources.addOverride(R.bool.config_smart_replies_in_notifications_show_in_heads_up, true); + resources.addOverride( + R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies, + 2); mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext); } @@ -178,6 +181,19 @@ public class SmartReplyConstantsTest extends SysuiTestCase { Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags); } + @Test + public void testGetMinNumSystemGeneratedRepliesWithNoConfig() { + assertTrue(mConstants.isEnabled()); + assertEquals(2, mConstants.getMinNumSystemGeneratedReplies()); + } + + @Test + public void testGetMinNumSystemGeneratedRepliesWithValidConfig() { + overrideSetting("enabled=true,min_num_system_generated_replies=5"); + triggerConstantsOnChange(); + assertEquals(5, mConstants.getMinNumSystemGeneratedReplies()); + } + private void triggerConstantsOnChange() { // Since Settings.Global is mocked in TestableContext, we need to manually trigger the // content observer. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index 1066bc1a04df..d1c4d0134f6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -96,6 +96,7 @@ public class SmartReplyViewTest extends SysuiTestCase { @Mock private SmartReplyController mLogger; private NotificationEntry mEntry; private Notification mNotification; + @Mock private SmartReplyConstants mConstants; @Mock ActivityStarter mActivityStarter; @Mock HeadsUpManager mHeadsUpManager; @@ -108,10 +109,14 @@ public class SmartReplyViewTest extends SysuiTestCase { mDependency.get(KeyguardDismissUtil.class).setDismissHandler(action -> action.onDismiss()); mDependency.injectMockDependency(ShadeController.class); mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter); + mDependency.injectTestDependency(SmartReplyConstants.class, mConstants); mContainer = new View(mContext, null); mView = SmartReplyView.inflate(mContext, null); + // Any number of replies are fine. + when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0); + when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3); final Resources res = mContext.getResources(); mSingleLinePaddingHorizontal = res.getDimensionPixelSize( @@ -403,7 +408,7 @@ public class SmartReplyViewTest extends SysuiTestCase { } private void setSmartReplies(CharSequence[] choices) { - setSmartReplies(choices, false); + setSmartReplies(choices, false /* fromAssistant */); } private void setSmartReplies(CharSequence[] choices, boolean fromAssistant) { @@ -440,9 +445,14 @@ public class SmartReplyViewTest extends SysuiTestCase { } private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) { - setSmartReplies(choices); + setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */); + } + + private void setSmartRepliesAndActions( + CharSequence[] choices, String[] actionTitles, boolean fromAssistant) { + setSmartReplies(choices, fromAssistant); mView.addSmartActions( - new SmartReplyView.SmartActions(createActions(actionTitles), false), + new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant), mLogger, mEntry, mHeadsUpManager); @@ -943,4 +953,78 @@ public class SmartReplyViewTest extends SysuiTestCase { expectedView.getChildAt(3), mView.getChildAt(4)); // a1 assertReplyButtonHidden(mView.getChildAt(5)); // long action } + + @Test + public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() { + when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3); + + // Add 2 replies when the minimum is 3 -> we should end up with 0 replies. + String[] choices = new String[] {"reply1", "reply2"}; + String[] actions = new String[] {"action1"}; + + ViewGroup expectedView = buildExpectedView(new String[] {}, 1, + createActions(new String[] {"action1"})); + expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + + setSmartRepliesAndActions(choices, actions, true /* fromAssistant */); + mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + + assertEqualMeasures(expectedView, mView); + // smart replies + assertReplyButtonHidden(mView.getChildAt(0)); + assertReplyButtonHidden(mView.getChildAt(1)); + // smart actions + assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(2)); + } + + @Test + public void testMeasure_minNumSystemGeneratedSmartReplies_enoughReplies() { + when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2); + + // Add 2 replies when the minimum is 3 -> we should end up with 0 replies. + String[] choices = new String[] {"reply1", "reply2"}; + String[] actions = new String[] {"action1"}; + + ViewGroup expectedView = buildExpectedView(new String[] {"reply1", "reply2"}, 1, + createActions(new String[] {"action1"})); + expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + + setSmartRepliesAndActions(choices, actions, true /* fromAssistant */); + mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + + assertEqualMeasures(expectedView, mView); + // smart replies + assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0)); + assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1)); + // smart actions + assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2)); + } + + /** + * Ensure actions that are squeezed when shown together with smart replies are unsqueezed if the + * replies are never added (because of the SmartReplyConstants.getMinNumSystemGeneratedReplies() + * flag). + */ + @Test + public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() { + when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2); + + // Add 2 replies when the minimum is 3 -> we should end up with 0 replies. + String[] choices = new String[] {"This is a very long two-line reply."}; + String[] actions = new String[] {"Short action"}; + + // The action should be displayed on one line only - since it fits! + ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */, + createActions(new String[] {"Short action"})); + expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + + setSmartRepliesAndActions(choices, actions, true /* fromAssistant */); + mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + + assertEqualMeasures(expectedView, mView); + // smart replies + assertReplyButtonHidden(mView.getChildAt(0)); + // smart actions + assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1)); + } } diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 8261fe89f778..efa4e79cc318 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -73,6 +73,10 @@ message MetricsEvent { // The view switched to summary mode (most relevant for notifications) TYPE_COLLAPSE = 14; + + // The notification was adjusted by the assistant. Enum value is + // out of sequence due to b/122737498. + TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT = 1573; } // Types of alerts, as bit field values @@ -232,6 +236,17 @@ message MetricsEvent { BLOCKING_HELPER_CLICK_UNDO = 7; } + // The (visual) location of a Notification. + enum NotificationLocation { + LOCATION_UNKNOWN = 0; + LOCATION_FIRST_HEADS_UP = 1; // visible heads-up + LOCATION_HIDDEN_TOP = 2; // hidden/scrolled away on the top + LOCATION_MAIN_AREA = 3; // visible in the shade + LOCATION_BOTTOM_STACK_PEEKING = 4; // in the bottom stack, and peeking + LOCATION_BOTTOM_STACK_HIDDEN = 5; // in the bottom stack, and hidden + LOCATION_GONE = 6; // the view isn't laid out at all + } + // Known visual elements: views or controls. enum View { // Unknown view @@ -4049,6 +4064,8 @@ message MetricsEvent { // - AUTOFILL_INVALID_DATASET_AUTHENTICATION // NOTE: starting on OS Q, it also added the following fields: // Tag FIELD_AUTOFILL_TEXT_LEN: length of the error message provided by the service + // Tag FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS: number of requests made to the augmented + // autofill service AUTOFILL_REQUEST = 907; // Tag of a field for a package of an autofill service @@ -6820,6 +6837,31 @@ message MetricsEvent { // OS: Q MOBILE_NETWORK_LIST = 1627; + // OPEN: Settings > Display > Adaptive sleep + // OS: Q + SETTINGS_ADAPTIVE_SLEEP = 1628; + + // Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION. + // The UI location of the notification containing the smart suggestions. + // This is a NotificationLocation object (see the NotificationLocation + // enum). + // OS: Q + NOTIFICATION_LOCATION = 1629; + + // The autofill system made request to the system-provided augmented autofill service. + // OS: Q + // Package: Package of app that is autofilled + // Tag FIELD_CLASS_NAME: Class name of the activity that is autofilled. + // Tag FIELD_AUTOFILL_SERVICE: Package of the augmented autofill service that processed the + // request + // Tag FIELD_AUTOFILL_SESSION_ID: id of the autofill session associated with this metric. + // Tag FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. + AUTOFILL_AUGMENTED_REQUEST = 1630; + + // Tag of a field for the number of augmented autofill requests in a session + // OS: Q + FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 121267675329..6ff2b35d49a0 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -216,9 +216,9 @@ message SystemMessage { // Package: android NOTE_SOFTAP_CONFIG_CHANGED = 50; - // Notify the user that connected to app suggested network. + // Notify the user that an app suggested network is available for connection. // Package: android - NOTE_CONNECTED_TO_NETWORK_SUGGESTION = 51; + NOTE_NETWORK_SUGGESTION_AVAILABLE = 51; // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index bcc43a763599..79b63bc55102 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -488,6 +488,9 @@ message WifiLog { // Counts the occurrences of each Wifi usability score provided by external app repeated WifiUsabilityScoreCount wifi_usability_score_count = 127; + + // List of PNO scan stats, one element for each mobility state + repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128; } // Information that gets logged for every WiFi connection. @@ -1816,4 +1819,33 @@ message WifiUsabilityStats { // The list of timestamped wifi usability stats repeated WifiUsabilityStatsEntry stats = 2; -}
\ No newline at end of file +} + +message DeviceMobilityStatePnoScanStats { + // see WifiManager.DEVICE_MOBILITY_STATE_* constants + enum DeviceMobilityState { + // Unknown mobility + UNKNOWN = 0; + + // High movement + HIGH_MVMT = 1; + + // Low movement + LOW_MVMT = 2; + + // Stationary + STATIONARY = 3; + } + + // The device mobility state + optional DeviceMobilityState device_mobility_state = 1; + + // The number of times that this state was entered + optional int32 num_times_entered_state = 2; + + // The total duration elapsed while in this mobility state, in ms + optional int64 total_duration_ms = 3; + + // the total duration elapsed while in this mobility state with PNO scans running, in ms + optional int64 pno_duration_ms = 4; +} diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 6eba914f15bc..2c075dc809d0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -53,6 +53,7 @@ import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import com.android.internal.annotations.GuardedBy; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.server.accessibility.AccessibilityManagerService.RemoteAccessibilityConnection; @@ -751,7 +752,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Override - public float getMagnificationScale() { + public float getMagnificationScale(int displayId) { synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { return 1.0f; @@ -759,14 +760,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } final long identity = Binder.clearCallingIdentity(); try { - return mSystemSupport.getMagnificationController().getScale(); + return mSystemSupport.getMagnificationController().getScale(displayId); } finally { Binder.restoreCallingIdentity(identity); } } @Override - public Region getMagnificationRegion() { + public Region getMagnificationRegion(int displayId) { synchronized (mLock) { final Region region = Region.obtain(); if (!isCalledForCurrentUserLocked()) { @@ -775,22 +776,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationController magnificationController = mSystemSupport.getMagnificationController(); boolean registeredJustForThisCall = - registerMagnificationIfNeeded(magnificationController); + registerMagnificationIfNeeded(displayId, magnificationController); final long identity = Binder.clearCallingIdentity(); try { - magnificationController.getMagnificationRegion(region); + magnificationController.getMagnificationRegion(displayId, region); return region; } finally { Binder.restoreCallingIdentity(identity); if (registeredJustForThisCall) { - magnificationController.unregister(); + magnificationController.unregister(displayId); } } } } @Override - public float getMagnificationCenterX() { + public float getMagnificationCenterX(int displayId) { synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { return 0.0f; @@ -798,21 +799,21 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationController magnificationController = mSystemSupport.getMagnificationController(); boolean registeredJustForThisCall = - registerMagnificationIfNeeded(magnificationController); + registerMagnificationIfNeeded(displayId, magnificationController); final long identity = Binder.clearCallingIdentity(); try { - return magnificationController.getCenterX(); + return magnificationController.getCenterX(displayId); } finally { Binder.restoreCallingIdentity(identity); if (registeredJustForThisCall) { - magnificationController.unregister(); + magnificationController.unregister(displayId); } } } } @Override - public float getMagnificationCenterY() { + public float getMagnificationCenterY(int displayId) { synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { return 0.0f; @@ -820,31 +821,31 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ MagnificationController magnificationController = mSystemSupport.getMagnificationController(); boolean registeredJustForThisCall = - registerMagnificationIfNeeded(magnificationController); + registerMagnificationIfNeeded(displayId, magnificationController); final long identity = Binder.clearCallingIdentity(); try { - return magnificationController.getCenterY(); + return magnificationController.getCenterY(displayId); } finally { Binder.restoreCallingIdentity(identity); if (registeredJustForThisCall) { - magnificationController.unregister(); + magnificationController.unregister(displayId); } } } } - private boolean registerMagnificationIfNeeded( + private boolean registerMagnificationIfNeeded(int displayId, MagnificationController magnificationController) { - if (!magnificationController.isRegisteredLocked() + if (!magnificationController.isRegistered(displayId) && mSecurityPolicy.canControlMagnification(this)) { - magnificationController.register(); + magnificationController.register(displayId); return true; } return false; } @Override - public boolean resetMagnification(boolean animate) { + public boolean resetMagnification(int displayId, boolean animate) { synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { return false; @@ -857,16 +858,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ try { MagnificationController magnificationController = mSystemSupport.getMagnificationController(); - return (magnificationController.reset(animate) - || !magnificationController.isMagnifying()); + return (magnificationController.reset(displayId, animate) + || !magnificationController.isMagnifying(displayId)); } finally { Binder.restoreCallingIdentity(identity); } } @Override - public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY, - boolean animate) { + public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, + float centerY, boolean animate) { synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { return false; @@ -878,11 +879,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ try { MagnificationController magnificationController = mSystemSupport.getMagnificationController(); - if (!magnificationController.isRegisteredLocked()) { - magnificationController.register(); + if (!magnificationController.isRegistered(displayId)) { + magnificationController.register(displayId); } return magnificationController - .setScaleAndCenter(scale, centerX, centerY, animate, mId); + .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId); } finally { Binder.restoreCallingIdentity(identity); } @@ -890,12 +891,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Override - public void setMagnificationCallbackEnabled(boolean enabled) { - mInvocationHandler.setMagnificationCallbackEnabled(enabled); + public void setMagnificationCallbackEnabled(int displayId, boolean enabled) { + mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled); } - public boolean isMagnificationCallbackEnabled() { - return mInvocationHandler.mIsMagnificationCallbackEnabled; + public boolean isMagnificationCallbackEnabled(int displayId) { + return mInvocationHandler.isMagnificationCallbackEnabled(displayId); } @Override @@ -1106,10 +1107,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE); } - public void notifyMagnificationChangedLocked(@NonNull Region region, + public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region, float scale, float centerX, float centerY) { mInvocationHandler - .notifyMagnificationChangedLocked(region, scale, centerX, centerY); + .notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY); } public void notifySoftKeyboardShowModeChangedLocked(int showState) { @@ -1128,12 +1129,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ * Called by the invocation handler to notify the service that the * state of magnification has changed. */ - private void notifyMagnificationChangedInternal(@NonNull Region region, + private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region, float scale, float centerX, float centerY) { final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { - listener.onMagnificationChanged(region, scale, centerX, centerY); + listener.onMagnificationChanged(displayId, region, scale, centerX, centerY); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re); } @@ -1251,7 +1252,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7; private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8; - private boolean mIsMagnificationCallbackEnabled = false; + /** List of magnification callback states, mapping from displayId -> Boolean */ + @GuardedBy("mlock") + private final SparseArray<Boolean> mMagnificationCallbackState = new SparseArray<>(0); private boolean mIsSoftKeyboardCallbackEnabled = false; public InvocationHandler(Looper looper) { @@ -1277,7 +1280,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final float scale = (float) args.arg2; final float centerX = (float) args.arg3; final float centerY = (float) args.arg4; - notifyMagnificationChangedInternal(region, scale, centerX, centerY); + final int displayId = args.argi1; + notifyMagnificationChangedInternal(displayId, region, scale, centerX, centerY); args.recycle(); } break; @@ -1301,11 +1305,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - public void notifyMagnificationChangedLocked(@NonNull Region region, float scale, - float centerX, float centerY) { - if (!mIsMagnificationCallbackEnabled) { - // Callback is disabled, don't bother packing args. - return; + public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region, + float scale, float centerX, float centerY) { + synchronized (mLock) { + if (mMagnificationCallbackState.get(displayId) == null) { + return; + } } final SomeArgs args = SomeArgs.obtain(); @@ -1313,13 +1318,26 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ args.arg2 = scale; args.arg3 = centerX; args.arg4 = centerY; + args.argi1 = displayId; final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args); msg.sendToTarget(); } - public void setMagnificationCallbackEnabled(boolean enabled) { - mIsMagnificationCallbackEnabled = enabled; + public void setMagnificationCallbackEnabled(int displayId, boolean enabled) { + synchronized (mLock) { + if (enabled) { + mMagnificationCallbackState.put(displayId, true); + } else { + mMagnificationCallbackState.remove(displayId); + } + } + } + + public boolean isMagnificationCallbackEnabled(int displayId) { + synchronized (mLock) { + return mMagnificationCallbackState.get(displayId) != null; + } } public void notifySoftKeyboardShowModeChangedLocked(int showState) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 763c16f771d0..dbe86c15c7a4 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -208,6 +208,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final WindowManagerInternal mWindowManagerService; + private final DisplayManager mDisplayManager; + private AppWidgetManagerInternal mAppWidgetService; private final SecurityPolicy mSecurityPolicy; @@ -304,10 +306,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mMainHandler = new MainHandler(mContext.getMainLooper()); mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService); + mDisplayManager = mContext.getSystemService(DisplayManager.class); registerBroadcastReceivers(); new AccessibilityContentObserver(mMainHandler).register( context.getContentResolver()); + registerDisplayListener(mMainHandler); } @Override @@ -523,6 +527,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }, UserHandle.ALL, intentFilter, null, null); } + private void registerDisplayListener(Handler handler) { + mDisplayManager.registerDisplayListener(new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + synchronized (mLock) { + UserState userState = getCurrentUserStateLocked(); + updateMagnificationLocked(userState); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + if (mMagnificationController != null) { + mMagnificationController.onDisplayRemoved(displayId); + } + } + + @Override + public void onDisplayChanged(int displayId) { + // do nothing + } + }, handler); + } + @Override public long addClient(IAccessibilityManagerClient callback, int userId) { synchronized (mLock) { @@ -968,17 +996,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * Called by the MagnificationController when the state of display * magnification changes. * + * @param displayId The logical display id. * @param region the new magnified region, may be empty if * magnification is not enabled (e.g. scale is 1) * @param scale the new scale * @param centerX the new screen-relative center X coordinate * @param centerY the new screen-relative center Y coordinate */ - public void notifyMagnificationChanged(@NonNull Region region, + public void notifyMagnificationChanged(int displayId, @NonNull Region region, float scale, float centerX, float centerY) { synchronized (mLock) { notifyClearAccessibilityCacheLocked(); - notifyMagnificationChangedLocked(region, scale, centerX, centerY); + notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY); } } @@ -1203,12 +1232,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void notifyMagnificationChangedLocked(@NonNull Region region, + private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region, float scale, float centerX, float centerY) { final UserState state = getCurrentUserStateLocked(); for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { final AccessibilityServiceConnection service = state.mBoundServices.get(i); - service.notifyMagnificationChangedLocked(region, scale, centerX, centerY); + service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY); } } @@ -2191,15 +2220,44 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } - if (!mUiAutomationManager.suppressingAccessibilityServicesLocked() - && (userState.mIsDisplayMagnificationEnabled - || userState.mIsNavBarMagnificationEnabled - || userHasListeningMagnificationServicesLocked(userState))) { - // Initialize the magnification controller if necessary - getMagnificationController(); - mMagnificationController.register(); - } else if (mMagnificationController != null) { - mMagnificationController.unregister(); + if (mUiAutomationManager.suppressingAccessibilityServicesLocked() + && mMagnificationController != null) { + mMagnificationController.unregisterAll(); + return; + } + + // register all display if global magnification is enabled. + final Display[] displays = mDisplayManager.getDisplays(); + if (userState.mIsDisplayMagnificationEnabled + || userState.mIsNavBarMagnificationEnabled) { + for (int i = 0; i < displays.length; i++) { + final Display display = displays[i]; + // Overlay display uses overlay window to simulate secondary displays in + // one display. It's not a real display and there's no input events for it. + // We should ignore it. + if (display.getType() == Display.TYPE_OVERLAY) { + continue; + } + getMagnificationController().register(display.getDisplayId()); + } + return; + } + + // register if display has listening magnification services. + for (int i = 0; i < displays.length; i++) { + final Display display = displays[i]; + // Overlay display uses overlay window to simulate secondary displays in + // one display. It's not a real display and there's no input events for it. + // We should ignore it. + if (display.getType() == Display.TYPE_OVERLAY) { + continue; + } + final int displayId = display.getDisplayId(); + if (userHasListeningMagnificationServicesLocked(userState, displayId)) { + getMagnificationController().register(displayId); + } else if (mMagnificationController != null) { + mMagnificationController.unregister(displayId); + } } } @@ -2222,12 +2280,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * Returns whether the specified user has any services that are capable of * controlling magnification and are actively listening for magnification updates. */ - private boolean userHasListeningMagnificationServicesLocked(UserState userState) { + private boolean userHasListeningMagnificationServicesLocked(UserState userState, + int displayId) { final List<AccessibilityServiceConnection> services = userState.mBoundServices; for (int i = 0, count = services.size(); i < count; i++) { final AccessibilityServiceConnection service = services.get(i); if (mSecurityPolicy.canControlMagnification(service) - && service.isMagnificationCallbackEnabled()) { + && service.isMagnificationCallbackEnabled(displayId)) { return true; } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 86132a8e6473..a19a84724d45 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -109,7 +109,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect UserState userState = mUserStateWeakReference.get(); if (userState == null) return; userState.removeServiceLocked(this); - mSystemSupport.getMagnificationController().resetIfNeeded(mId); + mSystemSupport.getMagnificationController().resetAllIfNeeded(mId); resetLocked(); } @@ -256,7 +256,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect userState.serviceDisconnectedLocked(this); } resetLocked(); - mSystemSupport.getMagnificationController().resetIfNeeded(mId); + mSystemSupport.getMagnificationController().resetAllIfNeeded(mId); mSystemSupport.onClientChangeLocked(false); } } diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java index 6a97fbbf30da..e784056d9e35 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java @@ -19,7 +19,6 @@ package com.android.server.accessibility; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -32,6 +31,7 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.MathUtils; import android.util.Slog; +import android.util.SparseArray; import android.view.Display; import android.view.MagnificationSpec; import android.view.View; @@ -39,6 +39,7 @@ import android.view.animation.DecelerateInterpolator; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.wm.WindowManagerInternal; @@ -68,9 +69,7 @@ public class MagnificationController { private final Object mLock; - private final AccessibilityManagerService mAms; - - private final SettingsBridge mSettingsBridge; + private final ControllerContext mControllerCtx; private final ScreenStateObserver mScreenStateObserver; @@ -78,11 +77,9 @@ public class MagnificationController { private final long mMainThreadId; - private Handler mHandler; - - private final WindowManagerInternal mWindowManager; - - private final DisplayMagnification mDisplay; + /** List of display Magnification, mapping from displayId -> DisplayMagnification. */ + @GuardedBy("mLock") + private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0); /** * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds @@ -107,46 +104,82 @@ public class MagnificationController { // Flag indicating that we are registered with window manager. private boolean mRegistered; private boolean mUnregisterPending; + private boolean mDeleteAfterUnregister; private final int mDisplayId; private static final int INVALID_ID = -1; private int mIdOfLastServiceToMagnify = INVALID_ID; - - DisplayMagnification(int displayId, SpecAnimationBridge specAnimation) { + DisplayMagnification(int displayId) { mDisplayId = displayId; - mSpecAnimationBridge = specAnimation; + mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId); } - void register() { - synchronized (mLock) { - if (!mRegistered) { - mWindowManager.setMagnificationCallbacks(this); - mSpecAnimationBridge.setEnabled(true); - // Obtain initial state. - mWindowManager.getMagnificationRegion(mMagnificationRegion); - mMagnificationRegion.getBounds(mMagnificationBounds); - mRegistered = true; - } + /** + * Registers magnification callback and get current magnification region from + * window manager. + * + * @return true if callback registers successful. + */ + @GuardedBy("mLock") + boolean register() { + mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks( + mDisplayId, this); + if (!mRegistered) { + Slog.w(LOG_TAG, "set magnification callbacks fail, displayId:" + mDisplayId); + return false; } + mSpecAnimationBridge.setEnabled(true); + // Obtain initial state. + mControllerCtx.getWindowManager().getMagnificationRegion( + mDisplayId, mMagnificationRegion); + mMagnificationRegion.getBounds(mMagnificationBounds); + return true; } - void unregister() { - synchronized (mLock) { - if (!isMagnifying()) { - unregisterInternalLocked(); - } else { - mUnregisterPending = true; - reset(true); - } + /** + * Unregisters magnification callback from window manager. Callbacks to + * {@link MagnificationController#unregisterCallbackLocked(int, boolean)} after + * unregistered. + * + * @param delete true if this instance should be removed from the SparseArray in + * MagnificationController after unregistered, for example, display removed. + */ + @GuardedBy("mLock") + void unregister(boolean delete) { + if (mRegistered) { + mSpecAnimationBridge.setEnabled(false); + mControllerCtx.getWindowManager().setMagnificationCallbacks( + mDisplayId, null); + mMagnificationRegion.setEmpty(); + mRegistered = false; + unregisterCallbackLocked(mDisplayId, delete); } + mUnregisterPending = false; } - boolean isRegisteredLocked() { + /** + * Reset magnification status with animation enabled. {@link #unregister(boolean)} will be + * called after animation finished. + * + * @param delete true if this instance should be removed from the SparseArray in + * MagnificationController after unregistered, for example, display removed. + */ + @GuardedBy("mLock") + void unregisterPending(boolean delete) { + mDeleteAfterUnregister = delete; + mUnregisterPending = true; + reset(true); + } + + boolean isRegistered() { return mRegistered; } + boolean isMagnifying() { + return mCurrentMagnificationSpec.scale > 1.0f; + } float getScale() { return mCurrentMagnificationSpec.scale; @@ -156,18 +189,20 @@ public class MagnificationController { return mCurrentMagnificationSpec.offsetX; } + float getOffsetY() { + return mCurrentMagnificationSpec.offsetY; + } + + @GuardedBy("mLock") float getCenterX() { - synchronized (mLock) { - return (mMagnificationBounds.width() / 2.0f - + mMagnificationBounds.left - getOffsetX()) / getScale(); - } + return (mMagnificationBounds.width() / 2.0f + + mMagnificationBounds.left - getOffsetX()) / getScale(); } + @GuardedBy("mLock") float getCenterY() { - synchronized (mLock) { - return (mMagnificationBounds.height() / 2.0f - + mMagnificationBounds.top - getOffsetY()) / getScale(); - } + return (mMagnificationBounds.height() / 2.0f + + mMagnificationBounds.top - getOffsetY()) / getScale(); } /** @@ -203,64 +238,35 @@ public class MagnificationController { return mSpecAnimationBridge.mSentMagnificationSpec.offsetY; } - boolean resetIfNeeded(boolean animate) { - synchronized (mLock) { - if (isMagnifying()) { - reset(animate); - return true; - } - return false; - } - } - - float getOffsetY() { - return mCurrentMagnificationSpec.offsetY; - } - - boolean isMagnifying() { - return mCurrentMagnificationSpec.scale > 1.0f; - } - - void unregisterInternalLocked() { - if (mRegistered) { - mSpecAnimationBridge.setEnabled(false); - mWindowManager.setMagnificationCallbacks(null); - mMagnificationRegion.setEmpty(); - - mRegistered = false; - } - mUnregisterPending = false; - } - - @Override public void onMagnificationRegionChanged(Region magnificationRegion) { final Message m = PooledLambda.obtainMessage( - DisplayMagnification.this::updateMagnificationRegion, + DisplayMagnification::updateMagnificationRegion, this, Region.obtain(magnificationRegion)); - mHandler.sendMessage(m); + mControllerCtx.getHandler().sendMessage(m); } @Override public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) { final Message m = PooledLambda.obtainMessage( - DisplayMagnification.this::requestRectangleOnScreen, left, top, right, bottom); - mHandler.sendMessage(m); + DisplayMagnification::requestRectangleOnScreen, this, + left, top, right, bottom); + mControllerCtx.getHandler().sendMessage(m); } @Override public void onRotationChanged(int rotation) { // Treat as context change and reset - final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded, - true); - mHandler.sendMessage(m); + final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded, + MagnificationController.this, mDisplayId, true); + mControllerCtx.getHandler().sendMessage(m); } @Override public void onUserContextChanged() { - final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded, - true); - mHandler.sendMessage(m); + final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded, + MagnificationController.this, mDisplayId, true); + mControllerCtx.getHandler().sendMessage(m); } /** @@ -298,8 +304,9 @@ public class MagnificationController { mSpecAnimationBridge.updateSentSpecMainThread(spec, animate); } else { final Message m = PooledLambda.obtainMessage( - this.mSpecAnimationBridge::updateSentSpecMainThread, spec, animate); - mHandler.sendMessage(m); + SpecAnimationBridge::updateSentSpecMainThread, + mSpecAnimationBridge, spec, animate); + mControllerCtx.getHandler().sendMessage(m); } } @@ -313,30 +320,26 @@ public class MagnificationController { } void onMagnificationChangedLocked() { - mAms.notifyMagnificationChanged(mMagnificationRegion, + mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, mMagnificationRegion, getScale(), getCenterX(), getCenterY()); if (mUnregisterPending && !isMagnifying()) { - unregisterInternalLocked(); + unregister(mDeleteAfterUnregister); } } + @GuardedBy("mLock") boolean magnificationRegionContains(float x, float y) { - synchronized (mLock) { - return mMagnificationRegion.contains((int) x, (int) y); - - } + return mMagnificationRegion.contains((int) x, (int) y); } + @GuardedBy("mLock") void getMagnificationBounds(@NonNull Rect outBounds) { - synchronized (mLock) { - outBounds.set(mMagnificationBounds); - } + outBounds.set(mMagnificationBounds); } + @GuardedBy("mLock") void getMagnificationRegion(@NonNull Region outRegion) { - synchronized (mLock) { - outRegion.set(mMagnificationRegion); - } + outRegion.set(mMagnificationRegion); } void requestRectangleOnScreen(int left, int top, int right, int bottom) { @@ -392,94 +395,76 @@ public class MagnificationController { outFrame.scale(1.0f / scale); } - /** - * Resets magnification if last magnifying service is disabled. - * - * @param connectionId the connection ID be disabled. - * @return {@code true} on success, {@code false} on failure - */ - boolean resetIfNeeded(int connectionId) { - if (mIdOfLastServiceToMagnify == connectionId) { - return resetIfNeeded(true /*animate*/); - } - return false; - } - + @GuardedBy("mLock") void setForceShowMagnifiableBounds(boolean show) { if (mRegistered) { - mWindowManager.setForceShowMagnifiableBounds(show); + mControllerCtx.getWindowManager().setForceShowMagnifiableBounds( + mDisplayId, show); } } + @GuardedBy("mLock") boolean reset(boolean animate) { - synchronized (mLock) { - if (!mRegistered) { - return false; - } - final MagnificationSpec spec = mCurrentMagnificationSpec; - final boolean changed = !spec.isNop(); - if (changed) { - spec.clear(); - onMagnificationChangedLocked(); - } - mIdOfLastServiceToMagnify = INVALID_ID; - sendSpecToAnimation(spec, animate); - return changed; + if (!mRegistered) { + return false; } + final MagnificationSpec spec = mCurrentMagnificationSpec; + final boolean changed = !spec.isNop(); + if (changed) { + spec.clear(); + onMagnificationChangedLocked(); + } + mIdOfLastServiceToMagnify = INVALID_ID; + sendSpecToAnimation(spec, animate); + return changed; } - + @GuardedBy("mLock") boolean setScale(float scale, float pivotX, float pivotY, boolean animate, int id) { - - synchronized (mLock) { - if (!mRegistered) { - return false; - } - // Constrain scale immediately for use in the pivot calculations. - scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); - - final Rect viewport = mTempRect; - mMagnificationRegion.getBounds(viewport); - final MagnificationSpec spec = mCurrentMagnificationSpec; - final float oldScale = spec.scale; - final float oldCenterX - = (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale; - final float oldCenterY - = (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale; - final float normPivotX = (pivotX - spec.offsetX) / oldScale; - final float normPivotY = (pivotY - spec.offsetY) / oldScale; - final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); - final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); - final float centerX = normPivotX + offsetX; - final float centerY = normPivotY + offsetY; - mIdOfLastServiceToMagnify = id; - - return setScaleAndCenter(scale, centerX, centerY, animate, id); + if (!mRegistered) { + return false; } + // Constrain scale immediately for use in the pivot calculations. + scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); + + final Rect viewport = mTempRect; + mMagnificationRegion.getBounds(viewport); + final MagnificationSpec spec = mCurrentMagnificationSpec; + final float oldScale = spec.scale; + final float oldCenterX = + (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale; + final float oldCenterY = + (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale; + final float normPivotX = (pivotX - spec.offsetX) / oldScale; + final float normPivotY = (pivotY - spec.offsetY) / oldScale; + final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); + final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); + final float centerX = normPivotX + offsetX; + final float centerY = normPivotY + offsetY; + mIdOfLastServiceToMagnify = id; + return setScaleAndCenter(scale, centerX, centerY, animate, id); } + @GuardedBy("mLock") boolean setScaleAndCenter(float scale, float centerX, float centerY, boolean animate, int id) { - - synchronized (mLock) { - if (!mRegistered) { - return false; - } - if (DEBUG) { - Slog.i(LOG_TAG, - "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX - + ", centerY = " + centerY + ", animate = " + animate - + ", id = " + id - + ")"); - } - final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY); - sendSpecToAnimation(mCurrentMagnificationSpec, animate); - if (isMagnifying() && (id != INVALID_ID)) { - mIdOfLastServiceToMagnify = id; - } - return changed; + if (!mRegistered) { + return false; + } + if (DEBUG) { + Slog.i(LOG_TAG, + "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX + + ", centerY = " + centerY + ", animate = " + animate + + ", id = " + id + + ")"); } + final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY); + sendSpecToAnimation(mCurrentMagnificationSpec, animate); + if (isMagnifying() && (id != INVALID_ID)) { + mIdOfLastServiceToMagnify = id; + } + return changed; } /** @@ -527,22 +512,21 @@ public class MagnificationController { return changed; } + @GuardedBy("mLock") void offsetMagnifiedRegion(float offsetX, float offsetY, int id) { - synchronized (mLock) { - if (!mRegistered) { - return; - } + if (!mRegistered) { + return; + } - final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; - final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; - if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { - onMagnificationChangedLocked(); - } - if (id != INVALID_ID) { - mIdOfLastServiceToMagnify = id; - } - sendSpecToAnimation(mCurrentMagnificationSpec, false); + final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; + final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; + if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { + onMagnificationChangedLocked(); } + if (id != INVALID_ID) { + mIdOfLastServiceToMagnify = id; + } + sendSpecToAnimation(mCurrentMagnificationSpec, false); } boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) { @@ -593,44 +577,38 @@ public class MagnificationController { @Override public String toString() { - return "DisplayMagnification{" + - "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec + - ", mMagnificationRegion=" + mMagnificationRegion + - ", mMagnificationBounds=" + mMagnificationBounds + - ", mDisplayId=" + mDisplayId + - ", mUserId=" + mUserId + - ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify + - ", mRegistered=" + mRegistered + - ", mUnregisterPending=" + mUnregisterPending + - '}'; + return "DisplayMagnification[" + + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec + + ", mMagnificationRegion=" + mMagnificationRegion + + ", mMagnificationBounds=" + mMagnificationBounds + + ", mDisplayId=" + mDisplayId + + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify + + ", mRegistered=" + mRegistered + + ", mUnregisterPending=" + mUnregisterPending + + ']'; } - } - public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) { - this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class), - new ValueAnimator(), new SettingsBridge(context.getContentResolver())); - mHandler = new Handler(context.getMainLooper()); + /** + * MagnificationController Constructor + */ + public MagnificationController(@NonNull Context context, + @NonNull AccessibilityManagerService ams, @NonNull Object lock) { + this(new ControllerContext(context, ams, + LocalServices.getService(WindowManagerInternal.class), + new Handler(context.getMainLooper()), + context.getResources().getInteger(R.integer.config_longAnimTime)), lock); } - public MagnificationController( - Context context, - AccessibilityManagerService ams, - Object lock, - Handler handler, - WindowManagerInternal windowManagerInternal, - ValueAnimator valueAnimator, - SettingsBridge settingsBridge) { - mHandler = handler; - mWindowManager = windowManagerInternal; - mMainThreadId = context.getMainLooper().getThread().getId(); - mAms = ams; - mScreenStateObserver = new ScreenStateObserver(context, this); + /** + * Constructor for tests + */ + @VisibleForTesting + public MagnificationController(@NonNull ControllerContext ctx, @NonNull Object lock) { + mControllerCtx = ctx; mLock = lock; - mSettingsBridge = settingsBridge; - //TODO (multidisplay): Magnification is supported only for the default display. - mDisplay = new DisplayMagnification(Display.DEFAULT_DISPLAY, - new SpecAnimationBridge(context, mLock, mWindowManager, valueAnimator)); + mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId(); + mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this); } /** @@ -639,54 +617,114 @@ public class MagnificationController { * * This tracking imposes a cost on the system, so we avoid tracking this data unless it's * required. + * + * @param displayId The logical display id. */ - public void register() { + public void register(int displayId) { synchronized (mLock) { - mScreenStateObserver.register(); + DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + display = new DisplayMagnification(displayId); + } + if (display.isRegistered()) { + return; + } + if (display.register()) { + mDisplays.put(displayId, display); + mScreenStateObserver.registerIfNecessary(); + } } - mDisplay.register(); } /** * Stop requiring tracking the magnification region. We may remain registered while we * reset magnification. + * + * @param displayId The logical display id. */ - public void unregister() { + public void unregister(int displayId) { synchronized (mLock) { - mScreenStateObserver.unregister(); + unregisterLocked(displayId, false); } - mDisplay.unregister(); } - + /** - * Check if we are registered. Note that we may be planning to unregister at any moment. + * Stop tracking all displays' magnification region. + */ + public void unregisterAll() { + synchronized (mLock) { + // display will be removed from array after unregister, we need to clone it to + // prevent error. + final SparseArray<DisplayMagnification> displays = mDisplays.clone(); + for (int i = 0; i < displays.size(); i++) { + unregisterLocked(displays.keyAt(i), false); + } + } + } + + /** + * Remove the display magnification with given id. + * + * @param displayId The logical display id. + */ + public void onDisplayRemoved(int displayId) { + synchronized (mLock) { + unregisterLocked(displayId, true); + } + } + + /** + * Check if we are registered on specified display. Note that we may be planning to unregister + * at any moment. + * + * @return {@code true} if the controller is registered on specified display. + * {@code false} otherwise. * - * @return {@code true} if the controller is registered. {@code false} otherwise. + * @param displayId The logical display id. */ - public boolean isRegisteredLocked() { - return mDisplay.isRegisteredLocked(); + public boolean isRegistered(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.isRegistered(); + } } /** + * @param displayId The logical display id. * @return {@code true} if magnification is active, e.g. the scale * is > 1, {@code false} otherwise */ - public boolean isMagnifying() { - return mDisplay.isMagnifying(); + public boolean isMagnifying(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.isMagnifying(); + } } /** * Returns whether the magnification region contains the specified * screen-relative coordinates. * + * @param displayId The logical display id. * @param x the screen-relative X coordinate to check * @param y the screen-relative Y coordinate to check * @return {@code true} if the coordinate is contained within the * magnified region, or {@code false} otherwise */ - public boolean magnificationRegionContains(float x, float y) { - return mDisplay.magnificationRegionContains(x, y); - + public boolean magnificationRegionContains(int displayId, float x, float y) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.magnificationRegionContains(x, y); + } } /** @@ -694,11 +732,18 @@ public class MagnificationController { * magnification region. If magnification is not enabled, the returned * bounds will be empty. * + * @param displayId The logical display id. * @param outBounds rect to populate with the bounds of the magnified * region */ - public void getMagnificationBounds(@NonNull Rect outBounds) { - mDisplay.getMagnificationBounds(outBounds); + public void getMagnificationBounds(int displayId, @NonNull Rect outBounds) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return; + } + display.getMagnificationBounds(outBounds); + } } /** @@ -706,76 +751,122 @@ public class MagnificationController { * region. If magnification is not enabled, then the returned region * will be empty. * + * @param displayId The logical display id. * @param outRegion the region to populate */ - public void getMagnificationRegion(@NonNull Region outRegion) { - mDisplay.getMagnificationRegion(outRegion); + public void getMagnificationRegion(int displayId, @NonNull Region outRegion) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return; + } + display.getMagnificationRegion(outRegion); + } } /** * Returns the magnification scale. If an animation is in progress, * this reflects the end state of the animation. * + * @param displayId The logical display id. * @return the scale */ - public float getScale() { - return mDisplay.getScale(); + public float getScale(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return 1.0f; + } + return display.getScale(); + } } /** * Returns the X offset of the magnification viewport. If an animation * is in progress, this reflects the end state of the animation. * + * @param displayId The logical display id. * @return the X offset */ - public float getOffsetX() { - return mDisplay.getOffsetX(); + public float getOffsetX(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return 0.0f; + } + return display.getOffsetX(); + } } - /** * Returns the screen-relative X coordinate of the center of the * magnification viewport. * + * @param displayId The logical display id. * @return the X coordinate */ - public float getCenterX() { - return mDisplay.getCenterX(); + public float getCenterX(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return 0.0f; + } + return display.getCenterX(); + } } /** * Returns the Y offset of the magnification viewport. If an animation * is in progress, this reflects the end state of the animation. * + * @param displayId The logical display id. * @return the Y offset */ - public float getOffsetY() { - return mDisplay.getOffsetY(); + public float getOffsetY(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return 0.0f; + } + return display.getOffsetY(); + } } /** * Returns the screen-relative Y coordinate of the center of the * magnification viewport. * + * @param displayId The logical display id. * @return the Y coordinate */ - public float getCenterY() { - return mDisplay.getCenterY(); + public float getCenterY(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return 0.0f; + } + return display.getCenterY(); + } } /** * Resets the magnification scale and center, optionally animating the * transition. * + * @param displayId The logical display id. * @param animate {@code true} to animate the transition, {@code false} * to transition immediately * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ - public boolean reset(boolean animate) { - - return mDisplay.reset(animate); - + public boolean reset(int displayId, boolean animate) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.reset(animate); + } } /** @@ -783,6 +874,7 @@ public class MagnificationController { * optionally animating the transition. If animation is disabled, the * transition is immediate. * + * @param displayId The logical display id. * @param scale the target scale, must be >= 1 * @param pivotX the screen-relative X coordinate around which to scale * @param pivotY the screen-relative Y coordinate around which to scale @@ -792,15 +884,22 @@ public class MagnificationController { * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ - public boolean setScale(float scale, float pivotX, float pivotY, boolean animate, int id) { - return mDisplay. - setScale(scale, pivotX, pivotY, animate, id); + public boolean setScale(int displayId, float scale, float pivotX, float pivotY, + boolean animate, int id) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.setScale(scale, pivotX, pivotY, animate, id); + } } /** * Sets the center of the magnified region, optionally animating the * transition. If animation is disabled, the transition is immediate. * + * @param displayId The logical display id. * @param centerX the screen-relative X coordinate around which to * center * @param centerY the screen-relative Y coordinate around which to @@ -811,9 +910,14 @@ public class MagnificationController { * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ - public boolean setCenter(float centerX, float centerY, boolean animate, int id) { - return mDisplay. - setScaleAndCenter(Float.NaN, centerX, centerY, animate, id); + public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.setScaleAndCenter(Float.NaN, centerX, centerY, animate, id); + } } /** @@ -821,6 +925,7 @@ public class MagnificationController { * animating the transition. If animation is disabled, the transition * is immediate. * + * @param displayId The logical display id. * @param scale the target scale, or {@link Float#NaN} to leave unchanged * @param centerX the screen-relative X coordinate around which to * center and scale, or {@link Float#NaN} to leave unchanged @@ -832,53 +937,66 @@ public class MagnificationController { * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ - public boolean setScaleAndCenter( - float scale, float centerX, float centerY, boolean animate, int id) { - return mDisplay. - setScaleAndCenter(scale, centerX, centerY, animate, id); + public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, + boolean animate, int id) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return false; + } + return display.setScaleAndCenter(scale, centerX, centerY, animate, id); + } } /** * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the * opposite direction as the offsets passed in here. * + * @param displayId The logical display id. * @param offsetX the amount in pixels to offset the region in the X direction, in current * screen pixels. * @param offsetY the amount in pixels to offset the region in the Y direction, in current * screen pixels. * @param id the ID of the service requesting the change */ - public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) { - mDisplay.offsetMagnifiedRegion(offsetX, offsetY, - id); + public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return; + } + display.offsetMagnifiedRegion(offsetX, offsetY, id); + } } /** * Get the ID of the last service that changed the magnification spec. * + * @param displayId The logical display id. * @return The id */ - public int getIdOfLastServiceToMagnify() { - return mDisplay.getIdOfLastServiceToMagnify(); + public int getIdOfLastServiceToMagnify(int displayId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return -1; + } + return display.getIdOfLastServiceToMagnify(); + } } /** - * Persists the current magnification scale to the current user's settings. + * Persists the default display magnification scale to the current user's settings. */ public void persistScale() { - persistScale(Display.DEFAULT_DISPLAY); - } - /** - * Persists the current magnification scale to the current user's settings. - */ - public void persistScale(int displayId) { - final float scale = mDisplay.getScale(); + // TODO: b/123047354, Need support multi-display? + final float scale = getScale(Display.DEFAULT_DISPLAY); final int userId = mUserId; new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { - mSettingsBridge.putMagnificationScale(scale, displayId, userId); + mControllerCtx.putMagnificationScale(scale, userId); return null; } }.execute(); @@ -892,7 +1010,7 @@ public class MagnificationController { * scale if none is available */ public float getPersistedScale() { - return mSettingsBridge.getMagnificationScale(Display.DEFAULT_DISPLAY, mUserId); + return mControllerCtx.getMagnificationScale(mUserId); } /** @@ -901,50 +1019,136 @@ public class MagnificationController { * @param userId the currently active user ID */ public void setUserId(int userId) { - if (mUserId != userId) { - mUserId = userId; + if (mUserId == userId) { + return; + } + mUserId = userId; + resetAllIfNeeded(false); + } - synchronized (mLock) { - if (isMagnifying()) { - reset(false); - } + /** + * Resets all displays' magnification if last magnifying service is disabled. + * + * @param connectionId + */ + public void resetAllIfNeeded(int connectionId) { + synchronized (mLock) { + for (int i = 0; i < mDisplays.size(); i++) { + resetIfNeeded(mDisplays.keyAt(i), connectionId); } } } - /** + /** * Resets magnification if magnification and auto-update are both enabled. * + * @param displayId The logical display id. * @param animate whether the animate the transition - * @return whether was {@link #isMagnifying magnifying} + * @return whether was {@link #isMagnifying(int) magnifying} */ - public boolean resetIfNeeded(boolean animate) { - return mDisplay.resetIfNeeded(animate); + boolean resetIfNeeded(int displayId, boolean animate) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null || !display.isMagnifying()) { + return false; + } + display.reset(animate); + return true; + } } /** * Resets magnification if last magnifying service is disabled. * + * @param displayId The logical display id. * @param connectionId the connection ID be disabled. * @return {@code true} on success, {@code false} on failure */ - public boolean resetIfNeeded(int connectionId) { - return mDisplay.resetIfNeeded(connectionId); + boolean resetIfNeeded(int displayId, int connectionId) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null || !display.isMagnifying() + || connectionId != display.getIdOfLastServiceToMagnify()) { + return false; + } + display.reset(true); + return true; + } } - void setForceShowMagnifiableBounds(boolean show) { - mDisplay.setForceShowMagnifiableBounds(show); + void setForceShowMagnifiableBounds(int displayId, boolean show) { + synchronized (mLock) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return; + } + display.setForceShowMagnifiableBounds(show); + } } private void onScreenTurnedOff() { final Message m = PooledLambda.obtainMessage( - mDisplay::resetIfNeeded, false); - mHandler.sendMessage(m); + MagnificationController::resetAllIfNeeded, this, false); + mControllerCtx.getHandler().sendMessage(m); + } + + private void resetAllIfNeeded(boolean animate) { + synchronized (mLock) { + for (int i = 0; i < mDisplays.size(); i++) { + resetIfNeeded(mDisplays.keyAt(i), animate); + } + } + } + + private void unregisterLocked(int displayId, boolean delete) { + final DisplayMagnification display = mDisplays.get(displayId); + if (display == null) { + return; + } + if (!display.isRegistered()) { + if (delete) { + mDisplays.remove(displayId); + } + return; + } + if (!display.isMagnifying()) { + display.unregister(delete); + } else { + display.unregisterPending(delete); + } + } + + /** + * Callbacks from DisplayMagnification after display magnification unregistered. It will remove + * DisplayMagnification instance if delete is true, and unregister screen state if + * there is no registered display magnification. + */ + private void unregisterCallbackLocked(int displayId, boolean delete) { + if (delete) { + mDisplays.remove(displayId); + } + // unregister screen state if necessary + boolean hasRegister = false; + for (int i = 0; i < mDisplays.size(); i++) { + final DisplayMagnification display = mDisplays.valueAt(i); + hasRegister = display.isRegistered(); + if (hasRegister) { + break; + } + } + if (!hasRegister) { + mScreenStateObserver.unregister(); + } } @Override public String toString() { - return mDisplay.toString(); + StringBuilder builder = new StringBuilder(); + builder.append("MagnificationController["); + builder.append("mUserId=").append(mUserId); + builder.append(", mDisplays=").append(mDisplays); + builder.append("]"); + return builder.toString(); } /** @@ -952,7 +1156,7 @@ public class MagnificationController { * updates to the window manager. */ private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener { - private final WindowManagerInternal mWindowManager; + private final ControllerContext mControllerCtx; /** * The magnification spec that was sent to the window manager. This should @@ -973,16 +1177,17 @@ public class MagnificationController { private final Object mLock; + private final int mDisplayId; + @GuardedBy("mLock") private boolean mEnabled = false; - private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm, - ValueAnimator animator) { + private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) { + mControllerCtx = ctx; mLock = lock; - mWindowManager = wm; - final long animationDuration = context.getResources().getInteger( - R.integer.config_longAnimTime); - mValueAnimator = animator; + mDisplayId = displayId; + final long animationDuration = mControllerCtx.getAnimationDuration(); + mValueAnimator = mControllerCtx.newValueAnimator(); mValueAnimator.setDuration(animationDuration); mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f)); mValueAnimator.setFloatValues(0.0f, 1.0f); @@ -999,7 +1204,8 @@ public class MagnificationController { mEnabled = enabled; if (!mEnabled) { mSentMagnificationSpec.clear(); - mWindowManager.setMagnificationSpec(mSentMagnificationSpec); + mControllerCtx.getWindowManager().setMagnificationSpec( + mDisplayId, mSentMagnificationSpec); } } } @@ -1031,7 +1237,8 @@ public class MagnificationController { } mSentMagnificationSpec.setTo(spec); - mWindowManager.setMagnificationSpec(spec); + mControllerCtx.getWindowManager().setMagnificationSpec( + mDisplayId, mSentMagnificationSpec); } } @@ -1054,9 +1261,7 @@ public class MagnificationController { mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY + (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY) * fract; - synchronized (mLock) { - setMagnificationSpecLocked(mTmpMagnificationSpec); - } + setMagnificationSpecLocked(mTmpMagnificationSpec); } } } @@ -1072,7 +1277,7 @@ public class MagnificationController { mController = controller; } - public void register() { + public void registerIfNecessary() { if (!mRegistered) { mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); mRegistered = true; @@ -1092,26 +1297,97 @@ public class MagnificationController { } } - // Extra class to get settings so tests can mock it - public static class SettingsBridge { - private final ContentResolver mContentResolver; + /** + * This class holds resources used between the classes in MagnificationController, and + * functions for tests to mock it. + */ + @VisibleForTesting + public static class ControllerContext { + private final Context mContext; + private final AccessibilityManagerService mAms; + private final WindowManagerInternal mWindowManager; + private final Handler mHandler; + private final Long mAnimationDuration; + + /** + * Constructor for ControllerContext. + */ + public ControllerContext(@NonNull Context context, + @NonNull AccessibilityManagerService ams, + @NonNull WindowManagerInternal windowManager, + @NonNull Handler handler, + long animationDuration) { + mContext = context; + mAms = ams; + mWindowManager = windowManager; + mHandler = handler; + mAnimationDuration = animationDuration; + } + + /** + * @return A context. + */ + @NonNull + public Context getContext() { + return mContext; + } + + /** + * @return AccessibilityManagerService + */ + @NonNull + public AccessibilityManagerService getAms() { + return mAms; + } + + /** + * @return WindowManagerInternal + */ + @NonNull + public WindowManagerInternal getWindowManager() { + return mWindowManager; + } - public SettingsBridge(ContentResolver contentResolver) { - mContentResolver = contentResolver; + /** + * @return Handler for main looper + */ + @NonNull + public Handler getHandler() { + return mHandler; } - public void putMagnificationScale(float value, int displayId, int userId) { - Settings.Secure.putFloatForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE + ( - Display.DEFAULT_DISPLAY == displayId ? "" : displayId), - value, userId); + /** + * Create a new ValueAnimator. + * + * @return ValueAnimator + */ + @NonNull + public ValueAnimator newValueAnimator() { + return new ValueAnimator(); } - public float getMagnificationScale(int displayId, int userId) { - return Settings.Secure.getFloatForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE - + (Display.DEFAULT_DISPLAY == displayId ? "" : displayId), + /** + * Write Settings of magnification scale. + */ + public void putMagnificationScale(float value, int userId) { + Settings.Secure.putFloatForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId); + } + + /** + * Get Settings of magnification scale. + */ + public float getMagnificationScale(int userId) { + return Settings.Secure.getFloatForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_MAGNIFICATION_SCALE, userId); } + + /** + * @return Configuration of animation duration. + */ + public long getAnimationDuration() { + return mAnimationDuration; + } } } diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 80049e80e1a9..49db488bc740 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -43,6 +43,7 @@ import android.util.Log; import android.util.MathUtils; import android.util.Slog; import android.util.TypedValue; +import android.view.Display; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; @@ -251,14 +252,16 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mScreenStateReceiver.unregister(); } // Check if need to reset when MagnificationGestureHandler is the last magnifying service. - mMagnificationController.resetIfNeeded( + mMagnificationController.resetAllIfNeeded( AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); clearAndTransitionToStateDetecting(); } void notifyShortcutTriggered() { if (mDetectShortcutTrigger) { - boolean wasMagnifying = mMagnificationController.resetIfNeeded(/* animate */ true); + // TODO: multi-display support for magnification gesture handler + boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY, + /* animate */ true); if (wasMagnifying) { clearAndTransitionToStateDetecting(); } else { @@ -419,8 +422,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX + " scrollY: " + distanceY); } - mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + // TODO: multi-display support for magnification gesture handler + mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX, + distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); return /* event consumed: */ true; } @@ -436,7 +440,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { return mScaling; } - final float initialScale = mMagnificationController.getScale(); + // TODO: multi-display support for magnification gesture handler + final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY); final float targetScale = initialScale * detector.getScaleFactor(); // Don't allow a gesture to move the user further outside the @@ -458,7 +463,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { final float pivotX = detector.getFocusX(); final float pivotY = detector.getFocusY(); if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x"); - mMagnificationController.setScale(scale, pivotX, pivotY, false, + // TODO: multi-display support for magnification gesture handler + mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); return /* handled: */ true; } @@ -518,8 +524,10 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } final float eventX = event.getX(); final float eventY = event.getY(); - if (mMagnificationController.magnificationRegionContains(eventX, eventY)) { - mMagnificationController.setCenter(eventX, eventY, + // TODO: multi-display support for magnification gesture handler + if (mMagnificationController.magnificationRegionContains( + Display.DEFAULT_DISPLAY, eventX, eventY)) { + mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY, /* animate */ mLastMoveOutsideMagnifiedRegion, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); mLastMoveOutsideMagnifiedRegion = false; @@ -657,8 +665,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + // TODO: multi-display support for magnification gesture handler if (!mMagnificationController.magnificationRegionContains( - event.getX(), event.getY())) { + Display.DEFAULT_DISPLAY, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); @@ -667,11 +676,16 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { // 3tap and hold afterLongTapTimeoutTransitionToDraggingState(event); + } else if (isTapOutOfDistanceSlop()) { + + transitionToDelegatingStateAndClear(); + } else if (mDetectTripleTap // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay // to ensure reachability of // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) - || mMagnificationController.isMagnifying()) { + // TODO: multi-display support for magnification gesture handler + || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { afterMultiTapTimeoutTransitionToDelegatingState(); @@ -683,7 +697,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } break; case ACTION_POINTER_DOWN: { - if (mMagnificationController.isMagnifying()) { + // TODO: multi-display support for magnification gesture handler + if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { transitionTo(mPanningScalingState); clear(); } else { @@ -712,8 +727,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD); + // TODO: multi-display support for magnification gesture handler if (!mMagnificationController.magnificationRegionContains( - event.getX(), event.getY())) { + Display.DEFAULT_DISPLAY, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); @@ -864,7 +880,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { clear(); // Toggle zoom - if (mMagnificationController.isMagnifying()) { + // TODO: multi-display support for magnification gesture handler + if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) { zoomOff(); } else { zoomOn(up.getX(), up.getY()); @@ -876,8 +893,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()"); clear(); + // TODO: multi-display support for magnification gesture handler mViewportDraggingState.mZoomedInBeforeDrag = - mMagnificationController.isMagnifying(); + mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY); zoomOn(down.getX(), down.getY()); @@ -904,7 +922,33 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")"); mShortcutTriggered = state; - mMagnificationController.setForceShowMagnifiableBounds(state); + // TODO: multi-display support for magnification gesture handler + mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state); + } + + /** + * Detects if last action down is out of distance slop between with previous + * one, when triple tap is enabled. + * + * @return true if tap is out of distance slop + */ + boolean isTapOutOfDistanceSlop() { + if (!mDetectTripleTap) return false; + if (mPreLastDown == null || mLastDown == null) { + return false; + } + final boolean outOfDistanceSlop = + GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance; + if (tapCount() > 0) { + return outOfDistanceSlop; + } + // There's no tap in the queue here. We still need to check if this is the case that + // user tap screen quickly and out of distance slop. + if (outOfDistanceSlop + && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) { + return true; + } + return false; } } @@ -914,7 +958,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { final float scale = MathUtils.constrain( mMagnificationController.getPersistedScale(), MIN_SCALE, MAX_SCALE); - mMagnificationController.setScaleAndCenter( + // TODO: multi-display support for magnification gesture handler + mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY, scale, centerX, centerY, /* animate */ true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); @@ -922,8 +967,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { private void zoomOff() { if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()"); - - mMagnificationController.reset(/* animate */ true); + // TODO: multi-display support for magnification gesture handler + mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true); } private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 7dfd8fef13c2..9f7d33f6796a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -261,6 +261,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private Runnable mAugmentedAutofillDestroyer; /** + * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics. + */ + @GuardedBy("mLock") + private ArrayList<LogMaker> mAugmentedRequestsLogs; + + /** * Receiver of assist data from the app's {@link Activity}. */ private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() { @@ -2541,8 +2547,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mService.resetLastResponse(); - // The default autofill service cannot fullfill the request, let's check if the intelligence - // service can. + // The default autofill service cannot fullfill the request, let's check if the augmented + // autofill service can. mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(); if (mAugmentedAutofillDestroyer == null) { if (sVerbose) { @@ -2605,8 +2611,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (sVerbose) { - Slog.v(TAG, "calling IntelligenseService on view " + mCurrentViewId - + " using suggestion mode " + smartSuggestionFlagsToString(mode) + Slog.v(TAG, "calling Augmented Autofill Service (" + + remoteService.getComponentName().toShortString() + ") on view " + + mCurrentViewId + " using suggestion mode " + + smartSuggestionFlagsToString(mode) + " when server returned null for session " + this.id); } @@ -2620,8 +2628,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue currentValue = mViewStates.get(mCurrentViewId).getCurrentValue(); // TODO(b/111330312): we might need to add a new state in the AutofillManager to optimize - // furgher AFM -> AFMS calls. - // TODO(b/119638958): add CTS tests + // further AFM -> AFMS calls. + + if (mAugmentedRequestsLogs == null) { + mAugmentedRequestsLogs = new ArrayList<>(); + } + final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST, + remoteService.getComponentName().getPackageName()); + mAugmentedRequestsLogs.add(log); + remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, mCurrentViewId, currentValue); @@ -2912,6 +2927,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mAugmentedAutofillDestroyer != null) { pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer"); } + if (mAugmentedRequestsLogs != null) { + pw.print(prefix); pw.print("number augmented requests: "); + pw.println(mAugmentedRequestsLogs.size()); + } + mRemoteFillService.dump(prefix, pw); } @@ -3053,8 +3073,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mMetricsLogger.write(log); } } - mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests)); + + final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0 + : mAugmentedRequestsLogs.size(); + if (totalAugmentedRequests > 0) { + if (sVerbose) { + Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests"); + } + for (int i = 0; i < totalAugmentedRequests; i++) { + final LogMaker log = mAugmentedRequestsLogs.get(i); + mMetricsLogger.write(log); + } + } + + final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests); + if (totalAugmentedRequests > 0) { + log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS, + totalAugmentedRequests); + } + mMetricsLogger.write(log); return mRemoteFillService; } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 0fa996ed7657..fcd136c65169 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -64,6 +64,7 @@ import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.ThreadLocalWorkSource; import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; @@ -1131,23 +1132,23 @@ class AlarmManagerService extends SystemService { final IBinder mListener; final WorkSource mWorkSource; final int mUid; + final int mCreatorUid; final String mTag; final BroadcastStats mBroadcastStats; final FilterStats mFilterStats; final int mAlarmType; - InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener, - WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag, - long nowELAPSED) { - mPendingIntent = pendingIntent; + InFlight(AlarmManagerService service, Alarm alarm, long nowELAPSED) { + mPendingIntent = alarm.operation; mWhenElapsed = nowELAPSED; - mListener = listener != null ? listener.asBinder() : null; - mWorkSource = workSource; - mUid = uid; - mTag = tag; - mBroadcastStats = (pendingIntent != null) - ? service.getStatsLocked(pendingIntent) - : service.getStatsLocked(uid, alarmPkg); + mListener = alarm.listener != null ? alarm.listener.asBinder() : null; + mWorkSource = alarm.workSource; + mUid = alarm.uid; + mCreatorUid = alarm.creatorUid; + mTag = alarm.statsTag; + mBroadcastStats = (alarm.operation != null) + ? service.getStatsLocked(alarm.operation) + : service.getStatsLocked(alarm.uid, alarm.packageName); FilterStats fs = mBroadcastStats.filterStats.get(mTag); if (fs == null) { fs = new FilterStats(mBroadcastStats, mTag); @@ -1155,7 +1156,7 @@ class AlarmManagerService extends SystemService { } fs.lastTime = nowELAPSED; mFilterStats = fs; - mAlarmType = alarmType; + mAlarmType = alarm.type; } @Override @@ -1165,6 +1166,7 @@ class AlarmManagerService extends SystemService { + ", when=" + mWhenElapsed + ", workSource=" + mWorkSource + ", uid=" + mUid + + ", creatorUid=" + mCreatorUid + ", tag=" + mTag + ", broadcastStats=" + mBroadcastStats + ", filterStats=" + mFilterStats @@ -3811,12 +3813,10 @@ class AlarmManagerService extends SystemService { /** * Attribute blame for a WakeLock. - * @param pi PendingIntent to attribute blame to if ws is null. * @param ws WorkSource to attribute blame. - * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender + * @param knownUid attribution uid; < 0 values are ignored. */ - void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag, - int knownUid, boolean first) { + void setWakelockWorkSource(WorkSource ws, int knownUid, String tag, boolean first) { try { mWakeLock.setHistoryTag(first ? tag : null); @@ -3825,11 +3825,8 @@ class AlarmManagerService extends SystemService { return; } - final int uid = (knownUid >= 0) - ? knownUid - : ActivityManager.getService().getUidForIntentSender(pi.getTarget()); - if (uid >= 0) { - mWakeLock.setWorkSource(new WorkSource(uid)); + if (knownUid >= 0) { + mWakeLock.setWorkSource(new WorkSource(knownUid)); return; } } catch (Exception e) { @@ -3839,6 +3836,14 @@ class AlarmManagerService extends SystemService { mWakeLock.setWorkSource(null); } + private static int getAlarmAttributionUid(Alarm alarm) { + if (alarm.workSource != null && !alarm.workSource.isEmpty()) { + return alarm.workSource.getAttributionUid(); + } + + return alarm.creatorUid; + } + @VisibleForTesting class AlarmHandler extends Handler { public static final int ALARM_EVENT = 1; @@ -4285,8 +4290,8 @@ class AlarmManagerService extends SystemService { // the next of our alarms is now in flight. reattribute the wakelock. if (mInFlight.size() > 0) { InFlight inFlight = mInFlight.get(0); - setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource, - inFlight.mAlarmType, inFlight.mTag, -1, false); + setWakelockWorkSource(inFlight.mWorkSource, inFlight.mCreatorUid, inFlight.mTag, + false); } else { // should never happen mLog.w("Alarm wakelock still held but sent queue empty"); @@ -4369,64 +4374,70 @@ class AlarmManagerService extends SystemService { */ @GuardedBy("mLock") public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) { - if (alarm.operation != null) { - // PendingIntent alarm - mSendCount++; - - try { - alarm.operation.send(getContext(), 0, - mBackgroundIntent.putExtra( - Intent.EXTRA_ALARM_COUNT, alarm.count), - mDeliveryTracker, mHandler, null, - allowWhileIdle ? mIdleOptions : null); - } catch (PendingIntent.CanceledException e) { - if (alarm.repeatInterval > 0) { - // This IntentSender is no longer valid, but this - // is a repeating alarm, so toss it - removeImpl(alarm.operation, null); + final long workSourceToken = ThreadLocalWorkSource.setUid( + getAlarmAttributionUid(alarm)); + try { + if (alarm.operation != null) { + // PendingIntent alarm + mSendCount++; + + try { + alarm.operation.send(getContext(), 0, + mBackgroundIntent.putExtra( + Intent.EXTRA_ALARM_COUNT, alarm.count), + mDeliveryTracker, mHandler, null, + allowWhileIdle ? mIdleOptions : null); + } catch (PendingIntent.CanceledException e) { + if (alarm.repeatInterval > 0) { + // This IntentSender is no longer valid, but this + // is a repeating alarm, so toss it + removeImpl(alarm.operation, null); + } + // No actual delivery was possible, so the delivery tracker's + // 'finished' callback won't be invoked. We also don't need + // to do any wakelock or stats tracking, so we have nothing + // left to do here but go on to the next thing. + mSendFinishCount++; + return; } - // No actual delivery was possible, so the delivery tracker's - // 'finished' callback won't be invoked. We also don't need - // to do any wakelock or stats tracking, so we have nothing - // left to do here but go on to the next thing. - mSendFinishCount++; - return; - } - } else { - // Direct listener callback alarm - mListenerCount++; - - if (RECORD_ALARMS_IN_HISTORY) { - if (alarm.listener == mTimeTickTrigger) { - mTickHistory[mNextTickHistory++] = nowELAPSED; - if (mNextTickHistory >= TICK_HISTORY_DEPTH) { - mNextTickHistory = 0; + } else { + // Direct listener callback alarm + mListenerCount++; + + if (RECORD_ALARMS_IN_HISTORY) { + if (alarm.listener == mTimeTickTrigger) { + mTickHistory[mNextTickHistory++] = nowELAPSED; + if (mNextTickHistory >= TICK_HISTORY_DEPTH) { + mNextTickHistory = 0; + } } } - } - try { - if (DEBUG_LISTENER_CALLBACK) { - Slog.v(TAG, "Alarm to uid=" + alarm.uid - + " listener=" + alarm.listener.asBinder()); - } - alarm.listener.doAlarm(this); - mHandler.sendMessageDelayed( - mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT, - alarm.listener.asBinder()), - mConstants.LISTENER_TIMEOUT); - } catch (Exception e) { - if (DEBUG_LISTENER_CALLBACK) { - Slog.i(TAG, "Alarm undeliverable to listener " - + alarm.listener.asBinder(), e); + try { + if (DEBUG_LISTENER_CALLBACK) { + Slog.v(TAG, "Alarm to uid=" + alarm.uid + + " listener=" + alarm.listener.asBinder()); + } + alarm.listener.doAlarm(this); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT, + alarm.listener.asBinder()), + mConstants.LISTENER_TIMEOUT); + } catch (Exception e) { + if (DEBUG_LISTENER_CALLBACK) { + Slog.i(TAG, "Alarm undeliverable to listener " + + alarm.listener.asBinder(), e); + } + // As in the PendingIntent.CanceledException case, delivery of the + // alarm was not possible, so we have no wakelock or timeout or + // stats management to do. It threw before we posted the delayed + // timeout message, so we're done here. + mListenerFinishCount++; + return; } - // As in the PendingIntent.CanceledException case, delivery of the - // alarm was not possible, so we have no wakelock or timeout or - // stats management to do. It threw before we posted the delayed - // timeout message, so we're done here. - mListenerFinishCount++; - return; } + } finally { + ThreadLocalWorkSource.restore(workSourceToken); } // The alarm is now in flight; now arrange wakelock and stats tracking @@ -4434,15 +4445,11 @@ class AlarmManagerService extends SystemService { Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1)); } if (mBroadcastRefCount == 0) { - setWakelockWorkSource(alarm.operation, alarm.workSource, - alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1, - true); + setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true); mWakeLock.acquire(); mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget(); } - final InFlight inflight = new InFlight(AlarmManagerService.this, - alarm.operation, alarm.listener, alarm.workSource, alarm.uid, - alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED); + final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED); mInFlight.add(inflight); mBroadcastRefCount++; if (allowWhileIdle) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 00550d9c660e..fae7a8d7bfab 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3183,6 +3183,15 @@ public class ConnectivityService extends IConnectivityManager.Stub return mMultinetworkPolicyTracker.getAvoidBadWifi(); } + @Override + public boolean getAvoidBadWifi() { + if (!checkNetworkStackPermission()) { + throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission"); + } + return avoidBadWifi(); + } + + private void rematchForAvoidBadWifiUpdate() { rematchAllNetworksAndRequests(null, 0); for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) { @@ -6346,6 +6355,20 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @GuardedBy("mVpns") + private Vpn getVpnIfOwner() { + final int uid = Binder.getCallingUid(); + final int user = UserHandle.getUserId(uid); + + final Vpn vpn = mVpns.get(user); + if (vpn == null) { + return null; + } else { + final VpnInfo info = vpn.getVpnInfo(); + return (info == null || info.ownerUid != uid) ? null : vpn; + } + } + /** * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission * for testing. @@ -6354,14 +6377,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (checkNetworkStackPermission()) { return null; } - final int uid = Binder.getCallingUid(); - final int user = UserHandle.getUserId(uid); synchronized (mVpns) { - Vpn vpn = mVpns.get(user); - try { - if (vpn.getVpnInfo().ownerUid == uid) return vpn; - } catch (NullPointerException e) { - /* vpn is null, or VPN is not connected and getVpnInfo() is null. */ + Vpn vpn = getVpnIfOwner(); + if (vpn != null) { + return vpn; } } throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK " @@ -6390,4 +6409,20 @@ public class ConnectivityService extends IConnectivityManager.Stub return uid; } + + @Override + public boolean isCallerCurrentAlwaysOnVpnApp() { + synchronized (mVpns) { + Vpn vpn = getVpnIfOwner(); + return vpn != null && vpn.getAlwaysOn(); + } + } + + @Override + public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { + synchronized (mVpns) { + Vpn vpn = getVpnIfOwner(); + return vpn != null && vpn.getLockdown(); + } + } } diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 121a830f05f5..39030aaf3eb4 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -341,6 +341,29 @@ public class DeviceIdleController extends SystemService @VisibleForTesting static final int STATE_QUICK_DOZE_DELAY = 7; + private static final int ACTIVE_REASON_UNKNOWN = 0; + private static final int ACTIVE_REASON_MOTION = 1; + private static final int ACTIVE_REASON_SCREEN = 2; + private static final int ACTIVE_REASON_CHARGING = 3; + private static final int ACTIVE_REASON_UNLOCKED = 4; + private static final int ACTIVE_REASON_FROM_BINDER_CALL = 5; + private static final int ACTIVE_REASON_FORCED = 6; + private static final int ACTIVE_REASON_ALARM = 7; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_UNINIT = -1; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_IGNORED = 0; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_OK = 1; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_NOT_SUPPORT = 2; + @VisibleForTesting + static final int SET_IDLE_FACTOR_RESULT_INVALID = 3; + @VisibleForTesting + static final long MIN_STATE_STEP_ALARM_CHANGE = 60 * 1000; + @VisibleForTesting + static final float MIN_PRE_IDLE_FACTOR_CHANGE = 0.05f; + @VisibleForTesting static String stateToString(int state) { switch (state) { @@ -405,6 +428,7 @@ public class DeviceIdleController extends SystemService private long mNextSensingTimeoutAlarmTime; private long mCurIdleBudget; private long mMaintenanceStartTime; + private long mIdleStartTime; private int mActiveIdleOpCount; private PowerManager.WakeLock mActiveIdleWakeLock; // held when there are operations in progress @@ -415,6 +439,17 @@ public class DeviceIdleController extends SystemService private boolean mAlarmsActive; private boolean mReportedMaintenanceActivity; + /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter + * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because: + * - Both of them are shorter + * - Device sensor might take time be to become be stabilized + * Also don't apply the factor if the device is in motion because device motion provides a + * stronger signal than a prediction algorithm. + */ + private float mPreIdleFactor; + private float mLastPreIdleFactor; + private int mActiveReason; + public final AtomicFile mConfigFile; private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners = @@ -760,6 +795,10 @@ public class DeviceIdleController extends SystemService * exit doze. Default = true */ private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock"; + private static final String KEY_PRE_IDLE_FACTOR_LONG = + "pre_idle_factor_long"; + private static final String KEY_PRE_IDLE_FACTOR_SHORT = + "pre_idle_factor_short"; /** * This is the time, after becoming inactive, that we go in to the first @@ -987,6 +1026,16 @@ public class DeviceIdleController extends SystemService */ public long NOTIFICATION_WHITELIST_DURATION; + /** + * Pre idle time factor use to make idle delay longer + */ + public float PRE_IDLE_FACTOR_LONG; + + /** + * Pre idle time factor use to make idle delay shorter + */ + public float PRE_IDLE_FACTOR_SHORT; + public boolean WAIT_FOR_UNLOCK; private final ContentResolver mResolver; @@ -1082,6 +1131,8 @@ public class DeviceIdleController extends SystemService NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis( KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L); WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false); + PRE_IDLE_FACTOR_LONG = mParser.getFloat(KEY_PRE_IDLE_FACTOR_LONG, 1.67f); + PRE_IDLE_FACTOR_SHORT = mParser.getFloat(KEY_PRE_IDLE_FACTOR_SHORT, 0.33f); } } @@ -1196,6 +1247,12 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("="); pw.println(WAIT_FOR_UNLOCK); + + pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_LONG); pw.print("="); + pw.println(PRE_IDLE_FACTOR_LONG); + + pw.print(" "); pw.print(KEY_PRE_IDLE_FACTOR_SHORT); pw.print("="); + pw.println(PRE_IDLE_FACTOR_SHORT); } } @@ -1244,6 +1301,8 @@ public class DeviceIdleController extends SystemService private static final int MSG_FINISH_IDLE_OP = 8; private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9; private static final int MSG_SEND_CONSTRAINT_MONITORING = 10; + private static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11; + private static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12; final class MyHandler extends Handler { MyHandler(Looper looper) { @@ -1373,6 +1432,13 @@ public class DeviceIdleController extends SystemService constraint.stopMonitoring(); } } break; + case MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR: { + updatePreIdleFactor(); + } break; + case MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR: { + updatePreIdleFactor(); + maybeDoImmediateMaintenance(); + } break; } } } @@ -1526,6 +1592,28 @@ public class DeviceIdleController extends SystemService DeviceIdleController.this.unregisterMaintenanceActivityListener(listener); } + @Override public int setPreIdleTimeoutMode(int mode) { + getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER, + null); + long ident = Binder.clearCallingIdentity(); + try { + return DeviceIdleController.this.setPreIdleTimeoutMode(mode); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void resetPreIdleTimeoutMode() { + getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER, + null); + long ident = Binder.clearCallingIdentity(); + try { + DeviceIdleController.this.resetPreIdleTimeoutMode(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { DeviceIdleController.this.dump(fd, pw, args); } @@ -1768,9 +1856,12 @@ public class DeviceIdleController extends SystemService // Start out assuming we are charging. If we aren't, we will at least get // a battery update the next time the level drops. mCharging = true; + mActiveReason = ACTIVE_REASON_UNKNOWN; mState = STATE_ACTIVE; mLightState = LIGHT_STATE_ACTIVE; mInactiveTimeout = mConstants.INACTIVE_TIMEOUT; + mPreIdleFactor = 1.0f; + mLastPreIdleFactor = 1.0f; } mBinderService = new BinderService(); @@ -2394,6 +2485,7 @@ public class DeviceIdleController extends SystemService public void exitIdleInternal(String reason) { synchronized (this) { + mActiveReason = ACTIVE_REASON_FROM_BINDER_CALL; becomeActiveLocked(reason, Binder.getCallingUid()); } } @@ -2463,6 +2555,7 @@ public class DeviceIdleController extends SystemService } else if (screenOn) { mScreenOn = true; if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) { + mActiveReason = ACTIVE_REASON_SCREEN; becomeActiveLocked("screen", Process.myUid()); } } @@ -2485,6 +2578,7 @@ public class DeviceIdleController extends SystemService } else if (charging) { mCharging = charging; if (!mForceIdle) { + mActiveReason = ACTIVE_REASON_CHARGING; becomeActiveLocked("charging", Process.myUid()); } } @@ -2516,6 +2610,7 @@ public class DeviceIdleController extends SystemService if (mScreenLocked != showing) { mScreenLocked = showing; if (mScreenOn && !mForceIdle && !mScreenLocked) { + mActiveReason = ACTIVE_REASON_UNLOCKED; becomeActiveLocked("unlocked", Process.myUid()); } } @@ -2587,7 +2682,11 @@ public class DeviceIdleController extends SystemService mState = STATE_INACTIVE; if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE"); resetIdleManagementLocked(); - scheduleAlarmLocked(mInactiveTimeout, false); + long delay = mInactiveTimeout; + if (shouldUseIdleTimeoutFactorLocked()) { + delay = (long) (mPreIdleFactor * delay); + } + scheduleAlarmLocked(delay, false); EventLogTags.writeDeviceIdle(mState, "no activity"); } } @@ -2605,6 +2704,7 @@ public class DeviceIdleController extends SystemService mNextIdlePendingDelay = 0; mNextIdleDelay = 0; mNextLightIdleDelay = 0; + mIdleStartTime = 0; cancelAlarmLocked(); cancelSensingTimeoutAlarmLocked(); cancelLocatingLocked(); @@ -2621,6 +2721,7 @@ public class DeviceIdleController extends SystemService if (mForceIdle) { mForceIdle = false; if (mScreenOn || mCharging) { + mActiveReason = ACTIVE_REASON_FORCED; becomeActiveLocked("exit-force", Process.myUid()); } } @@ -2740,6 +2841,7 @@ public class DeviceIdleController extends SystemService if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) { // Whoops, there is an upcoming alarm. We don't actually want to go idle. if (mState != STATE_ACTIVE) { + mActiveReason = ACTIVE_REASON_ALARM; becomeActiveLocked("alarm", Process.myUid()); becomeInactiveIfAppropriateLocked(); } @@ -2763,7 +2865,11 @@ public class DeviceIdleController extends SystemService // We have now been inactive long enough, it is time to start looking // for motion and sleep some more while doing so. startMonitoringMotionLocked(); - scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); + long delay = mConstants.IDLE_AFTER_INACTIVE_TIMEOUT; + if (shouldUseIdleTimeoutFactorLocked()) { + delay = (long) (mPreIdleFactor * delay); + } + scheduleAlarmLocked(delay, false); moveToStateLocked(STATE_IDLE_PENDING, reason); break; case STATE_IDLE_PENDING: @@ -2834,6 +2940,7 @@ public class DeviceIdleController extends SystemService " ms."); mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR); if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay); + mIdleStartTime = SystemClock.elapsedRealtime(); mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT); if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) { mNextIdleDelay = mConstants.IDLE_TIMEOUT; @@ -2934,6 +3041,127 @@ public class DeviceIdleController extends SystemService } } + @VisibleForTesting + int setPreIdleTimeoutMode(int mode) { + return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode)); + } + + @VisibleForTesting + float getPreIdleTimeoutByMode(int mode) { + switch (mode) { + case PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG: { + return mConstants.PRE_IDLE_FACTOR_LONG; + } + case PowerManager.PRE_IDLE_TIMEOUT_MODE_SHORT: { + return mConstants.PRE_IDLE_FACTOR_SHORT; + } + case PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL: { + return 1.0f; + } + default: { + Slog.w(TAG, "Invalid time out factor mode: " + mode); + return 1.0f; + } + } + } + + @VisibleForTesting + float getPreIdleTimeoutFactor() { + return mPreIdleFactor; + } + + @VisibleForTesting + int setPreIdleTimeoutFactor(float ratio) { + if (!mDeepEnabled) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Deep Idle disable"); + return SET_IDLE_FACTOR_RESULT_NOT_SUPPORT; + } else if (ratio <= MIN_PRE_IDLE_FACTOR_CHANGE) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: Invalid input"); + return SET_IDLE_FACTOR_RESULT_INVALID; + } else if (Math.abs(ratio - mPreIdleFactor) < MIN_PRE_IDLE_FACTOR_CHANGE) { + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: New factor same as previous factor"); + return SET_IDLE_FACTOR_RESULT_IGNORED; + } + synchronized (this) { + mLastPreIdleFactor = mPreIdleFactor; + mPreIdleFactor = ratio; + } + if (DEBUG) Slog.d(TAG, "setPreIdleTimeoutFactor: " + ratio); + postUpdatePreIdleFactor(); + return SET_IDLE_FACTOR_RESULT_OK; + } + + @VisibleForTesting + void resetPreIdleTimeoutMode() { + synchronized (this) { + mLastPreIdleFactor = mPreIdleFactor; + mPreIdleFactor = 1.0f; + } + if (DEBUG) Slog.d(TAG, "resetPreIdleTimeoutMode to 1.0"); + postResetPreIdleTimeoutFactor(); + } + + private void postUpdatePreIdleFactor() { + mHandler.sendEmptyMessage(MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR); + } + + private void postResetPreIdleTimeoutFactor() { + mHandler.sendEmptyMessage(MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR); + } + + @VisibleForTesting + void updatePreIdleFactor() { + synchronized (this) { + if (!shouldUseIdleTimeoutFactorLocked()) { + return; + } + if (mState == STATE_INACTIVE || mState == STATE_IDLE_PENDING) { + if (mNextAlarmTime == 0) { + return; + } + long delay = mNextAlarmTime - SystemClock.elapsedRealtime(); + if (delay < MIN_STATE_STEP_ALARM_CHANGE) { + return; + } + long newDelay = (long) (delay / mLastPreIdleFactor * mPreIdleFactor); + if (Math.abs(delay - newDelay) < MIN_STATE_STEP_ALARM_CHANGE) { + return; + } + scheduleAlarmLocked(newDelay, false); + } + } + } + + @VisibleForTesting + void maybeDoImmediateMaintenance() { + synchronized (this) { + if (mState == STATE_IDLE) { + long duration = SystemClock.elapsedRealtime() - mIdleStartTime; + /* Let's trgger a immediate maintenance, + * if it has been idle for a long time */ + if (duration > mConstants.IDLE_TIMEOUT) { + scheduleAlarmLocked(0, false); + } + } + } + } + + private boolean shouldUseIdleTimeoutFactorLocked() { + // exclude ACTIVE_REASON_MOTION, for exclude device in pocket case + if (mActiveReason == ACTIVE_REASON_MOTION) { + return false; + } + return true; + } + + /** Must only be used in tests. */ + @VisibleForTesting + void setIdleStartTimeForTest(long idleStartTime) { + synchronized (this) { + mIdleStartTime = idleStartTime; + } + } + void reportMaintenanceActivityIfNeededLocked() { boolean active = mJobsActive; if (active == mReportedMaintenanceActivity) { @@ -2945,6 +3173,11 @@ public class DeviceIdleController extends SystemService mHandler.sendMessage(msg); } + @VisibleForTesting + long getNextAlarmTime() { + return mNextAlarmTime; + } + boolean isOpsInactiveLocked() { return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive; } @@ -2994,6 +3227,7 @@ public class DeviceIdleController extends SystemService scheduleReportActiveLocked(type, Process.myUid()); addEvent(EVENT_NORMAL, type); } + mActiveReason = ACTIVE_REASON_MOTION; mState = STATE_ACTIVE; mInactiveTimeout = timeout; mCurIdleBudget = 0; @@ -3401,6 +3635,11 @@ public class DeviceIdleController extends SystemService + "and any [-d] is ignored"); pw.println(" motion"); pw.println(" Simulate a motion event to bring the device out of deep doze"); + pw.println(" pre-idle-factor [0|1|2]"); + pw.println(" Set a new factor to idle time before step to idle" + + "(inactive_to and idle_after_inactive_to)"); + pw.println(" reset-pre-idle-factor"); + pw.println(" Reset factor to idle time to default"); } class Shell extends ShellCommand { @@ -3571,6 +3810,7 @@ public class DeviceIdleController extends SystemService } } if (becomeActive) { + mActiveReason = ACTIVE_REASON_FORCED; becomeActiveLocked((arg == null ? "all" : arg) + "-disabled", Process.myUid()); } @@ -3820,6 +4060,52 @@ public class DeviceIdleController extends SystemService Binder.restoreCallingIdentity(token); } } + } else if ("pre-idle-factor".equals(cmd)) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + synchronized (this) { + long token = Binder.clearCallingIdentity(); + int ret = SET_IDLE_FACTOR_RESULT_UNINIT; + try { + String arg = shell.getNextArg(); + boolean valid = false; + int mode = 0; + if (arg != null) { + mode = Integer.parseInt(arg); + ret = setPreIdleTimeoutMode(mode); + if (ret == SET_IDLE_FACTOR_RESULT_OK) { + pw.println("pre-idle-factor: " + mode); + valid = true; + } else if (ret == SET_IDLE_FACTOR_RESULT_NOT_SUPPORT) { + valid = true; + pw.println("Deep idle not supported"); + } else if (ret == SET_IDLE_FACTOR_RESULT_IGNORED) { + valid = true; + pw.println("Idle timeout factor not changed"); + } + } + if (!valid) { + pw.println("Unknown idle timeout factor: " + arg + + ",(error code: " + ret + ")"); + } + } catch (NumberFormatException e) { + pw.println("Unknown idle timeout factor" + + ",(error code: " + ret + ")"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } else if ("reset-pre-idle-factor".equals(cmd)) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + synchronized (this) { + long token = Binder.clearCallingIdentity(); + try { + resetPreIdleTimeoutMode(); + } finally { + Binder.restoreCallingIdentity(token); + } + } } else { return shell.handleDefaultCommands(cmd); } @@ -4053,6 +4339,9 @@ public class DeviceIdleController extends SystemService if (mAlarmsActive) { pw.print(" mAlarmsActive="); pw.println(mAlarmsActive); } + if (Math.abs(mPreIdleFactor - 1.0f) > MIN_PRE_IDLE_FACTOR_CHANGE) { + pw.print(" mPreIdleFactor="); pw.println(mPreIdleFactor); + } } } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index add5e5feaa8a..b3a0f643ba4b 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -46,6 +46,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.Signature; import android.content.res.Resources; import android.database.ContentObserver; +import android.hardware.location.ActivityRecognitionHardware; import android.location.Address; import android.location.Criteria; import android.location.GeocoderParams; @@ -66,7 +67,6 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -87,11 +87,11 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.location.ProviderProperties; 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.internal.util.Preconditions; import com.android.server.location.AbstractLocationProvider; +import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.GeocoderProxy; import com.android.server.location.GeofenceManager; import com.android.server.location.GeofenceProxy; @@ -246,7 +246,7 @@ public class LocationManagerService extends ILocationManager.Stub { public LocationManagerService(Context context) { super(); mContext = context; - mHandler = BackgroundThread.getHandler(); + mHandler = FgThread.getHandler(); // Let the package manager query which are the default location // providers as they get certain permissions granted by default. @@ -736,6 +736,25 @@ public class LocationManagerService extends ILocationManager.Stub { Slog.d(TAG, "Unable to bind FLP Geofence proxy."); } + // bind to hardware activity recognition + boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported(); + ActivityRecognitionHardware activityRecognitionHardware = null; + if (activityRecognitionHardwareIsSupported) { + activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext); + } else { + Slog.d(TAG, "Hardware Activity-Recognition not supported."); + } + ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind( + mContext, + activityRecognitionHardwareIsSupported, + activityRecognitionHardware, + com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay, + com.android.internal.R.string.config_activityRecognitionHardwarePackageName, + com.android.internal.R.array.config_locationProviderPackageNames); + if (proxy == null) { + Slog.d(TAG, "Unable to bind ActivityRecognitionProxy."); + } + String[] testProviderStrings = resources.getStringArray( com.android.internal.R.array.config_testLocationProviders); for (String testProviderString : testProviderStrings) { @@ -954,7 +973,7 @@ public class LocationManagerService extends ILocationManager.Stub { public void onReportLocation(Location location) { // no security check necessary because this is coming from an internal-only interface // move calls coming from below LMS onto a different thread to avoid deadlock - runInternal(() -> { + mHandler.post(() -> { synchronized (mLock) { handleLocationChangedLocked(location, this); } @@ -965,7 +984,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void onReportLocation(List<Location> locations) { // move calls coming from below LMS onto a different thread to avoid deadlock - runInternal(() -> { + mHandler.post(() -> { synchronized (mLock) { LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER); if (gpsProvider == null || !gpsProvider.isUseableLocked()) { @@ -991,7 +1010,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void onSetEnabled(boolean enabled) { // move calls coming from below LMS onto a different thread to avoid deadlock - runInternal(() -> { + mHandler.post(() -> { synchronized (mLock) { if (enabled == mEnabled) { return; @@ -1113,16 +1132,6 @@ public class LocationManagerService extends ILocationManager.Stub { mUseable = false; updateProviderUseableLocked(this); } - - // binder transactions coming from below LMS (ie location providers) need to be moved onto - // a different thread to avoid potential deadlock as code reenters the location providers - private void runInternal(Runnable runnable) { - if (Looper.myLooper() == mHandler.getLooper()) { - runnable.run(); - } else { - mHandler.post(runnable); - } - } } private class MockLocationProvider extends LocationProvider { @@ -1278,7 +1287,11 @@ public class LocationManagerService extends ILocationManager.Stub { // are high power (has a high power provider with an interval under a threshold). for (UpdateRecord updateRecord : mUpdateRecords.values()) { LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider); - if (provider == null || !provider.isUseableLocked()) { + if (provider == null) { + continue; + } + if (!provider.isUseableLocked() + && !updateRecord.mRealRequest.isLocationSettingsIgnored()) { continue; } @@ -1960,14 +1973,22 @@ public class LocationManagerService extends ILocationManager.Stub { ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); if (records != null) { for (UpdateRecord record : records) { - if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { - // Sends a notification message to the receiver - if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) { - if (deadReceivers == null) { - deadReceivers = new ArrayList<>(); - } - deadReceivers.add(record.mReceiver); + if (!isCurrentProfileLocked( + UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { + continue; + } + + // requests that ignore location settings will never provider notifications + if (record.mRealRequest.isLocationSettingsIgnored()) { + continue; + } + + // Sends a notification message to the receiver + if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<>(); } + deadReceivers.add(record.mReceiver); } } } @@ -2007,40 +2028,48 @@ public class LocationManagerService extends ILocationManager.Stub { Binder.restoreCallingIdentity(identity); } - if (provider.isUseableLocked() && records != null && !records.isEmpty()) { + if (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 (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { - if (checkLocationAccess( - record.mReceiver.mIdentity.mPid, - record.mReceiver.mIdentity.mUid, - record.mReceiver.mIdentity.mPackageName, - record.mReceiver.mAllowedResolutionLevel)) { - LocationRequest locationRequest = record.mRealRequest; - long interval = locationRequest.getInterval(); - - if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) { - if (!record.mIsForegroundUid) { - interval = Math.max(interval, backgroundThrottleInterval); - } - if (interval != locationRequest.getInterval()) { - locationRequest = new LocationRequest(locationRequest); - locationRequest.setInterval(interval); - } - } + if (!isCurrentProfileLocked( + UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { + continue; + } + if (!checkLocationAccess( + record.mReceiver.mIdentity.mPid, + record.mReceiver.mIdentity.mUid, + record.mReceiver.mIdentity.mPackageName, + record.mReceiver.mAllowedResolutionLevel)) { + continue; + } + if (!provider.isUseableLocked() + && !record.mRealRequest.isLocationSettingsIgnored()) { + continue; + } - record.mRequest = locationRequest; - providerRequest.locationRequests.add(locationRequest); - if (!locationRequest.isLowPowerMode()) { - providerRequest.lowPowerMode = false; - } - if (interval < providerRequest.interval) { - providerRequest.reportLocation = true; - providerRequest.interval = interval; - } + LocationRequest locationRequest = record.mRealRequest; + long interval = locationRequest.getInterval(); + + if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) { + if (!record.mIsForegroundUid) { + interval = Math.max(interval, backgroundThrottleInterval); + } + if (interval != locationRequest.getInterval()) { + locationRequest = new LocationRequest(locationRequest); + locationRequest.setInterval(interval); } } + + record.mRequest = locationRequest; + providerRequest.locationRequests.add(locationRequest); + if (!locationRequest.isLowPowerMode()) { + providerRequest.lowPowerMode = false; + } + if (interval < providerRequest.interval) { + providerRequest.reportLocation = true; + providerRequest.interval = interval; + } } if (providerRequest.reportLocation) { @@ -2307,6 +2336,10 @@ public class LocationManagerService extends ILocationManager.Stub { mContext.enforceCallingOrSelfPermission( Manifest.permission.UPDATE_APP_OPS_STATS, null); } + if (request.isLocationSettingsIgnored()) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.WRITE_SECURE_SETTINGS, null); + } boolean callerHasLocationHardwarePermission = mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) == PERMISSION_GRANTED; @@ -2376,12 +2409,14 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - if (provider.isUseableLocked()) { - applyRequirementsLocked(name); - } else { - // Notify the listener that updates are currently disabled + if (!provider.isUseableLocked() && !request.isLocationSettingsIgnored()) { + // Notify the listener that updates are currently disabled - but only if the request + // does not ignore location settings receiver.callProviderEnabledLocked(name, false); } + + applyRequirementsLocked(name); + // Update the monitoring here just in case multiple location requests were added to the // same receiver (this request may be high power and the initial might not have been). receiver.updateMonitoring(true); @@ -2981,26 +3016,29 @@ public class LocationManagerService extends ILocationManager.Stub { return; } - if (!provider.isPassiveLocked()) { - // notify passive provider of the new location - mPassiveProvider.updateLocation(location); + // only notify passive provider and update last location for locations that come from + // useable providers + if (provider.isUseableLocked()) { + if (!provider.isPassiveLocked()) { + mPassiveProvider.updateLocation(location); + } } if (D) Log.d(TAG, "incoming location: " + location); long now = SystemClock.elapsedRealtime(); - updateLastLocationLocked(location, provider.getName()); - // mLastLocation should have been updated from the updateLastLocationLocked call above. - Location lastLocation = mLastLocation.get(provider.getName()); - if (lastLocation == null) { - Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed"); - return; + if (provider.isUseableLocked()) { + updateLastLocationLocked(location, provider.getName()); } // Update last known coarse interval location if enough time has passed. - Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName()); + Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get( + provider.getName()); if (lastLocationCoarseInterval == null) { lastLocationCoarseInterval = new Location(location); - mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); + + if (provider.isUseableLocked()) { + mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); + } } long timeDiffNanos = location.getElapsedRealtimeNanos() - lastLocationCoarseInterval.getElapsedRealtimeNanos(); @@ -3031,6 +3069,10 @@ public class LocationManagerService extends ILocationManager.Stub { Receiver receiver = r.mReceiver; boolean receiverDead = false; + if (!provider.isUseableLocked() && !r.mRealRequest.isLocationSettingsIgnored()) { + continue; + } + int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid); if (!isCurrentProfileLocked(receiverUserId) && !isLocationProviderLocked(receiver.mIdentity.mUid)) { @@ -3066,7 +3108,7 @@ public class LocationManagerService extends ILocationManager.Stub { if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) { notifyLocation = coarseLocation; // use coarse location } else { - notifyLocation = lastLocation; // use fine location + notifyLocation = location; // use fine location } if (notifyLocation != null) { Location lastLoc = r.mLastFixBroadcast; diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java index cee98c10c7f7..4a8706e7090f 100644 --- a/services/core/java/com/android/server/LooperStatsService.java +++ b/services/core/java/com/android/server/LooperStatsService.java @@ -122,6 +122,10 @@ public class LooperStatsService extends Binder { "exception_count")); pw.println(header); for (LooperStats.ExportedEntry entry : entries) { + if (entry.messageName.startsWith(LooperStats.DEBUG_ENTRY_PREFIX)) { + // Do not dump debug entries. + continue; + } pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", packageMap.mapUid(entry.workSourceUid), entry.threadName, diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index c84389bcee31..2ce2f8d1a152 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -46,7 +46,9 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; +import android.net.InetAddresses; import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; import android.net.INetworkManagementEventObserver; import android.net.ITetheringStatsProvider; import android.net.InterfaceConfiguration; @@ -205,6 +207,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private INetd mNetdService; + private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener; + private IBatteryStats mBatteryStats; private final Thread mThread; @@ -322,6 +326,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub mDaemonHandler = new Handler(FgThread.get().getLooper()); + mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); + // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -340,6 +346,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub mFgHandler = null; mThread = null; mServices = null; + mNetdUnsolicitedEventListener = null; } static NetworkManagementService create(Context context, String socket, SystemServices services) @@ -446,7 +453,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub // our sanity-checking state. mActiveAlerts.remove(iface); mActiveQuotas.remove(iface); - invokeForAllObservers(o -> o.interfaceRemoved(iface)); } @@ -552,7 +558,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } // No current code examines the interface parameter in a global alert. Just pass null. - notifyLimitReached(LIMIT_GLOBAL_ALERT, null); + mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null)); } } @@ -583,6 +589,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub private void connectNativeNetdService() { mNetdService = mServices.getNetd(); + try { + mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener); + if (DBG) Slog.d(TAG, "Register unsolicited event listener"); + } catch (RemoteException | ServiceSpecificException e) { + Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e); + } } /** @@ -709,14 +721,96 @@ public class NetworkManagementService extends INetworkManagementService.Stub /** * Notify our observers of a route change. */ - private void notifyRouteChange(String action, RouteInfo route) { - if (action.equals("updated")) { + private void notifyRouteChange(boolean updated, RouteInfo route) { + if (updated) { invokeForAllObservers(o -> o.routeUpdated(route)); } else { invokeForAllObservers(o -> o.routeRemoved(route)); } } + private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub { + @Override + public void onInterfaceClassActivityChanged(boolean isActive, + int label, long timestamp, int uid) throws RemoteException { + final long timestampNanos; + if (timestamp <= 0) { + timestampNanos = SystemClock.elapsedRealtimeNanos(); + } else { + timestampNanos = timestamp; + } + mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, + isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + timestampNanos, uid, false)); + } + + @Override + public void onQuotaLimitReached(String alertName, String ifName) + throws RemoteException { + mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName)); + } + + @Override + public void onInterfaceDnsServerInfo(String ifName, + long lifetime, String[] servers) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers)); + } + + @Override + public void onInterfaceAddressUpdated(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address)); + } + + @Override + public void onInterfaceAddressRemoved(String addr, + String ifName, int flags, int scope) throws RemoteException { + final LinkAddress address = new LinkAddress(addr, flags, scope); + mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address)); + } + + @Override + public void onInterfaceAdded(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceAdded(ifName)); + } + + @Override + public void onInterfaceRemoved(String ifName) throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName)); + } + + @Override + public void onInterfaceChanged(String ifName, boolean up) + throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); + } + + @Override + public void onInterfaceLinkStateChanged(String ifName, boolean up) + throws RemoteException { + mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); + } + + @Override + public void onRouteChanged(boolean updated, + String route, String gateway, String ifName) throws RemoteException { + final RouteInfo processRoute = new RouteInfo(new IpPrefix(route), + ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway), + ifName); + mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute)); + } + + @Override + public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { + // Don't need to post to mDaemonHandler because the only thing + // that notifyCleartextNetwork does is post to a handler + ActivityManager.getService().notifyCleartextNetwork(uid, + HexDump.hexStringToByteArray(hex)); + } + } + // // Netd Callback handling // @@ -904,7 +998,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub InetAddress gateway = null; if (via != null) gateway = InetAddress.parseNumericAddress(via); RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev); - notifyRouteChange(cooked[2], route); + notifyRouteChange(cooked[2].equals("updated"), route); return true; } catch (IllegalArgumentException e) {} } @@ -1367,13 +1461,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub if (ConnectivityManager.isNetworkTypeMobile(type)) { mNetworkActive = false; } - mDaemonHandler.post(new Runnable() { - @Override public void run() { - notifyInterfaceClassActivity(type, - DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, - SystemClock.elapsedRealtimeNanos(), -1, false); - } - }); + mDaemonHandler.post(() -> notifyInterfaceClassActivity(type, + DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, + SystemClock.elapsedRealtimeNanos(), -1, false)); } } @@ -1396,13 +1486,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException(e); } mActiveIdleTimers.remove(iface); - mDaemonHandler.post(new Runnable() { - @Override public void run() { - notifyInterfaceClassActivity(params.type, - DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, - SystemClock.elapsedRealtimeNanos(), -1, false); - } - }); + mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type, + DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + SystemClock.elapsedRealtimeNanos(), -1, false)); } } diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java index 1e9a00743a8b..190fff1f669c 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java @@ -31,6 +31,19 @@ public interface PersistentDataBlockManagerInternal { */ byte[] getFrpCredentialHandle(); + /** Stores the data used to enable the Test Harness Mode after factory-resetting. */ + void setTestHarnessModeData(byte[] data); + + /** + * Retrieves the data used to place the device into Test Harness Mode. + * + * @throws IllegalStateException if the underlying storage is corrupt or inaccessible. + */ + byte[] getTestHarnessModeData(); + + /** Clear out the Test Harness Mode data. */ + void clearTestHarnessModeData(); + /** Update the OEM unlock enabled bit, bypassing user restriction checks. */ void forceOemUnlockEnabled(boolean enabled); } diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 21093b9f6f88..bd5ad960a886 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -16,6 +16,8 @@ package com.android.server; +import static com.android.internal.util.Preconditions.checkArgument; + import android.Manifest; import android.app.ActivityManager; import android.content.Context; @@ -28,12 +30,10 @@ import android.os.UserHandle; import android.os.UserManager; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; -import android.util.Log; import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; import libcore.io.IoUtils; @@ -65,6 +65,40 @@ import java.util.concurrent.TimeUnit; * * Clients can read any number of bytes from the currently written block up to its total size by * invoking {@link IPersistentDataBlockService#read} + * + * The persistent data block is currently laid out as follows: + * | ---------BEGINNING OF PARTITION-------------| + * | Partition digest (32 bytes) | + * | --------------------------------------------| + * | PARTITION_TYPE_MARKER (4 bytes) | + * | --------------------------------------------| + * | FRP data block length (4 bytes) | + * | --------------------------------------------| + * | FRP data (variable length) | + * | --------------------------------------------| + * | ... | + * | --------------------------------------------| + * | Test mode data block (10000 bytes) | + * | --------------------------------------------| + * | | Test mode data length (4 bytes) | + * | --------------------------------------------| + * | | Test mode data (variable length) | + * | | ... | + * | --------------------------------------------| + * | FRP credential handle block (1000 bytes) | + * | --------------------------------------------| + * | | FRP credential handle length (4 bytes)| + * | --------------------------------------------| + * | | FRP credential handle (variable len) | + * | | ... | + * | --------------------------------------------| + * | OEM Unlock bit (1 byte) | + * | ---------END OF PARTITION-------------------| + * + * TODO: now that the persistent partition contains several blocks, next time someone wants a new + * block, we should look at adding more generic block definitions and get rid of the various raw + * XXX_RESERVED_SIZE and XXX_DATA_SIZE constants. That will ensure the code is easier to maintain + * and less likely to introduce out-of-bounds read/write. */ public class PersistentDataBlockService extends SystemService { private static final String TAG = PersistentDataBlockService.class.getSimpleName(); @@ -73,10 +107,16 @@ public class PersistentDataBlockService extends SystemService { private static final int HEADER_SIZE = 8; // Magic number to mark block device as adhering to the format consumed by this service private static final int PARTITION_TYPE_MARKER = 0x19901873; - /** Size of the block reserved for FPR credential, including 4 bytes for the size header. */ + /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */ private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; /** Maximum size of the FRP credential handle that can be stored. */ private static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4; + /** + * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header. + */ + private static final int TEST_MODE_RESERVED_SIZE = 10000; + /** Maximum size of the Test Harness Mode data that can be stored. */ + private static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4; // Limit to 100k as blocks larger than this might cause strain on Binder. private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100; @@ -221,6 +261,14 @@ public class PersistentDataBlockService extends SystemService { return mBlockDeviceSize; } + private long getFrpCredentialDataOffset() { + return getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE; + } + + private long getTestHarnessModeDataOffset() { + return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE; + } + private boolean enforceChecksumValidity() { byte[] storedDigest = new byte[DIGEST_SIZE_BYTES]; @@ -383,7 +431,7 @@ public class PersistentDataBlockService extends SystemService { private long doGetMaximumDataBlockSize() { long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES - - FRP_CREDENTIAL_RESERVED_SIZE - 1; + - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1; return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE; } @@ -391,6 +439,13 @@ public class PersistentDataBlockService extends SystemService { private native int nativeWipe(String path); private final IBinder mService = new IPersistentDataBlockService.Stub() { + + /** + * Write the data to the persistent data block. + * + * @return a positive integer of the number of bytes that were written if successful, + * otherwise a negative integer indicating there was a problem + */ @Override public int write(byte[] data) throws RemoteException { enforceUid(Binder.getCallingUid()); @@ -597,12 +652,51 @@ public class PersistentDataBlockService extends SystemService { @Override public void setFrpCredentialHandle(byte[] handle) { - Preconditions.checkArgument(handle == null || handle.length > 0, - "handle must be null or non-empty"); - Preconditions.checkArgument(handle == null - || handle.length <= MAX_FRP_CREDENTIAL_HANDLE_SIZE, - "handle must not be longer than " + MAX_FRP_CREDENTIAL_HANDLE_SIZE); + writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE); + } + @Override + public byte[] getFrpCredentialHandle() { + return readInternal(getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE); + } + + @Override + public void setTestHarnessModeData(byte[] data) { + writeInternal(data, getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE); + } + + @Override + public byte[] getTestHarnessModeData() { + byte[] data = readInternal(getTestHarnessModeDataOffset(), MAX_TEST_MODE_DATA_SIZE); + if (data == null) { + return new byte[0]; + } + return data; + } + + @Override + public void clearTestHarnessModeData() { + int size = Math.min(MAX_TEST_MODE_DATA_SIZE, getTestHarnessModeData().length) + 4; + writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size)); + } + + private void writeInternal(byte[] data, long offset, int dataLength) { + checkArgument(data == null || data.length > 0, "data must be null or non-empty"); + checkArgument( + data == null || data.length <= dataLength, + "data must not be longer than " + dataLength); + + ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength + 4); + dataBuffer.putInt(data == null ? 0 : data.length); + if (data != null) { + dataBuffer.put(data); + } + dataBuffer.flip(); + + writeDataBuffer(offset, dataBuffer); + } + + private void writeDataBuffer(long offset, ByteBuffer dataBuffer) { FileOutputStream outputStream; try { outputStream = new FileOutputStream(new File(mDataBlockFile)); @@ -610,25 +704,15 @@ public class PersistentDataBlockService extends SystemService { Slog.e(TAG, "partition not available", e); return; } - - ByteBuffer data = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); - data.putInt(handle == null ? 0 : handle.length); - if (handle != null) { - data.put(handle); - } - data.flip(); - synchronized (mLock) { if (!mIsWritable) { IoUtils.closeQuietly(outputStream); return; } - try { FileChannel channel = outputStream.getChannel(); - - channel.position(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE); - channel.write(data); + channel.position(offset); + channel.write(dataBuffer); outputStream.flush(); } catch (IOException e) { Slog.e(TAG, "unable to access persistent partition", e); @@ -641,8 +725,7 @@ public class PersistentDataBlockService extends SystemService { } } - @Override - public byte[] getFrpCredentialHandle() { + private byte[] readInternal(long offset, int maxLength) { if (!enforceChecksumValidity()) { throw new IllegalStateException("invalid checksum"); } @@ -652,14 +735,14 @@ public class PersistentDataBlockService extends SystemService { inputStream = new DataInputStream( new FileInputStream(new File(mDataBlockFile))); } catch (FileNotFoundException e) { - throw new IllegalStateException("frp partition not available"); + throw new IllegalStateException("persistent partition not available"); } try { synchronized (mLock) { - inputStream.skip(getBlockDeviceSize() - 1 - FRP_CREDENTIAL_RESERVED_SIZE); + inputStream.skip(offset); int length = inputStream.readInt(); - if (length <= 0 || length > MAX_FRP_CREDENTIAL_HANDLE_SIZE) { + if (length <= 0 || length > maxLength) { return null; } byte[] bytes = new byte[length]; @@ -667,7 +750,7 @@ public class PersistentDataBlockService extends SystemService { return bytes; } } catch (IOException e) { - throw new IllegalStateException("frp handle not readable", e); + throw new IllegalStateException("persistent partition not readable", e); } finally { IoUtils.closeQuietly(inputStream); } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 7731c04a94b8..9d810cd8f3ca 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1706,8 +1706,8 @@ class StorageManagerService extends IStorageManager.Stub int uid, String packageName, int[] ops) { long maxTime = 0; final List<AppOpsManager.PackageOps> pkgs = manager.getOpsForPackage(uid, packageName, ops); - for (AppOpsManager.PackageOps pkg : CollectionUtils.defeatNullable(pkgs)) { - for (AppOpsManager.OpEntry op : CollectionUtils.defeatNullable(pkg.getOps())) { + for (AppOpsManager.PackageOps pkg : CollectionUtils.emptyIfNull(pkgs)) { + for (AppOpsManager.OpEntry op : CollectionUtils.emptyIfNull(pkg.getOps())) { maxTime = Math.max(maxTime, op.getLastAccessTime()); } } diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java index fe27c495ef55..fd402cc08c0c 100644 --- a/services/core/java/com/android/server/am/AppCompactor.java +++ b/services/core/java/com/android/server/am/AppCompactor.java @@ -64,7 +64,7 @@ public final class AppCompactor { final private String COMPACT_ACTION_FILE = "file"; final private String COMPACT_ACTION_ANON = "anon"; - final private String COMPACT_ACTION_FULL = "full"; + final private String COMPACT_ACTION_FULL = "all"; final private String compactActionSome; final private String compactActionFull; diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 65aacdcb73e0..353749f211c1 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -45,6 +45,7 @@ import android.os.Trace; import android.os.UserHandle; import android.util.EventLog; import android.util.Slog; +import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -157,6 +158,9 @@ public final class BroadcastQueue { static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG; static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1; + // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing + boolean mLogLatencyMetrics = true; + final BroadcastHandler mHandler; private final class BroadcastHandler extends Handler { @@ -941,6 +945,12 @@ public final class BroadcastQueue { // adjustments. mService.updateOomAdjLocked(); } + + // when we have no more ordered broadcast on this queue, stop logging + if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) { + mLogLatencyMetrics = false; + } + return; } r = mOrderedBroadcasts.get(0); @@ -1036,6 +1046,13 @@ public final class BroadcastQueue { if (recIdx == 0) { r.dispatchTime = r.receiverTime; r.dispatchClockTime = System.currentTimeMillis(); + + if (mLogLatencyMetrics) { + StatsLog.write( + StatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED, + r.dispatchClockTime - r.enqueueClockTime); + } + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING), diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index bcce05289206..c981e6885a9e 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -240,6 +240,8 @@ class UserController implements Handler.Callback { private final LockPatternUtils mLockPatternUtils; + volatile boolean mBootCompleted; + UserController(ActivityManagerService service) { this(new Injector(service)); } @@ -567,6 +569,7 @@ class UserController implements Handler.Callback { Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws RemoteException { Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId); + mBootCompleted = true; } }, 0, null, null, new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index f60d6b06f451..27edbbf4f2d5 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -47,6 +47,7 @@ import android.service.attention.IAttentionService; import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; +import android.util.StatsLog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -73,7 +74,6 @@ public class AttentionManagerService extends SystemService { private final Context mContext; private final PowerManager mPowerManager; - private final ActivityManager mActivityManager; private final Object mLock; @GuardedBy("mLock") private final SparseArray<UserState> mUserStates = new SparseArray<>(); @@ -85,7 +85,6 @@ public class AttentionManagerService extends SystemService { super(context); mContext = Preconditions.checkNotNull(context); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); mLock = new Object(); mAttentionHandler = new AttentionHandler(); } @@ -96,7 +95,7 @@ public class AttentionManagerService extends SystemService { } @Override - public void onStopUser(int userId) { + public void onSwitchUser(int userId) { cancelAndUnbindLocked(peekUserStateLocked(userId), AttentionService.ATTENTION_FAILURE_UNKNOWN); } @@ -178,11 +177,16 @@ public class AttentionManagerService extends SystemService { userState.mAttentionCheckCache = new AttentionCheckCache( SystemClock.uptimeMillis(), result, timestamp); + + StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED, + result); } @Override public void onFailure(int requestCode, int error) { callback.onFailure(requestCode, error); + StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED, + error); } @Override @@ -201,11 +205,20 @@ public class AttentionManagerService extends SystemService { /** Cancels the specified attention check. */ public void cancelAttentionCheck(int requestCode) { - final UserState userState = getOrCreateCurrentUserStateLocked(); - try { - userState.mService.cancelAttentionCheck(requestCode); - } catch (RemoteException e) { - Slog.e(LOG_TAG, "Cannot call into the AttentionService"); + synchronized (mLock) { + final UserState userState = getOrCreateCurrentUserStateLocked(); + if (userState.mService == null) { + if (userState.mPendingAttentionCheck != null + && userState.mPendingAttentionCheck.mRequestCode == requestCode) { + userState.mPendingAttentionCheck = null; + } + return; + } + try { + userState.mService.cancelAttentionCheck(requestCode); + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Cannot call into the AttentionService"); + } } } @@ -224,7 +237,7 @@ public class AttentionManagerService extends SystemService { @GuardedBy("mLock") private UserState getOrCreateCurrentUserStateLocked() { - return getOrCreateUserStateLocked(mActivityManager.getCurrentUser()); + return getOrCreateUserStateLocked(ActivityManager.getCurrentUser()); } @GuardedBy("mLock") @@ -239,7 +252,7 @@ public class AttentionManagerService extends SystemService { @GuardedBy("mLock") UserState peekCurrentUserStateLocked() { - return peekUserStateLocked(mActivityManager.getCurrentUser()); + return peekUserStateLocked(ActivityManager.getCurrentUser()); } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 11299b690bb1..de389bc3aa01 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1223,11 +1223,13 @@ public class AudioService extends IAudioService.Stub private void checkMuteAffectedStreams() { // any stream with a min level > 0 is not muteable by definition - // STREAM_VOICE_CALL can be muted by applications that has the the MODIFY_PHONE_STATE permission. + // STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications + // that has the the MODIFY_PHONE_STATE permission. for (int i = 0; i < mStreamStates.length; i++) { final VolumeStreamState vss = mStreamStates[i]; if (vss.mIndexMin > 0 && - vss.mStreamType != AudioSystem.STREAM_VOICE_CALL) { + (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL && + vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) { mMuteAffectedStreams &= ~(1 << vss.mStreamType); } } @@ -1711,10 +1713,11 @@ public class AudioService extends IAudioService.Stub return; } - // If adjust is mute and the stream is STREAM_VOICE_CALL, make sure + // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure // that the calling app have the MODIFY_PHONE_STATE permission. if (isMuteAdjust && - streamType == AudioSystem.STREAM_VOICE_CALL && + (streamType == AudioSystem.STREAM_VOICE_CALL || + streamType == AudioSystem.STREAM_BLUETOOTH_SCO) && mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { @@ -2038,12 +2041,14 @@ public class AudioService extends IAudioService.Stub + " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage); return; } - if ((streamType == AudioManager.STREAM_VOICE_CALL) && + if ((streamType == AudioManager.STREAM_VOICE_CALL || + streamType == AudioManager.STREAM_BLUETOOTH_SCO) && (index == 0) && (mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED)) { - Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without" + Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL or" + + " STREAM_BLUETOOTH_SCO and index 0 without" + " MODIFY_PHONE_STATE callingPackage=" + callingPackage); return; } @@ -4746,12 +4751,18 @@ public class AudioService extends IAudioService.Stub private int getA2dpCodec(BluetoothDevice device) { synchronized (mA2dpAvrcpLock) { - if (mA2dp != null) { - BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); - BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); - return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); + if (mA2dp == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); + if (btCodecStatus == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; + } + BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); + if (btCodecConfig == null) { + return AudioSystem.AUDIO_FORMAT_DEFAULT; } - return AudioSystem.AUDIO_FORMAT_DEFAULT; + return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType()); } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 602aedbc2d00..c72c9ddf3f7a 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -60,7 +60,6 @@ import android.net.NetworkMisc; import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.UidRange; -import android.net.Uri; import android.net.VpnService; import android.os.Binder; import android.os.Build.VERSION_CODES; @@ -71,7 +70,6 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.Parcel; import android.os.ParcelFileDescriptor; -import android.os.PatternMatcher; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -100,6 +98,8 @@ import com.android.server.DeviceIdleController; import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; +import libcore.io.IoUtils; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -121,8 +121,6 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; -import libcore.io.IoUtils; - /** * @hide */ @@ -346,11 +344,18 @@ public class Vpn { * * @return {@code true} if VPN lockdown is enabled. */ - public boolean getLockdown() { + public synchronized boolean getLockdown() { return mLockdown; } /** + * Returns whether VPN is configured as always-on. + */ + public synchronized boolean getAlwaysOn() { + return mAlwaysOn; + } + + /** * Checks if a VPN app supports always-on mode. * * In order to support the always-on feature, an app has to diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java index 9223739406ee..3a58160cae8d 100644 --- a/services/core/java/com/android/server/display/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/ColorDisplayService.java @@ -28,6 +28,7 @@ import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.UserIdInt; import android.app.AlarmManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -51,6 +52,7 @@ import android.provider.Settings.Secure; import android.provider.Settings.System; import android.util.MathUtils; import android.util.Slog; +import android.view.SurfaceControl; import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; @@ -866,6 +868,12 @@ public final class ColorDisplayService extends SystemService { if (mDisplayWhiteBalanceListener != null && oldActivated != activated) { mDisplayWhiteBalanceListener.onDisplayWhiteBalanceStatusChanged(activated); } + + // If disabled, clear the tint. If enabled, do nothing more here and let the next + // temperature update set the correct tint. + if (!activated) { + applyTint(mDisplayWhiteBalanceTintController, false); + } } private boolean isDisplayWhiteBalanceSettingEnabled() { @@ -878,6 +886,21 @@ public final class ColorDisplayService extends SystemService { return dtm.isDeviceColorManaged(); } + private int getTransformCapabilitiesInternal() { + int availabilityFlags = ColorDisplayManager.CAPABILITY_NONE; + if (SurfaceControl.getProtectedContentSupport()) { + availabilityFlags |= ColorDisplayManager.CAPABILITY_PROTECTED_CONTENT; + } + final Resources res = getContext().getResources(); + if (res.getBoolean(R.bool.config_setColorTransformAccelerated)) { + availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_GLOBAL; + } + if (res.getBoolean(R.bool.config_setColorTransformAcceleratedPerLayer)) { + availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_PER_APP; + } + return availabilityFlags; + } + /** * Returns the last time the night display transform activation state was changed, or {@link * LocalDateTime#MIN} if night display has never been activated. @@ -1226,10 +1249,10 @@ public final class ColorDisplayService extends SystemService { * Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and * invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed. */ - public boolean attachColorTransformController(String packageName, int uid, + public boolean attachColorTransformController(String packageName, @UserIdInt int userId, WeakReference<ColorTransformController> controller) { return mAppSaturationController - .addColorTransformController(packageName, uid, controller); + .addColorTransformController(packageName, userId, controller); } } @@ -1318,6 +1341,18 @@ public final class ColorDisplayService extends SystemService { } } + public int getTransformCapabilities() { + getContext().enforceCallingPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to query transform capabilities"); + final long token = Binder.clearCallingIdentity(); + try { + return getTransformCapabilitiesInternal(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 63214ed651b9..cac1a95454fa 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -268,7 +268,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { mTvSystemAudioModeSupport = false; // Record the last state of System Audio Control before going to standby synchronized (mLock) { - mService.writeStringSetting( + mService.writeStringSystemProperty( Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, mSystemAudioActivated ? "true" : "false"); } @@ -330,7 +330,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @ServiceThreadOnly protected void setPreferredAddress(int addr) { assertRunOnServiceThread(); - mService.writeStringSetting( + mService.writeStringSystemProperty( Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr)); } @@ -469,7 +469,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { protected boolean handleRequestArcInitiate(HdmiCecMessage message) { assertRunOnServiceThread(); removeAction(ArcInitiationActionFromAvr.class); - if (!mService.readBooleanSetting(Constants.PROPERTY_ARC_SUPPORT, true)) { + if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); } else if (!isDirectConnectToTv()) { HdmiLogger.debug("AVR device is not directly connected with TV"); @@ -829,7 +829,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { boolean currentMuteStatus = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC); if (currentMuteStatus == newSystemAudioMode) { - if (mService.readBooleanSetting( + if (mService.readBooleanSystemProperty( Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true) || newSystemAudioMode) { mService.getAudioManager() diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 7a0c27906122..ef7d24159009 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -100,7 +100,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { @ServiceThreadOnly protected void setPreferredAddress(int addr) { assertRunOnServiceThread(); - mService.writeStringSetting(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK, + mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK, String.valueOf(addr)); } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 46219d5cbe8a..f3a1e46bc1e4 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -657,9 +657,13 @@ public class HdmiControlService extends SystemService { Global.putInt(cr, key, toInt(value)); } - void writeStringSetting(String key, String value) { - ContentResolver cr = getContext().getContentResolver(); - Global.putString(cr, key, value); + void writeStringSystemProperty(String key, String value) { + SystemProperties.set(key, value); + } + + @VisibleForTesting + boolean readBooleanSystemProperty(String key, boolean defVal) { + return SystemProperties.getBoolean(key, defVal); } private void initializeCec(int initiatedBy) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index d4b8eb2db007..944a95dda99b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -18,7 +18,6 @@ package com.android.server.inputmethod; import android.annotation.NonNull; import android.annotation.UserIdInt; -import android.content.ComponentName; import android.view.inputmethod.InputMethodInfo; import com.android.server.LocalServices; @@ -42,11 +41,6 @@ public abstract class InputMethodManagerInternal { public abstract void hideCurrentInputMethod(); /** - * Switches to VR InputMethod defined in the packageName of {@param componentName}. - */ - public abstract void startVrInputMethodNoCheck(ComponentName componentName); - - /** * Returns the list of installed input methods for the specified user. * * @param userId The user ID to be queried. @@ -76,10 +70,6 @@ public abstract class InputMethodManagerInternal { } @Override - public void startVrInputMethodNoCheck(ComponentName componentName) { - } - - @Override public List<InputMethodInfo> getInputMethodListAsUser(int userId) { return Collections.emptyList(); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 2d197bb4da96..eca371a78750 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -91,8 +91,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; import android.provider.Settings; -import android.service.vr.IVrManager; -import android.service.vr.IVrStateCallbacks; import android.text.TextUtils; import android.text.style.SuggestionSpan; import android.util.ArrayMap; @@ -203,7 +201,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_CREATE_SESSION = 1050; static final int MSG_START_INPUT = 2000; - static final int MSG_START_VR_INPUT = 2010; static final int MSG_UNBIND_CLIENT = 3000; static final int MSG_BIND_CLIENT = 3010; @@ -381,28 +378,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - /** - * VR state callback. - * Listens for when VR mode finishes. - */ - private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { - @Override - public void onVrStateChanged(boolean enabled) { - if (!enabled) { - restoreNonVrImeFromSettingsNoCheck(); - } - } - }; - - private void restoreNonVrImeFromSettingsNoCheck() { - // switch back to non-VR InputMethod from settings. - synchronized (mMethodMap) { - if (!mIsVrImeStarted) return; - mIsVrImeStarted = false; - updateFromSettingsLocked(false); - } - } - private static final class ClientDeathRecipient implements IBinder.DeathRecipient { private final InputMethodManagerService mImms; private final IInputMethodClient mClient; @@ -597,9 +572,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final ImeDisplayValidator mImeDisplayValidator; - /** True if VR IME started by {@link #startVrInputMethodNoCheck}. */ - boolean mIsVrImeStarted; - /** * If non-null, this is the input method service we are currently connected * to. @@ -977,31 +949,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** - * Start a VR InputMethod that matches IME with package name of {@param component}. - * Note: This method is called from {@link android.app.VrManager}. - */ - private void startVrInputMethodNoCheck(@Nullable ComponentName component) { - if (component == null) { - // clear the current VR-only IME (if any) and restore normal IME. - restoreNonVrImeFromSettingsNoCheck(); - return; - } - - synchronized (mMethodMap) { - String packageName = component.getPackageName(); - for (InputMethodInfo info : mMethodList) { - if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) { - // set this is as current inputMethod without updating settings. - setInputMethodEnabledLocked(info.getId(), true); - setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID); - mIsVrImeStarted = true; - break; - } - } - } - } - - /** * Handles {@link Intent#ACTION_LOCALE_CHANGED}. * * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all @@ -1452,15 +1399,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( mSettings, context); - // Register VR-state listener. - IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE); - if (vrManager != null) { - try { - vrManager.registerListener(mVrStateCallbacks); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to register VR mode state listener."); - } - } } private void resetDefaultImeLocked(Context context) { @@ -1607,7 +1545,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // 1) it comes from the system process // 2) the calling process' user id is identical to the current user id IMMS thinks. @GuardedBy("mMethodMap") - private boolean calledFromValidUserLocked(boolean allowCrossProfileAccess) { + private boolean calledFromValidUserLocked() { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserId(uid); if (DEBUG) { @@ -1623,7 +1561,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (userId == mSettings.getCurrentUserId()) { return true; } - if (allowCrossProfileAccess && mSettings.isCurrentProfile(userId)) { + if (!PER_PROFILE_IME_ENABLED && mSettings.isCurrentProfile(userId)) { return true; } @@ -1688,25 +1626,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final long ident = Binder.clearCallingIdentity(); try { - return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - @Override - public List<InputMethodInfo> getVrInputMethodList() { - final int callingUserId = UserHandle.getCallingUserId(); - synchronized (mMethodMap) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, - mSettings.getCurrentUserId(), null); - if (resolvedUserIds.length != 1) { - return Collections.emptyList(); - } - final long ident = Binder.clearCallingIdentity(); - try { - return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]); + return getInputMethodListLocked(resolvedUserIds[0]); } finally { Binder.restoreCallingIdentity(ident); } @@ -1732,8 +1652,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("mMethodMap") - private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly, - @UserIdInt int userId) { + private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) { final ArrayList<InputMethodInfo> methodList; if (userId == mSettings.getCurrentUserId()) { // Create a copy. @@ -1747,7 +1666,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, methodList); } - methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly); return methodList; } @@ -2021,8 +1939,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. - final int displayIdToShowIme = computeImeDisplayIdForTarget( - cs.selfReportedDisplayId, mIsVrImeStarted, mImeDisplayValidator); + final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId, + mImeDisplayValidator); if (mCurClient != cs) { // Was the keyguard locked when switching over to the new client? @@ -2131,17 +2049,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * Find the display where the IME should be shown. * * @param displayId the ID of the display where the IME client target is. - * @param isVrImeStarted {@code true} if VR IME started, {@code false} otherwise. * @param checker instance of {@link ImeDisplayValidator} which is used for * checking display config to adjust the final target display. * @return The ID of the display where the IME should be shown. */ - static int computeImeDisplayIdForTarget(int displayId, boolean isVrImeStarted, - @NonNull ImeDisplayValidator checker) { - // For VR IME, we always show in default display. - if (isVrImeStarted) { - return DEFAULT_DISPLAY; - } + static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { // We always assume that the default display id suitable to show the IME window. return DEFAULT_DISPLAY; @@ -2651,7 +2563,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ResultReceiver resultReceiver) { int uid = Binder.getCallingUid(); synchronized (mMethodMap) { - if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { + if (!calledFromValidUserLocked()) { return false; } final long ident = Binder.clearCallingIdentity(); @@ -2736,7 +2648,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ResultReceiver resultReceiver) { int uid = Binder.getCallingUid(); synchronized (mMethodMap) { - if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { + if (!calledFromValidUserLocked()) { return false; } final long ident = Binder.clearCallingIdentity(); @@ -3087,7 +2999,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void showInputMethodPickerFromClient( IInputMethodClient client, int auxiliarySubtypeMode) { synchronized (mMethodMap) { - if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { + if (!calledFromValidUserLocked()) { return; } if(!canShowInputMethodPickerLocked(client)) { @@ -3159,7 +3071,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub IInputMethodClient client, String inputMethodId) { synchronized (mMethodMap) { // TODO(yukawa): Should we verify the display ID? - if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { + if (!calledFromValidUserLocked()) { return; } executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( @@ -3274,7 +3186,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public InputMethodSubtype getLastInputMethodSubtype() { synchronized (mMethodMap) { - if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { + if (!calledFromValidUserLocked()) { return null; } final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); @@ -3312,7 +3224,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } synchronized (mMethodMap) { - if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { + if (!calledFromValidUserLocked()) { return; } if (!mSystemReady) { @@ -3627,9 +3539,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_SET_INTERACTIVE: handleSetInteractive(msg.arg1 != 0); return true; - case MSG_START_VR_INPUT: - startVrInputMethodNoCheck((ComponentName) msg.obj); - return true; case MSG_REPORT_FULLSCREEN_MODE: { final boolean fullscreen = msg.arg1 != 0; final ClientState clientState = (ClientState)msg.obj; @@ -3716,6 +3625,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { final InputMethodInfo imi = new InputMethodInfo(context, ri, additionalSubtypeMap.get(imeId)); + if (imi.isVrOnly()) { + continue; // Skip VR-only IME, which isn't supported for now. + } methodList.add(imi); methodMap.put(imi.getId(), imi); if (DEBUG) { @@ -4099,17 +4011,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly) { - // Updates to InputMethod are transient in VR mode. Its not included in history. - final boolean isVrInput = imi != null && imi.isVrOnly(); - if (!isVrInput) { - // Update the history of InputMethod and Subtype - mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); - } - - if (isVrInput) { - // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped. - return; - } + mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); // Set Subtype here if (imi == null || subtypeId < 0) { @@ -4158,7 +4060,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public InputMethodSubtype getCurrentInputMethodSubtype() { synchronized (mMethodMap) { // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { + if (!calledFromValidUserLocked()) { return null; } return getCurrentInputMethodSubtypeLocked(); @@ -4208,7 +4110,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { synchronized (mMethodMap) { // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { + if (!calledFromValidUserLocked()) { return false; } if (subtype != null && mCurMethodId != null) { @@ -4225,7 +4127,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { synchronized (mMethodMap) { - return getInputMethodListLocked(false, userId); + return getInputMethodListLocked(userId); } } @@ -4257,11 +4159,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) { - mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget(); - } - - @Override public List<InputMethodInfo> getInputMethodListAsUser(int userId) { return mService.getInputMethodListAsUser(userId); } @@ -4648,7 +4545,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { final List<InputMethodInfo> methods = all - ? getInputMethodListLocked(false, userId) + ? getInputMethodListLocked(userId) : getEnabledInputMethodListLocked(userId); if (userIds.length > 1) { pr.print("User #"); diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 6f0c5e83b4fe..a31b3b4b3b5a 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -159,11 +159,6 @@ public final class MultiClientInputMethodManagerService { } @Override - public void startVrInputMethodNoCheck(ComponentName componentName) { - reportNotSupported(); - } - - @Override public List<InputMethodInfo> getInputMethodListAsUser( @UserIdInt int userId) { return userIdToInputMethodInfoMapper.getAsList(userId); @@ -1244,13 +1239,6 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public List<InputMethodInfo> getVrInputMethodList() { - reportNotSupported(); - return Collections.emptyList(); - } - - @BinderThread - @Override public List<InputMethodInfo> getEnabledInputMethodList() { return mInputMethodInfoMap.getAsList(UserHandle.getUserId(Binder.getCallingUid())); } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index cc0ac9a7e0a9..bd12075fdad3 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -443,6 +443,16 @@ public class JobSchedulerService extends com.android.server.SystemService "qc_window_size_rare_ms"; private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = "qc_max_execution_time_ms"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + "qc_max_job_count_active"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + "qc_max_job_count_working"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + "qc_max_job_count_frequent"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + "qc_max_job_count_rare"; + private static final String KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = + "qc_max_count_per_allowed_time"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; @@ -479,6 +489,15 @@ public class JobSchedulerService extends com.android.server.SystemService 24 * 60 * 60 * 1000L; // 24 hours private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 4 * 60 * 60 * 1000L; // 4 hours + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + 200; // 1200/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + 1200; // 600/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + 1800; // 225/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + 2400; // 100/hr + private static final int DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20; /** * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things @@ -682,6 +701,41 @@ public class JobSchedulerService extends com.android.server.SystemService public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS; + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT; + + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE; + + /** + * The maximum number of jobs that can run within the past + * {@link #QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}. + */ + public int QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + private final KeyValueListParser mParser = new KeyValueListParser(','); void updateConstantsLocked(String value) { @@ -767,6 +821,21 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis( KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS, DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS); + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE); + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING); + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT); + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE); + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt( + KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME, + DEFAULT_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); } void dump(IndentingPrintWriter pw) { @@ -823,6 +892,16 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println(); pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS, QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING, + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT, + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE).println(); + pw.printPair(KEY_QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME, + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME).println(); pw.decreaseIndent(); } @@ -872,6 +951,16 @@ public class JobSchedulerService extends com.android.server.SystemService QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS); proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS, QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING, + QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT, + QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, + QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE); + proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME, + QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); proto.end(qcToken); proto.end(token); diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java index c16d1b4ecec5..5a0b991bc7de 100644 --- a/services/core/java/com/android/server/job/controllers/QuotaController.java +++ b/services/core/java/com/android/server/job/controllers/QuotaController.java @@ -230,10 +230,10 @@ public final class QuotaController extends StateController { @VisibleForTesting static class ExecutionStats { /** - * The time at which this record should be considered invalid, in the elapsed realtime - * timebase. + * The time after which this record should be considered invalid (out of date), in the + * elapsed realtime timebase. */ - public long invalidTimeElapsed; + public long expirationTimeElapsed; public long windowSizeMs; @@ -241,29 +241,45 @@ public final class QuotaController extends StateController { public long executionTimeInWindowMs; public int bgJobCountInWindow; - /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */ + /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */ public long executionTimeInMaxPeriodMs; public int bgJobCountInMaxPeriod; /** - * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals - * the quota. This is only valid if - * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or - * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}. + * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs} + * equals the quota. This is only valid if + * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or + * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}. */ public long quotaCutoffTimeElapsed; + /** + * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the + * elapsed realtime timebase. + */ + public long jobCountExpirationTimeElapsed; + + /** + * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}. + * It may contain a few stale entries since cleanup won't happen exactly every + * {@link #mAllowedTimePerPeriodMs}. + */ + public int jobCountInAllowedTime; + @Override public String toString() { return new StringBuilder() - .append("invalidTime=").append(invalidTimeElapsed).append(", ") + .append("expirationTime=").append(expirationTimeElapsed).append(", ") .append("windowSize=").append(windowSizeMs).append(", ") .append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ") .append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ") .append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs) .append(", ") .append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ") - .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed) + .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed).append(", ") + .append("jobCountExpirationTime").append(jobCountExpirationTimeElapsed) + .append(", ") + .append("jobCountInAllowedTime").append(jobCountInAllowedTime) .toString(); } @@ -271,13 +287,15 @@ public final class QuotaController extends StateController { public boolean equals(Object obj) { if (obj instanceof ExecutionStats) { ExecutionStats other = (ExecutionStats) obj; - return this.invalidTimeElapsed == other.invalidTimeElapsed + return this.expirationTimeElapsed == other.expirationTimeElapsed && this.windowSizeMs == other.windowSizeMs && this.executionTimeInWindowMs == other.executionTimeInWindowMs && this.bgJobCountInWindow == other.bgJobCountInWindow && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod - && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed; + && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed + && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed + && this.jobCountInAllowedTime == other.jobCountInAllowedTime; } else { return false; } @@ -286,13 +304,15 @@ public final class QuotaController extends StateController { @Override public int hashCode() { int result = 0; - result = 31 * result + hashLong(invalidTimeElapsed); + result = 31 * result + hashLong(expirationTimeElapsed); result = 31 * result + hashLong(windowSizeMs); result = 31 * result + hashLong(executionTimeInWindowMs); result = 31 * result + bgJobCountInWindow; result = 31 * result + hashLong(executionTimeInMaxPeriodMs); result = 31 * result + bgJobCountInMaxPeriod; result = 31 * result + hashLong(quotaCutoffTimeElapsed); + result = 31 * result + hashLong(jobCountExpirationTimeElapsed); + result = 31 * result + jobCountInAllowedTime; return result; } } @@ -320,7 +340,7 @@ public final class QuotaController extends StateController { /** * List of jobs that started while the UID was in the TOP state. There will be no more than - * 16 ({@link JobSchedulerService.MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is + * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is * fine. */ private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>(); @@ -343,7 +363,7 @@ public final class QuotaController extends StateController { private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; /** - * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS} + * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS} * window. */ private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS; @@ -355,17 +375,20 @@ public final class QuotaController extends StateController { private long mQuotaBufferMs = 30 * 1000L; // 30 seconds /** - * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when - * an app will have enough quota to transition from out-of-quota to in-quota. + * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine + * when an app will have enough quota to transition from out-of-quota to in-quota. */ private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; /** - * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an + * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an * app will have enough quota to transition from out-of-quota to in-quota. */ private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; + /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */ + private int mMaxJobCountPerAllowedTime = 20; + private long mNextCleanupTimeElapsed = 0; private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener = new AlarmManager.OnAlarmListener() { @@ -412,6 +435,23 @@ public final class QuotaController extends StateController { /** The maximum period any bucket can have. */ private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS; + /** + * The maximum number of jobs based on its standby bucket. For each max value count in the + * array, the app will not be allowed to run more than that many number of jobs within the + * latest time interval of its rolling window size. + * + * @see #mBucketPeriodsMs + */ + private final int[] mMaxBucketJobCounts = new int[] { + 200, // ACTIVE -- 1200/hr + 1200, // WORKING -- 600/hr + 1800, // FREQUENT -- 225/hr + 2400 // RARE -- 100/hr + }; + + /** The minimum number of jobs that any bucket will be allowed to run. */ + private static final int MIN_BUCKET_JOB_COUNT = 100; + /** An app has reached its quota. The message should contain a {@link Package} object. */ private static final int MSG_REACHED_QUOTA = 0; /** Drop any old timing sessions. */ @@ -463,17 +503,21 @@ public final class QuotaController extends StateController { @Override public void prepareForExecutionLocked(JobStatus jobStatus) { if (DEBUG) Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); + + final int uid = jobStatus.getSourceUid(); + if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) { + mTopStartedJobs.add(jobStatus); + // Top jobs won't count towards quota so there's no need to involve the Timer. + return; + } + final int userId = jobStatus.getSourceUserId(); final String packageName = jobStatus.getSourcePackageName(); - final int uid = jobStatus.getSourceUid(); Timer timer = mPkgTimers.get(userId, packageName); if (timer == null) { timer = new Timer(uid, userId, packageName); mPkgTimers.add(userId, packageName, timer); } - if (mActivityManagerInternal.getUidProcessState(uid) == ActivityManager.PROCESS_STATE_TOP) { - mTopStartedJobs.add(jobStatus); - } timer.startTrackingJob(jobStatus); } @@ -548,6 +592,36 @@ public final class QuotaController extends StateController { mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; changed = true; } + int newMaxCountPerAllowedPeriod = Math.max(10, + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME); + if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) { + mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod; + changed = true; + } + int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE)); + if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) { + mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount; + changed = true; + } + int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING)); + if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) { + mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount; + changed = true; + } + int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT)); + if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) { + mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount; + changed = true; + } + int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime, + Math.max(MIN_BUCKET_JOB_COUNT, mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE)); + if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) { + mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount; + changed = true; + } if (changed) { // Update job bookkeeping out of band. @@ -631,18 +705,39 @@ public final class QuotaController extends StateController { return isTopStartedJob(jobStatus) || isUidInForeground(jobStatus.getSourceUid()) || isWithinQuotaLocked( - jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); } - private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, + @VisibleForTesting + boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, final int standbyBucket) { if (standbyBucket == NEVER_INDEX) return false; // This check is needed in case the flag is toggled after a job has been registered. if (!mShouldThrottle) return true; // Quota constraint is not enforced while charging or when parole is on. - return mChargeTracker.isCharging() || mInParole - || getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0; + if (mChargeTracker.isCharging() || mInParole) { + return true; + } + + return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) > 0 + && isUnderJobCountQuotaLocked(userId, packageName, standbyBucket); + } + + private boolean isUnderJobCountQuotaLocked(final int userId, @NonNull final String packageName, + final int standbyBucket) { + ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket, false); + return isUnderJobCountQuotaLocked(stats, standbyBucket); + } + + private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats, + final int standbyBucket) { + final long now = sElapsedRealtimeClock.millis(); + final boolean isUnderAllowedTimeQuota = + (stats.jobCountExpirationTimeElapsed <= now + || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime); + return isUnderAllowedTimeQuota + && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]); } @VisibleForTesting @@ -679,6 +774,13 @@ public final class QuotaController extends StateController { @NonNull ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket) { + return getExecutionStatsLocked(userId, packageName, standbyBucket, true); + } + + @NonNull + private ExecutionStats getExecutionStatsLocked(final int userId, + @NonNull final String packageName, final int standbyBucket, + final boolean refreshStatsIfOld) { if (standbyBucket == NEVER_INDEX) { Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app."); return new ExecutionStats(); @@ -693,14 +795,16 @@ public final class QuotaController extends StateController { stats = new ExecutionStats(); appStats[standbyBucket] = stats; } - final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; - Timer timer = mPkgTimers.get(userId, packageName); - if ((timer != null && timer.isActive()) - || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis() - || stats.windowSizeMs != bucketWindowSizeMs) { - // The stats are no longer valid. - stats.windowSizeMs = bucketWindowSizeMs; - updateExecutionStatsLocked(userId, packageName, stats); + if (refreshStatsIfOld) { + final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; + Timer timer = mPkgTimers.get(userId, packageName); + if ((timer != null && timer.isActive()) + || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis() + || stats.windowSizeMs != bucketWindowSizeMs) { + // The stats are no longer valid. + stats.windowSizeMs = bucketWindowSizeMs; + updateExecutionStatsLocked(userId, packageName, stats); + } } return stats; @@ -717,14 +821,14 @@ public final class QuotaController extends StateController { Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); - stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS; + stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS; if (timer != null && timer.isActive()) { stats.executionTimeInWindowMs = stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); // If the timer is active, the value will be stale at the next method call, so // invalidate now. - stats.invalidTimeElapsed = nowElapsed; + stats.expirationTimeElapsed = nowElapsed; if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, nowElapsed - mAllowedTimeIntoQuotaMs); @@ -800,7 +904,7 @@ public final class QuotaController extends StateController { break; } } - stats.invalidTimeElapsed = nowElapsed + emptyTimeMs; + stats.expirationTimeElapsed = nowElapsed + emptyTimeMs; } private void invalidateAllExecutionStatsLocked(final int userId, @@ -811,13 +915,35 @@ public final class QuotaController extends StateController { for (int i = 0; i < appStats.length; ++i) { ExecutionStats stats = appStats[i]; if (stats != null) { - stats.invalidTimeElapsed = nowElapsed; + stats.expirationTimeElapsed = nowElapsed; } } } } @VisibleForTesting + void incrementJobCount(final int userId, @NonNull final String packageName, int count) { + final long now = sElapsedRealtimeClock.millis(); + ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); + if (appStats == null) { + appStats = new ExecutionStats[mBucketPeriodsMs.length]; + mExecutionStatsCache.add(userId, packageName, appStats); + } + for (int i = 0; i < appStats.length; ++i) { + ExecutionStats stats = appStats[i]; + if (stats == null) { + stats = new ExecutionStats(); + appStats[i] = stats; + } + if (stats.jobCountExpirationTimeElapsed <= now) { + stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs; + stats.jobCountInAllowedTime = 0; + } + stats.jobCountInAllowedTime += count; + } + } + + @VisibleForTesting void saveTimingSession(final int userId, @NonNull final String packageName, @NonNull final TimingSession session) { synchronized (mLock) { @@ -1023,9 +1149,12 @@ public final class QuotaController extends StateController { final String pkgString = string(userId, packageName); ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); + final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket); + QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName); if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs - && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) { + && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs + && isUnderJobCountQuota) { // Already in quota. Why was this method called? if (DEBUG) { Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString @@ -1042,18 +1171,22 @@ public final class QuotaController extends StateController { mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); return; } + if (alarmListener == null) { alarmListener = new QcAlarmListener(userId, packageName); mInQuotaAlarmListeners.add(userId, packageName, alarmListener); } // The time this app will have quota again. - long inQuotaTimeElapsed = - stats.quotaCutoffTimeElapsed + stats.windowSizeMs; + long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs; if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) { inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS); } + if (!isUnderJobCountQuota) { + inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, + stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs); + } // Only schedule the alarm if: // 1. There isn't one currently scheduled // 2. The new alarm is significantly earlier than the previous alarm (which could be the @@ -1228,6 +1361,7 @@ public final class QuotaController extends StateController { mRunningBgJobs.add(jobStatus); if (shouldTrackLocked()) { mBgJobCount++; + incrementJobCount(mPkg.userId, mPkg.packageName, 1); if (mRunningBgJobs.size() == 1) { // Started tracking the first job. mStartTimeElapsed = sElapsedRealtimeClock.millis(); @@ -1324,6 +1458,7 @@ public final class QuotaController extends StateController { // repeatedly plugged in and unplugged, or an app changes foreground state // very frequently, the job count for a package may be artificially high. mBgJobCount = mRunningBgJobs.size(); + incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount); // Starting the timer means that all cached execution stats are now // incorrect. invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); @@ -1604,6 +1739,12 @@ public final class QuotaController extends StateController { @VisibleForTesting @NonNull + int[] getBucketMaxJobCounts() { + return mMaxBucketJobCounts; + } + + @VisibleForTesting + @NonNull long[] getBucketWindowSizes() { return mBucketPeriodsMs; } @@ -1631,6 +1772,11 @@ public final class QuotaController extends StateController { } @VisibleForTesting + int getMaxJobCountPerAllowedTime() { + return mMaxJobCountPerAllowedTime; + } + + @VisibleForTesting @Nullable List<TimingSession> getTimingSessions(int userId, String packageName) { return mTimingSessions.get(userId, packageName); diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java new file mode 100644 index 000000000000..22fabb2cdd3e --- /dev/null +++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.Context; +import android.hardware.location.ActivityRecognitionHardware; +import android.hardware.location.IActivityRecognitionHardwareClient; +import android.hardware.location.IActivityRecognitionHardwareWatcher; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.os.BackgroundThread; +import com.android.server.ServiceWatcher; + +/** + * Proxy class to bind GmsCore to the ActivityRecognitionHardware. + * + * @hide + */ +public class ActivityRecognitionProxy { + + private static final String TAG = "ActivityRecognitionProxy"; + + /** + * Creates an instance of the proxy and binds it to the appropriate FusedProvider. + * + * @return An instance of the proxy if it could be bound, null otherwise. + */ + public static ActivityRecognitionProxy createAndBind( + Context context, + boolean activityRecognitionHardwareIsSupported, + ActivityRecognitionHardware activityRecognitionHardware, + int overlaySwitchResId, + int defaultServicePackageNameResId, + int initialPackageNameResId) { + ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy( + context, + activityRecognitionHardwareIsSupported, + activityRecognitionHardware, + overlaySwitchResId, + defaultServicePackageNameResId, + initialPackageNameResId); + + if (activityRecognitionProxy.mServiceWatcher.start()) { + return activityRecognitionProxy; + } else { + return null; + } + } + + private final ServiceWatcher mServiceWatcher; + private final boolean mIsSupported; + private final ActivityRecognitionHardware mInstance; + + private ActivityRecognitionProxy( + Context context, + boolean activityRecognitionHardwareIsSupported, + ActivityRecognitionHardware activityRecognitionHardware, + int overlaySwitchResId, + int defaultServicePackageNameResId, + int initialPackageNameResId) { + mIsSupported = activityRecognitionHardwareIsSupported; + mInstance = activityRecognitionHardware; + + mServiceWatcher = new ServiceWatcher( + context, + TAG, + "com.android.location.service.ActivityRecognitionProvider", + overlaySwitchResId, + defaultServicePackageNameResId, + initialPackageNameResId, + BackgroundThread.getHandler()) { + @Override + protected void onBind() { + runOnBinder(ActivityRecognitionProxy.this::initializeService); + } + }; + } + + private void initializeService(IBinder binder) { + try { + String descriptor = binder.getInterfaceDescriptor(); + + if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals( + descriptor)) { + IActivityRecognitionHardwareWatcher watcher = + IActivityRecognitionHardwareWatcher.Stub.asInterface(binder); + if (mInstance != null) { + watcher.onInstanceChanged(mInstance); + } + } else if (IActivityRecognitionHardwareClient.class.getCanonicalName() + .equals(descriptor)) { + IActivityRecognitionHardwareClient client = + IActivityRecognitionHardwareClient.Stub.asInterface(binder); + client.onAvailabilityChanged(mIsSupported, mInstance); + } else { + Log.e(TAG, "Invalid descriptor found on connection: " + descriptor); + } + } catch (RemoteException e) { + Log.w(TAG, e); + } + } +} diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 8734ceb614a9..a9ae74f67de7 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -947,6 +947,10 @@ public class LockSettingsService extends ILockSettings.Stub { public void setSeparateProfileChallengeEnabled(int userId, boolean enabled, String managedUserPassword) { checkWritePermission(userId); + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature."); + } synchronized (mSeparateChallengeLock) { setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword); } @@ -1305,6 +1309,10 @@ public class LockSettingsService extends ILockSettings.Stub { public void setLockCredential(String credential, int type, String savedCredential, int requestedQuality, int userId) throws RemoteException { + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature"); + } checkWritePermission(userId); synchronized (mSeparateChallengeLock) { setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId); @@ -2906,6 +2914,10 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, byte[] token, int requestedQuality, int userId) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + throw new UnsupportedOperationException( + "This operation requires secure lock screen feature."); + } try { return LockSettingsService.this.setLockCredentialWithToken(credential, type, tokenHandle, token, requestedQuality, userId); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 07f23ce2231a..6163077e1acf 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -18,6 +18,7 @@ package com.android.server.locksettings; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; + import static com.android.internal.widget.LockPatternUtils.stringToPattern; import android.app.ActivityManager; @@ -58,6 +59,18 @@ class LockSettingsShellCommand extends ShellCommand { mCurrentUserId = ActivityManager.getService().getCurrentUser().id; parseArgs(); + if (!mLockPatternUtils.hasSecureLockScreen()) { + switch (cmd) { + case COMMAND_HELP: + case COMMAND_GET_DISABLED: + case COMMAND_SET_DISABLED: + break; + default: + getErrPrintWriter().println( + "The device does not support lock screen - ignoring the command."); + return -1; + } + } if (!checkCredential()) { return -1; } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index d8c2432db856..af790f25564c 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -78,6 +78,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private final String mPackageName; private final String mTag; private final ControllerLink mController; + private final MediaSession.Token mSessionToken; private final SessionLink mSession; private final SessionCb mSessionCb; private final MediaSessionService.ServiceImpl mService; @@ -128,6 +129,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mPackageName = ownerPackageName; mTag = tag; mController = new ControllerLink(new ControllerStub()); + mSessionToken = new MediaSession.Token(mController); mSession = new SessionLink(new SessionStub()); mSessionCb = new SessionCb(cb); mService = service; @@ -157,6 +159,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * Get the session token for creating {@link MediaController}. + * + * @return The session token. + */ + public MediaSession.Token getSessionToken() { + return mSessionToken; + } + + /** * Get the info for this session. * * @return Info that identifies this session. diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java index e3ae8a7ac21f..1541b1d520cd 100644 --- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java @@ -51,7 +51,6 @@ import android.media.session.ICallback; import android.media.session.IOnMediaKeyListener; import android.media.session.IOnVolumeKeyLongPressListener; import android.media.session.ISession2TokensListener; -import android.media.session.ISessionController; import android.media.session.ISessionManager; import android.media.session.MediaSession; import android.media.session.MediaSessionManager; @@ -290,9 +289,7 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { return; } try { - mRvc.remoteVolumeChanged( - ISessionController.Stub.asInterface(session.getControllerLink().getBinder()), - flags); + mRvc.remoteVolumeChanged(session.getSessionToken(), flags); } catch (Exception e) { Log.wtf(TAG, "Error sending volume change to system UI.", e); } @@ -618,7 +615,7 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { int size = records.size(); ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>(); for (int i = 0; i < size; i++) { - tokens.add(new MediaSession.Token(records.get(i).getControllerLink())); + tokens.add(records.get(i).getSessionToken()); } pushRemoteVolumeUpdateLocked(userId); for (int i = mSessionsListeners.size() - 1; i >= 0; i--) { @@ -645,9 +642,7 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { return; } MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId); - mRvc.updateRemoteController(record == null ? null - : ISessionController.Stub.asInterface( - record.getControllerLink().getBinder())); + mRvc.updateRemoteController(record == null ? null : record.getSessionToken()); } catch (RemoteException e) { Log.wtf(TAG, "Error sending default remote volume to sys ui.", e); } @@ -864,7 +859,7 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked(); if (mediaButtonSession != null) { mCallback.onAddressedPlayerChangedToMediaSession( - new MediaSession.Token(mediaButtonSession.getControllerLink())); + mediaButtonSession.getSessionToken()); } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { mCallback.onAddressedPlayerChangedToMediaButtonReceiver( mCurrentFullUserRecord.mLastMediaButtonReceiver @@ -1804,7 +1799,7 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl { if (mCurrentFullUserRecord.mCallback != null) { try { mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession( - keyEvent, new MediaSession.Token(session.getControllerLink())); + keyEvent, session.getSessionToken()); } catch (RemoteException e) { Log.w(TAG, "Failed to send callback", e); } diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index ee60daa20837..c2dc554321c2 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -42,7 +42,8 @@ public interface NotificationDelegate { void onNotificationVisibilityChanged( NotificationVisibility[] newlyVisibleKeys, NotificationVisibility[] noLongerVisibleKeys); - void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); + void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded, + int notificationLocation); void onNotificationDirectReplied(String key); void onNotificationSettingsViewed(String key); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 20c4da416d1d..47a55971b9c7 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -868,7 +868,7 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationExpansionChanged(String key, - boolean userAction, boolean expanded) { + boolean userAction, boolean expanded, int notificationLocation) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 559874167429..02fc51f89e62 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1263,7 +1263,7 @@ public final class NotificationRecord { public LogMaker getAdjustmentLogMaker() { return getLogMaker() .setCategory(MetricsEvent.NOTIFICATION_ITEM) - .setType(MetricsEvent.NOTIFICATION_ASSISTANT_ADJUSTMENT); + .setType(MetricsEvent.TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT); } @VisibleForTesting diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java index 2ae424dd4b1b..5b765dfee3a4 100644 --- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java +++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java @@ -22,63 +22,117 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.os.Process; import android.os.ServiceManager; +import android.util.ByteStringUtils; +import android.util.EventLog; import android.util.Log; import com.android.server.pm.dex.DexLogger; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * Scheduled job to trigger logging of app dynamic code loading. This runs daily while idle and - * charging. The actual logging is performed by {@link DexLogger}. + * Scheduled jobs related to logging of app dynamic code loading. The idle logging job runs daily + * while idle and charging and calls {@link DexLogger} to write dynamic code information to the + * event log. The audit watching job scans the event log periodically while idle to find AVC audit + * messages indicating use of dynamic native code and adds the information to {@link DexLogger}. * {@hide} */ public class DynamicCodeLoggingService extends JobService { private static final String TAG = DynamicCodeLoggingService.class.getName(); - private static final int JOB_ID = 2030028; - private static final long PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + private static final boolean DEBUG = false; - private volatile boolean mStopRequested = false; + private static final int IDLE_LOGGING_JOB_ID = 2030028; + private static final int AUDIT_WATCHING_JOB_ID = 203142925; - private static final boolean DEBUG = false; + private static final long IDLE_LOGGING_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + private static final long AUDIT_WATCHING_PERIOD_MILLIS = TimeUnit.HOURS.toMillis(2); + + private static final int AUDIT_AVC = 1400; // Defined in linux/audit.h + private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " "; + + private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN = + Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*" + + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*" + + "\\bscontext=u:r:untrusted_app_2(?:5|7):.*" + + "\\btcontext=u:object_r:app_data_file:.*" + + "\\btclass=file\\b.*"); + + private volatile boolean mIdleLoggingStopRequested = false; + private volatile boolean mAuditWatchingStopRequested = false; /** - * Schedule our job with the {@link JobScheduler}. + * Schedule our jobs with the {@link JobScheduler}. */ public static void schedule(Context context) { ComponentName serviceName = new ComponentName( "android", DynamicCodeLoggingService.class.getName()); JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - js.schedule(new JobInfo.Builder(JOB_ID, serviceName) + js.schedule(new JobInfo.Builder(IDLE_LOGGING_JOB_ID, serviceName) .setRequiresDeviceIdle(true) .setRequiresCharging(true) - .setPeriodic(PERIOD_MILLIS) + .setPeriodic(IDLE_LOGGING_PERIOD_MILLIS) .build()); + js.schedule(new JobInfo.Builder(AUDIT_WATCHING_JOB_ID, serviceName) + .setRequiresDeviceIdle(true) + .setRequiresBatteryNotLow(true) + .setPeriodic(AUDIT_WATCHING_PERIOD_MILLIS) + .build()); + if (DEBUG) { - Log.d(TAG, "Job scheduled"); + Log.d(TAG, "Jobs scheduled"); } } @Override public boolean onStartJob(JobParameters params) { + int jobId = params.getJobId(); if (DEBUG) { - Log.d(TAG, "onStartJob"); + Log.d(TAG, "onStartJob " + jobId); + } + switch (jobId) { + case IDLE_LOGGING_JOB_ID: + mIdleLoggingStopRequested = false; + new IdleLoggingThread(params).start(); + return true; // Job is running on another thread + case AUDIT_WATCHING_JOB_ID: + mAuditWatchingStopRequested = false; + new AuditWatchingThread(params).start(); + return true; // Job is running on another thread + default: + // Shouldn't happen, but indicate nothing is running. + return false; } - mStopRequested = false; - new IdleLoggingThread(params).start(); - return true; // Job is running on another thread } @Override public boolean onStopJob(JobParameters params) { + int jobId = params.getJobId(); if (DEBUG) { - Log.d(TAG, "onStopJob"); + Log.d(TAG, "onStopJob " + jobId); } - mStopRequested = true; - return true; // Requests job be re-scheduled. + switch (jobId) { + case IDLE_LOGGING_JOB_ID: + mIdleLoggingStopRequested = true; + return true; // Requests job be re-scheduled. + case AUDIT_WATCHING_JOB_ID: + mAuditWatchingStopRequested = true; + return true; // Requests job be re-scheduled. + default: + return false; + } + } + + private static DexLogger getDexLogger() { + PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); + return pm.getDexManager().getDexLogger(); } private class IdleLoggingThread extends Thread { @@ -92,14 +146,13 @@ public class DynamicCodeLoggingService extends JobService { @Override public void run() { if (DEBUG) { - Log.d(TAG, "Starting logging run"); + Log.d(TAG, "Starting IdleLoggingJob run"); } - PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); - DexLogger dexLogger = pm.getDexManager().getDexLogger(); + DexLogger dexLogger = getDexLogger(); for (String packageName : dexLogger.getAllPackagesWithDynamicCodeLoading()) { - if (mStopRequested) { - Log.w(TAG, "Stopping logging run at scheduler request"); + if (mIdleLoggingStopRequested) { + Log.w(TAG, "Stopping IdleLoggingJob run at scheduler request"); return; } @@ -108,8 +161,128 @@ public class DynamicCodeLoggingService extends JobService { jobFinished(mParams, /* reschedule */ false); if (DEBUG) { - Log.d(TAG, "Finished logging run"); + Log.d(TAG, "Finished IdleLoggingJob run"); } } } + + private class AuditWatchingThread extends Thread { + private final JobParameters mParams; + + AuditWatchingThread(JobParameters params) { + super("DynamicCodeLoggingService_AuditWatchingJob"); + mParams = params; + } + + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "Starting AuditWatchingJob run"); + } + + if (processAuditEvents()) { + jobFinished(mParams, /* reschedule */ false); + if (DEBUG) { + Log.d(TAG, "Finished AuditWatchingJob run"); + } + } + } + + private boolean processAuditEvents() { + // Scan the event log for SELinux (avc) audit messages indicating when an + // (untrusted) app has executed native code from an app data + // file. Matches are recorded in DexLogger. + // + // These messages come from the kernel audit system via logd. (Note that + // some devices may not generate these messages at all, or the format may + // be different, in which case nothing will be recorded.) + // + // The messages use the auditd tag and the uid of the app that executed + // the code. + // + // A typical message might look like this: + // type=1400 audit(0.0:521): avc: granted { execute } for comm="executable" + // path="/data/data/com.dummy.app/executable" dev="sda13" ino=1655302 + // scontext=u:r:untrusted_app_27:s0:c66,c257,c512,c768 + // tcontext=u:object_r:app_data_file:s0:c66,c257,c512,c768 tclass=file + // + // The information we want is the uid and the path. (Note this may be + // either a quoted string, as shown above, or a sequence of hex-encoded + // bytes.) + // + // On each run we process all the matching events in the log. This may + // mean re-processing events we have already seen, and in any case there + // may be duplicate events for the same app+file. These are de-duplicated + // by DexLogger. + // + // Note that any app can write a message to the event log, including one + // that looks exactly like an AVC audit message, so the information may + // be spoofed by an app; in such a case the uid we see will be the app + // that generated the spoof message. + + try { + int[] tags = { EventLog.getTagCode("auditd") }; + if (tags[0] == -1) { + // auditd is not a registered tag on this system, so there can't be any messages + // of interest. + return true; + } + + DexLogger dexLogger = getDexLogger(); + + List<EventLog.Event> events = new ArrayList<>(); + EventLog.readEvents(tags, events); + + for (int i = 0; i < events.size(); ++i) { + if (mAuditWatchingStopRequested) { + Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request"); + return false; + } + + EventLog.Event event = events.get(i); + + // Discard clearly unrelated messages as quickly as we can. + int uid = event.getUid(); + if (!Process.isApplicationUid(uid)) { + continue; + } + Object data = event.getData(); + if (!(data instanceof String)) { + continue; + } + String message = (String) data; + if (!message.startsWith(AVC_PREFIX)) { + continue; + } + + // And then use a regular expression to verify it's one of the messages we're + // interested in and to extract the path of the file being loaded. + Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message); + if (!matcher.matches()) { + continue; + } + String path = matcher.group(1); + if (path == null) { + // If the path contains spaces or various weird characters the kernel + // hex-encodes the bytes; we need to undo that. + path = unhex(matcher.group(2)); + } + dexLogger.recordNative(uid, path); + } + + return true; + } catch (Exception e) { + Log.e(TAG, "AuditWatchingJob failed", e); + return true; + } + } + } + + private static String unhex(String hexEncodedPath) { + byte[] bytes = ByteStringUtils.fromHexToByteArray(hexEncodedPath); + if (bytes == null || bytes.length == 0) { + return ""; + } + return new String(bytes); + } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 8a6105cb7fa9..efafdfaf2b54 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -611,6 +611,31 @@ public class Installer extends SystemService { } } + public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) + throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.snapshotAppData(null, pkg, userId, storageFlags); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, + String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.restoreAppDataSnapshot(null, pkg, appId, ceDataInode, seInfo, userId, + storageFlags); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + private static void assertValidInstructionSet(String instructionSet) throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index bf4e272d4004..0ab2a7361ac0 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -310,7 +310,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements in.setInput(fis, StandardCharsets.UTF_8.name()); int type; - PackageInstallerSession currentSession = null; while ((type = in.next()) != END_DOCUMENT) { if (type == START_TAG) { final String tag = in.getName(); @@ -320,9 +319,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session = PackageInstallerSession.readFromXml(in, mInternalCallback, mContext, mPm, mInstallThread.getLooper(), mStagingManager, mSessionsDir, this); - currentSession = session; } catch (Exception e) { - currentSession = null; Slog.e(TAG, "Could not read session", e); continue; } @@ -347,10 +344,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements addHistoricalSessionLocked(session); } mAllocatedSessions.put(session.sessionId, true); - } else if (currentSession != null - && PackageInstallerSession.TAG_CHILD_SESSION.equals(tag)) { - currentSession.addChildSessionIdInternal( - PackageInstallerSession.readChildSessionIdFromXml(in)); } } } @@ -1132,6 +1125,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements public void onStagedSessionChanged(PackageInstallerSession session) { writeSessionsAsync(); + // TODO(b/118865310): don't send broadcast if system is not ready. mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 12d335dd87ab..b8825bbd2d72 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -992,8 +992,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Read transfers from the original owner stay open, but as the session's data // cannot be modified anymore, there is no leak of information. For staged sessions, - // further validation may be performed by the staging manager. + // further validation is performed by the staging manager. if (!params.isMultiPackage) { + if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) { + // For APEX, validation is done by StagingManager post-commit. + return; + } final PackageInfo pkgInfo = mPm.getPackageInfo( params.appPackageName, PackageManager.GET_SIGNATURES | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); @@ -1001,16 +1005,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { resolveStageDirLocked(); try { - if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) { - // TODO(b/118865310): Remove this when APEX validation is done via - // StagingManager. - validateApexInstallLocked(pkgInfo); - } else { - // Verify that stage looks sane with respect to existing application. - // This currently only ensures packageName, versionCode, and certificate - // consistency. - validateApkInstallLocked(pkgInfo); - } + validateApkInstallLocked(pkgInfo); } catch (PackageManagerException e) { throw e; } catch (Throwable e) { @@ -1301,54 +1296,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { (params.installFlags & PackageManager.DONT_KILL_APP) != 0; } - @GuardedBy("mLock") - private void validateApexInstallLocked(@Nullable PackageInfo pkgInfo) - throws PackageManagerException { - mResolvedStagedFiles.clear(); - mResolvedInheritedFiles.clear(); - - try { - resolveStageDirLocked(); - } catch (IOException e) { - throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, - "Failed to resolve stage location", e); - } - - final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter); - if (ArrayUtils.isEmpty(addedFiles)) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); - } - - if (addedFiles.length > 1) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, - "Only one APEX file at a time might be installed"); - } - File addedFile = addedFiles[0]; - final ApkLite apk; - try { - apk = PackageParser.parseApkLite( - addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES); - } catch (PackageParserException e) { - throw PackageManagerException.from(e); - } - - mPackageName = apk.packageName; - mVersionCode = apk.getLongVersionCode(); - mSigningDetails = apk.signingDetails; - mResolvedBaseFile = addedFile; - - // STOPSHIP: Ensure that we remove the non-staged version of APEX installs in production - // because we currently do not verify that signatures are consistent with the previously - // installed version in that case. - // - // When that happens, this hack can be reverted and we can rely on APEXd to map between - // APEX files and their package names instead of parsing it out of the AndroidManifest - // such as here. - if (params.appPackageName == null) { - params.appPackageName = mPackageName; - } - } - /** * Validate install by confirming that all application packages are have * consistent package name, version code, and signing certificates. @@ -1911,22 +1858,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override - public void addChildSessionId(int sessionId) { - final PackageInstallerSession session = mSessionProvider.getSession(sessionId); - if (session == null) { + public void addChildSessionId(int childSessionId) { + final PackageInstallerSession childSession = mSessionProvider.getSession(childSessionId); + if (childSession == null) { throw new RemoteException("Unable to add child.", - new PackageManagerException("Child session " + sessionId + " does not exist"), + new PackageManagerException("Child session " + childSessionId + + " does not exist"), + false, true).rethrowAsRuntimeException(); + } + // Session groups must be consistent wrt to isStaged parameter. Non-staging session + // cannot be grouped with staging sessions. + if (this.params.isStaged ^ childSession.params.isStaged) { + throw new RemoteException("Unable to add child.", + new PackageManagerException("Child session " + childSessionId + + " and parent session " + this.sessionId + " do not have consistent" + + " staging session settings."), false, true).rethrowAsRuntimeException(); } synchronized (mLock) { - final int indexOfSession = mChildSessionIds.indexOfKey(sessionId); + final int indexOfSession = mChildSessionIds.indexOfKey(childSessionId); if (indexOfSession >= 0) { return; } - session.setParentSessionId(this.sessionId); - // TODO: sanity check, if parent session is staged then child session should be - // marked as staged. - addChildSessionIdInternal(sessionId); + childSession.setParentSessionId(this.sessionId); + addChildSessionIdInternal(childSessionId); } } @@ -2057,6 +2012,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return mStagedSessionFailed; } + /** {@hide} */ + @StagedSessionErrorCode int getStagedSessionErrorCode() { + return mStagedSessionErrorCode; + } + private void destroyInternal() { synchronized (mLock) { mSealed = true; @@ -2221,35 +2181,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { out.endTag(null, TAG_SESSION); } - private static String[] readGrantedRuntimePermissions(XmlPullParser in) - throws IOException, XmlPullParserException { - List<String> permissions = null; - - final int outerDepth = in.getDepth(); - int type; - while ((type = in.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) { - String permission = readStringAttribute(in, ATTR_NAME); - if (permissions == null) { - permissions = new ArrayList<>(); - } - permissions.add(permission); - } - } - - if (permissions == null) { - return null; - } - - String[] permissionsArray = new String[permissions.size()]; - permissions.toArray(permissionsArray); - return permissionsArray; - } - // Sanity check to be performed when the session is restored from an external file. Only one // of the session states should be true, or none of them. private static boolean isStagedSessionStateValid(boolean isReady, boolean isApplied, @@ -2273,8 +2204,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * @param sessionProvider * @return The newly created session */ - // TODO(patb,109941548): modify readFromXml to consume to the next tag session tag so we - // can have a complete session for the constructor public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in, @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context, @NonNull PackageManagerService pm, Looper installerThread, @@ -2314,8 +2243,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID); params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON); - params.grantedRuntimePermissions = readGrantedRuntimePermissions(in); - final File appIconFile = buildAppIconFile(sessionId, sessionsDir); if (appIconFile.exists()) { params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath()); @@ -2324,16 +2251,51 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isReady = readBooleanAttribute(in, ATTR_IS_READY); final boolean isFailed = readBooleanAttribute(in, ATTR_IS_FAILED); final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED); - final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE); + final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE, + SessionInfo.NO_ERROR); if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) { throw new IllegalArgumentException("Can't restore staged session with invalid state."); } + // Parse sub tags of this session, typically used for repeated values / arrays. + // Sub tags can come in any order, therefore we need to keep track of what we find while + // parsing and only set the right values at the end. + + // Store the current depth. We should stop parsing when we reach an end tag at the same + // depth. + List<String> permissions = new ArrayList<>(); + List<Integer> childSessionIds = new ArrayList<>(); + int outerDepth = in.getDepth(); + int type; + while ((type = in.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) { + permissions.add(readStringAttribute(in, ATTR_NAME)); + } + if (TAG_CHILD_SESSION.equals(in.getName())) { + childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID)); + } + } + + if (permissions.size() > 0) { + params.grantedRuntimePermissions = permissions.stream().toArray(String[]::new); + } + + int[] childSessionIdsArray; + if (childSessionIds.size() > 0) { + childSessionIdsArray = childSessionIds.stream().mapToInt(i -> i).toArray(); + } else { + childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY; + } + return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerPackageName, installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed, - EMPTY_CHILD_SESSION_ARRAY, parentSessionId, isReady, isFailed, isApplied, + childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, stagedSessionErrorCode); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 09fe26d19019..b8342cf8f81e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRAD import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; +import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION; @@ -201,6 +202,7 @@ import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.IArtManager; +import android.content.rollback.IRollbackManager; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -13872,6 +13874,38 @@ public class PackageManagerService extends IPackageManager.Stub } } + // If this is an update to a package that might be potentially downgraded, then we + // need to check with the rollback manager whether there's any userdata that might + // need to be restored for the package. + // + // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL. + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) { + IRollbackManager rm = IRollbackManager.Stub.asInterface( + ServiceManager.getService(Context.ROLLBACK_SERVICE)); + + final String packageName = res.pkg.applicationInfo.packageName; + final String seInfo = res.pkg.applicationInfo.seInfo; + final PackageSetting ps; + int appId = -1; + long ceDataInode = -1; + synchronized (mSettings) { + ps = mSettings.getPackageLPr(packageName); + if (ps != null) { + appId = ps.appId; + ceDataInode = ps.getCeDataInode(userId); + } + } + + if (ps != null) { + try { + rm.restoreUserData(packageName, userId, appId, ceDataInode, seInfo, token); + } catch (RemoteException re) { + // Cannot happen, the RollbackManager is hosted in the same process. + } + doRestore = true; + } + } + if (!doRestore) { // No restore possible, or the Backup Manager was mysteriously not // available -- just fire the post-install work request directly. @@ -14569,6 +14603,9 @@ public class PackageManagerService extends IPackageManager.Stub enableRollbackIntent.putExtra( PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, installFlags); + enableRollbackIntent.putExtra( + PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS, + resolveUserIds(args.user.getIdentifier())); enableRollbackIntent.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)), PACKAGE_MIME_TYPE); enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -23791,6 +23828,11 @@ public class PackageManagerService extends IPackageManager.Stub } return mArtManagerService.compileLayouts(pkg); } + + @Override + public void finishPackageInstall(int token, boolean didLaunch) { + PackageManagerService.this.finishPackageInstall(token, didLaunch); + } } @GuardedBy("mPackages") diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 7bab0bbc7964..5311c2a55931 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -41,7 +41,9 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; /** * This class handles staged install sessions, i.e. install sessions that require packages to @@ -126,12 +128,24 @@ public class StagingManager { return false; } - private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) { + private static boolean submitSessionToApexService(@NonNull PackageInstallerSession session, + List<PackageInstallerSession> childSessions, + ApexInfoList apexInfoList) { + return sendSubmitStagedSessionRequest( + session.sessionId, + childSessions != null + ? childSessions.stream().mapToInt(s -> s.sessionId).toArray() : + new int[]{}, + apexInfoList); + } + + private static boolean sendSubmitStagedSessionRequest( + int sessionId, int[] childSessionIds, ApexInfoList apexInfoList) { final IApexService apex = IApexService.Stub.asInterface( ServiceManager.getService("apexservice")); boolean success; try { - success = apex.submitStagedSession(sessionId, new int[0], apexInfoList); + success = apex.submitStagedSession(sessionId, childSessionIds, apexInfoList); } catch (RemoteException re) { Slog.e(TAG, "Unable to contact apexservice", re); return false; @@ -139,31 +153,49 @@ public class StagingManager { return success; } + private static boolean isApexSession(@NonNull PackageInstallerSession session) { + return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0; + } + private void preRebootVerification(@NonNull PackageInstallerSession session) { boolean success = true; - if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) { - - final ApexInfoList apexInfoList = new ApexInfoList(); - - if (!submitSessionToApexService(session.sessionId, apexInfoList)) { - success = false; - } else { - // For APEXes, we validate the signature here before we mark the session as ready, - // so we fail the session early if there is a signature mismatch. For APKs, the - // signature verification will be done by the package manager at the point at which - // it applies the staged install. - // - // TODO: Decide whether we want to fail fast by detecting signature mismatches right - // away. - for (ApexInfo apexPackage : apexInfoList.apexInfos) { - if (!validateApexSignatureLocked(apexPackage.packagePath, - apexPackage.packageName)) { - success = false; - break; - } + + final ApexInfoList apexInfoList = new ApexInfoList(); + // APEX checks. For single-package sessions, check if they contain an APEX. For + // multi-package sessions, find all the child sessions that contain an APEX. + if (!session.isMultiPackage() + && isApexSession(session)) { + success = submitSessionToApexService(session, null, apexInfoList); + } else if (session.isMultiPackage()) { + List<PackageInstallerSession> childSessions = + Arrays.stream(session.getChildSessionIds()) + // Retrieve cached sessions matching ids. + .mapToObj(i -> mStagedSessions.get(i)) + // Filter only the ones containing APEX. + .filter(childSession -> isApexSession(childSession)) + .collect(Collectors.toList()); + if (!childSessions.isEmpty()) { + success = submitSessionToApexService(session, childSessions, apexInfoList); + } // else this is a staged multi-package session with no APEX files. + } + + if (success && (apexInfoList.apexInfos.length > 0)) { + // For APEXes, we validate the signature here before we mark the session as ready, + // so we fail the session early if there is a signature mismatch. For APKs, the + // signature verification will be done by the package manager at the point at which + // it applies the staged install. + // + // TODO: Decide whether we want to fail fast by detecting signature mismatches for APKs, + // right away. + for (ApexInfo apexPackage : apexInfoList.apexInfos) { + if (!validateApexSignatureLocked(apexPackage.packagePath, + apexPackage.packageName)) { + success = false; + break; } } } + if (success) { session.setStagedSessionReady(); } else { @@ -206,15 +238,59 @@ public class StagingManager { } } - void abortSession(@NonNull PackageInstallerSession sessionInfo) { - updateStoredSession(sessionInfo); + void abortSession(@NonNull PackageInstallerSession session) { synchronized (mStagedSessions) { - mStagedSessions.remove(sessionInfo.sessionId); + updateStoredSession(session); + mStagedSessions.remove(session.sessionId); + } + } + + @GuardedBy("mStagedSessions") + private boolean isMultiPackageSessionComplete(@NonNull PackageInstallerSession session) { + // This method assumes that the argument is either a parent session of a multi-package + // i.e. isMultiPackage() returns true, or that it is a child session, i.e. + // hasParentSessionId() returns true. + if (session.isMultiPackage()) { + // Parent session of a multi-package group. Check that we restored all the children. + for (int childSession : session.getChildSessionIds()) { + if (mStagedSessions.get(childSession) == null) { + return false; + } + } + return true; + } + if (session.hasParentSessionId()) { + PackageInstallerSession parent = mStagedSessions.get(session.getParentSessionId()); + if (parent == null) { + return false; + } + return isMultiPackageSessionComplete(parent); } + Slog.wtf(TAG, "Attempting to restore an invalid multi-package session."); + return false; } void restoreSession(@NonNull PackageInstallerSession session) { - updateStoredSession(session); + PackageInstallerSession sessionToResume = session; + synchronized (mStagedSessions) { + mStagedSessions.append(session.sessionId, session); + // For multi-package sessions, we don't know in which order they will be restored. We + // need to wait until we have restored all the session in a group before restoring them. + if (session.isMultiPackage() || session.hasParentSessionId()) { + if (!isMultiPackageSessionComplete(session)) { + // Still haven't recovered all sessions of the group, return. + return; + } + // Group recovered, find the parent if necessary and resume the installation. + if (session.hasParentSessionId()) { + sessionToResume = mStagedSessions.get(session.getParentSessionId()); + } + } + } + checkStateAndResume(sessionToResume); + } + + private void checkStateAndResume(@NonNull PackageInstallerSession session) { // Check the state of the session and decide what to do next. if (session.isStagedSessionFailed() || session.isStagedSessionApplied()) { // Final states, nothing to do. @@ -227,6 +303,8 @@ public class StagingManager { } else { // Session had already being marked ready. Start the checks to verify if there is any // follow-up work. + // TODO(b/118865310): should this be synchronous to ensure it completes before + // systemReady() finishes? mBgHandler.post(() -> resumeSession(session)); } } diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java index 78fa82c6bcdd..59cc0cfeef45 100644 --- a/services/core/java/com/android/server/pm/dex/DexLogger.java +++ b/services/core/java/com/android/server/pm/dex/DexLogger.java @@ -16,11 +16,15 @@ package com.android.server.pm.dex; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_NATIVE; + import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.FileUtils; import android.os.RemoteException; +import android.os.UserHandle; import android.os.storage.StorageManager; import android.util.ByteStringUtils; import android.util.EventLog; @@ -35,20 +39,23 @@ import com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; import com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import java.io.File; +import java.io.IOException; import java.util.Map; import java.util.Set; /** - * This class is responsible for logging data about secondary dex files. - * The data logged includes hashes of the name and content of each file. + * This class is responsible for logging data about secondary dex files and, despite the name, + * native code executed from an app's private directory. The data logged includes hashes of the + * name and content of each file. */ public class DexLogger { private static final String TAG = "DexLogger"; - // Event log tag & subtag used for SafetyNet logging of dynamic - // code loading (DCL) - see b/63927552. + // Event log tag & subtags used for SafetyNet logging of dynamic code loading (DCL) - + // see b/63927552. private static final int SNET_TAG = 0x534e4554; - private static final String DCL_SUBTAG = "dcl"; + private static final String DCL_DEX_SUBTAG = "dcl"; + private static final String DCL_NATIVE_SUBTAG = "dcln"; private final IPackageManager mPackageManager; private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; @@ -114,12 +121,11 @@ public class DexLogger { } int storageFlags; - if (appInfo.deviceProtectedDataDir != null - && FileUtils.contains(appInfo.deviceProtectedDataDir, filePath)) { - storageFlags = StorageManager.FLAG_STORAGE_DE; - } else if (appInfo.credentialProtectedDataDir != null - && FileUtils.contains(appInfo.credentialProtectedDataDir, filePath)) { + + if (fileIsUnder(filePath, appInfo.credentialProtectedDataDir)) { storageFlags = StorageManager.FLAG_STORAGE_CE; + } else if (fileIsUnder(filePath, appInfo.deviceProtectedDataDir)) { + storageFlags = StorageManager.FLAG_STORAGE_DE; } else { Slog.e(TAG, "Could not infer CE/DE storage for path " + filePath); needWrite |= mPackageDynamicCodeLoading.removeFile(packageName, filePath, userId); @@ -139,6 +145,9 @@ public class DexLogger { + ": " + e.getMessage()); } + String subtag = fileInfo.mFileType == FILE_TYPE_DEX + ? DCL_DEX_SUBTAG + : DCL_NATIVE_SUBTAG; String fileName = new File(filePath).getName(); String message = PackageUtils.computeSha256Digest(fileName.getBytes()); @@ -165,7 +174,7 @@ public class DexLogger { } if (loadingUid != -1) { - writeDclEvent(loadingUid, message); + writeDclEvent(subtag, loadingUid, message); } } } @@ -175,21 +184,58 @@ public class DexLogger { } } + private boolean fileIsUnder(String filePath, String directoryPath) { + if (directoryPath == null) { + return false; + } + + try { + return FileUtils.contains(new File(directoryPath).getCanonicalPath(), + new File(filePath).getCanonicalPath()); + } catch (IOException e) { + return false; + } + } + @VisibleForTesting PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); } @VisibleForTesting - void writeDclEvent(int uid, String message) { - EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message); + void writeDclEvent(String subtag, int uid, String message) { + EventLog.writeEvent(SNET_TAG, subtag, uid, message); } - void record(int loaderUserId, String dexPath, - String owningPackageName, String loadingPackageName) { + void recordDex(int loaderUserId, String dexPath, String owningPackageName, + String loadingPackageName) { if (mPackageDynamicCodeLoading.record(owningPackageName, dexPath, - PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, - loadingPackageName)) { + FILE_TYPE_DEX, loaderUserId, loadingPackageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } + + /** + * Record that an app running in the specified uid has executed native code from the file at + * {@link path}. + */ + public void recordNative(int loadingUid, String path) { + String[] packages; + try { + packages = mPackageManager.getPackagesForUid(loadingUid); + if (packages == null || packages.length == 0) { + return; + } + } catch (RemoteException e) { + // Can't happen, we're local. + return; + } + + String loadingPackageName = packages[0]; + int loadingUserId = UserHandle.getUserId(loadingUid); + + if (mPackageDynamicCodeLoading.record(loadingPackageName, path, + FILE_TYPE_NATIVE, loadingUserId, loadingPackageName)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } } 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 b54683673e7b..1a2b11559446 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -235,7 +235,7 @@ public class DexManager { continue; } - mDexLogger.record(loaderUserId, dexPath, searchResult.mOwningPackageName, + mDexLogger.recordDex(loaderUserId, dexPath, searchResult.mOwningPackageName, loadingAppInfo.packageName); if (classLoaderContexts != null) { diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java index 6d4bc8291611..cc26c9b5f76c 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java +++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java @@ -53,6 +53,9 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { // is represented in the text file format.) static final int FILE_TYPE_DEX = 'D'; + // Type code to indicate a secondary file containing native code. + static final int FILE_TYPE_NATIVE = 'N'; + private static final String TAG = "PackageDynamicCodeLoading"; private static final String FILE_VERSION_HEADER = "DCL1"; @@ -107,7 +110,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { */ boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId, String loadingPackageName) { - if (fileType != FILE_TYPE_DEX) { + if (!isValidFileType(fileType)) { throw new IllegalArgumentException("Bad file type: " + fileType); } synchronized (mLock) { @@ -120,6 +123,10 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { } } + private static boolean isValidFileType(int fileType) { + return fileType == FILE_TYPE_DEX || fileType == FILE_TYPE_NATIVE; + } + /** * Return all packages that contain records of secondary dex files. (Note that data updates * asynchronously, so {@link #getPackageDynamicCodeInfo} may still return null if passed @@ -407,7 +414,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { if (packages.length == 0) { throw new IOException("Malformed line: " + line); } - if (type != FILE_TYPE_DEX) { + if (!isValidFileType(type)) { throw new IOException("Unknown file type: " + line); } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index d5af31300bf5..20d6d4e2ca79 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1290,6 +1290,7 @@ public final class DefaultPermissionGrantPolicy { return mContext.getPackageManager().getPackageInfo(pkg, DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags); } catch (NameNotFoundException e) { + Slog.e(TAG, "PackageNot found: " + pkg, e); return null; } } diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index 5516b234925b..c0517fdc99d0 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -342,10 +342,21 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @WorkerThread private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) { RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId); - if (listeners == null) { - return; + if (listeners != null) { + notifyRoleHoldersChangedForListeners(listeners, roleName, userId); + } + + RemoteCallbackList<IOnRoleHoldersChangedListener> allUserListeners = getListeners( + UserHandle.USER_ALL); + if (allUserListeners != null) { + notifyRoleHoldersChangedForListeners(allUserListeners, roleName, userId); } + } + @WorkerThread + private void notifyRoleHoldersChangedForListeners( + @NonNull RemoteCallbackList<IOnRoleHoldersChangedListener> listeners, + @NonNull String roleName, @UserIdInt int userId) { int broadcastCount = listeners.beginBroadcast(); try { for (int i = 0; i < broadcastCount; i++) { @@ -395,7 +406,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C Slog.e(LOG_TAG, "user " + userId + " does not exist"); return Collections.emptyList(); } - userId = handleIncomingUser(userId, "getRoleHoldersAsUser"); + userId = handleIncomingUser(userId, "getRoleHoldersAsUser", false); getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, "getRoleHoldersAsUser"); @@ -423,7 +434,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C Slog.e(LOG_TAG, "user " + userId + " does not exist"); return; } - userId = handleIncomingUser(userId, "addRoleHolderAsUser"); + userId = handleIncomingUser(userId, "addRoleHolderAsUser", false); getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, "addRoleHolderAsUser"); @@ -440,7 +451,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C Slog.e(LOG_TAG, "user " + userId + " does not exist"); return; } - userId = handleIncomingUser(userId, "removeRoleHolderAsUser"); + userId = handleIncomingUser(userId, "removeRoleHolderAsUser", false); getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, "removeRoleHolderAsUser"); @@ -457,7 +468,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C Slog.e(LOG_TAG, "user " + userId + " does not exist"); return; } - userId = handleIncomingUser(userId, "clearRoleHoldersAsUser"); + userId = handleIncomingUser(userId, "clearRoleHoldersAsUser", false); getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, "clearRoleHoldersAsUser"); @@ -468,11 +479,12 @@ public class RoleManagerService extends SystemService implements RoleUserState.C public void addOnRoleHoldersChangedListenerAsUser( @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { Preconditions.checkNotNull(listener, "listener cannot be null"); - if (!mUserManagerInternal.exists(userId)) { + if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) { Slog.e(LOG_TAG, "user " + userId + " does not exist"); return; } - userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser"); + userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser", + true); getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS, "addOnRoleHoldersChangedListenerAsUser"); @@ -485,11 +497,12 @@ public class RoleManagerService extends SystemService implements RoleUserState.C public void removeOnRoleHoldersChangedListenerAsUser( @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { Preconditions.checkNotNull(listener, "listener cannot be null"); - if (!mUserManagerInternal.exists(userId)) { + if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) { Slog.e(LOG_TAG, "user " + userId + " does not exist"); return; } - userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser"); + userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser", + true); getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS, "removeOnRoleHoldersChangedListenerAsUser"); @@ -553,9 +566,10 @@ public class RoleManagerService extends SystemService implements RoleUserState.C } @CheckResult - private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) { + private int handleIncomingUser(@UserIdInt int userId, @NonNull String name, + boolean allowAll) { return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, - false, true, name, null); + allowAll, true, name, null); } @Override diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index d12f7eda9a1b..085b4afac0e2 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -43,34 +43,28 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.storage.StorageManager; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; +import com.android.server.pm.Installer; +import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PackageManagerServiceUtils; -import libcore.io.IoUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import java.io.File; import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Files; import java.time.Instant; -import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Implementation of service that manages APK level rollbacks. @@ -108,46 +102,22 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { @GuardedBy("mLock") private List<RollbackInfo> mRecentlyExecutedRollbacks; - // Data for available rollbacks and recently executed rollbacks is - // persisted in storage. Assuming the rollback data directory is - // /data/rollback, we use the following directory structure - // to store this data: - // /data/rollback/ - // available/ - // XXX/ - // com.package.A/ - // base.apk - // info.json - // enabled.txt - // YYY/ - // com.package.B/ - // base.apk - // info.json - // enabled.txt - // recently_executed.json - // - // * XXX, YYY are random strings from Files.createTempDirectory - // * info.json contains the package version to roll back from/to. - // * enabled.txt contains a timestamp for when the rollback was first - // made available. This file is not written until the rollback is made - // available. - // - // TODO: Use AtomicFile for all the .json files? - private final File mRollbackDataDir; - private final File mAvailableRollbacksDir; - private final File mRecentlyExecutedRollbacksFile; + private final RollbackStore mRollbackStore; private final Context mContext; private final HandlerThread mHandlerThread; + private final Installer mInstaller; RollbackManagerServiceImpl(Context context) { mContext = context; + // Note that we're calling onStart here because this object is only constructed on + // SystemService#onStart. + mInstaller = new Installer(mContext); + mInstaller.onStart(); mHandlerThread = new HandlerThread("RollbackManagerServiceHandler"); mHandlerThread.start(); - mRollbackDataDir = new File(Environment.getDataDirectory(), "rollback"); - mAvailableRollbacksDir = new File(mRollbackDataDir, "available"); - mRecentlyExecutedRollbacksFile = new File(mRollbackDataDir, "recently_executed.json"); + mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); // Kick off loading of the rollback data from strorage in a background // thread. @@ -158,8 +128,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // expiration. getHandler().post(() -> ensureRollbackDataLoaded()); - PackageInstaller installer = mContext.getPackageManager().getPackageInstaller(); - installer.registerSessionCallback(new SessionCallback(), getHandler()); + PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); + packageInstaller.registerSessionCallback(new SessionCallback(), getHandler()); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REPLACED); @@ -196,10 +166,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN, -1); int installFlags = intent.getIntExtra( PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALL_FLAGS, 0); + int[] installedUsers = intent.getIntArrayExtra( + PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_INSTALLED_USERS); File newPackageCodePath = new File(intent.getData().getPath()); getHandler().post(() -> { - boolean success = enableRollback(installFlags, newPackageCodePath); + boolean success = enableRollback(installFlags, newPackageCodePath, + installedUsers); int ret = PackageManagerInternal.ENABLE_ROLLBACK_SUCCEEDED; if (!success) { ret = PackageManagerInternal.ENABLE_ROLLBACK_FAILED; @@ -357,8 +330,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageManager pm = context.getPackageManager(); try { PackageInstaller packageInstaller = pm.getPackageInstaller(); + String installerPackageName = pm.getInstallerPackageName(targetPackageName); + if (installerPackageName == null) { + sendFailure(statusReceiver, "Cannot find installer package"); + return; + } PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); + parentParams.setInstallerPackageName(installerPackageName); parentParams.setAllowDowngrade(true); parentParams.setMultiPackage(); int parentSessionId = packageInstaller.createSession(parentParams); @@ -367,6 +346,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : data.packages) { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); + params.setInstallerPackageName(installerPackageName); params.setAllowDowngrade(true); int sessionId = packageInstaller.createSession(params); PackageInstaller.Session session = packageInstaller.openSession(sessionId); @@ -387,28 +367,30 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { parentSession.addChildSessionId(sessionId); } - final LocalIntentReceiver receiver = new LocalIntentReceiver(); - parentSession.commit(receiver.getIntentSender()); + final LocalIntentReceiver receiver = new LocalIntentReceiver( + (Intent result) -> { + int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status != PackageInstaller.STATUS_SUCCESS) { + sendFailure(statusReceiver, "Rollback downgrade install failed: " + + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)); + return; + } - Intent result = receiver.getResult(); - int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status != PackageInstaller.STATUS_SUCCESS) { - sendFailure(statusReceiver, "Rollback downgrade install failed: " - + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)); - return; - } + addRecentlyExecutedRollback(rollback); + sendSuccess(statusReceiver); - addRecentlyExecutedRollback(rollback); - sendSuccess(statusReceiver); + Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED, + Uri.fromParts("package", targetPackageName, + Manifest.permission.MANAGE_ROLLBACKS)); - Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED, - Uri.fromParts("package", targetPackageName, - Manifest.permission.MANAGE_ROLLBACKS)); + // TODO: This call emits the warning "Calling a method in the + // system process without a qualified user". Fix that. + mContext.sendBroadcast(broadcast); + } + ); - // TODO: This call emits the warning "Calling a method in the - // system process without a qualified user". Fix that. - mContext.sendBroadcast(broadcast); + parentSession.commit(receiver.getIntentSender()); } catch (IOException e) { Log.e(TAG, "Unable to roll back " + targetPackageName, e); sendFailure(statusReceiver, "IOException: " + e.toString()); @@ -447,7 +429,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : data.packages) { if (info.packageName.equals(packageName)) { iter.remove(); - removeFile(data.backupDir); + mRollbackStore.deleteAvailableRollback(data); break; } } @@ -474,87 +456,20 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { @GuardedBy("mLock") private void ensureRollbackDataLoadedLocked() { if (mAvailableRollbacks == null) { - loadRollbackDataLocked(); + loadAllRollbackDataLocked(); } } /** - * Load rollback data from storage. + * Load all rollback data from storage. * Note: We do potentially heavy IO here while holding mLock, because we * have to have the rollback data loaded before we can do anything else * meaningful. */ @GuardedBy("mLock") - private void loadRollbackDataLocked() { - mAvailableRollbacksDir.mkdirs(); - mAvailableRollbacks = new ArrayList<>(); - for (File rollbackDir : mAvailableRollbacksDir.listFiles()) { - File enabledFile = new File(rollbackDir, "enabled.txt"); - // TODO: Delete any directories without an enabled.txt? That could - // potentially delete pending rollback data if reloadPersistedData - // is called, though there's no reason besides testing for that to - // be called. - if (rollbackDir.isDirectory() && enabledFile.isFile()) { - RollbackData data = new RollbackData(rollbackDir); - try { - PackageRollbackInfo info = null; - for (File packageDir : rollbackDir.listFiles()) { - if (packageDir.isDirectory()) { - File jsonFile = new File(packageDir, "info.json"); - String jsonString = IoUtils.readFileAsString( - jsonFile.getAbsolutePath()); - JSONObject jsonObject = new JSONObject(jsonString); - String packageName = jsonObject.getString("packageName"); - long higherVersionCode = jsonObject.getLong("higherVersionCode"); - long lowerVersionCode = jsonObject.getLong("lowerVersionCode"); - - data.packages.add(new PackageRollbackInfo(packageName, - new PackageRollbackInfo.PackageVersion(higherVersionCode), - new PackageRollbackInfo.PackageVersion(lowerVersionCode))); - } - } - - if (data.packages.isEmpty()) { - throw new IOException("No package rollback info found"); - } - - String enabledString = IoUtils.readFileAsString(enabledFile.getAbsolutePath()); - data.timestamp = Instant.parse(enabledString.trim()); - mAvailableRollbacks.add(data); - } catch (IOException | JSONException | DateTimeParseException e) { - Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e); - removeFile(rollbackDir); - } - } - } - - mRecentlyExecutedRollbacks = new ArrayList<>(); - if (mRecentlyExecutedRollbacksFile.exists()) { - try { - // TODO: How to cope with changes to the format of this file from - // when RollbackStore is updated in the future? - String jsonString = IoUtils.readFileAsString( - mRecentlyExecutedRollbacksFile.getAbsolutePath()); - JSONObject object = new JSONObject(jsonString); - JSONArray array = object.getJSONArray("recentlyExecuted"); - for (int i = 0; i < array.length(); ++i) { - JSONObject element = array.getJSONObject(i); - String packageName = element.getString("packageName"); - long higherVersionCode = element.getLong("higherVersionCode"); - long lowerVersionCode = element.getLong("lowerVersionCode"); - PackageRollbackInfo target = new PackageRollbackInfo(packageName, - new PackageRollbackInfo.PackageVersion(higherVersionCode), - new PackageRollbackInfo.PackageVersion(lowerVersionCode)); - RollbackInfo rollback = new RollbackInfo(target); - mRecentlyExecutedRollbacks.add(rollback); - } - } catch (IOException | JSONException e) { - // TODO: What to do here? Surely we shouldn't just forget about - // everything after the point of exception? - Log.e(TAG, "Failed to read recently executed rollbacks", e); - } - } - + private void loadAllRollbackDataLocked() { + mAvailableRollbacks = mRollbackStore.loadAvailableRollbacks(); + mRecentlyExecutedRollbacks = mRollbackStore.loadRecentlyExecutedRollbacks(); scheduleExpiration(0); } @@ -578,7 +493,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { if (info.packageName.equals(packageName) && !info.higherVersion.equals(installedVersion)) { iter.remove(); - removeFile(data.backupDir); + mRollbackStore.deleteAvailableRollback(data); break; } } @@ -606,38 +521,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } if (changed) { - saveRecentlyExecutedRollbacksLocked(); - } - } - } - - /** - * Write the list of recently executed rollbacks to storage. - * Note: This happens while mLock is held, which should be okay because we - * expect executed rollbacks to be modified only in exceptional cases. - */ - @GuardedBy("mLock") - private void saveRecentlyExecutedRollbacksLocked() { - try { - JSONObject json = new JSONObject(); - JSONArray array = new JSONArray(); - json.put("recentlyExecuted", array); - - for (int i = 0; i < mRecentlyExecutedRollbacks.size(); ++i) { - RollbackInfo rollback = mRecentlyExecutedRollbacks.get(i); - JSONObject element = new JSONObject(); - element.put("packageName", rollback.targetPackage.packageName); - element.put("higherVersionCode", rollback.targetPackage.higherVersion.versionCode); - element.put("lowerVersionCode", rollback.targetPackage.lowerVersion.versionCode); - array.put(element); + mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks); } - - PrintWriter pw = new PrintWriter(mRecentlyExecutedRollbacksFile); - pw.println(json.toString()); - pw.close(); - } catch (IOException | JSONException e) { - // TODO: What to do here? - Log.e(TAG, "Failed to save recently executed rollbacks", e); } } @@ -650,7 +535,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { synchronized (mLock) { ensureRollbackDataLoadedLocked(); mRecentlyExecutedRollbacks.add(rollback); - saveRecentlyExecutedRollbacksLocked(); + mRollbackStore.saveRecentlyExecutedRollbacks(mRecentlyExecutedRollbacks); } } @@ -701,7 +586,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { RollbackData data = iter.next(); if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) { iter.remove(); - removeFile(data.backupDir); + mRollbackStore.deleteAvailableRollback(data); } else if (oldest == null || oldest.isAfter(data.timestamp)) { oldest = data.timestamp; } @@ -748,12 +633,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { * staged for install with rollback enabled. Called before the package has * been installed. * - * @param id the id of the enable rollback request * @param installFlags information about what is being installed. * @param newPackageCodePath path to the package about to be installed. + * @param installedUsers the set of users for which a given package is installed. * @return true if enabling the rollback succeeds, false otherwise. */ - private boolean enableRollback(int installFlags, File newPackageCodePath) { + private boolean enableRollback(int installFlags, File newPackageCodePath, + int[] installedUsers) { if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Log.e(TAG, "Rollbacks not supported for instant app install"); return false; @@ -818,6 +704,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { PackageRollbackInfo.PackageVersion installedVersion = new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode()); + for (int user : installedUsers) { + final int storageFlags; + if (StorageManager.isFileEncryptedNativeOrEmulated() + && !StorageManager.isUserKeyUnlocked(user)) { + // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy + // across app user data until the user unlocks their device. + Log.e(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup."); + storageFlags = Installer.FLAG_STORAGE_DE; + } else { + storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE; + } + + try { + mInstaller.snapshotAppData(packageName, user, storageFlags); + } catch (InstallerException ie) { + Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie); + } + } + PackageRollbackInfo info = new PackageRollbackInfo( packageName, newVersion, installedVersion); @@ -827,9 +732,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mChildSessions.put(childSessionId, parentSessionId); data = mPendingRollbacks.get(parentSessionId); if (data == null) { - File backupDir = Files.createTempDirectory( - mAvailableRollbacksDir.toPath(), null).toFile(); - data = new RollbackData(backupDir); + data = mRollbackStore.createAvailableRollback(); mPendingRollbacks.put(parentSessionId, data); } data.packages.add(info); @@ -839,78 +742,77 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return false; } - File packageDir = new File(data.backupDir, packageName); + File packageDir = mRollbackStore.packageCodePathForAvailableRollback(data, packageName); packageDir.mkdirs(); - try { - JSONObject json = new JSONObject(); - json.put("packageName", packageName); - json.put("higherVersionCode", newVersion.versionCode); - json.put("lowerVersionCode", installedVersion.versionCode); - - File jsonFile = new File(packageDir, "info.json"); - PrintWriter pw = new PrintWriter(jsonFile); - pw.println(json.toString()); - pw.close(); - } catch (IOException | JSONException e) { - Log.e(TAG, "Unable to create rollback for " + packageName, e); - removeFile(packageDir); - return false; - } // TODO: Copy by hard link instead to save on cpu and storage space? int status = PackageManagerServiceUtils.copyPackage(installedPackage.codePath, packageDir); if (status != PackageManager.INSTALL_SUCCEEDED) { Log.e(TAG, "Unable to copy package for rollback for " + packageName); - removeFile(packageDir); return false; } return true; } - // TODO: Don't copy this from PackageManagerShellCommand like this? - private static class LocalIntentReceiver { - private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>(); + @Override + public void restoreUserData(String packageName, int userId, int appId, long ceDataInode, + String seInfo, int token) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("restoureUserData may only be called by the system."); + } + + getHandler().post(() -> { + PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + // TODO(narayan): Should we make sure we're in the middle of a session commit for a + // a package with this package name ? Otherwise it's possible we may roll back data + // for some other downgrade. + if (getRollbackForPackage(packageName) == null) { + pmi.finishPackageInstall(token, false); + return; + } + + final int storageFlags; + if (StorageManager.isFileEncryptedNativeOrEmulated() + && !StorageManager.isUserKeyUnlocked(userId)) { + // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy + // across app user data until the user unlocks their device. + Log.e(TAG, "User: " + userId + " isn't unlocked, skipping CE userdata restore."); + + storageFlags = Installer.FLAG_STORAGE_DE; + } else { + storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE; + } + + try { + mInstaller.restoreAppDataSnapshot(packageName, appId, ceDataInode, + seInfo, userId, storageFlags); + } catch (InstallerException ie) { + Log.e(TAG, "Unable to restore app data snapshot: " + packageName, ie); + } + + pmi.finishPackageInstall(token, false); + }); + } + + private class LocalIntentReceiver { + final Consumer<Intent> mConsumer; + + LocalIntentReceiver(Consumer<Intent> consumer) { + mConsumer = consumer; + } private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { @Override public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - try { - mResult.offer(intent, 5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + getHandler().post(() -> mConsumer.accept(intent)); } }; public IntentSender getIntentSender() { return new IntentSender((IIntentSender) mLocalSender); } - - public Intent getResult() { - try { - return mResult.take(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - /** - * Deletes a file completely. - * If the file is a directory, its contents are deleted as well. - * Has no effect if the directory does not exist. - */ - private void removeFile(File file) { - if (file.isDirectory()) { - for (File child : file.listFiles()) { - removeFile(child); - } - } - if (file.exists()) { - file.delete(); - } } /** @@ -958,11 +860,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { if (success) { try { data.timestamp = Instant.now(); - File enabledFile = new File(data.backupDir, "enabled.txt"); - PrintWriter pw = new PrintWriter(enabledFile); - pw.println(data.timestamp.toString()); - pw.close(); + mRollbackStore.saveAvailableRollback(data); synchronized (mLock) { // Note: There is a small window of time between when // the session has been committed by the package @@ -981,12 +880,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); } catch (IOException e) { Log.e(TAG, "Unable to enable rollback", e); - removeFile(data.backupDir); + mRollbackStore.deleteAvailableRollback(data); } } else { // The install session was aborted, clean up the pending // install. - removeFile(data.backupDir); + mRollbackStore.deleteAvailableRollback(data); } } } diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java new file mode 100644 index 000000000000..eb06bb24c24c --- /dev/null +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -0,0 +1,256 @@ +/* + * 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.rollback; + +import android.content.rollback.PackageRollbackInfo; +import android.content.rollback.RollbackInfo; +import android.util.Log; + +import libcore.io.IoUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for loading and saving rollback data to persistent storage. + */ +class RollbackStore { + private static final String TAG = "RollbackManager"; + + // Assuming the rollback data directory is /data/rollback, we use the + // following directory structure to store persisted data for available and + // recently executed rollbacks: + // /data/rollback/ + // available/ + // XXX/ + // rollback.json + // com.package.A/ + // base.apk + // com.package.B/ + // base.apk + // YYY/ + // rollback.json + // com.package.C/ + // base.apk + // recently_executed.json + // + // * XXX, YYY are random strings from Files.createTempDirectory + // * rollback.json contains all relevant metadata for the rollback. This + // file is not written until the rollback is made available. + // + // TODO: Use AtomicFile for all the .json files? + private final File mRollbackDataDir; + private final File mAvailableRollbacksDir; + private final File mRecentlyExecutedRollbacksFile; + + RollbackStore(File rollbackDataDir) { + mRollbackDataDir = rollbackDataDir; + mAvailableRollbacksDir = new File(mRollbackDataDir, "available"); + mRecentlyExecutedRollbacksFile = new File(mRollbackDataDir, "recently_executed.json"); + } + + /** + * Reads the list of available rollbacks from persistent storage. + */ + List<RollbackData> loadAvailableRollbacks() { + List<RollbackData> availableRollbacks = new ArrayList<>(); + mAvailableRollbacksDir.mkdirs(); + for (File rollbackDir : mAvailableRollbacksDir.listFiles()) { + if (rollbackDir.isDirectory()) { + try { + RollbackData data = loadRollbackData(rollbackDir); + availableRollbacks.add(data); + } catch (IOException e) { + // Note: Deleting the rollbackDir here will cause pending + // rollbacks to be deleted. This should only ever happen + // if reloadPersistedData is called while there are + // pending rollbacks. The reloadPersistedData method is + // currently only for testing, so that should be okay. + Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e); + removeFile(rollbackDir); + } + } + } + return availableRollbacks; + } + + /** + * Reads the list of recently executed rollbacks from persistent storage. + */ + List<RollbackInfo> loadRecentlyExecutedRollbacks() { + List<RollbackInfo> recentlyExecutedRollbacks = new ArrayList<>(); + if (mRecentlyExecutedRollbacksFile.exists()) { + try { + // TODO: How to cope with changes to the format of this file from + // when RollbackStore is updated in the future? + String jsonString = IoUtils.readFileAsString( + mRecentlyExecutedRollbacksFile.getAbsolutePath()); + JSONObject object = new JSONObject(jsonString); + JSONArray array = object.getJSONArray("recentlyExecuted"); + for (int i = 0; i < array.length(); ++i) { + JSONObject element = array.getJSONObject(i); + String packageName = element.getString("packageName"); + long higherVersionCode = element.getLong("higherVersionCode"); + long lowerVersionCode = element.getLong("lowerVersionCode"); + PackageRollbackInfo target = new PackageRollbackInfo(packageName, + new PackageRollbackInfo.PackageVersion(higherVersionCode), + new PackageRollbackInfo.PackageVersion(lowerVersionCode)); + RollbackInfo rollback = new RollbackInfo(target); + recentlyExecutedRollbacks.add(rollback); + } + } catch (IOException | JSONException e) { + // TODO: What to do here? Surely we shouldn't just forget about + // everything after the point of exception? + Log.e(TAG, "Failed to read recently executed rollbacks", e); + } + } + + return recentlyExecutedRollbacks; + } + + /** + * Creates a new RollbackData instance with backupDir assigned. + */ + RollbackData createAvailableRollback() throws IOException { + File backupDir = Files.createTempDirectory(mAvailableRollbacksDir.toPath(), null).toFile(); + return new RollbackData(backupDir); + } + + /** + * Returns the directory where the code for a package should be stored for + * given rollback <code>data</code> and <code>packageName</code>. + */ + File packageCodePathForAvailableRollback(RollbackData data, String packageName) { + return new File(data.backupDir, packageName); + } + + /** + * Writes the metadata for an available rollback to persistent storage. + */ + void saveAvailableRollback(RollbackData data) throws IOException { + try { + JSONObject dataJson = new JSONObject(); + JSONArray packagesJson = new JSONArray(); + for (PackageRollbackInfo info : data.packages) { + JSONObject infoJson = new JSONObject(); + infoJson.put("packageName", info.packageName); + infoJson.put("higherVersionCode", info.higherVersion.versionCode); + infoJson.put("lowerVersionCode", info.lowerVersion.versionCode); + packagesJson.put(infoJson); + } + dataJson.put("packages", packagesJson); + dataJson.put("timestamp", data.timestamp.toString()); + + PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json")); + pw.println(dataJson.toString()); + pw.close(); + } catch (JSONException e) { + throw new IOException(e); + } + } + + /** + * Removes all persistant storage associated with the given available + * rollback. + */ + void deleteAvailableRollback(RollbackData data) { + // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the + // actual app. + removeFile(data.backupDir); + } + + /** + * Writes the list of recently executed rollbacks to storage. + */ + void saveRecentlyExecutedRollbacks(List<RollbackInfo> recentlyExecutedRollbacks) { + try { + JSONObject json = new JSONObject(); + JSONArray array = new JSONArray(); + json.put("recentlyExecuted", array); + + for (int i = 0; i < recentlyExecutedRollbacks.size(); ++i) { + RollbackInfo rollback = recentlyExecutedRollbacks.get(i); + JSONObject element = new JSONObject(); + element.put("packageName", rollback.targetPackage.packageName); + element.put("higherVersionCode", rollback.targetPackage.higherVersion.versionCode); + element.put("lowerVersionCode", rollback.targetPackage.lowerVersion.versionCode); + array.put(element); + } + + PrintWriter pw = new PrintWriter(mRecentlyExecutedRollbacksFile); + pw.println(json.toString()); + pw.close(); + } catch (IOException | JSONException e) { + // TODO: What to do here? + Log.e(TAG, "Failed to save recently executed rollbacks", e); + } + } + + /** + * Reads the metadata for a rollback from the given directory. + * @throws IOException in case of error reading the data. + */ + private RollbackData loadRollbackData(File backupDir) throws IOException { + try { + RollbackData data = new RollbackData(backupDir); + File rollbackJsonFile = new File(backupDir, "rollback.json"); + JSONObject dataJson = new JSONObject( + IoUtils.readFileAsString(rollbackJsonFile.getAbsolutePath())); + JSONArray packagesJson = dataJson.getJSONArray("packages"); + for (int i = 0; i < packagesJson.length(); ++i) { + JSONObject infoJson = packagesJson.getJSONObject(i); + String packageName = infoJson.getString("packageName"); + long higherVersionCode = infoJson.getLong("higherVersionCode"); + long lowerVersionCode = infoJson.getLong("lowerVersionCode"); + data.packages.add(new PackageRollbackInfo(packageName, + new PackageRollbackInfo.PackageVersion(higherVersionCode), + new PackageRollbackInfo.PackageVersion(lowerVersionCode))); + } + + data.timestamp = Instant.parse(dataJson.getString("timestamp")); + return data; + } catch (JSONException | DateTimeParseException e) { + throw new IOException(e); + } + } + + /** + * Deletes a file completely. + * If the file is a directory, its contents are deleted as well. + * Has no effect if the directory does not exist. + */ + private void removeFile(File file) { + if (file.isDirectory()) { + for (File child : file.listFiles()) { + removeFile(child); + } + } + if (file.exists()) { + file.delete(); + } + } +} diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 4e71a054fa80..40664fef2825 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -96,10 +96,10 @@ import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BinderCallsStats.ExportedCallStat; import com.android.internal.os.KernelCpuSpeedReader; import com.android.internal.os.KernelCpuThreadReader; -import com.android.internal.os.KernelUidCpuActiveTimeReader; -import com.android.internal.os.KernelUidCpuClusterTimeReader; -import com.android.internal.os.KernelUidCpuFreqTimeReader; -import com.android.internal.os.KernelUidCpuTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; +import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; import com.android.internal.os.KernelWakelockReader; import com.android.internal.os.KernelWakelockStats; import com.android.internal.os.LooperStats; @@ -231,14 +231,16 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final HashMap<Long, String> mDeletedFiles = new HashMap<>(); private final CompanionHandler mHandler; - private KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader(); + // Disables throttler on CPU time readers. + private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader = + new KernelCpuUidUserSysTimeReader(false); private KernelCpuSpeedReader[] mKernelCpuSpeedReaders; - private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader = - new KernelUidCpuFreqTimeReader(); - private KernelUidCpuActiveTimeReader mKernelUidCpuActiveTimeReader = - new KernelUidCpuActiveTimeReader(); - private KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader = - new KernelUidCpuClusterTimeReader(); + private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader = + new KernelCpuUidFreqTimeReader(false); + private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader = + new KernelCpuUidActiveTimeReader(false); + private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader = + new KernelCpuUidClusterTimeReader(false); private StoragedUidIoStatsReader mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(); @Nullable @@ -294,12 +296,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { numSpeedSteps); firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i); } - // use default throttling in - // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader - mKernelUidCpuFreqTimeReader.setThrottleInterval(0); - long[] freqs = mKernelUidCpuFreqTimeReader.readFreqs(powerProfile); - mKernelUidCpuClusterTimeReader.setThrottleInterval(0); - mKernelUidCpuActiveTimeReader.setThrottleInterval(0); // Enable push notifications of throttling from vendor thermal // management subsystem via thermalservice. @@ -914,7 +910,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullKernelUidCpuTime( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - mKernelUidCpuTimeReader.readAbsolute((uid, userTimeUs, systemTimeUs) -> { + mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> { + long userTimeUs = timesUs[0], systemTimeUs = timesUs[1]; StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); e.writeInt(uid); e.writeLong(userTimeUs); @@ -926,7 +923,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullKernelUidCpuFreqTime( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - mKernelUidCpuFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> { + mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> { for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) { if (cpuFreqTimeMs[freqIndex] != 0) { StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, @@ -943,7 +940,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullKernelUidCpuClusterTime( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - mKernelUidCpuClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> { + mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> { for (int i = 0; i < cpuClusterTimesMs.length; i++) { StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); @@ -958,7 +955,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullKernelUidCpuActiveTime( int tagId, long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) { - mKernelUidCpuActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> { + mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> { StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); e.writeInt(uid); e.writeLong((long) cpuActiveTimesMs); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 7c1e6198080d..8d2bab43875d 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1219,13 +1219,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onNotificationExpansionChanged(String key, boolean userAction, - boolean expanded) throws RemoteException { + public void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded, + int location) throws RemoteException { enforceStatusBarService(); long identity = Binder.clearCallingIdentity(); try { mNotificationDelegate.onNotificationExpansionChanged( - key, userAction, expanded); + key, userAction, expanded, location); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java new file mode 100644 index 000000000000..23c042a57ac8 --- /dev/null +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.testharness; + +import android.annotation.Nullable; +import android.app.KeyguardManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.os.BatteryManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.SystemService; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; + +/** + * Manages the Test Harness Mode service for setting up test harness mode on the device. + * + * <p>Test Harness Mode is a feature that allows the user to clean their device, retain ADB keys, + * and provision the device for Instrumentation testing. This means that all parts of the device + * that would otherwise interfere with testing (auto-syncing accounts, package verification, + * automatic updates, etc.) are all disabled by default but may be re-enabled by the user. + */ +public class TestHarnessModeService extends SystemService { + private static final String TAG = TestHarnessModeService.class.getSimpleName(); + private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness"; + + private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal; + + public TestHarnessModeService(Context context) { + super(context); + } + + @Override + public void onStart() { + publishBinderService("testharness", mService); + } + + @Override + public void onBootPhase(int phase) { + switch (phase) { + case PHASE_SYSTEM_SERVICES_READY: + setUpTestHarnessMode(); + break; + case PHASE_BOOT_COMPLETED: + disableAutoSync(); + break; + } + super.onBootPhase(phase); + } + + private void setUpTestHarnessMode() { + Slog.d(TAG, "Setting up test harness mode"); + byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData(); + if (testHarnessModeData == null || testHarnessModeData.length == 0) { + // There's no data to apply, so leave it as-is. + return; + } + PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData); + + SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0"); + writeAdbKeysFile(persistentData); + // Clear out the data block so that we don't revert the ADB keys on every boot. + getPersistentDataBlock().clearTestHarnessModeData(); + + ContentResolver cr = getContext().getContentResolver(); + if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 0) { + // Enable ADB + Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1); + } else { + // ADB is already enabled, we should restart the service so it picks up the new keys + android.os.SystemService.restart("adbd"); + } + + Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0); + Settings.Global.putInt( + cr, + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, + BatteryManager.BATTERY_PLUGGED_ANY); + Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1); + Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); + + setDeviceProvisioned(); + } + + private void disableAutoSync() { + UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser(); + ContentResolver + .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier()); + } + + private void writeAdbKeysFile(PersistentData persistentData) { + Path adbKeys = Paths.get("/data/misc/adb/adb_keys"); + try { + OutputStream fileOutputStream = Files.newOutputStream(adbKeys); + fileOutputStream.write(persistentData.mAdbKeys); + fileOutputStream.close(); + + Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(adbKeys); + permissions.add(PosixFilePermission.GROUP_READ); + Files.setPosixFilePermissions(adbKeys, permissions); + } catch (IOException e) { + Slog.e(TAG, "Failed to set up adb keys", e); + // Note: if a device enters this block, it will remain UNAUTHORIZED in ADB, but all + // other settings will be set up. + } + } + + // Setting the device as provisioned skips the setup wizard. + private void setDeviceProvisioned() { + ContentResolver cr = getContext().getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.DEVICE_PROVISIONED, 1); + Settings.Secure.putIntForUser( + cr, + Settings.Secure.USER_SETUP_COMPLETE, + 1, + UserHandle.USER_CURRENT); + } + + @Nullable + private PersistentDataBlockManagerInternal getPersistentDataBlock() { + if (mPersistentDataBlockManagerInternal == null) { + Slog.d(TAG, "Getting PersistentDataBlockManagerInternal from LocalServices"); + mPersistentDataBlockManagerInternal = + LocalServices.getService(PersistentDataBlockManagerInternal.class); + } + return mPersistentDataBlockManagerInternal; + } + + private final IBinder mService = new Binder() { + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + (new TestHarnessModeShellCommand()) + .exec(this, in, out, err, args, callback, resultReceiver); + } + }; + + private class TestHarnessModeShellCommand extends ShellCommand { + @Override + public int onCommand(String cmd) { + switch (cmd) { + case "enable": + case "restore": + checkPermissions(); + final long originalId = Binder.clearCallingIdentity(); + try { + if (isDeviceSecure()) { + getErrPrintWriter().println( + "Test Harness Mode cannot be enabled if there is a lock " + + "screen"); + return 2; + } + return handleEnable(); + } finally { + Binder.restoreCallingIdentity(originalId); + } + default: + return handleDefaultCommands(cmd); + } + } + + private void checkPermissions() { + getContext().enforceCallingPermission( + android.Manifest.permission.ENABLE_TEST_HARNESS_MODE, + "You must hold android.permission.ENABLE_TEST_HARNESS_MODE " + + "to enable Test Harness Mode"); + } + + private boolean isDeviceSecure() { + UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser(); + KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class); + return keyguardManager.isDeviceSecure(primaryUser.id); + } + + private int handleEnable() { + Path adbKeys = Paths.get("/data/misc/adb/adb_keys"); + if (!Files.exists(adbKeys)) { + // This should only be accessible on eng builds that haven't yet set up ADB keys + getErrPrintWriter() + .println("No ADB keys stored; not enabling test harness mode"); + return 1; + } + + try (InputStream inputStream = Files.newInputStream(adbKeys)) { + long size = Files.size(adbKeys); + byte[] adbKeysBytes = new byte[(int) size]; + int numBytes = inputStream.read(adbKeysBytes); + if (numBytes != size) { + getErrPrintWriter().println("Failed to read all bytes of adb_keys"); + return 1; + } + PersistentData persistentData = new PersistentData(true, adbKeysBytes); + getPersistentDataBlock().setTestHarnessModeData(persistentData.toBytes()); + } catch (IOException e) { + Slog.e(TAG, "Failed to store ADB keys.", e); + getErrPrintWriter().println("Failed to enable Test Harness Mode"); + return 1; + } + + Intent i = new Intent(Intent.ACTION_FACTORY_RESET); + i.setPackage("android"); + i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + i.putExtra(Intent.EXTRA_REASON, TAG); + i.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true); + getContext().sendBroadcastAsUser(i, UserHandle.SYSTEM); + return 0; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("About:"); + pw.println(" Test Harness Mode is a mode that the device can be placed in to prepare"); + pw.println(" the device for running UI tests. The device is placed into this mode by"); + pw.println(" first wiping all data from the device, preserving ADB keys."); + pw.println(); + pw.println(" By default, the following settings are configured:"); + pw.println(" * Package Verifier is disabled"); + pw.println(" * Stay Awake While Charging is enabled"); + pw.println(" * OTA Updates are disabled"); + pw.println(" * Auto-Sync for accounts is disabled"); + pw.println(); + pw.println(" Other apps may configure themselves differently in Test Harness Mode by"); + pw.println(" checking ActivityManager.isRunningInUserTestHarness()"); + pw.println(); + pw.println("Test Harness Mode commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println(" enable|restore"); + pw.println(" Erase all data from this device and enable Test Harness Mode,"); + pw.println(" preserving the stored ADB keys currently on the device and toggling"); + pw.println(" settings in a way that are conducive to Instrumentation testing."); + } + } + + /** + * The object that will serialize/deserialize the Test Harness Mode data to and from the + * persistent data block. + */ + public static class PersistentData { + static final byte VERSION_1 = 1; + + final int mVersion; + final boolean mEnabled; + final byte[] mAdbKeys; + + PersistentData(boolean enabled, byte[] adbKeys) { + this(VERSION_1, enabled, adbKeys); + } + + PersistentData(int version, boolean enabled, byte[] adbKeys) { + this.mVersion = version; + this.mEnabled = enabled; + this.mAdbKeys = adbKeys; + } + + static PersistentData fromBytes(byte[] bytes) { + try { + DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes)); + int version = is.readInt(); + boolean enabled = is.readBoolean(); + int adbKeysLength = is.readInt(); + byte[] adbKeys = new byte[adbKeysLength]; + is.readFully(adbKeys); + return new PersistentData(version, enabled, adbKeys); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + byte[] toBytes() { + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(os); + dos.writeInt(VERSION_1); + dos.writeBoolean(mEnabled); + dos.writeInt(mAdbKeys.length); + dos.write(mAdbKeys); + dos.close(); + return os.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index ced593565983..423ec4c869c1 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -22,10 +22,8 @@ import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; import android.app.admin.DevicePolicyManager; -import android.hardware.biometrics.BiometricSourceType; import android.app.trust.ITrustListener; import android.app.trust.ITrustManager; -import android.app.UserSwitchObserver; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -41,6 +39,7 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.ContentObserver; import android.graphics.drawable.Drawable; +import android.hardware.biometrics.BiometricSourceType; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -72,13 +71,15 @@ import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; + /** * Manages trust agents and trust listeners. @@ -119,7 +120,7 @@ public class TrustManagerService extends SystemService { private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000; private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser"; - private static final long TRUST_TIMEOUT_IN_MILLIS = 20 * 1000; //4 * 60 * 60 * 1000; + private static final long TRUST_TIMEOUT_IN_MILLIS = 4 * 60 * 60 * 1000; private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>(); private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>(); diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index b3eafa4d25ce..45689ce73c9f 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -65,7 +65,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; -import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.utils.ManagedApplicationService; import com.android.server.utils.ManagedApplicationService.BinderChecker; import com.android.server.utils.ManagedApplicationService.LogEvent; @@ -623,14 +622,6 @@ public class VrManagerService extends SystemService } @Override - public void setVrInputMethod(ComponentName componentName) { - enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS); - InputMethodManagerInternal imm = - LocalServices.getService(InputMethodManagerInternal.class); - imm.startVrInputMethodNoCheck(componentName); - } - - @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index caebf1528270..545b69b2fd54 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -49,6 +49,7 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; +import android.view.Display; import android.view.MagnificationSpec; import android.view.Surface; import android.view.Surface.OutOfResourcesException; @@ -83,23 +84,36 @@ final class AccessibilityController { mService = service; } - private DisplayMagnifier mDisplayMagnifier; + private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>(); private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver; - public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) { + public boolean setMagnificationCallbacksLocked(int displayId, + MagnificationCallbacks callbacks) { + boolean result = false; if (callbacks != null) { - if (mDisplayMagnifier != null) { + if (mDisplayMagnifiers.get(displayId) != null) { throw new IllegalStateException("Magnification callbacks already set!"); } - mDisplayMagnifier = new DisplayMagnifier(mService, callbacks); + final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); + if (dc != null) { + final Display display = dc.getDisplay(); + if (display != null && display.getType() != Display.TYPE_OVERLAY) { + mDisplayMagnifiers.put(displayId, new DisplayMagnifier( + mService, dc, display, callbacks)); + result = true; + } + } } else { - if (mDisplayMagnifier == null) { + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier == null) { throw new IllegalStateException("Magnification callbacks already cleared!"); } - mDisplayMagnifier.destroyLocked(); - mDisplayMagnifier = null; + displayMagnifier.destroyLocked(); + mDisplayMagnifiers.remove(displayId); + result = true; } + return result; } public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) { @@ -129,58 +143,72 @@ final class AccessibilityController { } } - public void setMagnificationSpecLocked(MagnificationSpec spec) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.setMagnificationSpecLocked(spec); + public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) { + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.setMagnificationSpecLocked(spec); } - if (mWindowsForAccessibilityObserver != null) { + // TODO: support multi-display for windows observer + if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) { mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked(); } } - public void getMagnificationRegionLocked(Region outMagnificationRegion) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.getMagnificationRegionLocked(outMagnificationRegion); + public void getMagnificationRegionLocked(int displayId, Region outMagnificationRegion) { + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.getMagnificationRegionLocked(outMagnificationRegion); } } - public void onRectangleOnScreenRequestedLocked(Rect rectangle) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle); + public void onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle) { + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.onRectangleOnScreenRequestedLocked(rectangle); } // Not relevant for the window observer. } - public void onWindowLayersChangedLocked() { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.onWindowLayersChangedLocked(); + public void onWindowLayersChangedLocked(int displayId) { + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.onWindowLayersChangedLocked(); } - if (mWindowsForAccessibilityObserver != null) { + // TODO: support multi-display for windows observer + if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) { mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked(); } } public void onRotationChangedLocked(DisplayContent displayContent) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.onRotationChangedLocked(displayContent); + final int displayId = displayContent.getDisplayId(); + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.onRotationChangedLocked(displayContent); } - if (mWindowsForAccessibilityObserver != null) { + // TODO: support multi-display for windows observer + if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) { mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked(); } } public void onAppWindowTransitionLocked(WindowState windowState, int transition) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.onAppWindowTransitionLocked(windowState, transition); + final int displayId = windowState.getDisplayId(); + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.onAppWindowTransitionLocked(windowState, transition); } // Not relevant for the window observer. } public void onWindowTransitionLocked(WindowState windowState, int transition) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.onWindowTransitionLocked(windowState, transition); + final int displayId = windowState.getDisplayId(); + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.onWindowTransitionLocked(windowState, transition); } - if (mWindowsForAccessibilityObserver != null) { + // TODO: support multi-display for windows observer + if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) { mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked(); } } @@ -197,7 +225,6 @@ final class AccessibilityController { } } - public void onSomeWindowResizedOrMovedLocked() { // Not relevant for the display magnifier. @@ -207,29 +234,34 @@ final class AccessibilityController { } /** NOTE: This has to be called within a surface transaction. */ - public void drawMagnifiedRegionBorderIfNeededLocked() { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(); + public void drawMagnifiedRegionBorderIfNeededLocked(int displayId) { + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(); } // Not relevant for the window observer. } public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) { - if (mDisplayMagnifier != null) { - return mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState); + final int displayId = windowState.getDisplayId(); + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + return displayMagnifier.getMagnificationSpecForWindowLocked(windowState); } return null; } public boolean hasCallbacksLocked() { - return (mDisplayMagnifier != null + // TODO: support multi-display for windows observer + return (mDisplayMagnifiers.size() > 0 || mWindowsForAccessibilityObserver != null); } - public void setForceShowMagnifiableBoundsLocked(boolean show) { - if (mDisplayMagnifier != null) { - mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show); - mDisplayMagnifier.showMagnificationBoundsIfNeeded(); + public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) { + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + displayMagnifier.setForceShowMagnifiableBoundsLocked(show); + displayMagnifier.showMagnificationBoundsIfNeeded(); } } @@ -263,6 +295,8 @@ final class AccessibilityController { private final WindowManagerService mService; private final MagnifiedViewport mMagnifedViewport; private final Handler mHandler; + private final DisplayContent mDisplayContent; + private final Display mDisplay; private final MagnificationCallbacks mCallbacks; @@ -271,10 +305,14 @@ final class AccessibilityController { private boolean mForceShowMagnifiableBounds = false; public DisplayMagnifier(WindowManagerService windowManagerService, + DisplayContent displayContent, + Display display, MagnificationCallbacks callbacks) { mContext = windowManagerService.mContext; mService = windowManagerService; mCallbacks = callbacks; + mDisplayContent = displayContent; + mDisplay = display; mHandler = new MyHandler(mService.mH.getLooper()); mMagnifedViewport = new MagnifiedViewport(); mLongAnimationDuration = mContext.getResources().getInteger( @@ -285,7 +323,7 @@ final class AccessibilityController { mMagnifedViewport.updateMagnificationSpecLocked(spec); mMagnifedViewport.recomputeBoundsLocked(); - mService.applyMagnificationSpec(spec); + mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec); mService.scheduleAnimationLocked(); } @@ -482,7 +520,7 @@ final class AccessibilityController { if (mContext.getResources().getConfiguration().isScreenRound()) { mCircularPath = new Path(); - mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + mDisplay.getRealSize(mTempPoint); final int centerXY = mTempPoint.x / 2; mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW); } else { @@ -512,7 +550,7 @@ final class AccessibilityController { } public void recomputeBoundsLocked() { - mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + mDisplay.getRealSize(mTempPoint); final int screenWidth = mTempPoint.x; final int screenHeight = mTempPoint.y; @@ -671,9 +709,8 @@ final class AccessibilityController { } private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) { - final DisplayContent dc = mService.getDefaultDisplayContentLocked(); mTempLayer = 0; - dc.forAllWindows((w) -> { + mDisplayContent.forAllWindows((w) -> { if (w.isOnScreen() && w.isVisibleLw() && (w.mAttrs.alpha != 0) && !w.mWinAnimator.mEnterAnimationPending) { @@ -703,8 +740,9 @@ final class AccessibilityController { public ViewportWindow(Context context) { SurfaceControl surfaceControl = null; try { - mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); - surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay() + mDisplay.getRealSize(mTempPoint); + surfaceControl = mDisplayContent + .makeOverlay() .setName(SURFACE_TITLE) .setBufferSize(mTempPoint.x, mTempPoint.y) // not a typo .setFormat(PixelFormat.TRANSLUCENT) diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index e817dd47e756..65d66f44b5dd 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -558,26 +558,22 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } /** - * Pause all activities in either all of the stacks or just the back stacks. This is done before - * resuming a new activity and to make sure that previously active activities are - * paused in stacks that are no longer visible or in pinned windowing mode. This does not - * pause activities in visible stacks, so if an activity is launched within the same stack/task, - * then we should explicitly pause that stack's top activity. + * Pause all activities in either all of the stacks or just the back stacks. * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). * @param resuming The resuming activity. * @param dontWait The resuming activity isn't going to wait for all activities to be paused * before resuming. - * @return {@code true} if any activity was paused as a result of this call. + * @return true if any activity was paused as a result of this call. */ boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { boolean someActivityPaused = false; for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = mStacks.get(stackNdx); - final ActivityRecord resumedActivity = stack.getResumedActivity(); - if (resumedActivity != null - && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) { + // TODO(b/111541062): Check if resumed activity on this display instead + if (!mRootActivityContainer.isTopDisplayFocusedStack(stack) + && stack.getResumedActivity() != null) { if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + - " mResumedActivity=" + resumedActivity); + " mResumedActivity=" + stack.getResumedActivity()); someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming, dontWait); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6213fa02cb9f..b8634d88319a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1946,84 +1946,30 @@ final class ActivityRecord extends ConfigurationContainer { try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, WindowVisibilityItem.obtain(true /* showWindow */)); - makeActiveIfNeeded(null /* activeActivity*/); - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); - } - } - - /** - * Make activity resumed or paused if needed. - * @param activeActivity an activity that is resumed or just completed pause action. - * We won't change the state of this activity. - */ - boolean makeActiveIfNeeded(ActivityRecord activeActivity) { - if (shouldResumeActivity(activeActivity)) { - if (DEBUG_VISIBILITY) { - Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this); - } - return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */, - null /* options */); - } else if (shouldPauseActivity(activeActivity)) { - if (DEBUG_VISIBILITY) { - Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this); - } - // An activity must be in the {@link PAUSING} state for the system to validate - // the move to {@link PAUSED}. - setState(PAUSING, "makeVisibleIfNeeded"); - try { + if (shouldPauseWhenBecomingVisible()) { + // An activity must be in the {@link PAUSING} state for the system to validate + // the move to {@link PAUSED}. + setState(PAUSING, "makeVisibleIfNeeded"); mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, PauseActivityItem.obtain(finishing, false /* userLeaving */, configChangeFlags, false /* dontReport */)); - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); } - return false; } - /** - * Check if activity should be moved to PAUSED state. The activity: - * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)}) - * - should be non-focusable - * - should not be currently pausing or paused - * @param activeActivity the activity that is active or just completed pause action. We won't - * resume if this activity is active. - */ - private boolean shouldPauseActivity(ActivityRecord activeActivity) { - return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED); - } - - /** - * Check if activity should be moved to RESUMED state. The activity: - * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)}) - * - should be focusable - * @param activeActivity the activity that is active or just completed pause action. We won't - * resume if this activity is active. - */ - private boolean shouldResumeActivity(ActivityRecord activeActivity) { - return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED); - } - - /** - * Check if activity is eligible to be made active (resumed of paused). The activity: - * - should be paused, stopped or stopping - * - should not be the currently active one - * - should be either the topmost in task, or right below the top activity that is finishing - * If all of these conditions are not met at the same time, the activity cannot be made active. - */ - private boolean shouldMakeActive(ActivityRecord activeActivity) { - // If the activity is stopped, stopping, cycle to an active state. We avoid doing + /** Check if activity should be moved to PAUSED state when it becomes visible. */ + private boolean shouldPauseWhenBecomingVisible() { + // If the activity is stopped or stopping, cycle to the paused state. We avoid doing // this when there is an activity waiting to become translucent as the extra binder // calls will lead to noticeable jank. A later call to - // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper - // active state. - if (!isState(RESUMED, PAUSED, STOPPED, STOPPING) - || getActivityStack().mTranslucentActivityWaiting != null) { - return false; - } - - if (this == activeActivity) { + // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper + // paused state. We also avoid doing this for the activity the stack supervisor + // considers the resumed activity, as normal means will bring the activity from STOPPED + // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles. + if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null + || isResumedActivityOnDisplay()) { return false; } @@ -2033,14 +1979,14 @@ final class ActivityRecord extends ConfigurationContainer { throw new IllegalStateException("Activity not found in its task"); } if (positionInTask == task.mActivities.size() - 1) { - // It's the topmost activity in the task - should become resumed now + // It's the topmost activity in the task - should become paused now return true; } // Check if activity above is finishing now and this one becomes the topmost in task. final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1); if (activityAbove.finishing && results == null) { - // We will only allow making active if activity above wasn't launched for result. - // Otherwise it will cause this activity to resume before getting result. + // We will only allow pausing if activity above wasn't launched for result. Otherwise it + // will cause this activity to resume before getting result. return true; } return false; diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 3aef8e1f84bf..891c3da90b93 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -357,11 +357,6 @@ class ActivityStack extends ConfigurationContainer { */ boolean mForceHidden = false; - /** - * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively - */ - boolean mInResumeTopActivity = false; - private boolean mUpdateBoundsDeferred; private boolean mUpdateBoundsDeferredCalled; private boolean mUpdateDisplayedBoundsDeferredCalled; @@ -1737,7 +1732,6 @@ class ActivityStack extends ConfigurationContainer { "Activity paused: token=" + token + ", timeout=" + timeout); final ActivityRecord r = isInStackLocked(token); - if (r != null) { mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { @@ -2094,7 +2088,8 @@ class ActivityStack extends ConfigurationContainer { boolean aboveTop = top != null; final boolean stackShouldBeVisible = shouldBeVisible(starting); boolean behindFullscreenActivity = !stackShouldBeVisible; - boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null; + boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this) + && (isInStackLocked(starting) == null); final boolean isTopNotPinnedStack = isAttached() && getDisplay().isTopNotPinnedStack(this); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { @@ -2155,10 +2150,6 @@ class ActivityStack extends ConfigurationContainer { if (r.handleAlreadyVisible()) { resumeNextActivity = false; } - - if (notifyClients) { - r.makeActiveIfNeeded(starting); - } } else { r.makeVisibleIfNeeded(starting, notifyClients); } @@ -2336,7 +2327,7 @@ class ActivityStack extends ConfigurationContainer { r.setVisible(true); } if (r != starting) { - mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */); + mStackSupervisor.startSpecificActivityLocked(r, andResume, false); return true; } } @@ -2514,7 +2505,7 @@ class ActivityStack extends ConfigurationContainer { */ @GuardedBy("mService") boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) { - if (mInResumeTopActivity) { + if (mStackSupervisor.inResumeTopActivity) { // Don't even start recursing. return false; } @@ -2522,7 +2513,7 @@ class ActivityStack extends ConfigurationContainer { boolean result = false; try { // Protect against recursion. - mInResumeTopActivity = true; + mStackSupervisor.inResumeTopActivity = true; result = resumeTopActivityInnerLocked(prev, options); // When resuming the top activity, it may be necessary to pause the top activity (for @@ -2537,7 +2528,7 @@ class ActivityStack extends ConfigurationContainer { checkReadyForSleep(); } } finally { - mInResumeTopActivity = false; + mStackSupervisor.inResumeTopActivity = false; } return result; @@ -2570,7 +2561,7 @@ class ActivityStack extends ConfigurationContainer { // Find the next top-most activity to resume in this stack that is not finishing and is // focusable. If it is not focusable, we will fall into the case below to resume the // top activity in the next focusable task. - ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); + final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); final boolean hasRunningActivity = next != null; @@ -2658,12 +2649,6 @@ class ActivityStack extends ConfigurationContainer { if (!mRootActivityContainer.allPausedActivitiesComplete()) { if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE, "resumeTopActivityLocked: Skip resume: some activity pausing."); - - // Adding previous activity to the waiting visible list, or it would be stopped - // before top activity being visible. - if (prev != null && !next.nowVisible) { - mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev); - } return false; } @@ -2873,9 +2858,7 @@ class ActivityStack extends ConfigurationContainer { // the screen based on the new activity order. boolean notUpdated = true; - // Activity should also be visible if set mLaunchTaskBehind to true (see - // ActivityRecord#shouldBeVisibleIgnoringKeyguard()). - if (shouldBeVisible(next)) { + if (isFocusedStackOnDisplay()) { // We have special rotation behavior when here is some active activity that // requests specific orientation or Keyguard is locked. Make sure all activity // visibilities are set correctly as well as the transition is updated if needed @@ -4104,12 +4087,6 @@ class ActivityStack extends ConfigurationContainer { mStackSupervisor.mFinishingActivities.add(r); r.resumeKeyDispatchingLocked(); mRootActivityContainer.resumeFocusedStacksTopActivities(); - // If activity was not paused at this point - explicitly pause it to start finishing - // process. Finishing will be completed once it reports pause back. - if (r.isState(RESUMED) && mPausingActivity != null) { - startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */, - false /* dontWait */); - } return r; } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index a83ef34f1cac..3a288ca5560d 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -327,6 +327,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { */ PowerManager.WakeLock mGoingToSleep; + /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */ + boolean inResumeTopActivity; + /** * Temporary rect used during docked stack resize calculation so we don't need to create a new * object each time. diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 43c12064a3c1..280709461c98 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -179,10 +179,7 @@ public class ActivityStartController { .setActivityOptions(options.toBundle()) .execute(); mLastHomeActivityStartRecord = tmpOutRecord[0]; - final ActivityDisplay display = - mService.mRootActivityContainer.getActivityDisplay(displayId); - final ActivityStack homeStack = display != null ? display.getHomeStack() : null; - if (homeStack != null && homeStack.mInResumeTopActivity) { + if (mSupervisor.inResumeTopActivity) { // If we are in resume section already, home activity will be initialized, but not // resumed (to avoid recursive resume) and will stay that way until something pokes it // again. We need to schedule another resume. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 4e2dffc2ba78..d36e545aa74f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1629,7 +1629,7 @@ class ActivityStarter { // Also, we don't want to resume activities in a task that currently has an overlay // as the starting activity just needs to be in the visible paused state until the // over is removed. - mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS); + 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().mDisplayContent.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 780eda49faf4..750c5ca5922e 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -25,7 +25,6 @@ import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; @@ -591,9 +590,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree delayed = runningAppAnimation = true; } final WindowState window = findMainWindow(); - //TODO (multidisplay): Magnification is supported only for the default display. - if (window != null && accessibilityController != null - && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) { + if (window != null && accessibilityController != null) { accessibilityController.onAppWindowTransitionLocked(window, transit); } changed = true; @@ -1975,7 +1972,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) { return STARTING_WINDOW_TYPE_SPLASH_SCREEN; } else if (taskSwitch && allowTaskSnapshot) { - if (mWmService.mLowRamTaskSnapshots) { + if (mWmService.mLowRamTaskSnapshotsAndRecents) { // For low RAM devices, we use the splash screen starting window instead of the // task snapshot starting window. return STARTING_WINDOW_TYPE_SPLASH_SCREEN; @@ -2407,7 +2404,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) { if (!mSurfaceAnimator.hasLeash()) { - t.reparent(mSurfaceControl, newParent.getHandle()); + t.reparent(mSurfaceControl, newParent); } } @@ -2453,7 +2450,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); // Reparent leash to animation bounds layer. - t.reparent(leash, mAnimationBoundsLayer.getHandle()); + t.reparent(leash, mAnimationBoundsLayer); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6527ca0e751d..8fefd352e027 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -30,11 +30,14 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_NAVIGATION_BAR; +import static android.view.InsetsState.TYPE_TOP_BAR; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.View.GONE; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_TOP; @@ -164,6 +167,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; import android.view.View; +import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; @@ -1051,6 +1055,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ void setInsetProvider(@InternalInsetType int type, WindowState win, @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) { + if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL) { + if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) { + return; + } + } mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider); } @@ -1457,11 +1466,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - // TODO (multi-display): Magnification is supported only for the default display. // Announce rotation only if we will not animate as we already have the // windows in final state. Otherwise, we make this call at the rotation end. - if (screenRotationAnimation == null && mWmService.mAccessibilityController != null - && isDefaultDisplay) { + if (screenRotationAnimation == null && mWmService.mAccessibilityController != null) { mWmService.mAccessibilityController.onRotationChangedLocked(this); } } @@ -4581,7 +4588,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Reparents the given surface to mOverlayLayer. */ void reparentToOverlay(Transaction transaction, SurfaceControl surface) { - transaction.reparent(surface, mOverlayLayer.getHandle()); + transaction.reparent(surface, mOverlayLayer); } void applyMagnificationSpec(MagnificationSpec spec) { @@ -4824,11 +4831,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and * {@link #mOverlayLayer} to the specified surfaceControl. * - * @param surfaceControlHandle The handle for the new SurfaceControl, where the DisplayContent's + * @param surfaceControlHandle The new SurfaceControl, where the DisplayContent's * surfaces will be re-parented to. */ - void reparentDisplayContent(IBinder surfaceControlHandle) { - mPendingTransaction.reparent(mWindowingLayer, surfaceControlHandle) - .reparent(mOverlayLayer, surfaceControlHandle); + void reparentDisplayContent(SurfaceControl sc) { + mPendingTransaction.reparent(mWindowingLayer, sc) + .reparent(mOverlayLayer, sc); } } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 786a3064ab4e..3f77e1c6886b 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -120,6 +120,8 @@ class DragState { // A surface used to catch input events for the drag-and-drop operation. SurfaceControl mInputSurface; + private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + private final Rect mTmpClipRect = new Rect(); /** @@ -240,7 +242,7 @@ class DragState { // Clear the internal variables. if (mSurfaceControl != null) { - mSurfaceControl.destroy(); + mTransaction.reparent(mSurfaceControl, null).apply(); mSurfaceControl = null; } if (mAnimator != null && !mAnimationCompleted) { @@ -500,18 +502,13 @@ class DragState { mCurrentY = y; // Move the surface to the given touch - if (SHOW_LIGHT_TRANSACTIONS) Slog.i( - TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked"); - mService.openSurfaceTransaction(); - try { - mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY); - if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " - + mSurfaceControl + ": pos=(" + - (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")"); - } finally { - mService.closeSurfaceTransaction("notifyMoveLw"); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i( - TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked"); + if (SHOW_LIGHT_TRANSACTIONS) { + Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked"); + } + mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply(); + if (SHOW_TRANSACTIONS) { + Slog.i(TAG_WM, " DRAG " + mSurfaceControl + ": pos=(" + (int) (x - mThumbOffsetX) + "," + + (int) (y - mThumbOffsetY) + ")"); } notifyLocationLocked(x, y); } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index d6f161645327..e49e4c0711bd 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; @@ -159,7 +161,7 @@ class InsetsSourceProvider { } boolean isClientVisible() { - return !ViewRootImpl.USE_NEW_INSETS || mClientVisible; + return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible; } private class ControlAdapter implements AnimationAdapter { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index bc01f7c2595d..32dbe96d39f7 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -19,6 +19,9 @@ package com.android.server.wm; import static android.view.InsetsState.TYPE_IME; import static android.view.InsetsState.TYPE_NAVIGATION_BAR; import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.ViewRootImpl.sNewInsetsMode; import android.annotation.NonNull; import android.annotation.Nullable; @@ -160,7 +163,7 @@ class InsetsStateController { } private void onControlChanged(int type, @Nullable WindowState win) { - if (!ViewRootImpl.USE_NEW_INSETS) { + if (sNewInsetsMode == NEW_INSETS_MODE_NONE) { return; } final WindowState previous = mTypeWinControlMap.get(type); diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index c4a853dc3483..9b7214120aed 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -1108,41 +1108,28 @@ class RootActivityContainer extends ConfigurationContainer return false; } - boolean result = false; if (targetStack != null && (targetStack.isTopStackOnDisplay() || getTopDisplayFocusedStack() == targetStack)) { - result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); + return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } + // Resume all top activities in focused stacks on all displays. for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - boolean resumedOnDisplay = false; final ActivityDisplay display = mActivityDisplays.get(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - final ActivityRecord topRunningActivity = stack.topRunningActivityLocked(); - if (!stack.isFocusableAndVisible() || topRunningActivity == null) { - continue; - } - if (topRunningActivity.isState(RESUMED)) { - // Kick off any lingering app transitions form the MoveTaskToFront operation. - stack.executeAppTransition(targetOptions); - } else { - resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target); - } + final ActivityStack focusedStack = display.getFocusedStack(); + if (focusedStack == null) { + continue; } - if (!resumedOnDisplay) { - // In cases when there are no valid activities (e.g. device just booted or launcher - // crashed) it's possible that nothing was resumed on a display. Requesting resume - // of top activity in focused stack explicitly will make sure that at least home - // activity is started and resumed, and no recursion occurs. - final ActivityStack focusedStack = display.getFocusedStack(); - if (focusedStack != null) { - focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions); - } + final ActivityRecord r = focusedStack.topRunningActivityLocked(); + if (r == null || !r.isState(RESUMED)) { + focusedStack.resumeTopActivityUncheckedLocked(null, null); + } else if (r.isState(RESUMED)) { + // Kick off any lingering app transitions form the MoveTaskToFront operation. + focusedStack.executeAppTransition(targetOptions); } } - return result; + return false; } void applySleepTokens(boolean applyToStacks) { diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 9d9b48a5b36a..1a8a9110c649 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -199,7 +199,7 @@ class SurfaceAnimator { * @see #setLayer */ void reparent(Transaction t, SurfaceControl newParent) { - t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent.getHandle()); + t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent); } /** @@ -228,8 +228,8 @@ class SurfaceAnimator { // Cancel source animation, but don't let animation runner cancel the animation. from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */); - t.reparent(surface, mLeash.getHandle()); - t.reparent(mLeash, parent.getHandle()); + t.reparent(surface, mLeash); + t.reparent(mLeash, parent); mAnimatable.onAnimationLeashCreated(t, mLeash); mService.mAnimationTransferMap.put(mAnimation, this); } @@ -275,7 +275,7 @@ class SurfaceAnimator { final boolean destroy = mLeash != null && surface != null && parent != null; if (destroy) { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent"); - t.reparent(surface, parent.getHandle()); + t.reparent(surface, parent); scheduleAnim = true; } mService.mAnimationTransferMap.remove(mAnimation); @@ -308,7 +308,7 @@ class SurfaceAnimator { if (!hidden) { t.show(leash); } - t.reparent(surface, leash.getHandle()); + t.reparent(surface, leash); return leash; } diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 0529ed128130..69dcaf473b12 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -698,14 +698,6 @@ class TaskRecord extends ConfigurationContainer { return false; } - final boolean toTopOfStack = position == MAX_VALUE; - if (toTopOfStack && toStack.getResumedActivity() != null - && toStack.topRunningActivityLocked() != null) { - // Pause the resumed activity on the target stack while re-parenting task on top of it. - toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, - null /* resuming */, false /* pauseImmediately */); - } - final int toStackWindowingMode = toStack.getWindowingMode(); final ActivityRecord topActivity = getTopActivity(); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 01a5622c2b60..beb3d82c05fb 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS; -import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -90,9 +89,8 @@ class TaskSnapshotController { private final WindowManagerService mService; private final TaskSnapshotCache mCache; - private final TaskSnapshotPersister mPersister = new TaskSnapshotPersister( - Environment::getDataSystemCeDirectory); - private final TaskSnapshotLoader mLoader = new TaskSnapshotLoader(mPersister); + private final TaskSnapshotPersister mPersister; + private final TaskSnapshotLoader mLoader; private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>(); private final ArraySet<Task> mTmpTasks = new ArraySet<>(); private final Handler mHandler = new Handler(); @@ -116,6 +114,8 @@ class TaskSnapshotController { TaskSnapshotController(WindowManagerService service) { mService = service; + mPersister = new TaskSnapshotPersister(mService, Environment::getDataSystemCeDirectory); + mLoader = new TaskSnapshotLoader(mPersister); mCache = new TaskSnapshotCache(mService, mLoader); mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_LEANBACK); @@ -270,7 +270,7 @@ class TaskSnapshotController { } final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); - final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f; + final float scaleFraction = isLowRamDevice ? mPersister.getReducedScale() : 1f; task.getBounds(mTmpRect); mTmpRect.offsetTo(0, 0); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java index 0e1570b6e462..d30843b9c589 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static com.android.server.wm.TaskSnapshotPersister.*; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -92,7 +91,7 @@ class TaskSnapshotLoader { proto.topActivityComponent); return new TaskSnapshot(topActivityComponent, buffer, proto.orientation, new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom), - reducedResolution, reducedResolution ? REDUCED_SCALE : 1f, + reducedResolution, reducedResolution ? mPersister.getReducedScale() : 1f, proto.isRealSnapshot, proto.windowingMode, proto.systemUiVisibility, proto.isTranslucent); } catch (IOException e) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java index 24b5b618210e..e6d646cb11d4 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java @@ -52,7 +52,9 @@ class TaskSnapshotPersister { private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM; private static final String SNAPSHOTS_DIRNAME = "snapshots"; private static final String REDUCED_POSTFIX = "_reduced"; - static final float REDUCED_SCALE = ActivityManager.isLowRamDeviceStatic() ? 0.6f : 0.5f; + private static final float REDUCED_SCALE = .5f; + private static final float LOW_RAM_REDUCED_SCALE = .6f; + private static final float LOW_RAM_RECENTS_REDUCED_SCALE = .1f; static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic(); private static final long DELAY_MS = 100; private static final int QUALITY = 95; @@ -71,6 +73,7 @@ class TaskSnapshotPersister { private boolean mStarted; private final Object mLock = new Object(); private final DirectoryResolver mDirectoryResolver; + private final float mReducedScale; /** * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was @@ -79,8 +82,16 @@ class TaskSnapshotPersister { @GuardedBy("mLock") private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>(); - TaskSnapshotPersister(DirectoryResolver resolver) { + TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) { mDirectoryResolver = resolver; + if (service.mLowRamTaskSnapshotsAndRecents) { + // Use very low res snapshots if we are using Go version of recents. + mReducedScale = LOW_RAM_RECENTS_REDUCED_SCALE; + } else { + // TODO(122671846) Replace the low RAM value scale with the above when it is fully built + mReducedScale = ActivityManager.isLowRamDeviceStatic() + ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE; + } } /** @@ -144,6 +155,15 @@ class TaskSnapshotPersister { } } + /** + * Gets the scaling the persister uses for low resolution task snapshots. + * + * @return the reduced scale of task snapshots when they are set to be low res + */ + float getReducedScale() { + return mReducedScale; + } + @TestApi void waitForQueueEmpty() { while (true) { @@ -350,8 +370,8 @@ class TaskSnapshotPersister { final Bitmap reduced = mSnapshot.isReducedResolution() ? swBitmap : Bitmap.createScaledBitmap(swBitmap, - (int) (bitmap.getWidth() * REDUCED_SCALE), - (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */); + (int) (bitmap.getWidth() * mReducedScale), + (int) (bitmap.getHeight() * mReducedScale), true /* filter */); try { FileOutputStream reducedFos = new FileOutputStream(reducedFile); reduced.compress(JPEG, QUALITY, reducedFos); diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index b8a0739b9c83..b8db98b3c2f3 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -167,13 +167,11 @@ public class WindowAnimator { screenRotationAnimation.kill(); displayAnimator.mScreenRotationAnimation = null; - //TODO (multidisplay): Accessibility supported only for the default // display. - if (accessibilityController != null && dc.isDefaultDisplay) { + if (accessibilityController != null) { // We just finished rotation animation which means we did not // announce the rotation and waited for it to end, announce now. - accessibilityController.onRotationChangedLocked( - mService.getDefaultDisplayContentLocked()); + accessibilityController.onRotationChangedLocked(dc); } } } @@ -197,9 +195,8 @@ public class WindowAnimator { screenRotationAnimation.updateSurfaces(mTransaction); } orAnimating(dc.getDockedDividerController().animate(mCurrentTime)); - //TODO (multidisplay): Magnification is supported only for the default display. - if (accessibilityController != null && dc.isDefaultDisplay) { - accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(); + if (accessibilityController != null) { + accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 1691dc0e3806..5267e7e55793 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -212,34 +212,40 @@ public abstract class WindowManagerInternal { * and has access to the raw window data while the accessibility layer serves * as a controller. * + * @param displayId The logical display id. * @param callbacks The callbacks to invoke. + * @return {@code false} if display id is not valid. */ - public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks); + public abstract boolean setMagnificationCallbacks(int displayId, + @Nullable MagnificationCallbacks callbacks); /** * Set by the accessibility layer to specify the magnification and panning to * be applied to all windows that should be magnified. * + * @param displayId The logical display id. * @param spec The MagnficationSpec to set. * - * @see #setMagnificationCallbacks(MagnificationCallbacks) + * @see #setMagnificationCallbacks(int, MagnificationCallbacks) */ - public abstract void setMagnificationSpec(MagnificationSpec spec); + public abstract void setMagnificationSpec(int displayId, MagnificationSpec spec); /** * Set by the accessibility framework to indicate whether the magnifiable regions of the display * should be shown. * + * @param displayId The logical display id. * @param show {@code true} to show magnifiable region bounds, {@code false} to hide */ - public abstract void setForceShowMagnifiableBounds(boolean show); + public abstract void setForceShowMagnifiableBounds(int displayId, boolean show); /** * Obtains the magnification regions. * + * @param displayId The logical display id. * @param magnificationRegion the current magnification region */ - public abstract void getMagnificationRegion(@NonNull Region magnificationRegion); + public abstract void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion); /** * Gets the magnification and translation applied to a window given its token. @@ -251,7 +257,7 @@ public abstract class WindowManagerInternal { * * @return The magnification spec for the window. * - * @see #setMagnificationCallbacks(MagnificationCallbacks) + * @see #setMagnificationCallbacks(int, MagnificationCallbacks) */ public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow( IBinder windowToken); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index fda7a85c1270..c6679a9ad0d7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -433,11 +433,12 @@ public class WindowManagerService extends IWindowManager.Stub final long mDrawLockTimeoutMillis; final boolean mAllowAnimationsInLowPowerMode; + // TODO(b/122671846) Remove the flag below in favor of isLowRam once feature is stable /** * Use very low resolution task snapshots. Replaces task snapshot starting windows with * splashscreen starting windows. Used on low RAM devices to save memory. */ - final boolean mLowRamTaskSnapshots; + final boolean mLowRamTaskSnapshotsAndRecents; final boolean mAllowBootMessages; @@ -955,7 +956,7 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_disableTransitionAnimation); mPerDisplayFocusEnabled = context.getResources().getBoolean( com.android.internal.R.bool.config_perDisplayFocusEnabled); - mLowRamTaskSnapshots = context.getResources().getBoolean( + mLowRamTaskSnapshotsAndRecents = context.getResources().getBoolean( com.android.internal.R.bool.config_lowRamTaskSnapshotsAndRecents); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); @@ -1845,9 +1846,9 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { if (mAccessibilityController != null) { WindowState window = mWindowMap.get(token); - //TODO (multidisplay): Magnification is supported only for the default display. - if (window != null && window.getDisplayId() == DEFAULT_DISPLAY) { - mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle); + if (window != null) { + mAccessibilityController.onRectangleOnScreenRequestedLocked( + window.getDisplayId(), rectangle); } } } @@ -2236,8 +2237,7 @@ public class WindowManagerService extends IWindowManager.Stub win.mDestroying = true; win.destroySurface(false, stopped); } - // TODO(multidisplay): Magnification is supported only for the default display. - if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) { + if (mAccessibilityController != null) { mAccessibilityController.onWindowTransitionLocked(win, transit); } @@ -6822,10 +6822,10 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setMagnificationSpec(MagnificationSpec spec) { + public void setMagnificationSpec(int displayId, MagnificationSpec spec) { synchronized (mGlobalLock) { if (mAccessibilityController != null) { - mAccessibilityController.setMagnificationSpecLocked(spec); + mAccessibilityController.setMagnificationSpecLocked(displayId, spec); } else { throw new IllegalStateException("Magnification callbacks not set!"); } @@ -6836,10 +6836,10 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setForceShowMagnifiableBounds(boolean show) { + public void setForceShowMagnifiableBounds(int displayId, boolean show) { synchronized (mGlobalLock) { if (mAccessibilityController != null) { - mAccessibilityController.setForceShowMagnifiableBoundsLocked(show); + mAccessibilityController.setForceShowMagnifiableBoundsLocked(displayId, show); } else { throw new IllegalStateException("Magnification callbacks not set!"); } @@ -6847,10 +6847,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void getMagnificationRegion(@NonNull Region magnificationRegion) { + public void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion) { synchronized (mGlobalLock) { if (mAccessibilityController != null) { - mAccessibilityController.getMagnificationRegionLocked(magnificationRegion); + mAccessibilityController.getMagnificationRegionLocked(displayId, + magnificationRegion); } else { throw new IllegalStateException("Magnification callbacks not set!"); } @@ -6878,16 +6879,19 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) { + public boolean setMagnificationCallbacks(int displayId, + @Nullable MagnificationCallbacks callbacks) { synchronized (mGlobalLock) { if (mAccessibilityController == null) { mAccessibilityController = new AccessibilityController( WindowManagerService.this); } - mAccessibilityController.setMagnificationCallbacksLocked(callbacks); + boolean result = mAccessibilityController.setMagnificationCallbacksLocked( + displayId, callbacks); if (!mAccessibilityController.hasCallbacksLocked()) { mAccessibilityController = null; } + return result; } } @@ -7266,8 +7270,12 @@ public class WindowManagerService extends IWindowManager.Stub }, false /* traverseTopToBottom */); } - public void applyMagnificationSpec(MagnificationSpec spec) { - getDefaultDisplayContentLocked().applyMagnificationSpec(spec); + /** Called from Accessibility Controller to apply magnification spec */ + public void applyMagnificationSpecLocked(int displayId, MagnificationSpec spec) { + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent != null) { + displayContent.applyMagnificationSpec(spec); + } } SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) { @@ -7326,7 +7334,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void reparentDisplayContent(int displayId, IBinder surfaceControlHandle) { + public void reparentDisplayContent(int displayId, SurfaceControl sc) { final Display display = mDisplayManager.getDisplay(displayId); if (display == null) { throw new IllegalArgumentException( @@ -7343,7 +7351,7 @@ public class WindowManagerService extends IWindowManager.Stub long token = Binder.clearCallingIdentity(); try { DisplayContent displayContent = getDisplayContentOrCreate(displayId, null); - displayContent.reparentDisplayContent(surfaceControlHandle); + displayContent.reparentDisplayContent(sc); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 8f86c003e4b0..ce5eb8476764 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1607,8 +1607,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWmService.mAccessibilityController; final int winTransit = TRANSIT_EXIT; mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */); - //TODO (multidisplay): Magnification is supported only for the default - if (accessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) { + if (accessibilityController != null) { accessibilityController.onWindowTransitionLocked(this, winTransit); } } @@ -1625,8 +1624,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (isVisibleNow()) { mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false); - //TODO (multidisplay): Magnification is supported only for the default - if (mWmService.mAccessibilityController != null && isDefaultDisplay()) { + if (mWmService.mAccessibilityController != null) { mWmService.mAccessibilityController.onWindowTransitionLocked(this, TRANSIT_EXIT); } changed = true; @@ -1915,9 +1913,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP setDisplayLayoutNeeded(); mWmService.requestTraversal(); } - //TODO (multidisplay): Magnification is supported only for the default display. - if (mWmService.mAccessibilityController != null - && displayId == DEFAULT_DISPLAY) { + if (mWmService.mAccessibilityController != null) { mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit); } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index fb5c556e1643..6b4d6d2bfb3c 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; import static android.view.WindowManager.LayoutParams.FLAG_SCALED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; @@ -1308,9 +1307,7 @@ class WindowStateAnimator { transit = WindowManagerPolicy.TRANSIT_SHOW; } applyAnimationLocked(transit, true); - //TODO (multidisplay): Magnification is supported only for the default display. - if (mService.mAccessibilityController != null - && mWin.getDisplayId() == DEFAULT_DISPLAY) { + if (mService.mAccessibilityController != null) { mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit); } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index fb00aebb622f..3729eaf63ddb 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -53,6 +53,7 @@ cc_library_static { ], include_dirs: [ + "bionic/libc/private", "frameworks/base/libs", "frameworks/native/services", "system/gatekeeper/include", diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index dc0d53b41595..159a4960731d 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -24,6 +24,8 @@ #include <sensorservice/SensorService.h> #include <sensorservicehidl/SensorManager.h> +#include <bionic_malloc.h> + #include <cutils/properties.h> #include <utils/Log.h> #include <utils/misc.h> @@ -64,6 +66,11 @@ static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject / ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err); } +static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */, + jobject /* clazz */) { + android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0); +} + /* * JNI registration. */ @@ -71,6 +78,8 @@ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService }, { "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices }, + { "initZygoteChildHeapProfiling", "()V", + (void*) android_server_SystemServer_initZygoteChildHeapProfiling }, }; int register_android_server_SystemServer(JNIEnv* env) diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f8e24ff03410..fb7e47d70d64 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4223,7 +4223,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordHistoryLength(ComponentName who, int length, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4246,13 +4246,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getPasswordHistoryLength(ComponentName who, int userHandle, boolean parent) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + return 0; + } return getStrictestPasswordRequirement(who, userHandle, parent, admin -> admin.passwordHistoryLength, PASSWORD_QUALITY_UNSPECIFIED); } @Override public void setPasswordExpirationTimeout(ComponentName who, long timeout, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4288,7 +4291,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public long getPasswordExpirationTimeout(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0L; } enforceFullCrossUsersPermission(userHandle); @@ -4423,7 +4426,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long getPasswordExpiration(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0L; } enforceFullCrossUsersPermission(userHandle); @@ -4770,6 +4773,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) { + if (!mLockPatternUtils.hasSecureLockScreen()) { + return 0; + } enforceFullCrossUsersPermission(userHandle); synchronized (getLockObject()) { if (!isCallerWithSystemUid()) { @@ -4789,7 +4795,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -4815,7 +4821,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return 0; } enforceFullCrossUsersPermission(userHandle); @@ -4829,7 +4835,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return UserHandle.USER_NULL; } enforceFullCrossUsersPermission(userHandle); @@ -4910,6 +4916,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException { + if (!mLockPatternUtils.hasSecureLockScreen()) { + Slog.w(LOG_TAG, "Cannot reset password when the device has no lock screen"); + return false; + } + final int callingUid = mInjector.binderGetCallingUid(); final int userHandle = mInjector.userHandleGetCallingUserId(); @@ -5252,7 +5263,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setRequiredStrongAuthTimeout(ComponentName who, long timeoutMs, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(who, "ComponentName is null"); @@ -5285,7 +5296,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public long getRequiredStrongAuthTimeout(ComponentName who, int userId, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS; } enforceFullCrossUsersPermission(userId); @@ -6494,7 +6505,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @Override public void setActivePasswordState(PasswordMetrics metrics, int userHandle) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } enforceFullCrossUsersPermission(userHandle); @@ -6514,7 +6525,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void reportPasswordChanged(@UserIdInt int userId) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } enforceFullCrossUsersPermission(userId); @@ -7656,18 +7667,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // Shutting down backup manager service permanently. - long ident = mInjector.binderClearCallingIdentity(); - try { - if (mInjector.getIBackupManager() != null) { - mInjector.getIBackupManager() - .setBackupServiceActive(UserHandle.USER_SYSTEM, false); - } - } catch (RemoteException e) { - throw new IllegalStateException("Failed deactivating backup service.", e); - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } - + toggleBackupServiceActive(UserHandle.USER_SYSTEM, /* makeActive= */ false); if (isAdb()) { // Log device owner provisioning was started using adb. MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER); @@ -7695,7 +7695,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveUserRestrictionsLocked(userId); } - ident = mInjector.binderClearCallingIdentity(); + long ident = mInjector.binderClearCallingIdentity(); try { // TODO Send to system too? sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); @@ -7952,6 +7952,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + // Shutting down backup manager service permanently. + toggleBackupServiceActive(userHandle, /* makeActive= */ false); + mOwners.setProfileOwner(who, ownerName, userHandle); mOwners.writeProfileOwner(userHandle); Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle); @@ -7975,6 +7978,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + + private void toggleBackupServiceActive(int userId, boolean makeActive) { + // Shutting down backup manager service permanently. + enforceUserUnlocked(userId); + long ident = mInjector.binderClearCallingIdentity(); + try { + if (mInjector.getIBackupManager() != null) { + mInjector.getIBackupManager() + .setBackupServiceActive(userId, makeActive); + } + } catch (RemoteException e) { + throw new IllegalStateException("Failed deactivating backup service.", e); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + + } + @Override public void clearProfileOwner(ComponentName who) { if (!mHasFeature) { @@ -8790,7 +8811,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, PersistableBundle args, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } Preconditions.checkNotNull(admin, "admin is null"); @@ -8807,7 +8828,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin, ComponentName agent, int userHandle, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return null; } Preconditions.checkNotNull(agent, "agent null"); @@ -12727,22 +12748,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(admin); - synchronized (getLockObject()) { - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - } - - final long ident = mInjector.binderClearCallingIdentity(); - try { - IBackupManager ibm = mInjector.getIBackupManager(); - if (ibm != null) { - ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, enabled); - } - } catch (RemoteException e) { - throw new IllegalStateException( - "Failed " + (enabled ? "" : "de") + "activating backup service.", e); - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } + enforceProfileOrDeviceOwner(admin); + int userId = mInjector.userHandleGetCallingUserId(); + toggleBackupServiceActive(userId, enabled); } @Override @@ -12751,11 +12759,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return true; } + + enforceProfileOrDeviceOwner(admin); synchronized (getLockObject()) { try { - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); IBackupManager ibm = mInjector.getIBackupManager(); - return ibm != null && ibm.isBackupServiceActive(UserHandle.USER_SYSTEM); + return ibm != null && ibm.isBackupServiceActive( + mInjector.userHandleGetCallingUserId()); } catch (RemoteException e) { throw new IllegalStateException("Failed requesting backup service state.", e); } @@ -13009,7 +13019,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new IllegalStateException("logging is not available"); } if (mNetworkLogger != null) { - return mNetworkLogger.forceBatchFinalization(); + final long ident = mInjector.binderClearCallingIdentity(); + try { + return mNetworkLogger.forceBatchFinalization(); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } } return 0; } @@ -13211,7 +13226,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean setResetPasswordToken(ComponentName admin, byte[] token) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return false; } if (token == null || token.length < 32) { @@ -13239,7 +13254,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean clearResetPasswordToken(ComponentName admin) { - if (!mHasFeature) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return false; } synchronized (getLockObject()) { @@ -13265,6 +13280,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isResetPasswordTokenActive(ComponentName admin) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { + return false; + } synchronized (getLockObject()) { final int userHandle = mInjector.userHandleGetCallingUserId(); getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -13286,6 +13304,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token, int flags) { + if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { + return false; + } Preconditions.checkNotNull(token); synchronized (getLockObject()) { final int userHandle = mInjector.userHandleGetCallingUserId(); diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java index 238f07715ce3..e99dd4f1cbae 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,9 +21,12 @@ import android.annotation.Nullable; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteCursor; +import android.database.sqlite.SQLiteCursorDriver; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQuery; import android.net.NetworkUtils; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.Status; @@ -35,6 +38,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; /** * Encapsulating class for using the SQLite database backing the memory store. @@ -46,6 +50,9 @@ import java.util.List; */ public class IpMemoryStoreDatabase { private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName(); + // A pair of NetworkAttributes objects is group-close if the confidence that they are + // the same is above this cutoff. See NetworkAttributes and SameL3NetworkResponse. + private static final float GROUPCLOSE_CONFIDENCE = 0.5f; /** * Contract class for the Network Attributes table. @@ -134,12 +141,14 @@ public class IpMemoryStoreDatabase { } /** Called when the database is created */ + @Override public void onCreate(@NonNull final SQLiteDatabase db) { db.execSQL(NetworkAttributesContract.CREATE_TABLE); db.execSQL(PrivateDataContract.CREATE_TABLE); } /** Called when the database is upgraded */ + @Override public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { // No upgrade supported yet. @@ -149,6 +158,7 @@ public class IpMemoryStoreDatabase { } /** Called when the database is downgraded */ + @Override public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { // Downgrades always nuke all data and recreate an empty table. @@ -184,30 +194,35 @@ public class IpMemoryStoreDatabase { return addresses; } + @NonNull + private static ContentValues toContentValues(@Nullable final NetworkAttributes attributes) { + final ContentValues values = new ContentValues(); + if (null == attributes) return values; + if (null != attributes.assignedV4Address) { + values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, + NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); + } + if (null != attributes.groupHint) { + values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); + } + if (null != attributes.dnsAddresses) { + values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES, + encodeAddressList(attributes.dnsAddresses)); + } + if (null != attributes.mtu) { + values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu); + } + return values; + } + // Convert a NetworkAttributes object to content values to store them in a table compliant // with the contract defined in NetworkAttributesContract. @NonNull private static ContentValues toContentValues(@NonNull final String key, @Nullable final NetworkAttributes attributes, final long expiry) { - final ContentValues values = new ContentValues(); + final ContentValues values = toContentValues(attributes); values.put(NetworkAttributesContract.COLNAME_L2KEY, key); values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry); - if (null != attributes) { - if (null != attributes.assignedV4Address) { - values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, - NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); - } - if (null != attributes.groupHint) { - values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); - } - if (null != attributes.dnsAddresses) { - values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES, - encodeAddressList(attributes.dnsAddresses)); - } - if (null != attributes.mtu) { - values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu); - } - } return values; } @@ -225,6 +240,32 @@ public class IpMemoryStoreDatabase { return values; } + @Nullable + private static NetworkAttributes readNetworkAttributesLine(@NonNull final Cursor cursor) { + // Make sure the data hasn't expired + final long expiry = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, -1L); + if (expiry < System.currentTimeMillis()) return null; + + final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); + final int assignedV4AddressInt = getInt(cursor, + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); + final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); + final byte[] dnsAddressesBlob = + getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); + final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); + if (0 != assignedV4AddressInt) { + builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); + } + builder.setGroupHint(groupHint); + if (null != dnsAddressesBlob) { + builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); + } + if (mtu >= 0) { + builder.setMtu(mtu); + } + return builder.build(); + } + private static final String[] EXPIRY_COLUMN = new String[] { NetworkAttributesContract.COLNAME_EXPIRYDATE }; @@ -247,7 +288,9 @@ public class IpMemoryStoreDatabase { // result here. 0 results means the key was not found. if (cursor.getCount() != 1) return EXPIRY_ERROR; cursor.moveToFirst(); - return cursor.getLong(0); // index in the EXPIRY_COLUMN array + final long result = cursor.getLong(0); // index in the EXPIRY_COLUMN array + cursor.close(); + return result; } static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive @@ -308,30 +351,9 @@ public class IpMemoryStoreDatabase { // result here. 0 results means the key was not found. if (cursor.getCount() != 1) return null; cursor.moveToFirst(); - - // Make sure the data hasn't expired - final long expiry = cursor.getLong( - cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE)); - if (expiry < System.currentTimeMillis()) return null; - - final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); - final int assignedV4AddressInt = getInt(cursor, - NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); - final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); - final byte[] dnsAddressesBlob = - getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); - final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); - if (0 != assignedV4AddressInt) { - builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); - } - builder.setGroupHint(groupHint); - if (null != dnsAddressesBlob) { - builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); - } - if (mtu >= 0) { - builder.setMtu(mtu); - } - return builder.build(); + final NetworkAttributes attributes = readNetworkAttributesLine(cursor); + cursor.close(); + return attributes; } private static final String[] DATA_COLUMN = new String[] { @@ -353,20 +375,139 @@ public class IpMemoryStoreDatabase { // get more than one result here. 0 results means the key was not found. if (cursor.getCount() != 1) return null; cursor.moveToFirst(); - return cursor.getBlob(0); // index in the DATA_COLUMN array + final byte[] result = cursor.getBlob(0); // index in the DATA_COLUMN array + cursor.close(); + return result; + } + + /** + * The following is a horrible hack that is necessary because the Android SQLite API does not + * have a way to query a binary blob. This, almost certainly, is an overlook. + * + * The Android SQLite API has two family of methods : one for query that returns data, and + * one for more general SQL statements that can execute any statement but may not return + * anything. All the query methods, however, take only String[] for the arguments. + * + * In principle it is simple to write a function that will encode the binary blob in the + * way SQLite expects it. However, because the API forces the argument to be coerced into a + * String, the SQLiteQuery object generated by the default query methods will bind all + * arguments as Strings and SQL will *sanitize* them. This works okay for numeric types, + * but the format for blobs is x'<hex string>'. Note the presence of quotes, which will + * be sanitized, changing the contents of the field, and the query will fail to match the + * blob. + * + * As far as I can tell, there are two possible ways around this problem. The first one + * is to put the data in the query string and eschew it being an argument. This would + * require doing the sanitizing by hand. The other is to call bindBlob directly on the + * generated SQLiteQuery object, which not only is a lot less dangerous than rolling out + * sanitizing, but also will do the right thing if the underlying format ever changes. + * + * But none of the methods that take an SQLiteQuery object can return data ; this *must* + * be called with SQLiteDatabase#query. This object is not accessible from outside. + * However, there is a #query version that accepts a CursorFactory and this is pretty + * straightforward to implement as all the arguments are coming in and the SQLiteCursor + * class is public API. + * With this, it's possible to intercept the SQLiteQuery object, and assuming the args + * are available, to bind them directly and work around the API's oblivious coercion into + * Strings. + * + * This is really sad, but I don't see another way of having this work than this or the + * hand-rolled sanitizing, and this is the lesser evil. + */ + private static class CustomCursorFactory implements SQLiteDatabase.CursorFactory { + @NonNull + private final ArrayList<Object> mArgs; + CustomCursorFactory(@NonNull final ArrayList<Object> args) { + mArgs = args; + } + @Override + public Cursor newCursor(final SQLiteDatabase db, final SQLiteCursorDriver masterQuery, + final String editTable, + final SQLiteQuery query) { + int index = 1; // bind is 1-indexed + for (final Object arg : mArgs) { + if (arg instanceof String) { + query.bindString(index++, (String) arg); + } else if (arg instanceof Long) { + query.bindLong(index++, (Long) arg); + } else if (arg instanceof Integer) { + query.bindLong(index++, Long.valueOf((Integer) arg)); + } else if (arg instanceof byte[]) { + query.bindBlob(index++, (byte[]) arg); + } else { + throw new IllegalStateException("Unsupported type CustomCursorFactory " + + arg.getClass().toString()); + } + } + return new SQLiteCursor(masterQuery, editTable, query); + } + } + + // Returns the l2key of the closest match, if and only if it matches + // closely enough (as determined by group-closeness). + @Nullable + static String findClosestAttributes(@NonNull final SQLiteDatabase db, + @NonNull final NetworkAttributes attr) { + if (attr.isEmpty()) return null; + final ContentValues values = toContentValues(attr); + + // Build the selection and args. To cut down on the number of lines to search, limit + // the search to those with at least one argument equals to the requested attributes. + // This works only because null attributes match only will not result in group-closeness. + final StringJoiner sj = new StringJoiner(" OR "); + final ArrayList<Object> args = new ArrayList<>(); + args.add(System.currentTimeMillis()); + for (final String field : values.keySet()) { + sj.add(field + " = ?"); + args.add(values.get(field)); + } + + final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND (" + + sj.toString() + ")"; + final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args), + false, // distinct + NetworkAttributesContract.TABLENAME, + null, // columns, null means everything + selection, // selection + null, // selectionArgs, horrendously passed to the cursor factory instead + null, // groupBy + null, // having + null, // orderBy + null); // limit + if (cursor.getCount() <= 0) return null; + cursor.moveToFirst(); + String bestKey = null; + float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this. + while (!cursor.isAfterLast()) { + final NetworkAttributes read = readNetworkAttributesLine(cursor); + final float confidence = read.getNetworkGroupSamenessConfidence(attr); + if (confidence > bestMatchConfidence) { + bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY); + bestMatchConfidence = confidence; + } + cursor.moveToNext(); + } + cursor.close(); + return bestKey; } // Helper methods - static String getString(final Cursor cursor, final String columnName) { + private static String getString(final Cursor cursor, final String columnName) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getString(columnIndex) : null; } - static byte[] getBlob(final Cursor cursor, final String columnName) { + private static byte[] getBlob(final Cursor cursor, final String columnName) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null; } - static int getInt(final Cursor cursor, final String columnName, final int defaultValue) { + private static int getInt(final Cursor cursor, final String columnName, + final int defaultValue) { final int columnIndex = cursor.getColumnIndex(columnName); return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue; } + private static long getLong(final Cursor cursor, final String columnName, + final long defaultValue) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue; + } } diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java index 444b299d49e6..d43dc6a24260 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java @@ -37,6 +37,7 @@ import android.net.ipmemorystore.IOnSameNetworkResponseListener; import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.NetworkAttributesParcelable; +import android.net.ipmemorystore.SameL3NetworkResponse; import android.net.ipmemorystore.Status; import android.net.ipmemorystore.StatusParcelable; import android.net.ipmemorystore.Utils; @@ -249,9 +250,26 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { * Through the listener, returns the L2 key if one matched, or null. */ @Override - public void findL2Key(@NonNull final NetworkAttributesParcelable attributes, - @NonNull final IOnL2KeyResponseListener listener) { - // TODO : implement this. + public void findL2Key(@Nullable final NetworkAttributesParcelable attributes, + @Nullable final IOnL2KeyResponseListener listener) { + if (null == listener) return; + mExecutor.execute(() -> { + try { + if (null == attributes) { + listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + return; + } + if (null == mDb) { + listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + return; + } + final String key = IpMemoryStoreDatabase.findClosestAttributes(mDb, + new NetworkAttributes(attributes)); + listener.onL2KeyResponse(makeStatus(SUCCESS), key); + } catch (final RemoteException e) { + // Client at the other end died + } + }); } /** @@ -264,9 +282,40 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { * Through the listener, a SameL3NetworkResponse containing the answer and confidence. */ @Override - public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2, - @NonNull final IOnSameNetworkResponseListener listener) { - // TODO : implement this. + public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2, + @Nullable final IOnSameNetworkResponseListener listener) { + if (null == listener) return; + mExecutor.execute(() -> { + try { + if (null == l2Key1 || null == l2Key2) { + listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + return; + } + if (null == mDb) { + listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null); + return; + } + try { + final NetworkAttributes attr1 = + IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key1); + final NetworkAttributes attr2 = + IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2); + if (null == attr1 || null == attr2) { + listener.onSameNetworkResponse(makeStatus(SUCCESS), + new SameL3NetworkResponse(l2Key1, l2Key2, + -1f /* never connected */).toParcelable()); + return; + } + final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2); + listener.onSameNetworkResponse(makeStatus(SUCCESS), + new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable()); + } catch (Exception e) { + listener.onSameNetworkResponse(makeStatus(ERROR_GENERIC), null); + } + } catch (final RemoteException e) { + // Client at the other end died + } + }); } /** @@ -285,21 +334,22 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { mExecutor.execute(() -> { try { if (null == l2Key) { - listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null); + listener.onNetworkAttributesRetrieved( + makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null); return; } if (null == mDb) { - listener.onL2KeyResponse(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, - null); + listener.onNetworkAttributesRetrieved( + makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, null); return; } try { final NetworkAttributes attributes = IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key); - listener.onL2KeyResponse(makeStatus(SUCCESS), l2Key, + listener.onNetworkAttributesRetrieved(makeStatus(SUCCESS), l2Key, null == attributes ? null : attributes.toParcelable()); } catch (final Exception e) { - listener.onL2KeyResponse(makeStatus(ERROR_GENERIC), l2Key, null); + listener.onNetworkAttributesRetrieved(makeStatus(ERROR_GENERIC), l2Key, null); } } catch (final RemoteException e) { // Client at the other end died diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index cef47caff740..623990ba211a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -136,6 +136,7 @@ import com.android.server.stats.StatsCompanionService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; import com.android.server.telecom.TelecomLoaderService; +import com.android.server.testharness.TestHarnessModeService; import com.android.server.textclassifier.TextClassificationManagerService; import com.android.server.textservices.TextServicesManagerService; import com.android.server.trust.TrustManagerService; @@ -327,6 +328,11 @@ public final class SystemServer { private static native void startHidlServices(); /** + * Mark this process' heap as profileable. Only for debug builds. + */ + private static native void initZygoteChildHeapProfiling(); + + /** * The main entry point from zygote. */ public static void main(String[] args) { @@ -448,6 +454,11 @@ public final class SystemServer { // Initialize native services. System.loadLibrary("android_servers"); + // Debug builds - allow heap profiling. + if (Build.IS_DEBUGGABLE) { + initZygoteChildHeapProfiling(); + } + // Check whether we failed to shut down last time we tried. // This call may not return. performPendingShutdown(); @@ -1147,6 +1158,10 @@ public final class SystemServer { traceBeginAndSlog("StartPersistentDataBlock"); mSystemServiceManager.startService(PersistentDataBlockService.class); traceEnd(); + + traceBeginAndSlog("StartTestHarnessMode"); + mSystemServiceManager.startService(TestHarnessModeService.class); + traceEnd(); } if (hasPdb || OemLockService.isHalPresent()) { diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 3351b25d0eec..494395285f5b 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -20,6 +20,7 @@ import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE; import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION; +import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; @@ -55,7 +56,6 @@ import android.os.PowerManager; import android.os.SystemClock; import android.system.ErrnoException; import android.system.Os; -import android.system.PacketSocketAddress; import android.text.format.DateUtils; import android.util.Log; import android.util.Pair; @@ -72,6 +72,7 @@ import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.BufferUnderflowException; @@ -472,7 +473,7 @@ public class ApfFilter { installNewProgramLocked(); } socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6); - PacketSocketAddress addr = new PacketSocketAddress( + SocketAddress addr = makePacketSocketAddress( (short) ETH_P_IPV6, mInterfaceParams.index); Os.bind(socket, addr); NetworkUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat); diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 15acc0ede8b2..04ac9a301813 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -28,6 +28,7 @@ import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; import static android.net.dhcp.DhcpPacket.INADDR_ANY; import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; +import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ETH_P_IP; @@ -35,7 +36,6 @@ import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_DGRAM; import static android.system.OsConstants.SOCK_RAW; import static android.system.OsConstants.SOL_SOCKET; -import static android.system.OsConstants.SO_BINDTODEVICE; import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_RCVBUF; import static android.system.OsConstants.SO_REUSEADDR; @@ -44,23 +44,22 @@ import android.content.Context; import android.net.DhcpResults; import android.net.NetworkUtils; import android.net.TrafficStats; +import android.net.ip.IpClient; import android.net.metrics.DhcpClientEvent; import android.net.metrics.DhcpErrorEvent; import android.net.metrics.IpConnectivityLog; import android.net.util.InterfaceParams; +import android.net.util.SocketUtils; import android.os.Message; import android.os.SystemClock; import android.system.ErrnoException; import android.system.Os; -import android.system.PacketSocketAddress; import android.util.EventLog; import android.util.Log; import android.util.SparseArray; -import android.util.TimeUtils; import com.android.internal.util.HexDump; import com.android.internal.util.MessageUtils; -import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; @@ -70,6 +69,7 @@ import libcore.io.IoBridge; import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet4Address; +import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.Arrays; @@ -117,7 +117,8 @@ public class DhcpClient extends StateMachine { // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter. private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; - private static final int PUBLIC_BASE = Protocol.BASE_DHCP; + // DhcpClient uses IpClient's handler. + private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; /* Commands from controller to start/stop DHCP */ public static final int CMD_START_DHCP = PUBLIC_BASE + 1; @@ -147,7 +148,7 @@ public class DhcpClient extends StateMachine { public static final int DHCP_FAILURE = 2; // Internal messages. - private static final int PRIVATE_BASE = Protocol.BASE_DHCP + 100; + private static final int PRIVATE_BASE = IpClient.DHCPCLIENT_CMD_BASE + 100; private static final int CMD_KICK = PRIVATE_BASE + 1; private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2; private static final int CMD_TIMEOUT = PRIVATE_BASE + 3; @@ -204,7 +205,7 @@ public class DhcpClient extends StateMachine { private InterfaceParams mIface; // TODO: MacAddress-ify more of this class hierarchy. private byte[] mHwAddr; - private PacketSocketAddress mInterfaceBroadcastAddr; + private SocketAddress mInterfaceBroadcastAddr; private int mTransactionId; private long mTransactionStartMillis; private DhcpResults mDhcpLease; @@ -293,7 +294,7 @@ public class DhcpClient extends StateMachine { } mHwAddr = mIface.macAddr.toByteArray(); - mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST); + mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST); return true; } @@ -309,7 +310,7 @@ public class DhcpClient extends StateMachine { private boolean initPacketSocket() { try { mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP); - PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.index); + SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index); Os.bind(mPacketSock, addr); NetworkUtils.attachDhcpFilter(mPacketSock); } catch(SocketException|ErrnoException e) { @@ -323,12 +324,11 @@ public class DhcpClient extends StateMachine { final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP); try { mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName); Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); - Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName); Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); - NetworkUtils.protectFromVpn(mUdpSock); } catch(SocketException|ErrnoException e) { Log.e(TAG, "Error creating UDP socket", e); return false; @@ -544,13 +544,13 @@ public class DhcpClient extends StateMachine { private String messageToString(Message message) { long now = SystemClock.uptimeMillis(); - StringBuilder b = new StringBuilder(" "); - TimeUtils.formatDuration(message.getWhen() - now, b); - b.append(" ").append(messageName(message.what)) + return new StringBuilder(" ") + .append(message.getWhen() - now) + .append(messageName(message.what)) .append(" ").append(message.arg1) .append(" ").append(message.arg2) - .append(" ").append(message.obj); - return b.toString(); + .append(" ").append(message.obj) + .toString(); } @Override diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java index bef425a37da4..385dd52e4576 100644 --- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java +++ b/services/net/java/android/net/ip/ConnectivityPacketTracker.java @@ -16,6 +16,7 @@ package android.net.ip; +import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ALL; @@ -28,7 +29,6 @@ import android.net.util.PacketReader; import android.os.Handler; import android.system.ErrnoException; import android.system.Os; -import android.system.PacketSocketAddress; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -103,7 +103,7 @@ public class ConnectivityPacketTracker { try { s = Os.socket(AF_PACKET, SOCK_RAW, 0); NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER); - Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mInterface.index)); + Os.bind(s, makePacketSocketAddress((short) ETH_P_ALL, mInterface.index)); } catch (ErrnoException | IOException e) { logError("Failed to create packet tracking socket: ", e); closeFd(s); diff --git a/services/net/java/android/net/ip/InterfaceController.java b/services/net/java/android/net/ip/InterfaceController.java index 55dfcef81890..b3af67cdbdc3 100644 --- a/services/net/java/android/net/ip/InterfaceController.java +++ b/services/net/java/android/net/ip/InterfaceController.java @@ -18,13 +18,14 @@ package android.net.ip; import android.net.INetd; import android.net.InterfaceConfiguration; +import android.net.InterfaceConfigurationParcel; import android.net.LinkAddress; import android.net.util.SharedLog; -import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.system.OsConstants; +import java.net.Inet4Address; import java.net.InetAddress; @@ -39,76 +40,96 @@ public class InterfaceController { private final static boolean DBG = false; private final String mIfName; - private final INetworkManagementService mNMS; private final INetd mNetd; private final SharedLog mLog; - public InterfaceController(String ifname, INetworkManagementService nms, INetd netd, - SharedLog log) { + public InterfaceController(String ifname, INetd netd, SharedLog log) { mIfName = ifname; - mNMS = nms; mNetd = netd; mLog = log; } - public boolean setIPv4Address(LinkAddress address) { - final InterfaceConfiguration ifcg = new InterfaceConfiguration(); - ifcg.setLinkAddress(address); + private boolean setInterfaceConfig(InterfaceConfiguration config) { + final InterfaceConfigurationParcel cfgParcel = config.toParcel(mIfName); + try { - mNMS.setInterfaceConfig(mIfName, ifcg); - if (DBG) mLog.log("IPv4 configuration succeeded"); - } catch (IllegalStateException | RemoteException e) { - logError("IPv4 configuration failed: %s", e); + mNetd.interfaceSetCfg(cfgParcel); + } catch (RemoteException | ServiceSpecificException e) { + logError("Setting IPv4 address to %s/%d failed: %s", + cfgParcel.ipv4Addr, cfgParcel.prefixLength, e); return false; } return true; } - public boolean clearIPv4Address() { - try { - final InterfaceConfiguration ifcg = new InterfaceConfiguration(); - ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0")); - mNMS.setInterfaceConfig(mIfName, ifcg); - } catch (IllegalStateException | RemoteException e) { - logError("Failed to clear IPv4 address on interface %s: %s", mIfName, e); + /** + * Set the IPv4 address of the interface. + */ + public boolean setIPv4Address(LinkAddress address) { + if (!(address.getAddress() instanceof Inet4Address)) { return false; } - return true; + final InterfaceConfiguration ifConfig = new InterfaceConfiguration(); + ifConfig.setLinkAddress(address); + return setInterfaceConfig(ifConfig); } - public boolean enableIPv6() { + /** + * Clear the IPv4Address of the interface. + */ + public boolean clearIPv4Address() { + final InterfaceConfiguration ifConfig = new InterfaceConfiguration(); + ifConfig.setLinkAddress(new LinkAddress("0.0.0.0/0")); + return setInterfaceConfig(ifConfig); + } + + private boolean setEnableIPv6(boolean enabled) { try { - mNMS.enableIpv6(mIfName); - } catch (IllegalStateException | RemoteException e) { - logError("enabling IPv6 failed: %s", e); + mNetd.interfaceSetEnableIPv6(mIfName, enabled); + } catch (RemoteException | ServiceSpecificException e) { + logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e); return false; } return true; } + /** + * Enable IPv6 on the interface. + */ + public boolean enableIPv6() { + return setEnableIPv6(true); + } + + /** + * Disable IPv6 on the interface. + */ public boolean disableIPv6() { - try { - mNMS.disableIpv6(mIfName); - } catch (IllegalStateException | RemoteException e) { - logError("disabling IPv6 failed: %s", e); - return false; - } - return true; + return setEnableIPv6(false); } + /** + * Enable or disable IPv6 privacy extensions on the interface. + * @param enabled Whether the extensions should be enabled. + */ public boolean setIPv6PrivacyExtensions(boolean enabled) { try { - mNMS.setInterfaceIpv6PrivacyExtensions(mIfName, enabled); - } catch (IllegalStateException | RemoteException e) { - logError("error setting IPv6 privacy extensions: %s", e); + mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled); + } catch (RemoteException | ServiceSpecificException e) { + logError("error %s IPv6 privacy extensions: %s", + (enabled ? "enabling" : "disabling"), e); return false; } return true; } + /** + * Set IPv6 address generation mode on the interface. + * + * <p>IPv6 should be disabled before changing the mode. + */ public boolean setIPv6AddrGenModeIfSupported(int mode) { try { - mNMS.setIPv6AddrGenMode(mIfName, mode); + mNetd.setIPv6AddrGenMode(mIfName, mode); } catch (RemoteException e) { logError("Unable to set IPv6 addrgen mode: %s", e); return false; @@ -121,10 +142,16 @@ public class InterfaceController { return true; } + /** + * Add an address to the interface. + */ public boolean addAddress(LinkAddress addr) { return addAddress(addr.getAddress(), addr.getPrefixLength()); } + /** + * Add an address to the interface. + */ public boolean addAddress(InetAddress ip, int prefixLen) { try { mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen); @@ -135,6 +162,9 @@ public class InterfaceController { return true; } + /** + * Remove an address from the interface. + */ public boolean removeAddress(InetAddress ip, int prefixLen) { try { mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen); @@ -145,9 +175,12 @@ public class InterfaceController { return true; } + /** + * Remove all addresses from the interface. + */ public boolean clearAllAddresses() { try { - mNMS.clearInterfaceAddresses(mIfName); + mNetd.interfaceClearAddrs(mIfName); } catch (Exception e) { logError("Failed to clear addresses: %s", e); return false; diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java index 9f1557354dc4..233b86f5f8b2 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/services/net/java/android/net/ip/IpClient.java @@ -19,12 +19,12 @@ package android.net.ip; import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; import android.content.Context; +import android.net.ConnectivityManager; import android.net.DhcpResults; import android.net.INetd; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.LinkProperties.ProvisioningChange; import android.net.Network; import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; @@ -38,7 +38,6 @@ import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpManagerEvent; import android.net.shared.InitialConfiguration; import android.net.util.InterfaceParams; -import android.net.util.MultinetworkPolicyTracker; import android.net.util.NetdService; import android.net.util.SharedLog; import android.os.ConditionVariable; @@ -359,6 +358,9 @@ public class IpClient extends StateMachine { private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101; private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102; + // IpClient shares a handler with DhcpClient: commands must not overlap + public static final int DHCPCLIENT_CMD_BASE = 1000; + private static final int MAX_LOG_RECORDS = 500; private static final int MAX_PACKET_RECORDS = 100; @@ -371,6 +373,11 @@ public class IpClient extends StateMachine { private static final int IMMEDIATE_FAILURE_DURATION = 0; + private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1; + private static final int PROV_CHANGE_LOST_PROVISIONING = 2; + private static final int PROV_CHANGE_GAINED_PROVISIONING = 3; + private static final int PROV_CHANGE_STILL_PROVISIONED = 4; + private final State mStoppedState = new StoppedState(); private final State mStoppingState = new StoppingState(); private final State mStartedState = new StartedState(); @@ -384,6 +391,7 @@ public class IpClient extends StateMachine { protected final IpClientCallbacks mCallback; private final Dependencies mDependencies; private final CountDownLatch mShutdownLatch; + private final ConnectivityManager mCm; private final INetworkManagementService mNwService; private final NetlinkTracker mNetlinkTracker; private final WakeupMessage mProvisioningTimeoutAlarm; @@ -401,7 +409,6 @@ public class IpClient extends StateMachine { */ private LinkProperties mLinkProperties; private android.net.shared.ProvisioningConfiguration mConfiguration; - private MultinetworkPolicyTracker mMultinetworkPolicyTracker; private IpReachabilityMonitor mIpReachabilityMonitor; private DhcpClient mDhcpClient; private DhcpResults mDhcpResults; @@ -469,6 +476,7 @@ public class IpClient extends StateMachine { mCallback = new LoggingCallbackWrapper(callback); mDependencies = deps; mShutdownLatch = new CountDownLatch(1); + mCm = mContext.getSystemService(ConnectivityManager.class); mNwService = deps.getNMS(); sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag)); @@ -479,7 +487,7 @@ public class IpClient extends StateMachine { // TODO: Consider creating, constructing, and passing in some kind of // InterfaceController.Dependencies class. - mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, deps.getNetd(), mLog); + mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog); mNetlinkTracker = new NetlinkTracker( mInterfaceName, @@ -911,18 +919,18 @@ public class IpClient extends StateMachine { // object that is a correct and complete assessment of what changed, taking // account of the asymmetries described in the comments in this function. // Then switch to using it everywhere (IpReachabilityMonitor, etc.). - private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { - ProvisioningChange delta; + private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { + int delta; InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; final boolean wasProvisioned = isProvisioned(oldLp, config); final boolean isProvisioned = isProvisioned(newLp, config); if (!wasProvisioned && isProvisioned) { - delta = ProvisioningChange.GAINED_PROVISIONING; + delta = PROV_CHANGE_GAINED_PROVISIONING; } else if (wasProvisioned && isProvisioned) { - delta = ProvisioningChange.STILL_PROVISIONED; + delta = PROV_CHANGE_STILL_PROVISIONED; } else if (!wasProvisioned && !isProvisioned) { - delta = ProvisioningChange.STILL_NOT_PROVISIONED; + delta = PROV_CHANGE_STILL_NOT_PROVISIONED; } else { // (wasProvisioned && !isProvisioned) // @@ -934,7 +942,7 @@ public class IpClient extends StateMachine { // that to be a network without DNS servers and connect anyway. // // See the comment below. - delta = ProvisioningChange.LOST_PROVISIONING; + delta = PROV_CHANGE_LOST_PROVISIONING; } final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned(); @@ -954,8 +962,9 @@ public class IpClient extends StateMachine { // Note that we can still be disconnected by IpReachabilityMonitor // if the IPv6 default gateway (but not the IPv6 DNS servers; see // accompanying code in IpReachabilityMonitor) is unreachable. - final boolean ignoreIPv6ProvisioningLoss = (mMultinetworkPolicyTracker != null) - && !mMultinetworkPolicyTracker.getAvoidBadWifi(); + final boolean ignoreIPv6ProvisioningLoss = + mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker + && mCm.getAvoidBadWifi(); // Additionally: // @@ -970,7 +979,7 @@ public class IpClient extends StateMachine { // delta will never be LOST_PROVISIONING. So check for loss of // provisioning here too. if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { - delta = ProvisioningChange.LOST_PROVISIONING; + delta = PROV_CHANGE_LOST_PROVISIONING; } // Additionally: @@ -979,15 +988,15 @@ public class IpClient extends StateMachine { // IPv6 default route then also consider the loss of that default route // to be a loss of provisioning. See b/27962810. if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { - delta = ProvisioningChange.LOST_PROVISIONING; + delta = PROV_CHANGE_LOST_PROVISIONING; } return delta; } - private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) { + private void dispatchCallback(int delta, LinkProperties newLp) { switch (delta) { - case GAINED_PROVISIONING: + case PROV_CHANGE_GAINED_PROVISIONING: if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); } @@ -995,7 +1004,7 @@ public class IpClient extends StateMachine { mCallback.onProvisioningSuccess(newLp); break; - case LOST_PROVISIONING: + case PROV_CHANGE_LOST_PROVISIONING: if (DBG) { Log.d(mTag, "onProvisioningFailure()"); } @@ -1015,7 +1024,7 @@ public class IpClient extends StateMachine { // Updates all IpClient-related state concerned with LinkProperties. // Returns a ProvisioningChange for possibly notifying other interested // parties that are not fronted by IpClient. - private ProvisioningChange setLinkProperties(LinkProperties newLp) { + private int setLinkProperties(LinkProperties newLp) { if (mApfFilter != null) { mApfFilter.setLinkProperties(newLp); } @@ -1023,10 +1032,10 @@ public class IpClient extends StateMachine { mIpReachabilityMonitor.updateLinkProperties(newLp); } - ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp); + int delta = compareProvisioning(mLinkProperties, newLp); mLinkProperties = new LinkProperties(newLp); - if (delta == ProvisioningChange.GAINED_PROVISIONING) { + if (delta == PROV_CHANGE_GAINED_PROVISIONING) { // TODO: Add a proper ProvisionedState and cancel the alarm in // its enter() method. mProvisioningTimeoutAlarm.cancel(); @@ -1122,17 +1131,17 @@ public class IpClient extends StateMachine { if (Objects.equals(newLp, mLinkProperties)) { return true; } - final ProvisioningChange delta = setLinkProperties(newLp); + final int delta = setLinkProperties(newLp); if (sendCallbacks) { dispatchCallback(delta, newLp); } - return (delta != ProvisioningChange.LOST_PROVISIONING); + return (delta != PROV_CHANGE_LOST_PROVISIONING); } private void handleIPv4Success(DhcpResults dhcpResults) { mDhcpResults = new DhcpResults(dhcpResults); final LinkProperties newLp = assembleLinkProperties(); - final ProvisioningChange delta = setLinkProperties(newLp); + final int delta = setLinkProperties(newLp); if (DBG) { Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); @@ -1160,7 +1169,7 @@ public class IpClient extends StateMachine { private void handleProvisioningFailure() { final LinkProperties newLp = assembleLinkProperties(); - ProvisioningChange delta = setLinkProperties(newLp); + int delta = setLinkProperties(newLp); // If we've gotten here and we're still not provisioned treat that as // a total loss of provisioning. // @@ -1169,12 +1178,12 @@ public class IpClient extends StateMachine { // timeout expired. // // Regardless: GAME OVER. - if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) { - delta = ProvisioningChange.LOST_PROVISIONING; + if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) { + delta = PROV_CHANGE_LOST_PROVISIONING; } dispatchCallback(delta, newLp); - if (delta == ProvisioningChange.LOST_PROVISIONING) { + if (delta == PROV_CHANGE_LOST_PROVISIONING) { transitionTo(mStoppingState); } } @@ -1246,7 +1255,7 @@ public class IpClient extends StateMachine { mCallback.onReachabilityLost(logMsg); } }, - mMultinetworkPolicyTracker); + mConfiguration.mUsingMultinetworkPolicyTracker); } catch (IllegalArgumentException iae) { // Failed to start IpReachabilityMonitor. Log it and call // onProvisioningFailure() immediately. @@ -1479,13 +1488,6 @@ public class IpClient extends StateMachine { return; } - if (mConfiguration.mUsingMultinetworkPolicyTracker) { - mMultinetworkPolicyTracker = new MultinetworkPolicyTracker( - mContext, getHandler(), - () -> mLog.log("OBSERVED AvoidBadWifi changed")); - mMultinetworkPolicyTracker.start(); - } - if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { doImmediateProvisioningFailure( IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); @@ -1503,11 +1505,6 @@ public class IpClient extends StateMachine { mIpReachabilityMonitor = null; } - if (mMultinetworkPolicyTracker != null) { - mMultinetworkPolicyTracker.shutdown(); - mMultinetworkPolicyTracker = null; - } - if (mDhcpClient != null) { mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); mDhcpClient.doQuit(); @@ -1646,7 +1643,7 @@ public class IpClient extends StateMachine { mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); } else { logError("Failed to set IPv4 address."); - dispatchCallback(ProvisioningChange.LOST_PROVISIONING, + dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, new LinkProperties(mLinkProperties)); transitionTo(mStoppingState); } diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/services/net/java/android/net/ip/IpNeighborMonitor.java index 34bf4b63a883..eb993a4243a9 100644 --- a/services/net/java/android/net/ip/IpNeighborMonitor.java +++ b/services/net/java/android/net/ip/IpNeighborMonitor.java @@ -19,6 +19,7 @@ package android.net.ip; import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH; import static android.net.netlink.NetlinkConstants.hexify; import static android.net.netlink.NetlinkConstants.stringForNlMsgType; +import static android.net.util.SocketUtils.makeNetlinkSocketAddress; import android.net.MacAddress; import android.net.netlink.NetlinkErrorMessage; @@ -31,7 +32,6 @@ import android.net.util.SharedLog; import android.os.Handler; import android.os.SystemClock; import android.system.ErrnoException; -import android.system.NetlinkSocketAddress; import android.system.Os; import android.system.OsConstants; import android.util.Log; @@ -148,15 +148,12 @@ public class IpNeighborMonitor extends PacketReader { try { fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE); - Os.bind(fd, (SocketAddress)(new NetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH))); - Os.connect(fd, (SocketAddress)(new NetlinkSocketAddress(0, 0))); + Os.bind(fd, makeNetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)); + NetlinkSocket.connectToKernel(fd); if (VDBG) { - final NetlinkSocketAddress nlAddr = (NetlinkSocketAddress) Os.getsockname(fd); - Log.d(TAG, "bound to sockaddr_nl{" - + BitUtils.uint32(nlAddr.getPortId()) + ", " - + nlAddr.getGroupsMask() - + "}"); + final SocketAddress nlAddr = Os.getsockname(fd); + Log.d(TAG, "bound to sockaddr_nl{" + nlAddr.toString() + "}"); } } catch (ErrnoException|SocketException e) { logError("Failed to create rtnetlink socket", e); diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java index 8b02156430f1..761db6822fb4 100644 --- a/services/net/java/android/net/ip/IpReachabilityMonitor.java +++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java @@ -22,15 +22,14 @@ import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST; import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC; import android.content.Context; +import android.net.ConnectivityManager; import android.net.LinkProperties; -import android.net.LinkProperties.ProvisioningChange; import android.net.RouteInfo; import android.net.ip.IpNeighborMonitor.NeighborEvent; import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpReachabilityEvent; import android.net.netlink.StructNdMsg; import android.net.util.InterfaceParams; -import android.net.util.MultinetworkPolicyTracker; import android.net.util.SharedLog; import android.os.Handler; import android.os.PowerManager; @@ -166,7 +165,8 @@ public class IpReachabilityMonitor { private final SharedLog mLog; private final Callback mCallback; private final Dependencies mDependencies; - private final MultinetworkPolicyTracker mMultinetworkPolicyTracker; + private final boolean mUsingMultinetworkPolicyTracker; + private final ConnectivityManager mCm; private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); private LinkProperties mLinkProperties = new LinkProperties(); private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>(); @@ -175,19 +175,21 @@ public class IpReachabilityMonitor { public IpReachabilityMonitor( Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback, - MultinetworkPolicyTracker tracker) { - this(ifParams, h, log, callback, tracker, Dependencies.makeDefault(context, ifParams.name)); + boolean usingMultinetworkPolicyTracker) { + this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker, + Dependencies.makeDefault(context, ifParams.name)); } @VisibleForTesting - IpReachabilityMonitor(InterfaceParams ifParams, Handler h, SharedLog log, Callback callback, - MultinetworkPolicyTracker tracker, Dependencies dependencies) { + IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h, SharedLog log, + Callback callback, boolean usingMultinetworkPolicyTracker, Dependencies dependencies) { if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams"); mInterfaceParams = ifParams; mLog = log.forSubComponent(TAG); mCallback = callback; - mMultinetworkPolicyTracker = tracker; + mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker; + mCm = context.getSystemService(ConnectivityManager.class); mDependencies = dependencies; mIpNeighborMonitor = new IpNeighborMonitor(h, mLog, @@ -308,10 +310,11 @@ public class IpReachabilityMonitor { } } - final ProvisioningChange delta = LinkProperties.compareProvisioning( - mLinkProperties, whatIfLp); + final boolean lostProvisioning = + (mLinkProperties.isIPv4Provisioned() && !whatIfLp.isIPv4Provisioned()) + || (mLinkProperties.isIPv6Provisioned() && !whatIfLp.isIPv6Provisioned()); - if (delta == ProvisioningChange.LOST_PROVISIONING) { + if (lostProvisioning) { final String logMsg = "FAILURE: LOST_PROVISIONING, " + event; Log.w(TAG, logMsg); if (mCallback != null) { @@ -320,11 +323,11 @@ public class IpReachabilityMonitor { mCallback.notifyLost(ip, logMsg); } } - logNudFailed(delta); + logNudFailed(lostProvisioning); } private boolean avoidingBadLinks() { - return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi(); + return !mUsingMultinetworkPolicyTracker || mCm.getAvoidBadWifi(); } public void probeAll() { @@ -370,11 +373,10 @@ public class IpReachabilityMonitor { mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType)); } - private void logNudFailed(ProvisioningChange delta) { + private void logNudFailed(boolean lostProvisioning) { long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs; boolean isFromProbe = (duration < getProbeWakeLockDuration()); - boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING); - int eventType = nudFailureEventType(isFromProbe, isProvisioningLost); + int eventType = nudFailureEventType(isFromProbe, lostProvisioning); mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType)); } diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index 8b22f68286af..7910c9a69310 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -226,7 +226,7 @@ public class IpServer extends StateMachine { mNetd = deps.getNetdService(); mStatsService = statsService; mCallback = callback; - mInterfaceCtrl = new InterfaceController(ifaceName, nMService, mNetd, mLog); + mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); mIfaceName = ifaceName; mInterfaceType = interfaceType; mLinkProperties = new LinkProperties(); diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java index 40098c1532b1..2a98d90e5577 100644 --- a/services/net/java/android/net/netlink/NetlinkSocket.java +++ b/services/net/java/android/net/netlink/NetlinkSocket.java @@ -16,26 +16,26 @@ package android.net.netlink; +import static android.net.util.SocketUtils.makeNetlinkSocketAddress; import static android.system.OsConstants.AF_NETLINK; import static android.system.OsConstants.EIO; import static android.system.OsConstants.EPROTO; import static android.system.OsConstants.ETIMEDOUT; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOL_SOCKET; import static android.system.OsConstants.SO_RCVBUF; import static android.system.OsConstants.SO_RCVTIMEO; import static android.system.OsConstants.SO_SNDTIMEO; -import static android.system.OsConstants.SOCK_DGRAM; -import static android.system.OsConstants.SOL_SOCKET; import android.system.ErrnoException; -import android.system.NetlinkSocketAddress; import android.system.Os; import android.system.StructTimeval; import android.util.Log; + import libcore.io.IoUtils; import java.io.FileDescriptor; import java.io.InterruptedIOException; -import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -106,7 +106,7 @@ public class NetlinkSocket { } public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException { - Os.connect(fd, (SocketAddress) (new NetlinkSocketAddress(0, 0))); + Os.connect(fd, makeNetlinkSocketAddress(0, 0)); } private static void checkTimeout(long timeoutMs) { diff --git a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java index d5213dfcebf8..51d955d5c2ad 100644 --- a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java +++ b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java @@ -182,9 +182,6 @@ public final class LinkPropertiesParcelableUtil { parcel.mtu = lp.getMtu(); parcel.tcpBufferSizes = lp.getTcpBufferSizes(); parcel.nat64Prefix = toStableParcelable(lp.getNat64Prefix()); - parcel.stackedLinks = toParcelableArray( - lp.getStackedLinks(), LinkPropertiesParcelableUtil::toStableParcelable, - LinkPropertiesParcelable.class); return parcel; } @@ -216,9 +213,6 @@ public final class LinkPropertiesParcelableUtil { lp.setMtu(parcel.mtu); lp.setTcpBufferSizes(parcel.tcpBufferSizes); lp.setNat64Prefix(fromStableParcelable(parcel.nat64Prefix)); - for (LinkPropertiesParcelable stackedLink : parcel.stackedLinks) { - lp.addStackedLink(fromStableParcelable(stackedLink)); - } return lp; } } diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index 1a16e568ca53..53d72bb9a415 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -1376,6 +1376,45 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_ACTIVE); } + @Test + public void testStepToIdleMode() { + float delta = mDeviceIdleController.MIN_PRE_IDLE_FACTOR_CHANGE; + for (int mode = PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL; + mode <= PowerManager.PRE_IDLE_TIMEOUT_MODE_LONG; + mode++) { + int ret = mDeviceIdleController.setPreIdleTimeoutMode(mode); + if (mode == PowerManager.PRE_IDLE_TIMEOUT_MODE_NORMAL) { + assertEquals("setPreIdleTimeoutMode: " + mode + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + mode + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + //TODO(b/123045185): Mocked Handler of DeviceIdleController to make message loop + //workable in this test class + mDeviceIdleController.updatePreIdleFactor(); + float expectedfactor = mDeviceIdleController.getPreIdleTimeoutByMode(mode); + float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor(); + assertEquals("Pre idle time factor of mode [" + mode + "].", + expectedfactor, curfactor, delta); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_INACTIVE); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_PENDING); + + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_SENSING); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_LOCATING); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_QUICK_DOZE_DELAY); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE_MAINTENANCE); + checkNextAlarmTimeWithNewPreIdleFactor(expectedfactor, STATE_IDLE); + checkMaybeDoAnImmediateMaintenance(expectedfactor); + } + float curfactor = mDeviceIdleController.getPreIdleTimeoutFactor(); + assertEquals("Pre idle time factor of mode default.", + 1.0f, curfactor, delta); + } + private void enterDeepState(int state) { switch (state) { case STATE_ACTIVE: @@ -1599,4 +1638,84 @@ public class DeviceIdleControllerTest { fail("Conditions for " + lightStateToString(expectedLightState) + " unknown."); } } + + private void checkNextAlarmTimeWithNewPreIdleFactor(float factor, int state) { + final long errorTolerance = 1000; + enterDeepState(state); + long now = SystemClock.elapsedRealtime(); + long alarm = mDeviceIdleController.getNextAlarmTime(); + if (state == STATE_INACTIVE || state == STATE_IDLE_PENDING) { + int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor); + if (Float.compare(factor, 1.0f) == 0) { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) { + mDeviceIdleController.updatePreIdleFactor(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + long newDelay = (long) ((alarm - now) * factor); + assertTrue("setPreIdleTimeoutFactor: " + factor, + Math.abs(newDelay - (newAlarm - now)) < errorTolerance); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("resetPreIdleTimeoutMode from: " + factor, + Math.abs(newAlarm - alarm) < errorTolerance); + mDeviceIdleController.setPreIdleTimeoutFactor(factor); + now = SystemClock.elapsedRealtime(); + enterDeepState(state); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("setPreIdleTimeoutFactor: " + factor + " before step to idle", + Math.abs(newDelay - (newAlarm - now)) < errorTolerance); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + } + } else { + mDeviceIdleController.setPreIdleTimeoutFactor(factor); + mDeviceIdleController.updatePreIdleFactor(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("setPreIdleTimeoutFactor: " + factor + + " shounld not change next alarm" , + (newAlarm == alarm)); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + mDeviceIdleController.maybeDoImmediateMaintenance(); + } + } + + private void checkMaybeDoAnImmediateMaintenance(float factor) { + int ret = mDeviceIdleController.setPreIdleTimeoutFactor(factor); + final long minuteInMillis = 60 * 1000; + if (Float.compare(factor, 1.0f) == 0) { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_IGNORED, ret); + } else { + assertEquals("setPreIdleTimeoutMode: " + factor + " failed.", + mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK, ret); + } + if (ret == mDeviceIdleController.SET_IDLE_FACTOR_RESULT_OK) { + enterDeepState(STATE_IDLE); + long now = SystemClock.elapsedRealtime(); + long alarm = mDeviceIdleController.getNextAlarmTime(); + mDeviceIdleController.setIdleStartTimeForTest( + now - (long) (mConstants.IDLE_TIMEOUT * 0.6)); + mDeviceIdleController.maybeDoImmediateMaintenance(); + long newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("maintenance not reschedule IDLE_TIMEOUT * 0.6", + newAlarm == alarm); + mDeviceIdleController.setIdleStartTimeForTest( + now - (long) (mConstants.IDLE_TIMEOUT * 1.2)); + mDeviceIdleController.maybeDoImmediateMaintenance(); + newAlarm = mDeviceIdleController.getNextAlarmTime(); + assertTrue("maintenance not reschedule IDLE_TIMEOUT * 1.2", + (newAlarm - now) < minuteInMillis); + mDeviceIdleController.resetPreIdleTimeoutMode(); + mDeviceIdleController.updatePreIdleFactor(); + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 57ee6dcad9f2..cad71a26a76b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; +import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; @@ -370,16 +371,19 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(0, "com.android.test.stay", one); ExecutionStats expectedStats = new ExecutionStats(); - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; - mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001); + final int uid = 10001; + mQuotaController.onAppRemovedLocked("com.android.test.remove", uid); assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay")); assertEquals(expectedStats, mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX)); assertNotEquals(expectedStats, mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX)); + + assertFalse(mQuotaController.getForegroundUids().get(uid)); } @Test @@ -405,7 +409,7 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(10, "com.android.test", one); ExecutionStats expectedStats = new ExecutionStats(); - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; mQuotaController.onUserRemovedLocked(0); @@ -440,14 +444,14 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; // Invalid time is now +24 hours since there are no sessions at all for the app. - expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats); assertEquals(expectedStats, inputStats); inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS; // Invalid time is now +18 hours since there are no sessions in the window but the earliest // session is 6 hours ago. - expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 0; expectedStats.bgJobCountInWindow = 0; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -457,7 +461,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; // Invalid time is now since the session straddles the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -468,7 +472,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -479,7 +483,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; // Invalid time is now +44 minutes since the earliest session in the window is now-5 // minutes. - expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 3; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -489,7 +493,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; // Invalid time is now since the session is at the very edge of the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 4; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -500,7 +504,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 5; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -510,7 +514,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; // Invalid time is now since the session straddles the window cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -523,7 +527,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS; // Invalid time is now +59 minutes since the earliest session in the window is now-121 // minutes. - expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -536,7 +540,7 @@ public class QuotaControllerTest { inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; // Invalid time is now since the start of the session is at the very edge of the window // cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; @@ -546,14 +550,14 @@ public class QuotaControllerTest { mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); assertEquals(expectedStats, inputStats); - // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period. + // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period. mQuotaController.getTimingSessions(0, "com.android.test") .add(0, createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3)); inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; // Invalid time is now +1 hour since the earliest session in the max period is 1 hour // before the end of the max period cutoff time. - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS; @@ -569,7 +573,7 @@ public class QuotaControllerTest { 2 * MINUTE_IN_MILLIS, 2)); inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; // Invalid time is now since the earlist session straddles the max period cutoff time. - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS; @@ -599,7 +603,7 @@ public class QuotaControllerTest { // Active expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS; expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 5; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -609,7 +613,7 @@ public class QuotaControllerTest { // Working expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now; + expectedStats.expirationTimeElapsed = now; expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 10; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -621,7 +625,7 @@ public class QuotaControllerTest { // Frequent expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 15; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -633,7 +637,7 @@ public class QuotaControllerTest { // Rare expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; - expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS; expectedStats.bgJobCountInWindow = 20; expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; @@ -675,7 +679,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); expectedStats.windowSizeMs = originalStatsActive.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow; expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs; @@ -688,7 +692,7 @@ public class QuotaControllerTest { assertEquals(expectedStats, newStatsActive); expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed; @@ -698,7 +702,7 @@ public class QuotaControllerTest { assertNotEquals(expectedStats, newStatsWorking); expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed; @@ -708,7 +712,7 @@ public class QuotaControllerTest { assertNotEquals(expectedStats, newStatsFrequent); expectedStats.windowSizeMs = originalStatsRare.windowSizeMs; - expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed; + expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed; expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs; expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow; expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed; @@ -719,6 +723,77 @@ public class QuotaControllerTest { } @Test + public void testIsWithinQuotaLocked_NeverApp() { + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_Charging() { + setCharging(); + assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.incrementJobCount(0, "com.android.test", 5); + assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + mQuotaController.saveTimingSession(0, "com.android.test.spam", + createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25)); + mQuotaController.saveTimingSession(0, "com.android.test.spam", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount)); + mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam", + WORKING_INDEX)); + + mQuotaController.saveTimingSession(0, "com.android.test.frequent", + createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000)); + mQuotaController.saveTimingSession(0, "com.android.test.frequent", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500)); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent", + FREQUENT_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5)); + mQuotaController.incrementJobCount(0, "com.android.test", 5); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test + public void testIsWithinQuotaLocked_OverDuration_OverJobCount() { + setDischarging(); + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME; + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25)); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount)); + mQuotaController.incrementJobCount(0, "com.android.test", jobCount); + assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); + } + + @Test public void testMaybeScheduleCleanupAlarmLocked() { // No sessions saved yet. mQuotaController.maybeScheduleCleanupAlarmLocked(); @@ -752,6 +827,7 @@ public class QuotaControllerTest { // Active window size is 10 minutes. final int standbyBucket = ACTIVE_INDEX; + setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); // No sessions saved yet. mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -1016,11 +1092,37 @@ public class QuotaControllerTest { .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX); - inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), - any()); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class)); } + @Test + public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + final int standbyBucket = WORKING_INDEX; + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + stats.jobCountInAllowedTime = + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2; + + // Invalid time in the past, so the count shouldn't be used. + stats.jobCountExpirationTimeElapsed = + now - mQuotaController.getAllowedTimePerPeriodMs() / 2; + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + // Invalid time in the future, so the count should be used. + stats.jobCountExpirationTimeElapsed = + now + mQuotaController.getAllowedTimePerPeriodMs() / 2; + final long expectedWorkingAlarmTime = + stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs(); + mQuotaController.maybeScheduleStartAlarmLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); + verify(mAlarmManager, times(1)) + .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + } /** * Tests that the start alarm is properly rescheduled if the earliest session that contributes @@ -1172,6 +1274,11 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500; mQuotaController.onConstantsUpdatedLocked(); @@ -1183,11 +1290,16 @@ public class QuotaControllerTest { mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); + assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime()); + assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); + assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); + assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); + assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); } @Test public void testConstantsUpdating_InvalidValues() { - // Test negatives + // Test negatives/too low. mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS; @@ -1195,6 +1307,11 @@ public class QuotaControllerTest { mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS; mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1; + mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0; mQuotaController.onConstantsUpdatedLocked(); @@ -1205,6 +1322,11 @@ public class QuotaControllerTest { assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); + assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime()); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); + assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); // Test larger than a day. Controller should cap at one day. mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS; @@ -1246,6 +1368,7 @@ public class QuotaControllerTest { @Test public void testTimerTracking_Discharging() { setDischarging(); + setProcessState(ActivityManager.PROCESS_STATE_BACKUP); JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); @@ -1293,6 +1416,8 @@ public class QuotaControllerTest { */ @Test public void testTimerTracking_ChargingAndDischarging() { + setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); + JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2); @@ -1363,6 +1488,7 @@ public class QuotaControllerTest { @Test public void testTimerTracking_AllBackground() { setDischarging(); + setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); @@ -1503,6 +1629,64 @@ public class QuotaControllerTest { } /** + * Tests that Timers don't track job counts while in the foreground. + */ + @Test + public void testTimerTracking_JobCount_Foreground() { + setDischarging(); + + final int standbyBucket = ACTIVE_INDEX; + JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1); + JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2); + + mQuotaController.maybeStartTrackingJobLocked(jobFg1, null); + mQuotaController.maybeStartTrackingJobLocked(jobFg2, null); + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + assertEquals(0, stats.jobCountInAllowedTime); + + setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + mQuotaController.prepareForExecutionLocked(jobFg1); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobFg2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false); + assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); + + assertEquals(0, stats.jobCountInAllowedTime); + } + + /** + * Tests that Timers properly track job counts while in the background. + */ + @Test + public void testTimerTracking_JobCount_Background() { + final int standbyBucket = WORKING_INDEX; + JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1); + JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2); + mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); + mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); + + ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, + SOURCE_PACKAGE, standbyBucket); + assertEquals(0, stats.jobCountInAllowedTime); + + setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); + mQuotaController.prepareForExecutionLocked(jobBg1); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.prepareForExecutionLocked(jobBg2); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + + assertEquals(2, stats.jobCountInAllowedTime); + } + + /** * Tests that Timers properly track overlapping top and background jobs. */ @Test @@ -1680,6 +1864,7 @@ public class QuotaControllerTest { JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window + setProcessState(ActivityManager.PROCESS_STATE_HOME); // Now the package only has two seconds to run. final long remainingTimeMs = 2 * SECOND_IN_MILLIS; mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -1707,6 +1892,7 @@ public class QuotaControllerTest { JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); Handler handler = mQuotaController.getHandler(); spyOn(handler); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java index feffeef3f044..773b8778c7bf 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java @@ -20,7 +20,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; @@ -36,19 +38,15 @@ import android.animation.ValueAnimator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.IntentFilter; -import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; -import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.view.MagnificationSpec; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.R; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks; @@ -81,48 +79,36 @@ public class MagnificationControllerTest { static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS); static final int SERVICE_ID_1 = 1; static final int SERVICE_ID_2 = 2; + static final int DISPLAY_0 = 0; + static final int DISPLAY_1 = 1; + static final int DISPLAY_COUNT = 2; + static final int INVALID_DISPLAY = 2; + final MagnificationController.ControllerContext mMockControllerCtx = + mock(MagnificationController.ControllerContext.class); final Context mMockContext = mock(Context.class); final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class); final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class); - final MessageCapturingHandler mMessageCapturingHandler = - new MessageCapturingHandler(null); + final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(null); - final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class); - MagnificationController.SettingsBridge mMockSettingsBridge; + ValueAnimator mMockValueAnimator; + ValueAnimator.AnimatorUpdateListener mTargetAnimationListener; MagnificationController mMagnificationController; - ValueAnimator.AnimatorUpdateListener mTargetAnimationListener; @Before public void setUp() { Looper looper = InstrumentationRegistry.getContext().getMainLooper(); // Pretending ID of the Thread associated with looper as main thread ID in controller when(mMockContext.getMainLooper()).thenReturn(looper); - Resources mockResources = mock(Resources.class); - when(mMockContext.getResources()).thenReturn(mockResources); - when(mockResources.getInteger(R.integer.config_longAnimTime)) - .thenReturn(1000); - mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class); - mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(), - mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator, - mMockSettingsBridge); - - doAnswer(new Answer<Void>() { - @Override - public Void answer(InvocationOnMock invocationOnMock) throws Throwable { - Object[] args = invocationOnMock.getArguments(); - Region regionArg = (Region) args[0]; - regionArg.set(INITIAL_MAGNIFICATION_REGION); - return null; - } - }).when(mMockWindowManager).getMagnificationRegion((Region) anyObject()); - - ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor = - ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class); - verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture()); - mTargetAnimationListener = listenerArgumentCaptor.getValue(); - Mockito.reset(mMockValueAnimator); // Ignore other initialization + when(mMockControllerCtx.getContext()).thenReturn(mMockContext); + when(mMockControllerCtx.getAms()).thenReturn(mMockAms); + when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager); + when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler); + when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L); + initMockWindowManager(); + + mMagnificationController = new MagnificationController(mMockControllerCtx, new Object()); } @After @@ -133,88 +119,132 @@ public class MagnificationControllerTest { @Test public void testRegister_WindowManagerAndContextRegisterListeners() { - mMagnificationController.register(); + register(DISPLAY_0); + register(DISPLAY_1); + register(INVALID_DISPLAY); verify(mMockContext).registerReceiver( (BroadcastReceiver) anyObject(), (IntentFilter) anyObject()); - verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject()); - assertTrue(mMagnificationController.isRegisteredLocked()); + verify(mMockWindowManager).setMagnificationCallbacks( + eq(DISPLAY_0), (MagnificationCallbacks) anyObject()); + verify(mMockWindowManager).setMagnificationCallbacks( + eq(DISPLAY_1), (MagnificationCallbacks) anyObject()); + verify(mMockWindowManager).setMagnificationCallbacks( + eq(INVALID_DISPLAY), (MagnificationCallbacks) anyObject()); + assertTrue(mMagnificationController.isRegistered(DISPLAY_0)); + assertTrue(mMagnificationController.isRegistered(DISPLAY_1)); + assertFalse(mMagnificationController.isRegistered(INVALID_DISPLAY)); } @Test public void testRegister_WindowManagerAndContextUnregisterListeners() { - mMagnificationController.register(); - mMagnificationController.unregister(); - + register(DISPLAY_0); + register(DISPLAY_1); + mMagnificationController.unregister(DISPLAY_0); + verify(mMockContext, times(0)).unregisterReceiver((BroadcastReceiver) anyObject()); + mMagnificationController.unregister(DISPLAY_1); verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject()); - verify(mMockWindowManager).setMagnificationCallbacks(null); - assertFalse(mMagnificationController.isRegisteredLocked()); + verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_0), eq(null)); + verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_1), eq(null)); + assertFalse(mMagnificationController.isRegistered(DISPLAY_0)); + assertFalse(mMagnificationController.isRegistered(DISPLAY_1)); } @Test public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(i); + resetMockWindowManager(); + } + } + + private void initialState_noMagnificationAndMagnificationRegionReadFromWindowManager( + int displayId) { + register(displayId); MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f); Region initialMagRegion = new Region(); Rect initialBounds = new Rect(); - assertEquals(expectedInitialSpec, getCurrentMagnificationSpec()); - mMagnificationController.getMagnificationRegion(initialMagRegion); - mMagnificationController.getMagnificationBounds(initialBounds); + assertEquals(expectedInitialSpec, getCurrentMagnificationSpec(displayId)); + mMagnificationController.getMagnificationRegion(displayId, initialMagRegion); + mMagnificationController.getMagnificationBounds(displayId, initialBounds); assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion); assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds); assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(), - mMagnificationController.getCenterX(), 0.0f); + mMagnificationController.getCenterX(displayId), 0.0f); assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(), - mMagnificationController.getCenterY(), 0.0f); + mMagnificationController.getCenterY(displayId), 0.0f); } @Test public void testNotRegistered_publicMethodsShouldBeBenign() { - assertFalse(mMagnificationController.isMagnifying()); - assertFalse(mMagnificationController.magnificationRegionContains(100, 100)); - assertFalse(mMagnificationController.reset(true)); - assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0)); - assertFalse(mMagnificationController.setCenter(100, 100, false, 1)); - assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2)); - assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0); - - mMagnificationController.getMagnificationRegion(new Region()); - mMagnificationController.getMagnificationBounds(new Rect()); - mMagnificationController.getScale(); - mMagnificationController.getOffsetX(); - mMagnificationController.getOffsetY(); - mMagnificationController.getCenterX(); - mMagnificationController.getCenterY(); - mMagnificationController.offsetMagnifiedRegion(50, 50, 1); - mMagnificationController.unregister(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + notRegistered_publicMethodsShouldBeBenign(i); + resetMockWindowManager(); + } + } + + private void notRegistered_publicMethodsShouldBeBenign(int displayId) { + assertFalse(mMagnificationController.isMagnifying(displayId)); + assertFalse(mMagnificationController.magnificationRegionContains(displayId, 100, 100)); + assertFalse(mMagnificationController.reset(displayId, true)); + assertFalse(mMagnificationController.setScale(displayId, 2, 100, 100, true, 0)); + assertFalse(mMagnificationController.setCenter(displayId, 100, 100, false, 1)); + assertFalse(mMagnificationController.setScaleAndCenter(displayId, + 1.5f, 100, 100, false, 2)); + assertTrue(mMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0); + + mMagnificationController.getMagnificationRegion(displayId, new Region()); + mMagnificationController.getMagnificationBounds(displayId, new Rect()); + mMagnificationController.getScale(displayId); + mMagnificationController.getOffsetX(displayId); + mMagnificationController.getOffsetY(displayId); + mMagnificationController.getCenterX(displayId); + mMagnificationController.getCenterY(displayId); + mMagnificationController.offsetMagnifiedRegion(displayId, 50, 50, 1); + mMagnificationController.unregister(displayId); } @Test public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i); + resetMockWindowManager(); + } + } + + private void setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(int displayId) { + register(displayId); final float scale = 2.0f; final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER; final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale); assertTrue(mMagnificationController - .setScale(scale, center.x, center.y, false, SERVICE_ID_1)); + .setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec))); - assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec)); - assertEquals(center.x, mMagnificationController.getCenterX(), 0.0); - assertEquals(center.y, mMagnificationController.getCenterY(), 0.0); + verify(mMockWindowManager).setMagnificationSpec( + eq(displayId), argThat(closeTo(expectedSpec))); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedSpec)); + assertEquals(center.x, mMagnificationController.getCenterX(displayId), 0.0); + assertEquals(center.y, mMagnificationController.getCenterY(displayId), 0.0); verify(mMockValueAnimator, times(0)).start(); } @Test public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() { - mMagnificationController.register(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(i); + resetMockWindowManager(); + } + } + + private void setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(int displayId) { + register(displayId); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); float scale = 2.0f; PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; assertTrue(mMagnificationController - .setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1)); + .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); // New center should be halfway between original center and pivot @@ -223,467 +253,645 @@ public class MagnificationControllerTest { PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale); MagnificationSpec endSpec = getMagnificationSpec(scale, offsets); - assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5); - assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5); - assertThat(getCurrentMagnificationSpec(), closeTo(endSpec)); + assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5); + assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec)); verify(mMockValueAnimator).start(); // Initial value when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec(startSpec); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec)); // Intermediate point Mockito.reset(mMockWindowManager); float fraction = 0.5f; when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec( + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction)))); // Final value Mockito.reset(mMockWindowManager); when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec))); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); } @Test public void testSetCenter_whileMagnifying_noAnimation_centerMoves() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + setCenter_whileMagnifying_noAnimation_centerMoves(i); + resetMockWindowManager(); + } + } + + private void setCenter_whileMagnifying_noAnimation_centerMoves(int displayId) { + register(displayId); // First zoom in float scale = 2.0f; - assertTrue(mMagnificationController.setScale(scale, + assertTrue(mMagnificationController.setScale(displayId, scale, INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(), false, SERVICE_ID_1)); Mockito.reset(mMockWindowManager); PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; assertTrue(mMagnificationController - .setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1)); + .setCenter(displayId, newCenter.x, newCenter.y, false, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale); MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec))); - assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0); - assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0); + verify(mMockWindowManager).setMagnificationSpec( + eq(displayId), argThat(closeTo(expectedSpec))); + assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0); + assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0); verify(mMockValueAnimator, times(0)).start(); } @Test public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() { - mMagnificationController.register(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + setScaleAndCenter_animated_stateChangesAndAnimationHappens(i); + resetMockWindowManager(); + } + } + + private void setScaleAndCenter_animated_stateChangesAndAnimationHappens(int displayId) { + register(displayId); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); float scale = 2.5f; PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale); MagnificationSpec endSpec = getMagnificationSpec(scale, offsets); - assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y, - true, SERVICE_ID_1)); + assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale, newCenter.x, + newCenter.y, true, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); - assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5); - assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5); - assertThat(getCurrentMagnificationSpec(), closeTo(endSpec)); - verify(mMockAms).notifyMagnificationChanged( + assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5); + assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec)); + verify(mMockAms).notifyMagnificationChanged(displayId, INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y); verify(mMockValueAnimator).start(); // Initial value when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec(startSpec); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec)); // Intermediate point Mockito.reset(mMockWindowManager); float fraction = 0.33f; when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec( + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction)))); // Final value Mockito.reset(mMockWindowManager); when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec))); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); } @Test public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() { - mMagnificationController.register(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(i); + resetMockWindowManager(); + } + } + + private void setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(int displayId) { + register(displayId); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, MagnificationController.MAX_SCALE); MagnificationSpec endSpec = getMagnificationSpec( MagnificationController.MAX_SCALE, offsets); - assertTrue(mMagnificationController.setScaleAndCenter( + assertTrue(mMagnificationController.setScaleAndCenter(displayId, MagnificationController.MAX_SCALE + 1.0f, newCenter.x, newCenter.y, false, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); - assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5); - assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec))); + assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5); + assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); Mockito.reset(mMockWindowManager); // Verify that we can't zoom below 1x - assertTrue(mMagnificationController.setScaleAndCenter(0.5f, + assertTrue(mMagnificationController.setScaleAndCenter(displayId, 0.5f, INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y, false, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, - mMagnificationController.getCenterX(), 0.5); + mMagnificationController.getCenterX(displayId), 0.5); assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y, - mMagnificationController.getCenterY(), 0.5); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec))); + mMagnificationController.getCenterY(displayId), 0.5); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec))); } @Test public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + setScaleAndCenter_centerOutOfBounds_cappedAtLimits(i); + resetMockWindowManager(); + } + } + + private void setScaleAndCenter_centerOutOfBounds_cappedAtLimits(int displayId) { + register(displayId); float scale = 2.0f; // Off the edge to the top and left - assertTrue(mMagnificationController.setScaleAndCenter( + assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale, -100f, -200f, false, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER; PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale); - assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5); - assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5); - verify(mMockWindowManager).setMagnificationSpec( + assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5); + assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(getMagnificationSpec(scale, newOffsets)))); Mockito.reset(mMockWindowManager); // Off the edge to the bottom and right - assertTrue(mMagnificationController.setScaleAndCenter(scale, + assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale, INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1, false, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale); - assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5); - assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5); - verify(mMockWindowManager).setMagnificationSpec( + assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5); + assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(getMagnificationSpec(scale, newOffsets)))); } @Test public void testMagnificationRegionChanged_serviceNotified() { - mMagnificationController.register(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + magnificationRegionChanged_serviceNotified(i); + resetMockWindowManager(); + } + } + + private void magnificationRegionChanged_serviceNotified(int displayId) { + register(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); callbacks.onMagnificationRegionChanged(OTHER_REGION); mMessageCapturingHandler.sendAllMessages(); - verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f, + verify(mMockAms).notifyMagnificationChanged(displayId, OTHER_REGION, 1.0f, OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY()); } @Test public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + offsetMagnifiedRegion_whileMagnifying_offsetsMove(i); + resetMockWindowManager(); + } + } + + private void offsetMagnifiedRegion_whileMagnifying_offsetsMove(int displayId) { + register(displayId); PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; float scale = 2.0f; PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale); // First zoom in assertTrue(mMagnificationController - .setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1)); + .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false, + SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); Mockito.reset(mMockWindowManager); PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale); - mMagnificationController.offsetMagnifiedRegion( - startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1); + mMagnificationController.offsetMagnifiedRegion(displayId, + startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, + SERVICE_ID_1); mMessageCapturingHandler.sendAllMessages(); MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec))); - assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0); - assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), + argThat(closeTo(expectedSpec))); + assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0); + assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0); verify(mMockValueAnimator, times(0)).start(); } @Test public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(i); + resetMockWindowManager(); + } + } + + private void offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(int displayId) { + register(displayId); Mockito.reset(mMockWindowManager); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); - mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1); - assertThat(getCurrentMagnificationSpec(), closeTo(startSpec)); - mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1); - assertThat(getCurrentMagnificationSpec(), closeTo(startSpec)); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); + mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10, SERVICE_ID_1); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); + mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10, SERVICE_ID_1); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); verifyNoMoreInteractions(mMockWindowManager); } @Test public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(i); + resetMockWindowManager(); + } + } + + private void offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(int displayId) { + register(displayId); float scale = 2.0f; // Upper left edges PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER; assertTrue(mMagnificationController - .setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1)); + .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false, + SERVICE_ID_1)); Mockito.reset(mMockWindowManager); - MagnificationSpec ulSpec = getCurrentMagnificationSpec(); - mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1); - assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec)); + MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId); + mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10, + SERVICE_ID_1); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(ulSpec)); verifyNoMoreInteractions(mMockWindowManager); // Lower right edges PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; assertTrue(mMagnificationController - .setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1)); + .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false, + SERVICE_ID_1)); Mockito.reset(mMockWindowManager); - MagnificationSpec lrSpec = getCurrentMagnificationSpec(); - mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1); - assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec)); + MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId); + mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10, + SERVICE_ID_1); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(lrSpec)); verifyNoMoreInteractions(mMockWindowManager); } @Test public void testGetIdOfLastServiceToChange_returnsCorrectValue() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + getIdOfLastServiceToChange_returnsCorrectValue(i); + resetMockWindowManager(); + } + } + + private void getIdOfLastServiceToChange_returnsCorrectValue(int displayId) { + register(displayId); PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; assertTrue(mMagnificationController - .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1)); - assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify()); + .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false, + SERVICE_ID_1)); + assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify(displayId)); assertTrue(mMagnificationController - .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2)); - assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify()); + .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false, + SERVICE_ID_2)); + assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify(displayId)); } @Test public void testResetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(i); + resetMockWindowManager(); + } + } + + private void resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(int displayId) { + register(displayId); PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; mMagnificationController - .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1); + .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false, + SERVICE_ID_1); mMagnificationController - .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2); - assertFalse(mMagnificationController.resetIfNeeded(SERVICE_ID_1)); - assertTrue(mMagnificationController.isMagnifying()); - assertTrue(mMagnificationController.resetIfNeeded(SERVICE_ID_2)); - assertFalse(mMagnificationController.isMagnifying()); + .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false, + SERVICE_ID_2); + assertFalse(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1)); + assertTrue(mMagnificationController.isMagnifying(displayId)); + assertTrue(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2)); + assertFalse(mMagnificationController.isMagnifying(displayId)); } @Test public void testSetUserId_resetsOnlyIfIdChanges() { + for (int i = 0; i < DISPLAY_COUNT; i++) { + testSetUserId_resetsOnlyIfIdChanges(i); + resetMockWindowManager(); + } + } + + private void testSetUserId_resetsOnlyIfIdChanges(int displayId) { final int userId1 = 1; final int userId2 = 2; - mMagnificationController.register(); + register(displayId); mMagnificationController.setUserId(userId1); PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; float scale = 2.0f; - mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1); + mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false, + SERVICE_ID_1); mMagnificationController.setUserId(userId1); - assertTrue(mMagnificationController.isMagnifying()); + assertTrue(mMagnificationController.isMagnifying(displayId)); mMagnificationController.setUserId(userId2); - assertFalse(mMagnificationController.isMagnifying()); + assertFalse(mMagnificationController.isMagnifying(displayId)); } @Test public void testResetIfNeeded_doesWhatItSays() { - mMagnificationController.register(); - zoomIn2xToMiddle(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + testResetIfNeeded_doesWhatItSays(i); + resetMockWindowManager(); + } + } + + private void testResetIfNeeded_doesWhatItSays(int displayId) { + register(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); reset(mMockAms); - assertTrue(mMagnificationController.resetIfNeeded(false)); - verify(mMockAms).notifyMagnificationChanged( + assertTrue(mMagnificationController.resetIfNeeded(displayId, false)); + verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat()); - assertFalse(mMagnificationController.isMagnifying()); - assertFalse(mMagnificationController.resetIfNeeded(false)); + assertFalse(mMagnificationController.isMagnifying(displayId)); + assertFalse(mMagnificationController.resetIfNeeded(displayId, false)); } @Test public void testTurnScreenOff_resetsMagnification() { - mMagnificationController.register(); + register(DISPLAY_0); + register(DISPLAY_1); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext).registerReceiver( broadcastReceiverCaptor.capture(), (IntentFilter) anyObject()); BroadcastReceiver br = broadcastReceiverCaptor.getValue(); - zoomIn2xToMiddle(); + zoomIn2xToMiddle(DISPLAY_0); + zoomIn2xToMiddle(DISPLAY_1); mMessageCapturingHandler.sendAllMessages(); br.onReceive(mMockContext, null); mMessageCapturingHandler.sendAllMessages(); - assertFalse(mMagnificationController.isMagnifying()); + assertFalse(mMagnificationController.isMagnifying(DISPLAY_0)); + assertFalse(mMagnificationController.isMagnifying(DISPLAY_1)); } @Test public void testUserContextChange_resetsMagnification() { - mMagnificationController.register(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); - zoomIn2xToMiddle(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + contextChange_resetsMagnification(i); + resetMockWindowManager(); + } + } + + private void contextChange_resetsMagnification(int displayId) { + register(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); callbacks.onUserContextChanged(); mMessageCapturingHandler.sendAllMessages(); - assertFalse(mMagnificationController.isMagnifying()); + assertFalse(mMagnificationController.isMagnifying(displayId)); } @Test public void testRotation_resetsMagnification() { - mMagnificationController.register(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); - zoomIn2xToMiddle(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + rotation_resetsMagnification(i); + resetMockWindowManager(); + } + } + + private void rotation_resetsMagnification(int displayId) { + register(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); - assertTrue(mMagnificationController.isMagnifying()); + assertTrue(mMagnificationController.isMagnifying(displayId)); callbacks.onRotationChanged(0); mMessageCapturingHandler.sendAllMessages(); - assertFalse(mMagnificationController.isMagnifying()); + assertFalse(mMagnificationController.isMagnifying(displayId)); } @Test public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() { + for (int i = 0; i < DISPLAY_COUNT; i++) { + boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(i); + resetMockWindowManager(); + } + } + + private void boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(int displayId) { // Going from a small region to a large one leads to no issues - mMagnificationController.register(); - zoomIn2xToMiddle(); + register(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); Mockito.reset(mMockWindowManager); callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT); mMessageCapturingHandler.sendAllMessages(); - assertThat(getCurrentMagnificationSpec(), closeTo(startSpec)); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); verifyNoMoreInteractions(mMockWindowManager); } @Test public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() { - mMagnificationController.register(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(i); + resetMockWindowManager(); + } + } + + private void boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(int displayId) { + register(displayId); PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; float scale = 2.0f; // setting animate parameter to true is differ from zoomIn2xToMiddle() - mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); + mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true, + SERVICE_ID_1); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); Mockito.reset(mMockWindowManager); callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT); mMessageCapturingHandler.sendAllMessages(); - assertThat(getCurrentMagnificationSpec(), closeTo(startSpec)); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); verifyNoMoreInteractions(mMockWindowManager); } @Test public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() { + for (int i = 0; i < DISPLAY_COUNT; i++) { + boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(i); + resetMockWindowManager(); + } + } + + private void boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained( + int displayId) { // In a large region, pan to the farthest point possible - mMagnificationController.register(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); + register(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); callbacks.onMagnificationRegionChanged(OTHER_REGION); mMessageCapturingHandler.sendAllMessages(); PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER; float scale = 2.0f; - mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1); + mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false, + SERVICE_ID_1); mMessageCapturingHandler.sendAllMessages(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec))); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec))); Mockito.reset(mMockWindowManager); callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION); mMessageCapturingHandler.sendAllMessages(); - MagnificationSpec endSpec = getCurrentMagnificationSpec(); + MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId); assertThat(endSpec, CoreMatchers.not(closeTo(startSpec))); PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale); assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets))); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec))); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); } @Test public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() { - mMagnificationController.register(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(i); + resetMockWindowManager(); + } + } + + private void boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec( + int displayId) { + register(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); callbacks.onMagnificationRegionChanged(OTHER_REGION); mMessageCapturingHandler.sendAllMessages(); PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER; float scale = 2.0f; - mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1); + mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true, + SERVICE_ID_1); mMessageCapturingHandler.sendAllMessages(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); when(mMockValueAnimator.isRunning()).thenReturn(true); callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION); mMessageCapturingHandler.sendAllMessages(); verify(mMockValueAnimator).cancel(); - MagnificationSpec endSpec = getCurrentMagnificationSpec(); + MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId); assertThat(endSpec, CoreMatchers.not(closeTo(startSpec))); PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale); assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets))); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec))); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); } @Test public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() { - mMagnificationController.register(); - zoomIn2xToMiddle(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + requestRectOnScreen_rectAlreadyOnScreen_doesNothing(i); + resetMockWindowManager(); + } + } + + private void requestRectOnScreen_rectAlreadyOnScreen_doesNothing(int displayId) { + register(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); Mockito.reset(mMockWindowManager); int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x; int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y; callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1); mMessageCapturingHandler.sendAllMessages(); - assertThat(getCurrentMagnificationSpec(), closeTo(startSpec)); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); verifyNoMoreInteractions(mMockWindowManager); } @Test public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() { - mMagnificationController.register(); - zoomIn2xToMiddle(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(i); + resetMockWindowManager(); + } + } + + private void requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(int displayId) { + register(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); Mockito.reset(mMockWindowManager); callbacks.onRectangleOnScreenRequested(0, 0, 1, 1); mMessageCapturingHandler.sendAllMessages(); MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0); - assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec)); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec))); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec)); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), + argThat(closeTo(expectedEndSpec))); } @Test public void testRequestRectOnScreen_garbageInput_doesNothing() { - mMagnificationController.register(); - zoomIn2xToMiddle(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + requestRectOnScreen_garbageInput_doesNothing(i); + resetMockWindowManager(); + } + } + + private void requestRectOnScreen_garbageInput_doesNothing(int displayId) { + register(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); Mockito.reset(mMockWindowManager); callbacks.onRectangleOnScreenRequested(0, 0, -50, -50); mMessageCapturingHandler.sendAllMessages(); - assertThat(getCurrentMagnificationSpec(), closeTo(startSpec)); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec)); verifyNoMoreInteractions(mMockWindowManager); } @Test public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() { + for (int i = 0; i < DISPLAY_COUNT; i++) { + requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(i); + resetMockWindowManager(); + } + } + + private void requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale( + int displayId) { Locale.setDefault(new Locale("en", "us")); - mMagnificationController.register(); - zoomIn2xToMiddle(); + register(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); Mockito.reset(mMockWindowManager); Rect wideRect = new Rect(0, 50, 100, 51); callbacks.onRectangleOnScreenRequested( wideRect.left, wideRect.top, wideRect.right, wideRect.bottom); mMessageCapturingHandler.sendAllMessages(); MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY); - assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec)); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec))); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec)); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), + argThat(closeTo(expectedEndSpec))); Mockito.reset(mMockWindowManager); // Repeat with RTL @@ -692,50 +900,66 @@ public class MagnificationControllerTest { wideRect.left, wideRect.top, wideRect.right, wideRect.bottom); mMessageCapturingHandler.sendAllMessages(); expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY); - assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec)); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec))); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec)); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), + argThat(closeTo(expectedEndSpec))); } @Test public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() { - mMagnificationController.register(); - zoomIn2xToMiddle(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(i); + resetMockWindowManager(); + } + } + + private void requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(int displayId) { + register(displayId); + zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); - MagnificationCallbacks callbacks = getMagnificationCallbacks(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); + MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); Mockito.reset(mMockWindowManager); Rect tallRect = new Rect(50, 0, 51, 100); callbacks.onRectangleOnScreenRequested( tallRect.left, tallRect.top, tallRect.right, tallRect.bottom); mMessageCapturingHandler.sendAllMessages(); MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0); - assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec)); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec))); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec)); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), + argThat(closeTo(expectedEndSpec))); } @Test public void testChangeMagnification_duringAnimation_animatesToNewValue() { - mMagnificationController.register(); - MagnificationSpec startSpec = getCurrentMagnificationSpec(); + for (int i = 0; i < DISPLAY_COUNT; i++) { + changeMagnification_duringAnimation_animatesToNewValue(i); + resetMockWindowManager(); + } + } + + private void changeMagnification_duringAnimation_animatesToNewValue(int displayId) { + register(displayId); + MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); float scale = 2.5f; PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; MagnificationSpec firstEndSpec = getMagnificationSpec( scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale)); - assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y, - true, SERVICE_ID_1)); + assertTrue(mMagnificationController.setScaleAndCenter(displayId, + scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); - assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5); - assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5); - assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec)); + assertEquals(firstCenter.x, mMagnificationController.getCenterX(displayId), 0.5); + assertEquals(firstCenter.y, mMagnificationController.getCenterY(displayId), 0.5); + assertThat(getCurrentMagnificationSpec(displayId), closeTo(firstEndSpec)); verify(mMockValueAnimator, times(1)).start(); // Initial value when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec(startSpec); - verify(mMockAms).notifyMagnificationChanged( + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec)); + verify(mMockAms).notifyMagnificationChanged(displayId, INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y); Mockito.reset(mMockWindowManager); @@ -745,32 +969,34 @@ public class MagnificationControllerTest { mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); MagnificationSpec intermediateSpec1 = getInterpolatedMagSpec(startSpec, firstEndSpec, fraction); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1))); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), + argThat(closeTo(intermediateSpec1))); Mockito.reset(mMockWindowManager); PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER; MagnificationSpec newEndSpec = getMagnificationSpec( scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale)); - assertTrue(mMagnificationController.setCenter( + assertTrue(mMagnificationController.setCenter(displayId, newCenter.x, newCenter.y, true, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); // Animation should have been restarted verify(mMockValueAnimator, times(2)).start(); - verify(mMockAms).notifyMagnificationChanged( + verify(mMockAms).notifyMagnificationChanged(displayId, INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y); // New starting point should be where we left off when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1))); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), + argThat(closeTo(intermediateSpec1))); Mockito.reset(mMockWindowManager); // Second intermediate point fraction = 0.5f; when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec( + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction)))); Mockito.reset(mMockWindowManager); @@ -778,21 +1004,54 @@ public class MagnificationControllerTest { Mockito.reset(mMockWindowManager); when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec))); + verify(mMockWindowManager).setMagnificationSpec(eq(displayId), + argThat(closeTo(newEndSpec))); + } + + private void initMockWindowManager() { + for (int i = 0; i < DISPLAY_COUNT; i++) { + when(mMockWindowManager.setMagnificationCallbacks(eq(i), any())).thenReturn(true); + } + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocationOnMock) throws Throwable { + Object[] args = invocationOnMock.getArguments(); + Region regionArg = (Region) args[1]; + regionArg.set(INITIAL_MAGNIFICATION_REGION); + return null; + } + }).when(mMockWindowManager).getMagnificationRegion(anyInt(), (Region) anyObject()); + } + + private void resetMockWindowManager() { + Mockito.reset(mMockWindowManager); + initMockWindowManager(); + } + + private void register(int displayId) { + mMockValueAnimator = mock(ValueAnimator.class); + when(mMockControllerCtx.newValueAnimator()).thenReturn(mMockValueAnimator); + mMagnificationController.register(displayId); + ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor = + ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class); + verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture()); + mTargetAnimationListener = listenerArgumentCaptor.getValue(); + Mockito.reset(mMockValueAnimator); // Ignore other initialization } - private void zoomIn2xToMiddle() { + private void zoomIn2xToMiddle(int displayId) { PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER; float scale = 2.0f; - mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1); - assertTrue(mMagnificationController.isMagnifying()); + mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false, + SERVICE_ID_1); + assertTrue(mMagnificationController.isMagnifying(displayId)); } - private MagnificationCallbacks getMagnificationCallbacks() { + private MagnificationCallbacks getMagnificationCallbacks(int displayId) { ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor = ArgumentCaptor.forClass(MagnificationCallbacks.class); verify(mMockWindowManager) - .setMagnificationCallbacks(magnificationCallbacksCaptor.capture()); + .setMagnificationCallbacks(eq(displayId), magnificationCallbacksCaptor.capture()); return magnificationCallbacksCaptor.getValue(); } @@ -823,9 +1082,10 @@ public class MagnificationControllerTest { return spec; } - private MagnificationSpec getCurrentMagnificationSpec() { - return getMagnificationSpec(mMagnificationController.getScale(), - mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY()); + private MagnificationSpec getCurrentMagnificationSpec(int displayId) { + return getMagnificationSpec(mMagnificationController.getScale(displayId), + mMagnificationController.getOffsetX(displayId), + mMagnificationController.getOffsetY(displayId)); } private MagSpecMatcher closeTo(MagnificationSpec spec) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java index 032074a7e398..d91ce39ea92c 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java @@ -26,16 +26,19 @@ import static com.android.server.testutils.TestUtils.strictMock; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.Context; +import android.os.Handler; import android.os.Message; import android.util.DebugUtils; import android.view.InputDevice; @@ -104,14 +107,9 @@ public class MagnificationGestureHandlerTest { public static final float DEFAULT_X = 301; public static final float DEFAULT_Y = 299; + private static final int DISPLAY_0 = 0; + private Context mContext; - final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class); - final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class); - final MessageCapturingHandler mMessageCapturingHandler = - new MessageCapturingHandler(null); - final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class); - MagnificationController.SettingsBridge mMockSettingsBridge = - mock(MagnificationController.SettingsBridge.class); MagnificationController mMagnificationController; private OffsettableClock mClock; @@ -123,18 +121,26 @@ public class MagnificationGestureHandlerTest { @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); - mMagnificationController = new MagnificationController(mContext, mMockAms, new Object(), - mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator, - mMockSettingsBridge) { + final MagnificationController.ControllerContext mockController = + mock(MagnificationController.ControllerContext.class); + final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class); + when(mockController.getContext()).thenReturn(mContext); + when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class)); + when(mockController.getWindowManager()).thenReturn(mockWindowManager); + when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper())); + when(mockController.newValueAnimator()).thenReturn(new ValueAnimator()); + when(mockController.getAnimationDuration()).thenReturn(1000L); + when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true); + mMagnificationController = new MagnificationController(mockController, new Object()) { @Override - public boolean magnificationRegionContains(float x, float y) { + public boolean magnificationRegionContains(int displayId, float x, float y) { return true; } @Override - void setForceShowMagnifiableBounds(boolean show) {} + void setForceShowMagnifiableBounds(int displayId, boolean show) {} }; - mMagnificationController.register(); + mMagnificationController.register(DISPLAY_0); mClock = new OffsettableClock.Stopped(); boolean detectTripleTap = true; @@ -144,7 +150,7 @@ public class MagnificationGestureHandlerTest { @After public void tearDown() { - mMagnificationController.unregister(); + mMagnificationController.unregister(DISPLAY_0); } @NonNull @@ -302,6 +308,24 @@ public class MagnificationGestureHandlerTest { assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED); } + @Test + public void testMultiTap_outOfDistanceSlop_shouldInIdle() { + // All delay motion events should be sent, if multi-tap with out of distance slop. + // STATE_IDLE will check if tapCount() < 2. + allowEventDelegation(); + assertStaysIn(STATE_IDLE, () -> { + tap(); + tap(DEFAULT_X * 2, DEFAULT_Y * 2); + }); + assertStaysIn(STATE_IDLE, () -> { + tap(); + tap(DEFAULT_X * 2, DEFAULT_Y * 2); + tap(); + tap(DEFAULT_X * 2, DEFAULT_Y * 2); + tap(); + }); + } + private void assertZoomsImmediatelyOnSwipeFrom(int state) { goFromStateIdleTo(state); swipeAndHold(); @@ -509,7 +533,7 @@ public class MagnificationGestureHandlerTest { } private boolean isZoomed() { - return mMgh.mMagnificationController.isMagnifying(); + return mMgh.mMagnificationController.isMagnifying(DISPLAY_0); } private int tapCount() { @@ -525,6 +549,11 @@ public class MagnificationGestureHandlerTest { send(upEvent()); } + private void tap(float x, float y) { + send(downEvent(x, y)); + send(upEvent(x, y)); + } + private void swipe() { swipeAndHold(); send(upEvent()); @@ -566,18 +595,26 @@ public class MagnificationGestureHandlerTest { } private MotionEvent downEvent() { + return downEvent(DEFAULT_X, DEFAULT_Y); + } + + private MotionEvent downEvent(float x, float y) { mLastDownTime = mClock.now(); return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime, - ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0)); + ACTION_DOWN, x, y, 0)); } private MotionEvent upEvent() { - return upEvent(mLastDownTime); + return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime); + } + + private MotionEvent upEvent(float x, float y) { + return upEvent(x, y, mLastDownTime); } - private MotionEvent upEvent(long downTime) { + private MotionEvent upEvent(float x, float y, long downTime) { return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(), - MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0)); + MotionEvent.ACTION_UP, x, y, 0)); } private MotionEvent pointerEvent(int action, float x, float y) { 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 38e8ac2d8f4c..0813e6fa0252 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -206,6 +206,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID; setUpUserManager(); + + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); } private TransferOwnershipMetadataManager getMockTransferMetadataManager() { @@ -836,6 +838,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { MockUtils.checkIntent(intent), MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID)); } + /** * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully. */ @@ -2618,6 +2621,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().lockPatternUtils .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false); dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER); + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true); verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER); verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM); @@ -4233,6 +4237,41 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.isActivePasswordSufficient()); } + public void testIsActivePasswordSufficient_noLockScreen() throws Exception { + // If there is no lock screen, the password is considered empty no matter what, because + // it provides no security. + when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false); + + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + mContext.packageName = admin1.getPackageName(); + setupDeviceOwner(); + + // If no password requirements are set, isActivePasswordSufficient should succeed. + assertTrue(dpm.isActivePasswordSufficient()); + + // Now set some password quality requirements. + dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + + reset(mContext.spiedContext); + final int userHandle = UserHandle.getUserId(mContext.binder.callingUid); + PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9, + 8, 2, + 6, 1, + 0, 1); + // This should be ignored, as there is no lock screen. + dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle); + dpm.reportPasswordChanged(userHandle); + + // No broadcast should be sent. + verify(mContext.spiedContext, times(0)).sendBroadcastAsUser( + MockUtils.checkIntentAction(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED), + MockUtils.checkUserHandle(userHandle)); + + // The active (nonexistent) password doesn't comply with the requirements. + assertFalse(dpm.isActivePasswordSufficient()); + } + private void setActivePasswordState(PasswordMetrics passwordMetrics) throws Exception { final int userHandle = UserHandle.getUserId(mContext.binder.callingUid); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 3a6cdc2ad7c9..a89198ae3708 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -147,12 +147,12 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @Override - void writeStringSetting(String key, String value) { + void writeStringSystemProperty(String key, String value) { // do nothing } @Override - boolean readBooleanSetting(String key, boolean defVal) { + boolean readBooleanSystemProperty(String key, boolean defVal) { switch (key) { case Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE: return mMutingEnabled; diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java index 0328621ce5d1..8afc3d30efa3 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java @@ -22,7 +22,6 @@ import static android.view.Display.INVALID_DISPLAY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -58,8 +57,7 @@ public class InputMethodManagerServiceTests { // Make sure that there is a short-circuit for DEFAULT_DISPLAY. assertEquals(DEFAULT_DISPLAY, InputMethodManagerService.computeImeDisplayIdForTarget( - DEFAULT_DISPLAY, false /* isVrImeStarted */, - sMustNotBeCalledChecker)); + DEFAULT_DISPLAY, sMustNotBeCalledChecker)); } @Test @@ -67,17 +65,7 @@ public class InputMethodManagerServiceTests { // Make sure that there is a short-circuit for INVALID_DISPLAY. assertEquals(DEFAULT_DISPLAY, InputMethodManagerService.computeImeDisplayIdForTarget( - INVALID_DISPLAY, false /* isVrImeStarted */, - sMustNotBeCalledChecker)); - } - - @Test - public void testComputeImeDisplayId_VrIme() { - // Make sure that there is a short-circuit for VR IME. - assertEquals(DEFAULT_DISPLAY, - InputMethodManagerService.computeImeDisplayIdForTarget( - SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, true /* isVrImeStarted */, - sMustNotBeCalledChecker)); + INVALID_DISPLAY, sMustNotBeCalledChecker)); } @Test @@ -86,8 +74,7 @@ public class InputMethodManagerServiceTests { // Make sure IME displayId is DEFAULT_DISPLAY. assertEquals(DEFAULT_DISPLAY, InputMethodManagerService.computeImeDisplayIdForTarget( - NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */, - sChecker)); + NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker)); } @Test @@ -96,7 +83,6 @@ public class InputMethodManagerServiceTests { // Make sure IME displayId is the same display. assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, InputMethodManagerService.computeImeDisplayIdForTarget( - SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */, - sChecker)); + SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker)); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 2dc3510a82e5..cf89cb8f7a15 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -37,8 +37,8 @@ import android.os.FileUtils; import android.os.IProgressListener; import android.os.RemoteException; import android.os.UserManager; -import android.os.storage.StorageManager; import android.os.storage.IStorageManager; +import android.os.storage.StorageManager; import android.security.KeyStore; import android.test.AndroidTestCase; @@ -46,6 +46,7 @@ import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerInternal; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -85,6 +86,8 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { KeyStore mKeyStore; MockSyntheticPasswordManager mSpManager; IAuthSecret mAuthSecretService; + WindowManagerInternal mMockWindowManager; + protected boolean mHasSecureLockScreen; @Override protected void setUp() throws Exception { @@ -97,10 +100,13 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { mActivityManager = mock(IActivityManager.class); mDevicePolicyManager = mock(DevicePolicyManager.class); mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class); + mMockWindowManager = mock(WindowManagerInternal.class); LocalServices.removeServiceForTest(LockSettingsInternal.class); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal); + LocalServices.addService(WindowManagerInternal.class, mMockWindowManager); mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager, mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class), @@ -114,11 +120,17 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { storageDir.mkdirs(); } + mHasSecureLockScreen = true; mLockPatternUtils = new LockPatternUtils(mContext) { @Override public ILockSettings getLockSettings() { return mService; } + + @Override + public boolean hasSecureLockScreen() { + return mHasSecureLockScreen; + } }; mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService, mUserManager); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index e12f6d3be71e..5124803ee298 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -26,13 +26,12 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSW import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; import android.os.RemoteException; -import android.os.UserHandle; import android.service.gatekeeper.GateKeeperResponse; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; -import com.android.server.locksettings.LockSettingsStorage.CredentialHash; import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle; +import com.android.server.locksettings.LockSettingsStorage.CredentialHash; /** * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests @@ -54,11 +53,21 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { PASSWORD_QUALITY_ALPHABETIC); } + public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException { + testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password", + CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC); + } + public void testCreatePatternPrimaryUser() throws RemoteException { testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING); } + public void testCreatePatternFailsWithoutLockScreen() throws RemoteException { + testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789", + CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING); + } + public void testChangePasswordPrimaryUser() throws RemoteException { testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN, "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC); @@ -198,6 +207,21 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { assertVerifyCredentials(userId, credential, type, -1); } + private void testCreateCredentialFailsWithoutLockScreen( + int userId, String credential, int type, int quality) throws RemoteException { + mHasSecureLockScreen = false; + + try { + mService.setLockCredential(credential, type, null, quality, userId); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + + assertFalse(mService.havePassword(userId)); + assertFalse(mService.havePattern(userId)); + } + private void testChangeCredentials(int userId, String newCredential, int newType, String oldCredential, int oldType, int quality) throws RemoteException { final long sid = 1234; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java index a28a5a10e832..929c3b525db9 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java @@ -27,6 +27,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.ActivityManager; @@ -77,6 +78,7 @@ public class LockSettingsShellCommandTest { final Context context = InstrumentationRegistry.getTargetContext(); mUserId = ActivityManager.getCurrentUser(); mCommand = new LockSettingsShellCommand(mLockPatternUtils); + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true); } @Test @@ -103,6 +105,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePin_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-pin", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testChangePassword() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true); @@ -115,6 +127,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePassword_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-password", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testChangePattern() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false); @@ -126,6 +148,16 @@ public class LockSettingsShellCommandTest { } @Test + public void testChangePattern_noLockScreen() throws Exception { + when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false); + assertEquals(-1, mCommand.exec(new Binder(), in, out, err, + new String[] { "set-pattern", "--old", "1234", "4321" }, + mShellCallback, mResultReceiver)); + verify(mLockPatternUtils).hasSecureLockScreen(); + verifyNoMoreInteractions(mLockPatternUtils); + } + + @Test public void testClear() throws Exception { when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true); when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 94e02bc4d35f..0595a5b2e9a0 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -40,10 +40,10 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationRe import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; import com.android.server.locksettings.SyntheticPasswordManager.PasswordData; -import java.util.ArrayList; - import org.mockito.ArgumentCaptor; +import java.util.ArrayList; + /** * runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests @@ -448,6 +448,37 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); } + public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception { + final String password = "password"; + final String pattern = "123654"; + final String token = "some-high-entropy-secure-token"; + + mHasSecureLockScreen = false; + enableSyntheticPassword(); + long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID); + assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); + + try { + mLocalService.setLockCredentialWithToken(password, + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + assertFalse(mService.havePassword(PRIMARY_USER_ID)); + + try { + mLocalService.setLockCredentialWithToken(pattern, + LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token.getBytes(), + PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID); + fail("An exception should have been thrown."); + } catch (UnsupportedOperationException e) { + // Success - the exception was expected. + } + assertFalse(mService.havePattern(PRIMARY_USER_ID)); + } + public void testgetHashFactorPrimaryUser() throws RemoteException { final String password = "password"; mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java new file mode 100644 index 000000000000..73e96134167a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import android.content.pm.PackageInstaller; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.FastXmlSerializer; + +import libcore.io.IoUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class PackageInstallerSessionTest { + private File mTmpDir; + private AtomicFile mSessionsFile; + private static final String TAG_SESSIONS = "sessions"; + + @Mock + PackageManagerService mMockPackageManagerInternal; + + @Before + public void setUp() throws Exception { + mTmpDir = IoUtils.createTemporaryDirectory("PackageInstallerSessionTest"); + mSessionsFile = new AtomicFile( + new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session"); + MockitoAnnotations.initMocks(this); + } + + @Test + public void testWriteAndRestoreSessionXmlSimpleSession() { + PackageInstallerSession session = createSimpleSession(); + dumpSession(session); + List<PackageInstallerSession> restored = restoreSessions(); + assertEquals(1, restored.size()); + assertSessionsEquivalent(session, restored.get(0)); + } + + @Test + public void testWriteAndRestoreSessionXmlStagedSession() { + PackageInstallerSession session = createStagedSession(); + dumpSession(session); + List<PackageInstallerSession> restored = restoreSessions(); + assertEquals(1, restored.size()); + assertSessionsEquivalent(session, restored.get(0)); + } + + @Test + public void testWriteAndRestoreSessionXmlGrantedPermission() { + PackageInstallerSession session = createSessionWithGrantedPermissions(); + dumpSession(session); + List<PackageInstallerSession> restored = restoreSessions(); + assertEquals(1, restored.size()); + assertSessionsEquivalent(session, restored.get(0)); + } + + @Test + public void testWriteAndRestoreSessionXmlMultiPackageSessions() { + PackageInstallerSession session = createMultiPackageParentSession(123, new int[]{234, 345}); + PackageInstallerSession childSession1 = createMultiPackageChildSession(234, 123); + PackageInstallerSession childSession2 = createMultiPackageChildSession(345, 123); + List<PackageInstallerSession> sessionGroup = + Arrays.asList(session, childSession1, childSession2); + dumpSessions(sessionGroup); + List<PackageInstallerSession> restored = restoreSessions(); + assertEquals(3, restored.size()); + assertSessionsEquivalent(sessionGroup, restored); + } + + private PackageInstallerSession createSimpleSession() { + return createSession(false, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID, + null); + } + + private PackageInstallerSession createStagedSession() { + return createSession(true, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID, + null); + } + + private PackageInstallerSession createSessionWithGrantedPermissions() { + return createSession(false, true, 123, false, PackageInstaller.SessionInfo.INVALID_ID, + null); + } + + private PackageInstallerSession createMultiPackageParentSession(int sessionId, + int[] childSessionIds) { + return createSession(false, false, sessionId, true, + PackageInstaller.SessionInfo.INVALID_ID, childSessionIds); + } + + private PackageInstallerSession createMultiPackageChildSession(int sessionId, + int parentSessionId) { + return createSession(false, false, sessionId, false, parentSessionId, null); + } + + private PackageInstallerSession createSession(boolean staged, boolean withGrantedPermissions, + int sessionId, boolean isMultiPackage, + int parentSessionId, int[] childSessionIds) { + PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + if (staged) { + params.isStaged = true; + } + if (withGrantedPermissions) { + params.grantedRuntimePermissions = new String[]{"permission1", "permission2"}; + } + if (isMultiPackage) { + params.isMultiPackage = true; + } + return new PackageInstallerSession( + /* callback */ null, + /* context */null, + /* pm */ mMockPackageManagerInternal, + /* sessionProvider */ null, + /* looper */ BackgroundThread.getHandler().getLooper(), + /* stagingManager */ null, + /* sessionId */ sessionId, + /* userId */ 456, + /* installerPackageName */ "testInstaller", + /* installerUid */ -1, + /* sessionParams */ params, + /* createdMillis */ 0L, + /* stageDir */ mTmpDir, + /* stageCid */ null, + /* prepared */ true, + /* sealed */ false, // Setting to true would trigger some PM logic. + /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0], + /* parentSessionId */ parentSessionId, + /* isReady */ staged ? true : false, + /* isFailed */ false, + /* isApplied */false, + /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED); + } + + private void dumpSession(PackageInstallerSession session) { + dumpSessions(Arrays.asList(session)); + } + + private void dumpSessions(List<PackageInstallerSession> sessions) { + FileOutputStream fos = null; + try { + fos = mSessionsFile.startWrite(); + + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.startTag(null, TAG_SESSIONS); + for (PackageInstallerSession session : sessions) { + session.write(out, mTmpDir); + } + out.endTag(null, TAG_SESSIONS); + out.endDocument(); + + mSessionsFile.finishWrite(fos); + Slog.d("PackageInstallerSessionTest", new String(mSessionsFile.readFully())); + } catch (IOException e) { + if (fos != null) { + mSessionsFile.failWrite(fos); + } + } + } + + // This is roughly the logic used in PackageInstallerService to read the session. Note that + // this test stresses readFromXml method from PackageInstallerSession, and doesn't cover the + // PackageInstallerService portion of the parsing. + private List<PackageInstallerSession> restoreSessions() { + List<PackageInstallerSession> ret = new ArrayList<>(); + FileInputStream fis = null; + try { + fis = mSessionsFile.openRead(); + final XmlPullParser in = Xml.newPullParser(); + in.setInput(fis, StandardCharsets.UTF_8.name()); + + int type; + while ((type = in.next()) != END_DOCUMENT) { + if (type == START_TAG) { + final String tag = in.getName(); + if (PackageInstallerSession.TAG_SESSION.equals(tag)) { + final PackageInstallerSession session; + try { + session = PackageInstallerSession.readFromXml(in, null, + null, mMockPackageManagerInternal, + BackgroundThread.getHandler().getLooper(), null, + mTmpDir, null); + ret.add(session); + } catch (Exception e) { + Slog.e("PackageInstallerSessionTest", "Exception ", e); + continue; + } + } + } + } + } catch (FileNotFoundException e) { + // Missing sessions are okay, probably first boot + } catch (IOException | XmlPullParserException e) { + + } finally { + IoUtils.closeQuietly(fis); + } + return ret; + } + + private void assertSessionParamsEquivalent(PackageInstaller.SessionParams expected, + PackageInstaller.SessionParams actual) { + assertEquals(expected.mode, actual.mode); + assertEquals(expected.installFlags, actual.installFlags); + assertEquals(expected.installLocation, actual.installLocation); + assertEquals(expected.installReason, actual.installReason); + assertEquals(expected.sizeBytes, actual.sizeBytes); + assertEquals(expected.appPackageName, actual.appPackageName); + assertEquals(expected.appIcon, actual.appIcon); + assertEquals(expected.originatingUri, actual.originatingUri); + assertEquals(expected.originatingUid, actual.originatingUid); + assertEquals(expected.referrerUri, actual.referrerUri); + assertEquals(expected.abiOverride, actual.abiOverride); + assertEquals(expected.volumeUuid, actual.volumeUuid); + assertArrayEquals(expected.grantedRuntimePermissions, actual.grantedRuntimePermissions); + assertEquals(expected.installerPackageName, actual.installerPackageName); + assertEquals(expected.isMultiPackage, actual.isMultiPackage); + assertEquals(expected.isStaged, actual.isStaged); + } + + private void assertSessionsEquivalent(List<PackageInstallerSession> expected, + List<PackageInstallerSession> actual) { + assertEquals(expected.size(), actual.size()); + for (PackageInstallerSession expectedSession : expected) { + boolean foundSession = false; + for (PackageInstallerSession actualSession : actual) { + if (expectedSession.sessionId == actualSession.sessionId) { + // We should only encounter each expected session once. + assertFalse(foundSession); + foundSession = true; + assertSessionsEquivalent(expectedSession, actualSession); + } + } + assertTrue(foundSession); + } + } + + private void assertSessionsEquivalent(PackageInstallerSession expected, + PackageInstallerSession actual) { + assertEquals(expected.sessionId, actual.sessionId); + assertEquals(expected.userId, actual.userId); + assertSessionParamsEquivalent(expected.params, actual.params); + assertEquals(expected.getInstallerUid(), actual.getInstallerUid()); + assertEquals(expected.stageDir.getAbsolutePath(), actual.stageDir.getAbsolutePath()); + assertEquals(expected.stageCid, actual.stageCid); + assertEquals(expected.isPrepared(), actual.isPrepared()); + assertEquals(expected.isStaged(), actual.isStaged()); + assertEquals(expected.isStagedSessionApplied(), actual.isStagedSessionApplied()); + assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed()); + assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady()); + assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode()); + assertEquals(expected.isPrepared(), actual.isPrepared()); + assertEquals(expected.isSealed(), actual.isSealed()); + assertEquals(expected.isMultiPackage(), actual.isMultiPackage()); + assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId()); + assertEquals(expected.getParentSessionId(), actual.getParentSessionId()); + assertArrayEquals(expected.getChildSessionIds(), actual.getChildSessionIds()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java index f817e8e33b31..6da202b93065 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexLoggerTests.java @@ -16,8 +16,6 @@ package com.android.server.pm.dex; -import static com.android.server.pm.dex.PackageDynamicCodeLoading.FILE_TYPE_DEX; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.atMost; @@ -26,10 +24,12 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.os.UserHandle; import android.os.storage.StorageManager; import androidx.test.filters.SmallTest; @@ -56,40 +56,44 @@ import org.mockito.stubbing.Stubber; public class DexLoggerTests { private static final String OWNING_PACKAGE_NAME = "package.name"; private static final String VOLUME_UUID = "volUuid"; - private static final String DEX_PATH = "/bar/foo.jar"; + private static final String FILE_PATH = "/bar/foo.jar"; private static final int STORAGE_FLAGS = StorageManager.FLAG_STORAGE_DE; private static final int OWNER_UID = 43; private static final int OWNER_USER_ID = 44; // Obtained via: echo -n "foo.jar" | sha256sum - private static final String DEX_FILENAME_HASH = + private static final String FILENAME_HASH = "91D7B844D7CC9673748FF057D8DC83972280FC28537D381AA42015A9CF214B9F"; - private static final byte[] CONTENT_HASH_BYTES = new byte[] { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + private static final byte[] CONTENT_HASH_BYTES = new byte[]{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; private static final String CONTENT_HASH = "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20"; private static final byte[] EMPTY_BYTES = {}; - @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + private static final String EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH = + "dcl:" + FILENAME_HASH; + private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH = + EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH + " " + CONTENT_HASH; + private static final String EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH = + "dcln:" + FILENAME_HASH + " " + CONTENT_HASH; + + @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.LENIENT); @Mock IPackageManager mPM; @Mock Installer mInstaller; - private PackageDynamicCodeLoading mPackageDynamicCodeLoading; private DexLogger mDexLogger; private final ListMultimap<Integer, String> mMessagesForUid = ArrayListMultimap.create(); private boolean mWriteTriggered = false; - private static final String EXPECTED_MESSAGE_WITH_CONTENT_HASH = - DEX_FILENAME_HASH + " " + CONTENT_HASH; @Before public void setup() throws Exception { // Disable actually attempting to do file writes. - mPackageDynamicCodeLoading = new PackageDynamicCodeLoading() { + PackageDynamicCodeLoading packageDynamicCodeLoading = new PackageDynamicCodeLoading() { @Override void maybeWriteAsync() { mWriteTriggered = true; @@ -102,13 +106,13 @@ public class DexLoggerTests { }; // For test purposes capture log messages as well as sending to the event log. - mDexLogger = new DexLogger(mPM, mInstaller, mPackageDynamicCodeLoading) { + mDexLogger = new DexLogger(mPM, mInstaller, packageDynamicCodeLoading) { @Override - void writeDclEvent(int uid, String message) { - super.writeDclEvent(uid, message); - mMessagesForUid.put(uid, message); - } - }; + void writeDclEvent(String subtag, int uid, String message) { + super.writeDclEvent(subtag, uid, message); + mMessagesForUid.put(uid, subtag + ":" + message); + } + }; // Make the owning package exist in our mock PackageManager. ApplicationInfo appInfo = new ApplicationInfo(); @@ -124,9 +128,9 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_withFileHash() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); @@ -139,13 +143,13 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_noFileHash() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(EMPTY_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(EMPTY_BYTES)); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); - assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); // File should be removed from the DCL list, since we can't hash it. assertThat(mWriteTriggered).isTrue(); @@ -154,13 +158,14 @@ public class DexLoggerTests { @Test public void testOneLoader_ownFile_hashingFails() throws Exception { - whenFileIsHashed(DEX_PATH, doThrow(new InstallerException("Intentional failure for test"))); + whenFileIsHashed(FILE_PATH, + doThrow(new InstallerException("Intentional failure for test"))); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); - assertThat(mMessagesForUid).containsEntry(OWNER_UID, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); // File should be removed from the DCL list, since we can't hash it. assertThat(mWriteTriggered).isTrue(); @@ -178,11 +183,23 @@ public class DexLoggerTests { } @Test + public void testOneLoader_pathTraversal() throws Exception { + String filePath = "/bar/../secret/foo.jar"; + whenFileIsHashed(filePath, doReturn(CONTENT_HASH_BYTES)); + setPackageUid(OWNING_PACKAGE_NAME, -1); + + recordLoad(OWNING_PACKAGE_NAME, filePath); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); + + assertThat(mMessagesForUid).isEmpty(); + } + + @Test public void testOneLoader_differentOwner() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); setPackageUid("other.package.name", 1001); - recordLoad("other.package.name", DEX_PATH); + recordLoad("other.package.name", FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(1001); @@ -192,10 +209,10 @@ public class DexLoggerTests { @Test public void testOneLoader_differentOwner_uninstalled() throws Exception { - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); setPackageUid("other.package.name", -1); - recordLoad("other.package.name", DEX_PATH); + recordLoad("other.package.name", FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid).isEmpty(); @@ -203,22 +220,38 @@ public class DexLoggerTests { } @Test + public void testNativeCodeLoad() throws Exception { + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); + + recordLoadNative(FILE_PATH); + mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); + + assertThat(mMessagesForUid.keys()).containsExactly(OWNER_UID); + assertThat(mMessagesForUid) + .containsEntry(OWNER_UID, EXPECTED_MESSAGE_NATIVE_WITH_CONTENT_HASH); + + assertThat(mWriteTriggered).isFalse(); + assertThat(mDexLogger.getAllPackagesWithDynamicCodeLoading()) + .containsExactly(OWNING_PACKAGE_NAME); + } + + @Test public void testMultipleLoadersAndFiles() throws Exception { String otherDexPath = "/bar/nosuchdir/foo.jar"; - whenFileIsHashed(DEX_PATH, doReturn(CONTENT_HASH_BYTES)); + whenFileIsHashed(FILE_PATH, doReturn(CONTENT_HASH_BYTES)); whenFileIsHashed(otherDexPath, doReturn(EMPTY_BYTES)); setPackageUid("other.package.name1", 1001); setPackageUid("other.package.name2", 1002); - recordLoad("other.package.name1", DEX_PATH); + recordLoad("other.package.name1", FILE_PATH); recordLoad("other.package.name1", otherDexPath); - recordLoad("other.package.name2", DEX_PATH); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad("other.package.name2", FILE_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid.keys()).containsExactly(1001, 1001, 1002, OWNER_UID); assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITH_CONTENT_HASH); - assertThat(mMessagesForUid).containsEntry(1001, DEX_FILENAME_HASH); + assertThat(mMessagesForUid).containsEntry(1001, EXPECTED_MESSAGE_WITHOUT_CONTENT_HASH); assertThat(mMessagesForUid).containsEntry(1002, EXPECTED_MESSAGE_WITH_CONTENT_HASH); assertThat(mMessagesForUid).containsEntry(OWNER_UID, EXPECTED_MESSAGE_WITH_CONTENT_HASH); @@ -233,7 +266,7 @@ public class DexLoggerTests { @Test public void testUnknownOwner() { reset(mPM); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading("other.package.name"); assertThat(mMessagesForUid).isEmpty(); @@ -244,7 +277,7 @@ public class DexLoggerTests { @Test public void testUninstalledPackage() { reset(mPM); - recordLoad(OWNING_PACKAGE_NAME, DEX_PATH); + recordLoad(OWNING_PACKAGE_NAME, FILE_PATH); mDexLogger.logDynamicCodeLoading(OWNING_PACKAGE_NAME); assertThat(mMessagesForUid).isEmpty(); @@ -262,7 +295,16 @@ public class DexLoggerTests { } private void recordLoad(String loadingPackageName, String dexPath) { - mPackageDynamicCodeLoading.record( - OWNING_PACKAGE_NAME, dexPath, FILE_TYPE_DEX, OWNER_USER_ID, loadingPackageName); + mDexLogger.recordDex(OWNER_USER_ID, dexPath, OWNING_PACKAGE_NAME, loadingPackageName); + mWriteTriggered = false; + } + + private void recordLoadNative(String nativePath) throws Exception { + int loadingUid = UserHandle.getUid(OWNER_USER_ID, OWNER_UID); + String[] packageNames = { OWNING_PACKAGE_NAME }; + when(mPM.getPackagesForUid(loadingUid)).thenReturn(packageNames); + + mDexLogger.recordNative(loadingUid, nativePath); + mWriteTriggered = false; } } 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 94b21af65799..c0f9b80b2e08 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -185,6 +185,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private NotificationChannel mTestNotificationChannel = new NotificationChannel( TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + + private static final int NOTIFICATION_LOCATION_UNKNOWN = 0; + @Mock private NotificationListeners mListeners; @Mock private NotificationAssistants mAssistants; @@ -2528,11 +2531,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); - mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true); + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true, + NOTIFICATION_LOCATION_UNKNOWN); verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((true))); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); - mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false); + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false, + NOTIFICATION_LOCATION_UNKNOWN); verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(true), eq((false))); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); } @@ -2542,11 +2547,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); - mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true); + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, + NOTIFICATION_LOCATION_UNKNOWN); assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.sbn), eq(false), eq((true))); - mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false); + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false, + NOTIFICATION_LOCATION_UNKNOWN); assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded()); verify(mAssistants).notifyAssistantExpansionChangedLocked( eq(r.sbn), eq(false), eq((false))); @@ -3793,7 +3800,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); - mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true); + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, + NOTIFICATION_LOCATION_UNKNOWN); NotificationVisibility[] notificationVisibility = new NotificationVisibility[] { NotificationVisibility.obtain(r.getKey(), 0, 0, true) }; @@ -3808,7 +3816,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); - mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true); + mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true, + NOTIFICATION_LOCATION_UNKNOWN); assertEquals(0, mService.countLogSmartSuggestionsVisible); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 319ffed3778c..8be63fc43adb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -31,7 +31,6 @@ import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; -import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING; @@ -76,9 +75,6 @@ public class ActivityRecordTests extends ActivityTestsBase { mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build(); mTask = mStack.getChildAt(0); mActivity = mTask.getTopActivity(); - - doReturn(false).when(mService).isBooting(); - doReturn(true).when(mService).isBooted(); } @Test @@ -121,23 +117,22 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); - // The activity is in the focused stack so it should be resumed. + // The activity is in the focused stack so it should not move to paused. mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); - assertTrue(mActivity.isState(RESUMED)); + assertTrue(mActivity.isState(STOPPED)); assertFalse(pauseFound.value); - // Make the activity non focusable - mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); - doReturn(false).when(mActivity).isFocusable(); + // Clear focused stack + final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay(); + when(display.getFocusedStack()).thenReturn(null); - // If the activity is not focusable, it should move to paused. + // In the unfocused stack, the activity should move to paused. mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); assertTrue(mActivity.isState(PAUSING)); assertTrue(pauseFound.value); // Make sure that the state does not change for current non-stopping states. mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped"); - doReturn(true).when(mActivity).isFocusable(); mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); 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 ea8f33f0c630..68df87e3e27d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -55,7 +55,6 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.Looper; -import android.os.PowerManager; import android.os.Process; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; @@ -426,7 +425,6 @@ class ActivityTestsBase { doReturn(mock(IPackageManager.class)).when(this).getPackageManager(); // allow background activity starts by default doReturn(true).when(this).isBackgroundActivityStartsEnabled(); - doNothing().when(this).updateCpuStats(); } void setup(IntentFirewall intentFirewall, PendingIntentController intentController, @@ -582,8 +580,6 @@ class ActivityTestsBase { doNothing().when(this).acquireLaunchWakelock(); doReturn(mKeyguardController).when(this).getKeyguardController(); - mLaunchingActivity = mock(PowerManager.WakeLock.class); - initialize(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java index d0b9225715c4..ea5ab7bf0621 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java @@ -70,9 +70,9 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); verify(mTransaction).reparent(eq(mToken.getSurfaceControl()), - eq(mToken.mSurfaceAnimator.mLeash.getHandle())); + eq(mToken.mSurfaceAnimator.mLeash)); verify(mTransaction).reparent(eq(mToken.mSurfaceAnimator.mLeash), - eq(mToken.mAnimationBoundsLayer.getHandle())); + eq(mToken.mAnimationBoundsLayer)); } @Test @@ -111,7 +111,7 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); verify(mTransaction).reparent(eq(mToken.getSurfaceControl()), - eq(mToken.mSurfaceAnimator.mLeash.getHandle())); + eq(mToken.mSurfaceAnimator.mLeash)); assertThat(mToken.mAnimationBoundsLayer).isNull(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java index ad80cd6ddfb7..9b84215a8f3b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -90,7 +90,7 @@ public class SurfaceAnimatorTest extends WindowTestsBase { final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass( OnAnimationFinishedCallback.class); assertAnimating(mAnimatable); - verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle())); + verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash)); verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); callbackCaptor.getValue().onAnimationFinished(mSpec); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java index b996bfbf2101..c595868db484 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java @@ -19,6 +19,10 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.TaskPositioner.MIN_ASPECT; import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; @@ -38,13 +42,12 @@ import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; /** * Tests for the {@link TaskPositioner} class. * * Build/Install/Run: - * atest FrameworksServicesTests:TaskPositionerTests + * atest WmTests:TaskPositionerTests */ @SmallTest public class TaskPositionerTests extends WindowTestsBase { @@ -73,18 +76,17 @@ public class TaskPositionerTests extends WindowTestsBase { mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm); mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm); - mPositioner = new TaskPositioner(mWm, Mockito.mock(IActivityTaskManager.class)); + mPositioner = new TaskPositioner(mWm, mock(IActivityTaskManager.class)); mPositioner.register(mDisplayContent); - mWindow = Mockito.spy(createWindow(null, TYPE_BASE_APPLICATION, "window")); - final Task task = Mockito.spy(mWindow.getTask()); - Mockito.when(mWindow.getTask()).thenReturn(task); - - Mockito.doAnswer(invocation -> { + mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); + final Task task = mWindow.getTask(); + spyOn(task); + doAnswer(invocation -> { final Rect rect = (Rect) invocation.getArguments()[0]; rect.set(mDimBounds); - return (Void) null; - }).when(task).getDimBounds(Mockito.any(Rect.class)); + return null; + }).when(task).getDimBounds(any(Rect.class)); mWindow.getStack().setWindowingMode(WINDOWING_MODE_FREEFORM); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index 946ffb60c759..d29e3fa6f546 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -53,7 +53,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { public void setUp() { final UserManager um = UserManager.get(getInstrumentation().getTargetContext()); mTestUserId = um.getUserHandle(); - mPersister = new TaskSnapshotPersister(userId -> FILES_DIR); + mPersister = new TaskSnapshotPersister(mWm, userId -> FILES_DIR); mLoader = new TaskSnapshotLoader(mPersister); mPersister.start(); } diff --git a/startop/OWNERS b/startop/OWNERS index bfe96d3ed15c..762cd8e48be4 100644 --- a/startop/OWNERS +++ b/startop/OWNERS @@ -3,3 +3,4 @@ chriswailes@google.com eholk@google.com iam@google.com sehr@google.com +mathieuc@google.com diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index a39e885204b7..7d1f8ce75919 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -19,6 +19,7 @@ package android.telecom; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.telecom.Connection.VideoProvider; @@ -64,6 +65,10 @@ public abstract class Conference extends Conferenceable { public void onStatusHintsChanged(Conference conference, StatusHints statusHints) {} public void onExtrasChanged(Conference c, Bundle extras) {} public void onExtrasRemoved(Conference c, List<String> keys) {} + public void onConferenceStateChanged(Conference c, boolean isConference) {} + public void onAddressChanged(Conference c, Uri newAddress, int presentation) {} + public void onCallerDisplayNameChanged( + Conference c, String callerDisplayName, int presentation) {} } private final Set<Listener> mListeners = new CopyOnWriteArraySet<>(); @@ -946,6 +951,62 @@ public abstract class Conference extends Conferenceable { public void onExtrasChanged(Bundle extras) {} /** + * Set whether Telecom should treat this {@link Conference} as a conference call or if it + * should treat it as a single-party call. + * This method is used as part of a workaround regarding IMS conference calls and user + * expectation. In IMS, once a conference is formed, the UE is connected to an IMS conference + * server. If all participants of the conference drop out of the conference except for one, the + * UE is still connected to the IMS conference server. At this point, the user logically + * assumes they're no longer in a conference, yet the underlying network actually is. + * To help provide a better user experiece, IMS conference calls can pretend to actually be a + * single-party call when the participant count drops to 1. Although the dialer/phone app + * could perform this trickery, it makes sense to do this in Telephony since a fix there will + * ensure that bluetooth head units, auto and wearable apps all behave consistently. + * + * @param isConference {@code true} if this {@link Conference} should be treated like a + * conference call, {@code false} if it should be treated like a single-party call. + * @hide + */ + public void setConferenceState(boolean isConference) { + for (Listener l : mListeners) { + l.onConferenceStateChanged(this, isConference); + } + } + + /** + * Sets the address of this {@link Conference}. Used when {@link #setConferenceState(boolean)} + * is called to mark a conference temporarily as NOT a conference. + * + * @param address The new address. + * @param presentation The presentation requirements for the address. + * See {@link TelecomManager} for valid values. + * @hide + */ + public final void setAddress(Uri address, int presentation) { + Log.d(this, "setAddress %s", address); + for (Listener l : mListeners) { + l.onAddressChanged(this, address, presentation); + } + } + + /** + * Sets the caller display name (CNAP) of this {@link Conference}. Used when + * {@link #setConferenceState(boolean)} is called to mark a conference temporarily as NOT a + * conference. + * + * @param callerDisplayName The new display name. + * @param presentation The presentation requirements for the handle. + * See {@link TelecomManager} for valid values. + * @hide + */ + public final void setCallerDisplayName(String callerDisplayName, int presentation) { + Log.d(this, "setCallerDisplayName %s", callerDisplayName); + for (Listener l : mListeners) { + l.onCallerDisplayNameChanged(this, callerDisplayName, presentation); + } + } + + /** * Handles a change to extras received from Telecom. * * @param extras The new extras. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 4d5f5e140a87..9bafbe09998e 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -1254,6 +1254,31 @@ public abstract class ConnectionService extends Service { mAdapter.removeExtras(id, keys); } } + + @Override + public void onConferenceStateChanged(Conference c, boolean isConference) { + String id = mIdByConference.get(c); + if (id != null) { + mAdapter.setConferenceState(id, isConference); + } + } + + @Override + public void onAddressChanged(Conference c, Uri newAddress, int presentation) { + String id = mIdByConference.get(c); + if (id != null) { + mAdapter.setAddress(id, newAddress, presentation); + } + } + + @Override + public void onCallerDisplayNameChanged(Conference c, String callerDisplayName, + int presentation) { + String id = mIdByConference.get(c); + if (id != null) { + mAdapter.setCallerDisplayName(id, callerDisplayName, presentation); + } + } }; private final Connection.Listener mConnectionListener = new Connection.Listener() { diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java index 520e7eda6f69..6c3f4f34ff2d 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java @@ -653,4 +653,22 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } } + + /** + * Sets whether a conference is treated as a conference or a single party call. + * See {@link Conference#setConferenceState(boolean)} for more information. + * + * @param callId The ID of the telecom call. + * @param isConference {@code true} if this call should be treated as a conference, + * {@code false} otherwise. + */ + void setConferenceState(String callId, boolean isConference) { + Log.v(this, "setConferenceState: %s %b", callId, isConference); + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.setConferenceState(callId, isConference, Log.getExternalSession()); + } catch (RemoteException ignored) { + } + } + } } diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java index 78d65e643abc..f99b218bd9b9 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -74,6 +74,7 @@ final class ConnectionServiceAdapterServant { private static final int MSG_ON_RTT_UPGRADE_REQUEST = 33; private static final int MSG_SET_PHONE_ACCOUNT_CHANGED = 34; private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35; + private static final int MSG_SET_CONFERENCE_STATE = 36; private final IConnectionServiceAdapter mDelegate; @@ -333,6 +334,14 @@ final class ConnectionServiceAdapterServant { case MSG_CONNECTION_SERVICE_FOCUS_RELEASED: mDelegate.onConnectionServiceFocusReleased(null /*Session.Info*/); break; + case MSG_SET_CONFERENCE_STATE: + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.setConferenceState((String) args.arg1, (Boolean) args.arg2, + (Session.Info) args.arg3); + } finally { + args.recycle(); + } } } }; @@ -615,6 +624,16 @@ final class ConnectionServiceAdapterServant { public void resetConnectionTime(String callId, Session.Info sessionInfo) { // Do nothing } + + @Override + public void setConferenceState(String callId, boolean isConference, + Session.Info sessionInfo) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = isConference; + args.arg3 = sessionInfo; + mHandler.obtainMessage(MSG_SET_CONFERENCE_STATE, args).sendToTarget(); + } }; public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) { diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 9821dcbce85e..744544eb01f1 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -471,6 +471,12 @@ final class RemoteConnectionService { public void resetConnectionTime(String callId, Session.Info sessionInfo) { // Do nothing } + + @Override + public void setConferenceState(String callId, boolean isConference, + Session.Info sessionInfo) { + // Do nothing + } }; private final ConnectionServiceAdapterServant mServant = diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 6c4b1af8c2a1..0fe5e080d1f8 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -793,15 +793,17 @@ public class TelecomManager { * <p> * Apps must be prepared for this method to return {@code null}, indicating that there currently * exists no user-chosen default {@code PhoneAccount}. + * <p> + * The default dialer has access to use this method. * * @return The user outgoing phone account selected by the user. - * @hide */ - @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() { try { if (isServiceConnected()) { - return getTelecomService().getUserSelectedOutgoingPhoneAccount(); + return getTelecomService().getUserSelectedOutgoingPhoneAccount( + mContext.getOpPackageName()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getUserSelectedOutgoingPhoneAccount", e); @@ -810,10 +812,14 @@ public class TelecomManager { } /** - * Sets the user-chosen default for making outgoing phone calls. + * Sets the user-chosen default {@link PhoneAccountHandle} for making outgoing phone calls. + * + * @param accountHandle The {@link PhoneAccountHandle} which will be used by default for making + * outgoing voice calls. * @hide */ - @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @SystemApi public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl index 0157a5863363..76ac88e9fbaa 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl @@ -123,4 +123,6 @@ oneway interface IConnectionServiceAdapter { void onConnectionServiceFocusReleased(in Session.Info sessionInfo); void resetConnectionTime(String callIdi, in Session.Info sessionInfo); + + void setConferenceState(String callId, boolean isConference, in Session.Info sessionInfo); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 954a7098f6be..e1d5c17d5e3a 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -45,7 +45,7 @@ interface ITelecomService { /** * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount */ - PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(); + PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage); /** * @see TelecomServiceImpl#setUserSelectedOutgoingPhoneAccount diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index 26ec6ded8224..ca264f738e1d 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -42,7 +42,7 @@ public final class DataFailCause { // This series of errors as specified by the standards // specified in ril.h - /** Operator determined barring. */ + /** Operator determined barring. (no retry) */ public static final int OPERATOR_BARRED = 0x08; /** NAS signalling. */ public static final int NAS_SIGNALLING = 0x0E; @@ -91,6 +91,11 @@ public final class DataFailCause { public static final int FILTER_SYTAX_ERROR = 0x2D; /** Packet Data Protocol (PDP) without active traffic flow template (TFT). */ public static final int PDP_WITHOUT_ACTIVE_TFT = 0x2E; + /** + * UE requested to modify QoS parameters or the bearer control mode, which is not compatible + * with the selected bearer control mode. + */ + public static final int ACTIVATION_REJECTED_BCM_VIOLATION = 0x30; /** Packet Data Protocol (PDP) type IPv4 only allowed. */ public static final int ONLY_IPV4_ALLOWED = 0x32; /* no retry */ /** Packet Data Protocol (PDP) type IPv6 only allowed. */ @@ -103,6 +108,27 @@ public final class DataFailCause { public static final int PDN_CONN_DOES_NOT_EXIST = 0x36; /** Multiple connections to a same PDN is not allowed. */ public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 0x37; + /** + * Network has already initiated the activation, modification, or deactivation of bearer + * resources that was requested by the UE. + */ + public static final int COLLISION_WITH_NETWORK_INITIATED_REQUEST = 0x38; + /** + * Network supports IPv4v6 PDP type only. Non-IP type is not allowed. In LTE mode of operation, + * this is a PDN throttling cause code, meaning the UE may throttle further requests to the + * same APN. + */ + public static final int ONLY_IPV4V6_ALLOWED = 0x39; + /** + * Network supports non-IP PDP type only. IPv4, IPv6 and IPv4v6 is not allowed. In LTE mode of + * operation, this is a PDN throttling cause code, meaning the UE can throttle further requests + * to the same APN. + */ + public static final int ONLY_NON_IP_ALLOWED = 0x3A; + /** QCI (QoS Class Identifier) indicated in the UE request cannot be supported. */ + public static final int UNSUPPORTED_QCI_VALUE = 0x3B; + /** Procedure requested by the UE was rejected because the bearer handling is not supported. */ + public static final int BEARER_HANDLING_NOT_SUPPORTED = 0x3C; /** Max number of Packet Data Protocol (PDP) context reached. */ public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 0x41; /** Unsupported APN in current public land mobile network (PLMN). */ @@ -146,6 +172,742 @@ public final class DataFailCause { public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 0x79; /** Authentication failure on emergency call. */ public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 0x7A; + /** Not receiving a DNS address that was mandatory. */ + public static final int INVALID_DNS_ADDR = 0x7B; + /** Not receiving either a PCSCF or a DNS address, one of them being mandatory. */ + public static final int INVALID_PCSCF_OR_DNS_ADDRESS = 0x7C; + /** Emergency call bring up on a different ePDG. */ + public static final int CALL_PREEMPT_BY_EMERGENCY_APN = 0x7F; + /** UE performs a detach or disconnect PDN action based on TE requirements. */ + public static final int UE_INITIATED_DETACH_OR_DISCONNECT = 0x80; + + /** Reason unspecified for foreign agent rejected MIP (Mobile IP) registration. */ + public static final int MIP_FA_REASON_UNSPECIFIED = 0x7D0; + /** Foreign agent administratively prohibited MIP (Mobile IP) registration. */ + public static final int MIP_FA_ADMIN_PROHIBITED = 0x7D1; + /** Foreign agent rejected MIP (Mobile IP) registration because of insufficient resources. */ + public static final int MIP_FA_INSUFFICIENT_RESOURCES = 0x7D2; + /** + * Foreign agent rejected MIP (Mobile IP) registration because of MN-AAA authenticator was + * wrong. + */ + public static final int MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE = 0x7D3; + /** + * Foreign agent rejected MIP (Mobile IP) registration because of home agent authentication + * failure. + */ + public static final int MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE = 0x7D4; + /** + * Foreign agent rejected MIP (Mobile IP) registration because of requested lifetime was too + * long. + */ + public static final int MIP_FA_REQUESTED_LIFETIME_TOO_LONG = 0x7D5; + /** Foreign agent rejected MIP (Mobile IP) registration because of malformed request. */ + public static final int MIP_FA_MALFORMED_REQUEST = 0x7D6; + /** Foreign agent rejected MIP (Mobile IP) registration because of malformed reply. */ + public static final int MIP_FA_MALFORMED_REPLY = 0x7D7; + /** + * Foreign agent rejected MIP (Mobile IP) registration because of requested encapsulation was + * unavailable. + */ + public static final int MIP_FA_ENCAPSULATION_UNAVAILABLE = 0x7D8; + /** + * Foreign agent rejected MIP (Mobile IP) registration of VJ Header Compression was + * unavailable. + */ + public static final int MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE = 0x7D9; + /** + * Foreign agent rejected MIP (Mobile IP) registration because of reverse tunnel was + * unavailable. + */ + public static final int MIP_FA_REVERSE_TUNNEL_UNAVAILABLE = 0x7DA; + /** + * Foreign agent rejected MIP (Mobile IP) registration because of reverse tunnel was mandatory + * but not requested by device. + */ + public static final int MIP_FA_REVERSE_TUNNEL_IS_MANDATORY = 0x7DB; + /** + * Foreign agent rejected MIP (Mobile IP) registration because of delivery style was not + * supported. + */ + public static final int MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED = 0x7DC; + /** + * Foreign agent rejected MIP (Mobile IP) registration because of missing NAI (Network Access + * Identifier). + */ + public static final int MIP_FA_MISSING_NAI = 0x7DD; + /** Foreign agent rejected MIP (Mobile IP) registration because of missing Home Agent. */ + public static final int MIP_FA_MISSING_HOME_AGENT = 0x7DE; + /** Foreign agent rejected MIP (Mobile IP) registration because of missing Home Address. */ + public static final int MIP_FA_MISSING_HOME_ADDRESS = 0x7DF; + /** Foreign agent rejected MIP (Mobile IP) registration because of unknown challenge. */ + public static final int MIP_FA_UNKNOWN_CHALLENGE = 0x7E0; + /** Foreign agent rejected MIP (Mobile IP) registration because of missing challenge. */ + public static final int MIP_FA_MISSING_CHALLENGE = 0x7E1; + /** Foreign agent rejected MIP (Mobile IP) registration because of stale challenge. */ + public static final int MIP_FA_STALE_CHALLENGE = 0x7E2; + /** Reason unspecified for home agent rejected MIP (Mobile IP) registration. */ + public static final int MIP_HA_REASON_UNSPECIFIED = 0x7E3; + /** Home agent administratively prohibited MIP (Mobile IP) registration. */ + public static final int MIP_HA_ADMIN_PROHIBITED = 0x7E4; + /** Home agent rejected MIP (Mobile IP) registration because of insufficient resources. */ + public static final int MIP_HA_INSUFFICIENT_RESOURCES = 0x7E5; + /** + * Home agent rejected MIP (Mobile IP) registration because of MN-HA authenticator was + * wrong. + */ + public static final int MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE = 0x7E6; + /** + * Home agent rejected MIP (Mobile IP) registration because of foreign agent authentication + * failure. + */ + public static final int MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE = 0x7E7; + /** Home agent rejected MIP (Mobile IP) registration because of registration id mismatch. */ + public static final int MIP_HA_REGISTRATION_ID_MISMATCH = 0x7E8; + /** Home agent rejected MIP (Mobile IP) registration because of malformed request. */ + public static final int MIP_HA_MALFORMED_REQUEST = 0x7E9; + /** Home agent rejected MIP (Mobile IP) registration because of unknown home agent address. */ + public static final int MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS = 0x7EA; + /** + * Home agent rejected MIP (Mobile IP) registration because of reverse tunnel was + * unavailable. + */ + public static final int MIP_HA_REVERSE_TUNNEL_UNAVAILABLE = 0x7EB; + /** + * Home agent rejected MIP (Mobile IP) registration because of reverse tunnel is mandatory but + * not requested by device. + */ + public static final int MIP_HA_REVERSE_TUNNEL_IS_MANDATORY = 0x7EC; + /** Home agent rejected MIP (Mobile IP) registration because of encapsulation unavailable. */ + public static final int MIP_HA_ENCAPSULATION_UNAVAILABLE = 0x7ED; + /** Tearing down is in progress. */ + public static final int CLOSE_IN_PROGRESS = 0x7EE; + /** Brought down by the network. */ + public static final int NETWORK_INITIATED_TERMINATION = 0x7EF; + /** Another application in modem preempts the data call. */ + public static final int MODEM_APP_PREEMPTED = 0x7F0; + /** + * IPV4 PDN is in throttled state due to network providing only IPV6 address during the + * previous VSNCP bringup (subs_limited_to_v6). + */ + public static final int PDN_IPV4_CALL_DISALLOWED = 0x7F1; + /** IPV4 PDN is in throttled state due to previous VSNCP bringup failure(s). */ + public static final int PDN_IPV4_CALL_THROTTLED = 0x7F2; + /** + * IPV6 PDN is in throttled state due to network providing only IPV4 address during the + * previous VSNCP bringup (subs_limited_to_v4). + */ + public static final int PDN_IPV6_CALL_DISALLOWED = 0x7F3; + /** IPV6 PDN is in throttled state due to previous VSNCP bringup failure(s). */ + public static final int PDN_IPV6_CALL_THROTTLED = 0x7F4; + /** Modem restart. */ + public static final int MODEM_RESTART = 0x7F5; + /** PDP PPP calls are not supported. */ + public static final int PDP_PPP_NOT_SUPPORTED = 0x7F6; + /** RAT on which the data call is attempted/connected is no longer the preferred RAT. */ + public static final int UNPREFERRED_RAT = 0x7F7; + /** Physical link is in the process of cleanup. */ + public static final int PHYSICAL_LINK_CLOSE_IN_PROGRESS = 0x7F8; + /** Interface bring up is attempted for an APN that is yet to be handed over to target RAT. */ + public static final int APN_PENDING_HANDOVER = 0x7F9; + /** APN bearer type in the profile does not match preferred network mode. */ + public static final int PROFILE_BEARER_INCOMPATIBLE = 0x7FA; + /** Card was refreshed or removed. */ + public static final int SIM_CARD_CHANGED = 0x7FB; + /** Device is going into lower power mode or powering down. */ + public static final int LOW_POWER_MODE_OR_POWERING_DOWN = 0x7FC; + /** APN has been disabled. */ + public static final int APN_DISABLED = 0x7FD; + /** Maximum PPP inactivity timer expired. */ + public static final int MAX_PPP_INACTIVITY_TIMER_EXPIRED = 0x7FE; + /** IPv6 address transfer failed. */ + public static final int IPV6_ADDRESS_TRANSFER_FAILED = 0x7FF; + /** Target RAT swap failed. */ + public static final int TRAT_SWAP_FAILED = 0x800; + /** Device falls back from eHRPD to HRPD. */ + public static final int EHRPD_TO_HRPD_FALLBACK = 0x801; + /** + * UE is in MIP-only configuration but the MIP configuration fails on call bring up due to + * incorrect provisioning. + */ + public static final int MIP_CONFIG_FAILURE = 0x802; + /** + * PDN inactivity timer expired due to no data transmission in a configurable duration of time. + */ + public static final int PDN_INACTIVITY_TIMER_EXPIRED = 0x803; + /** + * IPv4 data call bring up is rejected because the UE already maintains the allotted maximum + * number of IPv4 data connections. + */ + public static final int MAX_IPV4_CONNECTIONS = 0x804; + /** + * IPv6 data call bring up is rejected because the UE already maintains the allotted maximum + * number of IPv6 data connections. + */ + public static final int MAX_IPV6_CONNECTIONS = 0x805; + /** + * New PDN bring up is rejected during interface selection because the UE has already allotted + * the available interfaces for other PDNs. + */ + public static final int APN_MISMATCH = 0x806; + /** + * New call bring up is rejected since the existing data call IP type doesn't match the + * requested IP. + */ + public static final int IP_VERSION_MISMATCH = 0x807; + /** Dial up networking (DUN) call bring up is rejected since UE is in eHRPD RAT. */ + public static final int DUN_CALL_DISALLOWED = 0x808; + /*** Rejected/Brought down since UE is transition between EPC and NONEPC RAT. */ + public static final int INTERNAL_EPC_NONEPC_TRANSITION = 0x809; + /** The current interface is being in use. */ + public static final int INTERFACE_IN_USE = 0x80A; + /** PDN connection to the APN is disallowed on the roaming network. */ + public static final int APN_DISALLOWED_ON_ROAMING = 0x80B; + /** APN-related parameters are changed. */ + public static final int APN_PARAMETERS_CHANGED = 0x80C; + /** PDN is attempted to be brought up with NULL APN but NULL APN is not supported. */ + public static final int NULL_APN_DISALLOWED = 0x80D; + /** + * Thermal level increases and causes calls to be torn down when normal mode of operation is + * not allowed. + */ + public static final int THERMAL_MITIGATION = 0x80E; + /** + * PDN Connection to a given APN is disallowed because data is disabled from the device user + * interface settings. + */ + public static final int DATA_SETTINGS_DISABLED = 0x80F; + /** + * PDN Connection to a given APN is disallowed because data roaming is disabled from the device + * user interface settings and the UE is roaming. + */ + public static final int DATA_ROAMING_SETTINGS_DISABLED = 0x810; + /** DDS (Default data subscription) switch occurs. */ + public static final int DDS_SWITCHED = 0x811; + /** PDN being brought up with an APN that is part of forbidden APN Name list. */ + public static final int FORBIDDEN_APN_NAME = 0x812; + /** Default data subscription switch is in progress. */ + public static final int DDS_SWITCH_IN_PROGRESS = 0x813; + /** Roaming is disallowed during call bring up. */ + public static final int CALL_DISALLOWED_IN_ROAMING = 0x814; + /** + * UE is unable to bring up a non-IP data call because the device is not camped on a NB1 cell. + */ + public static final int NON_IP_NOT_SUPPORTED = 0x815; + /** Non-IP PDN is in throttled state due to previous VSNCP bringup failure(s). */ + public static final int PDN_NON_IP_CALL_THROTTLED = 0x816; + /** Non-IP PDN is in disallowed state due to the network providing only an IP address. */ + public static final int PDN_NON_IP_CALL_DISALLOWED = 0x817; + /** Device in CDMA locked state. */ + public static final int CDMA_LOCK = 0x818; + /** Received an intercept order from the base station. */ + public static final int CDMA_INTERCEPT = 0x819; + /** Receiving a reorder from the base station. */ + public static final int CDMA_REORDER = 0x81A; + /** Receiving a release from the base station with a SO (Service Option) Reject reason. */ + public static final int CDMA_RELEASE_DUE_TO_SO_REJECTION = 0x81B; + /** Receiving an incoming call from the base station. */ + public static final int CDMA_INCOMING_CALL = 0x81C; + /** Received an alert stop from the base station due to incoming only. */ + public static final int CDMA_ALERT_STOP = 0x81D; + /** + * Channel acquisition failures. This indicates that device has failed acquiring all the + * channels in the PRL. + */ + public static final int CHANNEL_ACQUISITION_FAILURE = 0x81E; + /** Maximum access probes transmitted. */ + public static final int MAX_ACCESS_PROBE = 0x81F; + /** Concurrent service is not supported by base station. */ + public static final int CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION = 0x820; + /** There was no response received from the base station. */ + public static final int NO_RESPONSE_FROM_BASE_STATION = 0x821; + /** The base station rejecting the call. */ + public static final int REJECTED_BY_BASE_STATION = 0x822; + /** The concurrent services requested were not compatible. */ + public static final int CONCURRENT_SERVICES_INCOMPATIBLE = 0x823; + /** Device does not have CDMA service. */ + public static final int NO_CDMA_SERVICE = 0x824; + /** RUIM not being present. */ + public static final int RUIM_NOT_PRESENT = 0x825; + /** Receiving a retry order from the base station. */ + public static final int CDMA_RETRY_ORDER = 0x826; + /** Access blocked by the base station. */ + public static final int ACCESS_BLOCK = 0x827; + /** Access blocked by the base station for all mobile devices. */ + public static final int ACCESS_BLOCK_ALL = 0x828; + /** Maximum access probes for the IS-707B call. */ + public static final int IS707B_MAX_ACCESS_PROBES = 0x829; + /** Put device in thermal emergency. */ + public static final int THERMAL_EMERGENCY = 0x82A; + /** In favor of a voice call or SMS when concurrent voice and data are not supported. */ + public static final int CONCURRENT_SERVICES_NOT_ALLOWED = 0x82B; + /** The other clients rejected incoming call. */ + public static final int INCOMING_CALL_REJECTED = 0x82C; + /** No service on the gateway. */ + public static final int NO_SERVICE_ON_GATEWAY = 0x82D; + /** GPRS context is not available. */ + public static final int NO_GPRS_CONTEXT = 0x82E; + /** + * Network refuses service to the MS because either an identity of the MS is not acceptable to + * the network or the MS does not pass the authentication check. + */ + public static final int ILLEGAL_MS = 0x82F; + /** ME could not be authenticated and the ME used is not acceptable to the network. */ + public static final int ILLEGAL_ME = 0x830; + /** Not allowed to operate either GPRS or non-GPRS services. */ + public static final int GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED = 0x831; + /** MS is not allowed to operate GPRS services. */ + public static final int GPRS_SERVICES_NOT_ALLOWED = 0x832; + /** No matching identity or context could be found in the network. */ + public static final int MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK = 0x833; + /** + * Mobile reachable timer has expired, or the GMM context data related to the subscription does + * not exist in the SGSN. + */ + public static final int IMPLICITLY_DETACHED = 0x834; + /** + * UE requests GPRS service, or the network initiates a detach request in a PLMN which does not + * offer roaming for GPRS services to that MS. + */ + public static final int PLMN_NOT_ALLOWED = 0x835; + /** + * MS requests service, or the network initiates a detach request, in a location area where the + * HPLMN determines that the MS, by subscription, is not allowed to operate. + */ + public static final int LOCATION_AREA_NOT_ALLOWED = 0x836; + /** + * UE requests GPRS service or the network initiates a detach request in a PLMN that does not + * offer roaming for GPRS services. + */ + public static final int GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN = 0x837; + /** PDP context already exists. */ + public static final int PDP_DUPLICATE = 0x838; + /** RAT change on the UE. */ + public static final int UE_RAT_CHANGE = 0x839; + /** Network cannot serve a request from the MS due to congestion. */ + public static final int CONGESTION = 0x83A; + /** + * MS requests an establishment of the radio access bearers for all active PDP contexts by + * sending a service request message indicating data to the network, but the SGSN does not have + * any active PDP context. + */ + public static final int NO_PDP_CONTEXT_ACTIVATED = 0x83B; + /** Access class blocking restrictions for the current camped cell. */ + public static final int ACCESS_CLASS_DSAC_REJECTION = 0x83C; + /** SM attempts PDP activation for a maximum of four attempts. */ + public static final int PDP_ACTIVATE_MAX_RETRY_FAILED = 0x83D; + /** Radio access bearer failure. */ + public static final int RADIO_ACCESS_BEARER_FAILURE = 0x83E; + /** Invalid EPS bearer identity in the request. */ + public static final int ESM_UNKNOWN_EPS_BEARER_CONTEXT = 0x83F; + /** Data radio bearer is released by the RRC. */ + public static final int DRB_RELEASED_BY_RRC = 0x840; + /** Indicate the connection was released. */ + public static final int CONNECTION_RELEASED = 0x841; + /** UE is detached. */ + public static final int EMM_DETACHED = 0x842; + /** Attach procedure is rejected by the network. */ + public static final int EMM_ATTACH_FAILED = 0x843; + /** Attach procedure is started for EMC purposes. */ + public static final int EMM_ATTACH_STARTED = 0x844; + /** Service request procedure failure. */ + public static final int LTE_NAS_SERVICE_REQUEST_FAILED = 0x845; + /** Active dedicated bearer was requested using the same default bearer ID. */ + public static final int DUPLICATE_BEARER_ID = 0x846; + /** Collision scenarios for the UE and network-initiated procedures. */ + public static final int ESM_COLLISION_SCENARIOS = 0x847; + /** Bearer must be deactivated to synchronize with the network. */ + public static final int ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK = 0x848; + /** Active dedicated bearer was requested for an existing default bearer. */ + public static final int ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER = 0x849; + /** Bad OTA message is received from the network. */ + public static final int ESM_BAD_OTA_MESSAGE = 0x84A; + /** Download server rejected the call. */ + public static final int ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL = 0x84B; + /** PDN was disconnected by the downlaod server due to IRAT. */ + public static final int ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT = 0x84C; + /** Dedicated bearer will be deactivated regardless of the network response. */ + public static final int DS_EXPLICIT_DEACTIVATION = 0x84D; + /** No specific local cause is mentioned, usually a valid OTA cause. */ + public static final int ESM_LOCAL_CAUSE_NONE = 0x84E; + /** Throttling is not needed for this service request failure. */ + public static final int LTE_THROTTLING_NOT_REQUIRED = 0x84F; + /** Access control list check failure at the lower layer. */ + public static final int ACCESS_CONTROL_LIST_CHECK_FAILURE = 0x850; + /** Service is not allowed on the requested PLMN. */ + public static final int SERVICE_NOT_ALLOWED_ON_PLMN = 0x851; + /** T3417 timer expiration of the service request procedure. */ + public static final int EMM_T3417_EXPIRED = 0x852; + /** Extended service request fails due to expiration of the T3417 EXT timer. */ + public static final int EMM_T3417_EXT_EXPIRED = 0x853; + /** Transmission failure of radio resource control (RRC) uplink data. */ + public static final int RRC_UPLINK_DATA_TRANSMISSION_FAILURE = 0x854; + /** Radio resource control (RRC) uplink data delivery failed due to a handover. */ + public static final int RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER = 0x855; + /** Radio resource control (RRC) uplink data delivery failed due to a connection release. */ + public static final int RRC_UPLINK_CONNECTION_RELEASE = 0x856; + /** Radio resource control (RRC) uplink data delivery failed due to a radio link failure. */ + public static final int RRC_UPLINK_RADIO_LINK_FAILURE = 0x857; + /** + * Radio resource control (RRC) is not connected but the non-access stratum (NAS) sends an + * uplink data request. + */ + public static final int RRC_UPLINK_ERROR_REQUEST_FROM_NAS = 0x858; + /** Radio resource control (RRC) connection failure at access stratum. */ + public static final int RRC_CONNECTION_ACCESS_STRATUM_FAILURE = 0x859; + /** + * Radio resource control (RRC) connection establishment is aborted due to another procedure. + */ + public static final int RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS = 0x85A; + /** Radio resource control (RRC) connection establishment failed due to access barrred. */ + public static final int RRC_CONNECTION_ACCESS_BARRED = 0x85B; + /** + * Radio resource control (RRC) connection establishment failed due to cell reselection at + * access stratum. + */ + public static final int RRC_CONNECTION_CELL_RESELECTION = 0x85C; + /** + * Connection establishment failed due to configuration failure at the radio resource control + * (RRC). + */ + public static final int RRC_CONNECTION_CONFIG_FAILURE = 0x85D; + /** Radio resource control (RRC) connection could not be established in the time limit. */ + public static final int RRC_CONNECTION_TIMER_EXPIRED = 0x85E; + /** + * Connection establishment failed due to a link failure at the radio resource control (RRC). + */ + public static final int RRC_CONNECTION_LINK_FAILURE = 0x85F; + /** + * Connection establishment failed as the radio resource control (RRC) is not camped on any + * cell. + */ + public static final int RRC_CONNECTION_CELL_NOT_CAMPED = 0x860; + /** + * Connection establishment failed due to a service interval failure at the radio resource + * control (RRC). + */ + public static final int RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE = 0x861; + /** + * Radio resource control (RRC) connection establishment failed due to the network rejecting + * the UE connection request. + */ + public static final int RRC_CONNECTION_REJECT_BY_NETWORK = 0x862; + /** Normal radio resource control (RRC) connection release. */ + public static final int RRC_CONNECTION_NORMAL_RELEASE = 0x863; + /** + * Radio resource control (RRC) connection release failed due to radio link failure conditions. + */ + public static final int RRC_CONNECTION_RADIO_LINK_FAILURE = 0x864; + /** Radio resource control (RRC) connection re-establishment failure. */ + public static final int RRC_CONNECTION_REESTABLISHMENT_FAILURE = 0x865; + /** UE is out of service during the call register. */ + public static final int RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER = 0x866; + /** + * Connection has been released by the radio resource control (RRC) due to an abort request. + */ + public static final int RRC_CONNECTION_ABORT_REQUEST = 0x867; + /** + * Radio resource control (RRC) connection released due to a system information block read + * error. + */ + public static final int RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR = 0x868; + /** Network-initiated detach with reattach. */ + public static final int NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH = 0x869; + /** Network-initiated detach without reattach. */ + public static final int NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH = 0x86A; + /** ESM procedure maximum attempt timeout failure. */ + public static final int ESM_PROCEDURE_TIME_OUT = 0x86B; + /** + * No PDP exists with the given connection ID while modifying or deactivating or activation for + * an already active PDP. + */ + public static final int INVALID_CONNECTION_ID = 0x86C; + /** Maximum NSAPIs have been exceeded during PDP activation. */ + public static final int MAXIMIUM_NSAPIS_EXCEEDED = 0x86D; + /** Primary context for NSAPI does not exist. */ + public static final int INVALID_PRIMARY_NSAPI = 0x86E; + /** Unable to encode the OTA message for MT PDP or deactivate PDP. */ + public static final int CANNOT_ENCODE_OTA_MESSAGE = 0x86F; + /** + * Radio access bearer is not established by the lower layers during activation, modification, + * or deactivation. + */ + public static final int RADIO_ACCESS_BEARER_SETUP_FAILURE = 0x870; + /** Expiration of the PDP establish timer with a maximum of five retries. */ + public static final int PDP_ESTABLISH_TIMEOUT_EXPIRED = 0x871; + /** Expiration of the PDP modify timer with a maximum of four retries. */ + public static final int PDP_MODIFY_TIMEOUT_EXPIRED = 0x872; + /** Expiration of the PDP deactivate timer with a maximum of four retries. */ + public static final int PDP_INACTIVE_TIMEOUT_EXPIRED = 0x873; + /** PDP activation failed due to RRC_ABORT or a forbidden PLMN. */ + public static final int PDP_LOWERLAYER_ERROR = 0x874; + /** MO PDP modify collision when the MT PDP is already in progress. */ + public static final int PDP_MODIFY_COLLISION = 0x875; + /** Maximum size of the L2 message was exceeded. */ + public static final int MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED = 0x876; + /** Non-access stratum (NAS) request was rejected by the network. */ + public static final int NAS_REQUEST_REJECTED_BY_NETWORK = 0x877; + /** + * Radio resource control (RRC) connection establishment failure due to an error in the request + * message. + */ + public static final int RRC_CONNECTION_INVALID_REQUEST = 0x878; + /** + * Radio resource control (RRC) connection establishment failure due to a change in the + * tracking area ID. + */ + public static final int RRC_CONNECTION_TRACKING_AREA_ID_CHANGED = 0x879; + /** + * Radio resource control (RRC) connection establishment failure due to the RF was unavailable. + */ + public static final int RRC_CONNECTION_RF_UNAVAILABLE = 0x87A; + /** + * Radio resource control (RRC) connection was aborted before deactivating the LTE stack due to + * a successful LTE to WCDMA/GSM/TD-SCDMA IRAT change. + */ + public static final int RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE = 0x87B; + /** + * If the UE has an LTE radio link failure before security is established, the radio resource + * control (RRC) connection must be released and the UE must return to idle. + */ + public static final int RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE = 0x87C; + /** + * Radio resource control (RRC) connection was aborted by the non-access stratum (NAS) after an + * IRAT to LTE IRAT handover. + */ + public static final int RRC_CONNECTION_ABORTED_AFTER_HANDOVER = 0x87D; + /** + * Radio resource control (RRC) connection was aborted before deactivating the LTE stack after + * a successful LTE to GSM/EDGE IRAT cell change order procedure. + */ + public static final int RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE = 0x87E; + /** + * Radio resource control (RRC) connection was aborted in the middle of a LTE to GSM IRAT cell + * change order procedure. + */ + public static final int RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE = 0x87F; + /** IMSI present in the UE is unknown in the home subscriber server. */ + public static final int IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER = 0x880; + /** IMEI of the UE is not accepted by the network. */ + public static final int IMEI_NOT_ACCEPTED = 0x881; + /** EPS and non-EPS services are not allowed by the network. */ + public static final int EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED = 0x882; + /** EPS services are not allowed in the PLMN. */ + public static final int EPS_SERVICES_NOT_ALLOWED_IN_PLMN = 0x883; + /** Mobile switching center is temporarily unreachable. */ + public static final int MSC_TEMPORARILY_NOT_REACHABLE = 0x884; + /** CS domain is not available. */ + public static final int CS_DOMAIN_NOT_AVAILABLE = 0x885; + /** ESM level failure. */ + public static final int ESM_FAILURE = 0x886; + /** MAC level failure. */ + public static final int MAC_FAILURE = 0x887; + /** Synchronization failure. */ + public static final int SYNCHRONIZATION_FAILURE = 0x888; + /** UE security capabilities mismatch. */ + public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 0x889; + /** Unspecified security mode reject. */ + public static final int SECURITY_MODE_REJECTED = 0x88A; + /** Unacceptable non-EPS authentication. */ + public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 0x88B; + /** CS fallback call establishment is not allowed. */ + public static final int CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED = 0x88C; + /** No EPS bearer context was activated. */ + public static final int NO_EPS_BEARER_CONTEXT_ACTIVATED = 0x88D; + /** Invalid EMM state. */ + public static final int INVALID_EMM_STATE = 0x88E; + /** Non-Access Spectrum layer failure. */ + public static final int NAS_LAYER_FAILURE = 0x88F; + /** Multiple PDP call feature is disabled. */ + public static final int MULTIPLE_PDP_CALL_NOT_ALLOWED = 0x890; + /** Data call has been brought down because EMBMS is not enabled at the RRC layer. */ + public static final int EMBMS_NOT_ENABLED = 0x891; + /** Data call was unsuccessfully transferred during the IRAT handover. */ + public static final int IRAT_HANDOVER_FAILED = 0x892; + /** EMBMS data call has been successfully brought down. */ + public static final int EMBMS_REGULAR_DEACTIVATION = 0x893; + /** Test loop-back data call has been successfully brought down. */ + public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 0x894; + /** Lower layer registration failure. */ + public static final int LOWER_LAYER_REGISTRATION_FAILURE = 0x895; + /** + * Network initiates a detach on LTE with error cause ""data plan has been replenished or has + * expired. + */ + public static final int DATA_PLAN_EXPIRED = 0x896; + /** UMTS interface is brought down due to handover from UMTS to iWLAN. */ + public static final int UMTS_HANDOVER_TO_IWLAN = 0x897; + /** Received a connection deny due to general or network busy on EVDO network. */ + public static final int EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY = 0x898; + /** Received a connection deny due to billing or authentication failure on EVDO network. */ + public static final int EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE = 0x899; + /** HDR system has been changed due to redirection or the PRL was not preferred. */ + public static final int EVDO_HDR_CHANGED = 0x89A; + /** Device exited HDR due to redirection or the PRL was not preferred. */ + public static final int EVDO_HDR_EXITED = 0x89B; + /** Device does not have an HDR session. */ + public static final int EVDO_HDR_NO_SESSION = 0x89C; + /** It is ending an HDR call origination in favor of a GPS fix. */ + public static final int EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL = 0x89D; + /** Connection setup on the HDR system was time out. */ + public static final int EVDO_HDR_CONNECTION_SETUP_TIMEOUT = 0x89E; + /** Device failed to acquire a co-located HDR for origination. */ + public static final int FAILED_TO_ACQUIRE_COLOCATED_HDR = 0x89F; + /** OTASP commit is in progress. */ + public static final int OTASP_COMMIT_IN_PROGRESS = 0x8A0; + /** Device has no hybrid HDR service. */ + public static final int NO_HYBRID_HDR_SERVICE = 0x8A1; + /** HDR module could not be obtained because of the RF locked. */ + public static final int HDR_NO_LOCK_GRANTED = 0x8A2; + /** DBM or SMS is in progress. */ + public static final int DBM_OR_SMS_IN_PROGRESS = 0x8A3; + /** HDR module released the call due to fade. */ + public static final int HDR_FADE = 0x8A4; + /** HDR system access failure. */ + public static final int HDR_ACCESS_FAILURE = 0x8A5; + /** + * P_rev supported by 1 base station is less than 6, which is not supported for a 1X data call. + * The UE must be in the footprint of BS which has p_rev >= 6 to support this SO33 call. + */ + public static final int UNSUPPORTED_1X_PREV = 0x8A6; + /** Client ended the data call. */ + public static final int LOCAL_END = 0x8A7; + /** Device has no service. */ + public static final int NO_SERVICE = 0x8A8; + /** Device lost the system due to fade. */ + public static final int FADE = 0x8A9; + /** Receiving a release from the base station with no reason. */ + public static final int NORMAL_RELEASE = 0x8AA; + /** Access attempt is already in progress. */ + public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 0x8AB; + /** Device is in the process of redirecting or handing off to a different target system. */ + public static final int REDIRECTION_OR_HANDOFF_IN_PROGRESS = 0x8AC; + /** Device is operating in Emergency mode. */ + public static final int EMERGENCY_MODE = 0x8AD; + /** Device is in use (e.g., voice call). */ + public static final int PHONE_IN_USE = 0x8AE; + /** + * Device operational mode is different from the mode requested in the traffic channel bring up. + */ + public static final int INVALID_MODE = 0x8AF; + /** SIM was marked by the network as invalid for the circuit and/or packet service domain. */ + public static final int INVALID_SIM_STATE = 0x8B0; + /** There is no co-located HDR. */ + public static final int NO_COLLOCATED_HDR = 0x8B1; + /** UE is entering power save mode. */ + public static final int UE_IS_ENTERING_POWERSAVE_MODE = 0x8B2; + /** Dual switch from single standby to dual standby is in progress. */ + public static final int DUAL_SWITCH = 0x8B3; + /** + * Data call bring up fails in the PPP setup due to a timeout. + * (e.g., an LCP conf ack was not received from the network) + */ + public static final int PPP_TIMEOUT = 0x8B4; + /** + * Data call bring up fails in the PPP setup due to an authorization failure. + * (e.g., authorization is required, but not negotiated with the network during an LCP phase) + */ + public static final int PPP_AUTH_FAILURE = 0x8B5; + /** Data call bring up fails in the PPP setup due to an option mismatch. */ + public static final int PPP_OPTION_MISMATCH = 0x8B6; + /** Data call bring up fails in the PPP setup due to a PAP failure. */ + public static final int PPP_PAP_FAILURE = 0x8B7; + /** Data call bring up fails in the PPP setup due to a CHAP failure. */ + public static final int PPP_CHAP_FAILURE = 0x8B8; + /** + * Data call bring up fails in the PPP setup because the PPP is in the process of cleaning the + * previous PPP session. + */ + public static final int PPP_CLOSE_IN_PROGRESS = 0x8B9; + /** + * IPv6 interface bring up fails because the network provided only the IPv4 address for the + * upcoming PDN permanent client can reattempt a IPv6 call bring up after the IPv4 interface is + * also brought down. However, there is no guarantee that the network will provide a IPv6 + * address. + */ + public static final int LIMITED_TO_IPV4 = 0x8BA; + /** + * IPv4 interface bring up fails because the network provided only the IPv6 address for the + * upcoming PDN permanent client can reattempt a IPv4 call bring up after the IPv6 interface is + * also brought down. However there is no guarantee that the network will provide a IPv4 + * address. + */ + public static final int LIMITED_TO_IPV6 = 0x8BB; + /** Data call bring up fails in the VSNCP phase due to a VSNCP timeout error. */ + public static final int VSNCP_TIMEOUT = 0x8BC; + /** + * Data call bring up fails in the VSNCP phase due to a general error. It's used when there is + * no other specific error code available to report the failure. + */ + public static final int VSNCP_GEN_ERROR = 0x8BD; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request because the requested APN is unauthorized. + */ + public static final int VSNCP_APN_UNATHORIZED = 0x8BE; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request because the PDN limit has been exceeded. + */ + public static final int VSNCP_PDN_LIMIT_EXCEEDED = 0x8BF; + /** + * Data call bring up fails in the VSNCP phase due to the network rejected the VSNCP + * configuration request due to no PDN gateway address. + */ + public static final int VSNCP_NO_PDN_GATEWAY_ADDRESS = 0x8C0; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request because the PDN gateway is unreachable. + */ + public static final int VSNCP_PDN_GATEWAY_UNREACHABLE = 0x8C1; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request due to a PDN gateway reject. + */ + public static final int VSNCP_PDN_GATEWAY_REJECT = 0x8C2; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request with the reason of insufficient parameter. + */ + public static final int VSNCP_INSUFFICIENT_PARAMETERS = 0x8C3; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request with the reason of resource unavailable. + */ + public static final int VSNCP_RESOURCE_UNAVAILABLE = 0x8C4; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request with the reason of administratively prohibited at the HSGW. + */ + public static final int VSNCP_ADMINISTRATIVELY_PROHIBITED = 0x8C5; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of PDN ID in use, or + * all existing PDNs are brought down with this end reason because one of the PDN bring up was + * rejected by the network with the reason of PDN ID in use. + */ + public static final int VSNCP_PDN_ID_IN_USE = 0x8C6; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request for the reason of subscriber limitation. + */ + public static final int VSNCP_SUBSCRIBER_LIMITATION = 0x8C7; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request because the PDN exists for this APN. + */ + public static final int VSNCP_PDN_EXISTS_FOR_THIS_APN = 0x8C8; + /** + * Data call bring up fails in the VSNCP phase due to a network rejection of the VSNCP + * configuration request with reconnect to this PDN not allowed, or an active data call is + * terminated by the network because reconnection to this PDN is not allowed. Upon receiving + * this error code from the network, the modem infinitely throttles the PDN until the next + * power cycle. + */ + public static final int VSNCP_RECONNECT_NOT_ALLOWED = 0x8C9; + /** Device failure to obtain the prefix from the network. */ + public static final int IPV6_PREFIX_UNAVAILABLE = 0x8CA; + /** System preference change back to SRAT during handoff */ + public static final int HANDOFF_PREFERENCE_CHANGED = 0x8CB; // OEM sepecific error codes. To be used by OEMs when they don't // want to reveal error code which would be replaced by ERROR_UNSPECIFIED @@ -199,6 +961,13 @@ public final class DataFailCause { /** @hide */ public static final int RESET_BY_FRAMEWORK = 0x10005; + /** + * Data handover failed. + * + * @hide + */ + public static final int HANDOVER_FAILED = 0x10006; + /** @hide */ @IntDef(value = { NONE, @@ -226,12 +995,18 @@ public final class DataFailCause { FILTER_SEMANTIC_ERROR, FILTER_SYTAX_ERROR, PDP_WITHOUT_ACTIVE_TFT, + ACTIVATION_REJECTED_BCM_VIOLATION, ONLY_IPV4_ALLOWED, ONLY_IPV6_ALLOWED, ONLY_SINGLE_BEARER_ALLOWED, ESM_INFO_NOT_RECEIVED, PDN_CONN_DOES_NOT_EXIST, MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED, + COLLISION_WITH_NETWORK_INITIATED_REQUEST, + ONLY_IPV4V6_ALLOWED, + ONLY_NON_IP_ALLOWED, + UNSUPPORTED_QCI_VALUE, + BEARER_HANDLING_NOT_SUPPORTED, ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED, UNSUPPORTED_APN_IN_CURRENT_PLMN, INVALID_TRANSACTION_ID, @@ -242,7 +1017,7 @@ public final class DataFailCause { UNKNOWN_INFO_ELEMENT, CONDITIONAL_IE_ERROR, MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE, - PROTOCOL_ERRORS, /* no retry */ + PROTOCOL_ERRORS, APN_TYPE_CONFLICT, INVALID_PCSCF_ADDR, INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN, @@ -254,6 +1029,262 @@ public final class DataFailCause { IFACE_AND_POL_FAMILY_MISMATCH, EMM_ACCESS_BARRED_INFINITE_RETRY, AUTH_FAILURE_ON_EMERGENCY_CALL, + INVALID_DNS_ADDR, + INVALID_PCSCF_OR_DNS_ADDRESS, + CALL_PREEMPT_BY_EMERGENCY_APN, + UE_INITIATED_DETACH_OR_DISCONNECT, + MIP_FA_REASON_UNSPECIFIED, + MIP_FA_ADMIN_PROHIBITED, + MIP_FA_INSUFFICIENT_RESOURCES, + MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE, + MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE, + MIP_FA_REQUESTED_LIFETIME_TOO_LONG, + MIP_FA_MALFORMED_REQUEST, + MIP_FA_MALFORMED_REPLY, + MIP_FA_ENCAPSULATION_UNAVAILABLE, + MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE, + MIP_FA_REVERSE_TUNNEL_UNAVAILABLE, + MIP_FA_REVERSE_TUNNEL_IS_MANDATORY, + MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED, + MIP_FA_MISSING_NAI, + MIP_FA_MISSING_HOME_AGENT, + MIP_FA_MISSING_HOME_ADDRESS, + MIP_FA_UNKNOWN_CHALLENGE, + MIP_FA_MISSING_CHALLENGE, + MIP_FA_STALE_CHALLENGE, + MIP_HA_REASON_UNSPECIFIED, + MIP_HA_ADMIN_PROHIBITED, + MIP_HA_INSUFFICIENT_RESOURCES, + MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE, + MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE, + MIP_HA_REGISTRATION_ID_MISMATCH, + MIP_HA_MALFORMED_REQUEST, + MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS, + MIP_HA_REVERSE_TUNNEL_UNAVAILABLE, + MIP_HA_REVERSE_TUNNEL_IS_MANDATORY, + MIP_HA_ENCAPSULATION_UNAVAILABLE, + CLOSE_IN_PROGRESS, + NETWORK_INITIATED_TERMINATION, + MODEM_APP_PREEMPTED, + PDN_IPV4_CALL_DISALLOWED, + PDN_IPV4_CALL_THROTTLED, + PDN_IPV6_CALL_DISALLOWED, + PDN_IPV6_CALL_THROTTLED, + MODEM_RESTART, + PDP_PPP_NOT_SUPPORTED, + UNPREFERRED_RAT, + PHYSICAL_LINK_CLOSE_IN_PROGRESS, + APN_PENDING_HANDOVER, + PROFILE_BEARER_INCOMPATIBLE, + SIM_CARD_CHANGED, + LOW_POWER_MODE_OR_POWERING_DOWN, + APN_DISABLED, + MAX_PPP_INACTIVITY_TIMER_EXPIRED, + IPV6_ADDRESS_TRANSFER_FAILED, + TRAT_SWAP_FAILED, + EHRPD_TO_HRPD_FALLBACK, + MIP_CONFIG_FAILURE, + PDN_INACTIVITY_TIMER_EXPIRED, + MAX_IPV4_CONNECTIONS, + MAX_IPV6_CONNECTIONS, + APN_MISMATCH, + IP_VERSION_MISMATCH, + DUN_CALL_DISALLOWED, + INTERNAL_EPC_NONEPC_TRANSITION, + INTERFACE_IN_USE, + APN_DISALLOWED_ON_ROAMING, + APN_PARAMETERS_CHANGED, + NULL_APN_DISALLOWED, + THERMAL_MITIGATION, + DATA_SETTINGS_DISABLED, + DATA_ROAMING_SETTINGS_DISABLED, + DDS_SWITCHED, + FORBIDDEN_APN_NAME, + DDS_SWITCH_IN_PROGRESS, + CALL_DISALLOWED_IN_ROAMING, + NON_IP_NOT_SUPPORTED, + PDN_NON_IP_CALL_THROTTLED, + PDN_NON_IP_CALL_DISALLOWED, + CDMA_LOCK, + CDMA_INTERCEPT, + CDMA_REORDER, + CDMA_RELEASE_DUE_TO_SO_REJECTION, + CDMA_INCOMING_CALL, + CDMA_ALERT_STOP, + CHANNEL_ACQUISITION_FAILURE, + MAX_ACCESS_PROBE, + CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION, + NO_RESPONSE_FROM_BASE_STATION, + REJECTED_BY_BASE_STATION, + CONCURRENT_SERVICES_INCOMPATIBLE, + NO_CDMA_SERVICE, + RUIM_NOT_PRESENT, + CDMA_RETRY_ORDER, + ACCESS_BLOCK, + ACCESS_BLOCK_ALL, + IS707B_MAX_ACCESS_PROBES, + THERMAL_EMERGENCY, + CONCURRENT_SERVICES_NOT_ALLOWED, + INCOMING_CALL_REJECTED, + NO_SERVICE_ON_GATEWAY, + NO_GPRS_CONTEXT, + ILLEGAL_MS, + ILLEGAL_ME, + GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED, + GPRS_SERVICES_NOT_ALLOWED, + MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK, + IMPLICITLY_DETACHED, + PLMN_NOT_ALLOWED, + LOCATION_AREA_NOT_ALLOWED, + GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN, + PDP_DUPLICATE, + UE_RAT_CHANGE, + CONGESTION, + NO_PDP_CONTEXT_ACTIVATED, + ACCESS_CLASS_DSAC_REJECTION, + PDP_ACTIVATE_MAX_RETRY_FAILED, + RADIO_ACCESS_BEARER_FAILURE, + ESM_UNKNOWN_EPS_BEARER_CONTEXT, + DRB_RELEASED_BY_RRC, + CONNECTION_RELEASED, + EMM_DETACHED, + EMM_ATTACH_FAILED, + EMM_ATTACH_STARTED, + LTE_NAS_SERVICE_REQUEST_FAILED, + DUPLICATE_BEARER_ID, + ESM_COLLISION_SCENARIOS, + ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK, + ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER, + ESM_BAD_OTA_MESSAGE, + ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL, + ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT, + DS_EXPLICIT_DEACTIVATION, + ESM_LOCAL_CAUSE_NONE, + LTE_THROTTLING_NOT_REQUIRED, + ACCESS_CONTROL_LIST_CHECK_FAILURE, + SERVICE_NOT_ALLOWED_ON_PLMN, + EMM_T3417_EXPIRED, + EMM_T3417_EXT_EXPIRED, + RRC_UPLINK_DATA_TRANSMISSION_FAILURE, + RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER, + RRC_UPLINK_CONNECTION_RELEASE, + RRC_UPLINK_RADIO_LINK_FAILURE, + RRC_UPLINK_ERROR_REQUEST_FROM_NAS, + RRC_CONNECTION_ACCESS_STRATUM_FAILURE, + RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS, + RRC_CONNECTION_ACCESS_BARRED, + RRC_CONNECTION_CELL_RESELECTION, + RRC_CONNECTION_CONFIG_FAILURE, + RRC_CONNECTION_TIMER_EXPIRED, + RRC_CONNECTION_LINK_FAILURE, + RRC_CONNECTION_CELL_NOT_CAMPED, + RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE, + RRC_CONNECTION_REJECT_BY_NETWORK, + RRC_CONNECTION_NORMAL_RELEASE, + RRC_CONNECTION_RADIO_LINK_FAILURE, + RRC_CONNECTION_REESTABLISHMENT_FAILURE, + RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER, + RRC_CONNECTION_ABORT_REQUEST, + RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR, + NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH, + NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH, + ESM_PROCEDURE_TIME_OUT, + INVALID_CONNECTION_ID, + MAXIMIUM_NSAPIS_EXCEEDED, + INVALID_PRIMARY_NSAPI, + CANNOT_ENCODE_OTA_MESSAGE, + RADIO_ACCESS_BEARER_SETUP_FAILURE, + PDP_ESTABLISH_TIMEOUT_EXPIRED, + PDP_MODIFY_TIMEOUT_EXPIRED, + PDP_INACTIVE_TIMEOUT_EXPIRED, + PDP_LOWERLAYER_ERROR, + PDP_MODIFY_COLLISION, + MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED, + NAS_REQUEST_REJECTED_BY_NETWORK, + RRC_CONNECTION_INVALID_REQUEST, + RRC_CONNECTION_TRACKING_AREA_ID_CHANGED, + RRC_CONNECTION_RF_UNAVAILABLE, + RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE, + RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE, + RRC_CONNECTION_ABORTED_AFTER_HANDOVER, + RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE, + RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE, + IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER, + IMEI_NOT_ACCEPTED, + EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED, + EPS_SERVICES_NOT_ALLOWED_IN_PLMN, + MSC_TEMPORARILY_NOT_REACHABLE, + CS_DOMAIN_NOT_AVAILABLE, + ESM_FAILURE, + MAC_FAILURE, + SYNCHRONIZATION_FAILURE, + UE_SECURITY_CAPABILITIES_MISMATCH, + SECURITY_MODE_REJECTED, + UNACCEPTABLE_NON_EPS_AUTHENTICATION, + CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED, + NO_EPS_BEARER_CONTEXT_ACTIVATED, + INVALID_EMM_STATE, + NAS_LAYER_FAILURE, + MULTIPLE_PDP_CALL_NOT_ALLOWED, + EMBMS_NOT_ENABLED, + IRAT_HANDOVER_FAILED, + EMBMS_REGULAR_DEACTIVATION, + TEST_LOOPBACK_REGULAR_DEACTIVATION, + LOWER_LAYER_REGISTRATION_FAILURE, + DATA_PLAN_EXPIRED, + UMTS_HANDOVER_TO_IWLAN, + EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY, + EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE, + EVDO_HDR_CHANGED, + EVDO_HDR_EXITED, + EVDO_HDR_NO_SESSION, + EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL, + EVDO_HDR_CONNECTION_SETUP_TIMEOUT, + FAILED_TO_ACQUIRE_COLOCATED_HDR, + OTASP_COMMIT_IN_PROGRESS, + NO_HYBRID_HDR_SERVICE, + HDR_NO_LOCK_GRANTED, + DBM_OR_SMS_IN_PROGRESS, + HDR_FADE, + HDR_ACCESS_FAILURE, + UNSUPPORTED_1X_PREV, + LOCAL_END, + NO_SERVICE, + FADE, + NORMAL_RELEASE, + ACCESS_ATTEMPT_ALREADY_IN_PROGRESS, + REDIRECTION_OR_HANDOFF_IN_PROGRESS, + EMERGENCY_MODE, + PHONE_IN_USE, + INVALID_MODE, + INVALID_SIM_STATE, + NO_COLLOCATED_HDR, + UE_IS_ENTERING_POWERSAVE_MODE, + DUAL_SWITCH, + PPP_TIMEOUT, + PPP_AUTH_FAILURE, + PPP_OPTION_MISMATCH, + PPP_PAP_FAILURE, + PPP_CHAP_FAILURE, + PPP_CLOSE_IN_PROGRESS, + LIMITED_TO_IPV4, + LIMITED_TO_IPV6, + VSNCP_TIMEOUT, + VSNCP_GEN_ERROR, + VSNCP_APN_UNATHORIZED, + VSNCP_PDN_LIMIT_EXCEEDED, + VSNCP_NO_PDN_GATEWAY_ADDRESS, + VSNCP_PDN_GATEWAY_UNREACHABLE, + VSNCP_PDN_GATEWAY_REJECT, + VSNCP_INSUFFICIENT_PARAMETERS, + VSNCP_RESOURCE_UNAVAILABLE, + VSNCP_ADMINISTRATIVELY_PROHIBITED, + VSNCP_PDN_ID_IN_USE, + VSNCP_SUBSCRIBER_LIMITATION, + VSNCP_PDN_EXISTS_FOR_THIS_APN, + VSNCP_RECONNECT_NOT_ALLOWED, + IPV6_PREFIX_UNAVAILABLE, + HANDOFF_PREFERENCE_CHANGED, OEM_DCFAILCAUSE_1, OEM_DCFAILCAUSE_2, OEM_DCFAILCAUSE_3, @@ -317,6 +1348,7 @@ public final class DataFailCause { sFailCauseMap.put(FILTER_SEMANTIC_ERROR, "FILTER_SEMANTIC_ERROR"); sFailCauseMap.put(FILTER_SYTAX_ERROR, "FILTER_SYTAX_ERROR"); sFailCauseMap.put(PDP_WITHOUT_ACTIVE_TFT, "PDP_WITHOUT_ACTIVE_TFT"); + sFailCauseMap.put(ACTIVATION_REJECTED_BCM_VIOLATION, "ACTIVATION_REJECTED_BCM_VIOLATION"); sFailCauseMap.put(ONLY_IPV4_ALLOWED, "ONLY_IPV4_ALLOWED"); sFailCauseMap.put(ONLY_IPV6_ALLOWED, "ONLY_IPV6_ALLOWED"); sFailCauseMap.put(ONLY_SINGLE_BEARER_ALLOWED, "ONLY_SINGLE_BEARER_ALLOWED"); @@ -324,6 +1356,12 @@ public final class DataFailCause { sFailCauseMap.put(PDN_CONN_DOES_NOT_EXIST, "PDN_CONN_DOES_NOT_EXIST"); sFailCauseMap.put(MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED, "MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED"); + sFailCauseMap.put(COLLISION_WITH_NETWORK_INITIATED_REQUEST, + "COLLISION_WITH_NETWORK_INITIATED_REQUEST"); + sFailCauseMap.put(ONLY_IPV4V6_ALLOWED, "ONLY_IPV4V6_ALLOWED"); + sFailCauseMap.put(ONLY_NON_IP_ALLOWED, "ONLY_NON_IP_ALLOWED"); + sFailCauseMap.put(UNSUPPORTED_QCI_VALUE, "UNSUPPORTED_QCI_VALUE"); + sFailCauseMap.put(BEARER_HANDLING_NOT_SUPPORTED, "BEARER_HANDLING_NOT_SUPPORTED"); sFailCauseMap.put(ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED, "ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED"); sFailCauseMap.put(UNSUPPORTED_APN_IN_CURRENT_PLMN, @@ -353,6 +1391,301 @@ public final class DataFailCause { "EMM_ACCESS_BARRED_INFINITE_RETRY"); sFailCauseMap.put(AUTH_FAILURE_ON_EMERGENCY_CALL, "AUTH_FAILURE_ON_EMERGENCY_CALL"); + sFailCauseMap.put(INVALID_DNS_ADDR, "INVALID_DNS_ADDR"); + sFailCauseMap.put(INVALID_PCSCF_OR_DNS_ADDRESS, "INVALID_PCSCF_OR_DNS_ADDRESS"); + sFailCauseMap.put(CALL_PREEMPT_BY_EMERGENCY_APN, "CALL_PREEMPT_BY_EMERGENCY_APN"); + sFailCauseMap.put(UE_INITIATED_DETACH_OR_DISCONNECT, "UE_INITIATED_DETACH_OR_DISCONNECT"); + sFailCauseMap.put(MIP_FA_REASON_UNSPECIFIED, "MIP_FA_REASON_UNSPECIFIED"); + sFailCauseMap.put(MIP_FA_ADMIN_PROHIBITED, "MIP_FA_ADMIN_PROHIBITED"); + sFailCauseMap.put(MIP_FA_INSUFFICIENT_RESOURCES, "MIP_FA_INSUFFICIENT_RESOURCES"); + sFailCauseMap.put(MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE, + "MIP_FA_MOBILE_NODE_AUTHENTICATION_FAILURE"); + sFailCauseMap.put(MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE, + "MIP_FA_HOME_AGENT_AUTHENTICATION_FAILURE"); + sFailCauseMap.put(MIP_FA_REQUESTED_LIFETIME_TOO_LONG, "MIP_FA_REQUESTED_LIFETIME_TOO_LONG"); + sFailCauseMap.put(MIP_FA_MALFORMED_REQUEST, "MIP_FA_MALFORMED_REQUEST"); + sFailCauseMap.put(MIP_FA_MALFORMED_REPLY, "MIP_FA_MALFORMED_REPLY"); + sFailCauseMap.put(MIP_FA_ENCAPSULATION_UNAVAILABLE, "MIP_FA_ENCAPSULATION_UNAVAILABLE"); + sFailCauseMap.put(MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE, + "MIP_FA_VJ_HEADER_COMPRESSION_UNAVAILABLE"); + sFailCauseMap.put(MIP_FA_REVERSE_TUNNEL_UNAVAILABLE, "MIP_FA_REVERSE_TUNNEL_UNAVAILABLE"); + sFailCauseMap.put(MIP_FA_REVERSE_TUNNEL_IS_MANDATORY, "MIP_FA_REVERSE_TUNNEL_IS_MANDATORY"); + sFailCauseMap.put(MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED, + "MIP_FA_DELIVERY_STYLE_NOT_SUPPORTED"); + sFailCauseMap.put(MIP_FA_MISSING_NAI, "MIP_FA_MISSING_NAI"); + sFailCauseMap.put(MIP_FA_MISSING_HOME_AGENT, "MIP_FA_MISSING_HOME_AGENT"); + sFailCauseMap.put(MIP_FA_MISSING_HOME_ADDRESS, "MIP_FA_MISSING_HOME_ADDRESS"); + sFailCauseMap.put(MIP_FA_UNKNOWN_CHALLENGE, "MIP_FA_UNKNOWN_CHALLENGE"); + sFailCauseMap.put(MIP_FA_MISSING_CHALLENGE, "MIP_FA_MISSING_CHALLENGE"); + sFailCauseMap.put(MIP_FA_STALE_CHALLENGE, "MIP_FA_STALE_CHALLENGE"); + sFailCauseMap.put(MIP_HA_REASON_UNSPECIFIED, "MIP_HA_REASON_UNSPECIFIED"); + sFailCauseMap.put(MIP_HA_ADMIN_PROHIBITED, "MIP_HA_ADMIN_PROHIBITED"); + sFailCauseMap.put(MIP_HA_INSUFFICIENT_RESOURCES, "MIP_HA_INSUFFICIENT_RESOURCES"); + sFailCauseMap.put(MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE, + "MIP_HA_MOBILE_NODE_AUTHENTICATION_FAILURE"); + sFailCauseMap.put(MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE, + "MIP_HA_FOREIGN_AGENT_AUTHENTICATION_FAILURE"); + sFailCauseMap.put(MIP_HA_REGISTRATION_ID_MISMATCH, "MIP_HA_REGISTRATION_ID_MISMATCH"); + sFailCauseMap.put(MIP_HA_MALFORMED_REQUEST, "MIP_HA_MALFORMED_REQUEST"); + sFailCauseMap.put(MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS, "MIP_HA_UNKNOWN_HOME_AGENT_ADDRESS"); + sFailCauseMap.put(MIP_HA_REVERSE_TUNNEL_UNAVAILABLE, "MIP_HA_REVERSE_TUNNEL_UNAVAILABLE"); + sFailCauseMap.put(MIP_HA_REVERSE_TUNNEL_IS_MANDATORY, "MIP_HA_REVERSE_TUNNEL_IS_MANDATORY"); + sFailCauseMap.put(MIP_HA_ENCAPSULATION_UNAVAILABLE, "MIP_HA_ENCAPSULATION_UNAVAILABLE"); + sFailCauseMap.put(CLOSE_IN_PROGRESS, "CLOSE_IN_PROGRESS"); + sFailCauseMap.put(NETWORK_INITIATED_TERMINATION, "NETWORK_INITIATED_TERMINATION"); + sFailCauseMap.put(MODEM_APP_PREEMPTED, "MODEM_APP_PREEMPTED"); + sFailCauseMap.put(PDN_IPV4_CALL_DISALLOWED, "PDN_IPV4_CALL_DISALLOWED"); + sFailCauseMap.put(PDN_IPV4_CALL_THROTTLED, "PDN_IPV4_CALL_THROTTLED"); + sFailCauseMap.put(PDN_IPV6_CALL_DISALLOWED, "PDN_IPV6_CALL_DISALLOWED"); + sFailCauseMap.put(PDN_IPV6_CALL_THROTTLED, "PDN_IPV6_CALL_THROTTLED"); + sFailCauseMap.put(MODEM_RESTART, "MODEM_RESTART"); + sFailCauseMap.put(PDP_PPP_NOT_SUPPORTED, "PDP_PPP_NOT_SUPPORTED"); + sFailCauseMap.put(UNPREFERRED_RAT, "UNPREFERRED_RAT"); + sFailCauseMap.put(PHYSICAL_LINK_CLOSE_IN_PROGRESS, "PHYSICAL_LINK_CLOSE_IN_PROGRESS"); + sFailCauseMap.put(APN_PENDING_HANDOVER, "APN_PENDING_HANDOVER"); + sFailCauseMap.put(PROFILE_BEARER_INCOMPATIBLE, "PROFILE_BEARER_INCOMPATIBLE"); + sFailCauseMap.put(SIM_CARD_CHANGED, "SIM_CARD_CHANGED"); + sFailCauseMap.put(LOW_POWER_MODE_OR_POWERING_DOWN, "LOW_POWER_MODE_OR_POWERING_DOWN"); + sFailCauseMap.put(APN_DISABLED, "APN_DISABLED"); + sFailCauseMap.put(MAX_PPP_INACTIVITY_TIMER_EXPIRED, "MAX_PPP_INACTIVITY_TIMER_EXPIRED"); + sFailCauseMap.put(IPV6_ADDRESS_TRANSFER_FAILED, "IPV6_ADDRESS_TRANSFER_FAILED"); + sFailCauseMap.put(TRAT_SWAP_FAILED, "TRAT_SWAP_FAILED"); + sFailCauseMap.put(EHRPD_TO_HRPD_FALLBACK, "EHRPD_TO_HRPD_FALLBACK"); + sFailCauseMap.put(MIP_CONFIG_FAILURE, "MIP_CONFIG_FAILURE"); + sFailCauseMap.put(PDN_INACTIVITY_TIMER_EXPIRED, "PDN_INACTIVITY_TIMER_EXPIRED"); + sFailCauseMap.put(MAX_IPV4_CONNECTIONS, "MAX_IPV4_CONNECTIONS"); + sFailCauseMap.put(MAX_IPV6_CONNECTIONS, "MAX_IPV6_CONNECTIONS"); + sFailCauseMap.put(APN_MISMATCH, "APN_MISMATCH"); + sFailCauseMap.put(IP_VERSION_MISMATCH, "IP_VERSION_MISMATCH"); + sFailCauseMap.put(DUN_CALL_DISALLOWED, "DUN_CALL_DISALLOWED"); + sFailCauseMap.put(INTERNAL_EPC_NONEPC_TRANSITION, "INTERNAL_EPC_NONEPC_TRANSITION"); + sFailCauseMap.put(INTERFACE_IN_USE, "INTERFACE_IN_USE"); + sFailCauseMap.put(APN_DISALLOWED_ON_ROAMING, "APN_DISALLOWED_ON_ROAMING"); + sFailCauseMap.put(APN_PARAMETERS_CHANGED, "APN_PARAMETERS_CHANGED"); + sFailCauseMap.put(NULL_APN_DISALLOWED, "NULL_APN_DISALLOWED"); + sFailCauseMap.put(THERMAL_MITIGATION, "THERMAL_MITIGATION"); + sFailCauseMap.put(DATA_SETTINGS_DISABLED, "DATA_SETTINGS_DISABLED"); + sFailCauseMap.put(DATA_ROAMING_SETTINGS_DISABLED, "DATA_ROAMING_SETTINGS_DISABLED"); + sFailCauseMap.put(DDS_SWITCHED, "DDS_SWITCHED"); + sFailCauseMap.put(FORBIDDEN_APN_NAME, "FORBIDDEN_APN_NAME"); + sFailCauseMap.put(DDS_SWITCH_IN_PROGRESS, "DDS_SWITCH_IN_PROGRESS"); + sFailCauseMap.put(CALL_DISALLOWED_IN_ROAMING, "CALL_DISALLOWED_IN_ROAMING"); + sFailCauseMap.put(NON_IP_NOT_SUPPORTED, "NON_IP_NOT_SUPPORTED"); + sFailCauseMap.put(PDN_NON_IP_CALL_THROTTLED, "PDN_NON_IP_CALL_THROTTLED"); + sFailCauseMap.put(PDN_NON_IP_CALL_DISALLOWED, "PDN_NON_IP_CALL_DISALLOWED"); + sFailCauseMap.put(CDMA_LOCK, "CDMA_LOCK"); + sFailCauseMap.put(CDMA_INTERCEPT, "CDMA_INTERCEPT"); + sFailCauseMap.put(CDMA_REORDER, "CDMA_REORDER"); + sFailCauseMap.put(CDMA_RELEASE_DUE_TO_SO_REJECTION, "CDMA_RELEASE_DUE_TO_SO_REJECTION"); + sFailCauseMap.put(CDMA_INCOMING_CALL, "CDMA_INCOMING_CALL"); + sFailCauseMap.put(CDMA_ALERT_STOP, "CDMA_ALERT_STOP"); + sFailCauseMap.put(CHANNEL_ACQUISITION_FAILURE, "CHANNEL_ACQUISITION_FAILURE"); + sFailCauseMap.put(MAX_ACCESS_PROBE, "MAX_ACCESS_PROBE"); + sFailCauseMap.put(CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION, + "CONCURRENT_SERVICE_NOT_SUPPORTED_BY_BASE_STATION"); + sFailCauseMap.put(NO_RESPONSE_FROM_BASE_STATION, "NO_RESPONSE_FROM_BASE_STATION"); + sFailCauseMap.put(REJECTED_BY_BASE_STATION, "REJECTED_BY_BASE_STATION"); + sFailCauseMap.put(CONCURRENT_SERVICES_INCOMPATIBLE, "CONCURRENT_SERVICES_INCOMPATIBLE"); + sFailCauseMap.put(NO_CDMA_SERVICE, "NO_CDMA_SERVICE"); + sFailCauseMap.put(RUIM_NOT_PRESENT, "RUIM_NOT_PRESENT"); + sFailCauseMap.put(CDMA_RETRY_ORDER, "CDMA_RETRY_ORDER"); + sFailCauseMap.put(ACCESS_BLOCK, "ACCESS_BLOCK"); + sFailCauseMap.put(ACCESS_BLOCK_ALL, "ACCESS_BLOCK_ALL"); + sFailCauseMap.put(IS707B_MAX_ACCESS_PROBES, "IS707B_MAX_ACCESS_PROBES"); + sFailCauseMap.put(THERMAL_EMERGENCY, "THERMAL_EMERGENCY"); + sFailCauseMap.put(CONCURRENT_SERVICES_NOT_ALLOWED, "CONCURRENT_SERVICES_NOT_ALLOWED"); + sFailCauseMap.put(INCOMING_CALL_REJECTED, "INCOMING_CALL_REJECTED"); + sFailCauseMap.put(NO_SERVICE_ON_GATEWAY, "NO_SERVICE_ON_GATEWAY"); + sFailCauseMap.put(NO_GPRS_CONTEXT, "NO_GPRS_CONTEXT"); + sFailCauseMap.put(ILLEGAL_MS, "ILLEGAL_MS"); + sFailCauseMap.put(ILLEGAL_ME, "ILLEGAL_ME"); + sFailCauseMap.put(GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED, + "GPRS_SERVICES_AND_NON_GPRS_SERVICES_NOT_ALLOWED"); + sFailCauseMap.put(GPRS_SERVICES_NOT_ALLOWED, "GPRS_SERVICES_NOT_ALLOWED"); + sFailCauseMap.put(MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK, + "MS_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK"); + sFailCauseMap.put(IMPLICITLY_DETACHED, "IMPLICITLY_DETACHED"); + sFailCauseMap.put(PLMN_NOT_ALLOWED, "PLMN_NOT_ALLOWED"); + sFailCauseMap.put(LOCATION_AREA_NOT_ALLOWED, "LOCATION_AREA_NOT_ALLOWED"); + sFailCauseMap.put(GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN, + "GPRS_SERVICES_NOT_ALLOWED_IN_THIS_PLMN"); + sFailCauseMap.put(PDP_DUPLICATE, "PDP_DUPLICATE"); + sFailCauseMap.put(UE_RAT_CHANGE, "UE_RAT_CHANGE"); + sFailCauseMap.put(CONGESTION, "CONGESTION"); + sFailCauseMap.put(NO_PDP_CONTEXT_ACTIVATED, "NO_PDP_CONTEXT_ACTIVATED"); + sFailCauseMap.put(ACCESS_CLASS_DSAC_REJECTION, "ACCESS_CLASS_DSAC_REJECTION"); + sFailCauseMap.put(PDP_ACTIVATE_MAX_RETRY_FAILED, "PDP_ACTIVATE_MAX_RETRY_FAILED"); + sFailCauseMap.put(RADIO_ACCESS_BEARER_FAILURE, "RADIO_ACCESS_BEARER_FAILURE"); + sFailCauseMap.put(ESM_UNKNOWN_EPS_BEARER_CONTEXT, "ESM_UNKNOWN_EPS_BEARER_CONTEXT"); + sFailCauseMap.put(DRB_RELEASED_BY_RRC, "DRB_RELEASED_BY_RRC"); + sFailCauseMap.put(CONNECTION_RELEASED, "CONNECTION_RELEASED"); + sFailCauseMap.put(EMM_DETACHED, "EMM_DETACHED"); + sFailCauseMap.put(EMM_ATTACH_FAILED, "EMM_ATTACH_FAILED"); + sFailCauseMap.put(EMM_ATTACH_STARTED, "EMM_ATTACH_STARTED"); + sFailCauseMap.put(LTE_NAS_SERVICE_REQUEST_FAILED, "LTE_NAS_SERVICE_REQUEST_FAILED"); + sFailCauseMap.put(DUPLICATE_BEARER_ID, "DUPLICATE_BEARER_ID"); + sFailCauseMap.put(ESM_COLLISION_SCENARIOS, "ESM_COLLISION_SCENARIOS"); + sFailCauseMap.put(ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK, + "ESM_BEARER_DEACTIVATED_TO_SYNC_WITH_NETWORK"); + sFailCauseMap.put(ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER, + "ESM_NW_ACTIVATED_DED_BEARER_WITH_ID_OF_DEF_BEARER"); + sFailCauseMap.put(ESM_BAD_OTA_MESSAGE, "ESM_BAD_OTA_MESSAGE"); + sFailCauseMap.put(ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL, + "ESM_DOWNLOAD_SERVER_REJECTED_THE_CALL"); + sFailCauseMap.put(ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT, + "ESM_CONTEXT_TRANSFERRED_DUE_TO_IRAT"); + sFailCauseMap.put(DS_EXPLICIT_DEACTIVATION, "DS_EXPLICIT_DEACTIVATION"); + sFailCauseMap.put(ESM_LOCAL_CAUSE_NONE, "ESM_LOCAL_CAUSE_NONE"); + sFailCauseMap.put(LTE_THROTTLING_NOT_REQUIRED, "LTE_THROTTLING_NOT_REQUIRED"); + sFailCauseMap.put(ACCESS_CONTROL_LIST_CHECK_FAILURE, + "ACCESS_CONTROL_LIST_CHECK_FAILURE"); + sFailCauseMap.put(SERVICE_NOT_ALLOWED_ON_PLMN, "SERVICE_NOT_ALLOWED_ON_PLMN"); + sFailCauseMap.put(EMM_T3417_EXPIRED, "EMM_T3417_EXPIRED"); + sFailCauseMap.put(EMM_T3417_EXT_EXPIRED, "EMM_T3417_EXT_EXPIRED"); + sFailCauseMap.put(RRC_UPLINK_DATA_TRANSMISSION_FAILURE, + "RRC_UPLINK_DATA_TRANSMISSION_FAILURE"); + sFailCauseMap.put(RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER, + "RRC_UPLINK_DELIVERY_FAILED_DUE_TO_HANDOVER"); + sFailCauseMap.put(RRC_UPLINK_CONNECTION_RELEASE, "RRC_UPLINK_CONNECTION_RELEASE"); + sFailCauseMap.put(RRC_UPLINK_RADIO_LINK_FAILURE, "RRC_UPLINK_RADIO_LINK_FAILURE"); + sFailCauseMap.put(RRC_UPLINK_ERROR_REQUEST_FROM_NAS, "RRC_UPLINK_ERROR_REQUEST_FROM_NAS"); + sFailCauseMap.put(RRC_CONNECTION_ACCESS_STRATUM_FAILURE, + "RRC_CONNECTION_ACCESS_STRATUM_FAILURE"); + sFailCauseMap.put(RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS, + "RRC_CONNECTION_ANOTHER_PROCEDURE_IN_PROGRESS"); + sFailCauseMap.put(RRC_CONNECTION_ACCESS_BARRED, "RRC_CONNECTION_ACCESS_BARRED"); + sFailCauseMap.put(RRC_CONNECTION_CELL_RESELECTION, "RRC_CONNECTION_CELL_RESELECTION"); + sFailCauseMap.put(RRC_CONNECTION_CONFIG_FAILURE, "RRC_CONNECTION_CONFIG_FAILURE"); + sFailCauseMap.put(RRC_CONNECTION_TIMER_EXPIRED, "RRC_CONNECTION_TIMER_EXPIRED"); + sFailCauseMap.put(RRC_CONNECTION_LINK_FAILURE, "RRC_CONNECTION_LINK_FAILURE"); + sFailCauseMap.put(RRC_CONNECTION_CELL_NOT_CAMPED, "RRC_CONNECTION_CELL_NOT_CAMPED"); + sFailCauseMap.put(RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE, + "RRC_CONNECTION_SYSTEM_INTERVAL_FAILURE"); + sFailCauseMap.put(RRC_CONNECTION_REJECT_BY_NETWORK, "RRC_CONNECTION_REJECT_BY_NETWORK"); + sFailCauseMap.put(RRC_CONNECTION_NORMAL_RELEASE, "RRC_CONNECTION_NORMAL_RELEASE"); + sFailCauseMap.put(RRC_CONNECTION_RADIO_LINK_FAILURE, "RRC_CONNECTION_RADIO_LINK_FAILURE"); + sFailCauseMap.put(RRC_CONNECTION_REESTABLISHMENT_FAILURE, + "RRC_CONNECTION_REESTABLISHMENT_FAILURE"); + sFailCauseMap.put(RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER, + "RRC_CONNECTION_OUT_OF_SERVICE_DURING_CELL_REGISTER"); + sFailCauseMap.put(RRC_CONNECTION_ABORT_REQUEST, "RRC_CONNECTION_ABORT_REQUEST"); + sFailCauseMap.put(RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR, + "RRC_CONNECTION_SYSTEM_INFORMATION_BLOCK_READ_ERROR"); + sFailCauseMap.put(NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH, + "NETWORK_INITIATED_DETACH_WITH_AUTO_REATTACH"); + sFailCauseMap.put(NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH, + "NETWORK_INITIATED_DETACH_NO_AUTO_REATTACH"); + sFailCauseMap.put(ESM_PROCEDURE_TIME_OUT, "ESM_PROCEDURE_TIME_OUT"); + sFailCauseMap.put(INVALID_CONNECTION_ID, "INVALID_CONNECTION_ID"); + sFailCauseMap.put(MAXIMIUM_NSAPIS_EXCEEDED, "MAXIMIUM_NSAPIS_EXCEEDED"); + sFailCauseMap.put(INVALID_PRIMARY_NSAPI, "INVALID_PRIMARY_NSAPI"); + sFailCauseMap.put(CANNOT_ENCODE_OTA_MESSAGE, "CANNOT_ENCODE_OTA_MESSAGE"); + sFailCauseMap.put(RADIO_ACCESS_BEARER_SETUP_FAILURE, "RADIO_ACCESS_BEARER_SETUP_FAILURE"); + sFailCauseMap.put(PDP_ESTABLISH_TIMEOUT_EXPIRED, "PDP_ESTABLISH_TIMEOUT_EXPIRED"); + sFailCauseMap.put(PDP_MODIFY_TIMEOUT_EXPIRED, "PDP_MODIFY_TIMEOUT_EXPIRED"); + sFailCauseMap.put(PDP_INACTIVE_TIMEOUT_EXPIRED, "PDP_INACTIVE_TIMEOUT_EXPIRED"); + sFailCauseMap.put(PDP_LOWERLAYER_ERROR, "PDP_LOWERLAYER_ERROR"); + sFailCauseMap.put(PDP_MODIFY_COLLISION, "PDP_MODIFY_COLLISION"); + sFailCauseMap.put(MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED, + "MAXINUM_SIZE_OF_L2_MESSAGE_EXCEEDED"); + sFailCauseMap.put(NAS_REQUEST_REJECTED_BY_NETWORK, "NAS_REQUEST_REJECTED_BY_NETWORK"); + sFailCauseMap.put(RRC_CONNECTION_INVALID_REQUEST, "RRC_CONNECTION_INVALID_REQUEST"); + sFailCauseMap.put(RRC_CONNECTION_TRACKING_AREA_ID_CHANGED, + "RRC_CONNECTION_TRACKING_AREA_ID_CHANGED"); + sFailCauseMap.put(RRC_CONNECTION_RF_UNAVAILABLE, "RRC_CONNECTION_RF_UNAVAILABLE"); + sFailCauseMap.put(RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE, + "RRC_CONNECTION_ABORTED_DUE_TO_IRAT_CHANGE"); + sFailCauseMap.put(RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE, + "RRC_CONNECTION_RELEASED_SECURITY_NOT_ACTIVE"); + sFailCauseMap.put(RRC_CONNECTION_ABORTED_AFTER_HANDOVER, + "RRC_CONNECTION_ABORTED_AFTER_HANDOVER"); + sFailCauseMap.put(RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE, + "RRC_CONNECTION_ABORTED_AFTER_IRAT_CELL_CHANGE"); + sFailCauseMap.put(RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE, + "RRC_CONNECTION_ABORTED_DURING_IRAT_CELL_CHANGE"); + sFailCauseMap.put(IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER, + "IMSI_UNKNOWN_IN_HOME_SUBSCRIBER_SERVER"); + sFailCauseMap.put(IMEI_NOT_ACCEPTED, "IMEI_NOT_ACCEPTED"); + sFailCauseMap.put(EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED, + "EPS_SERVICES_AND_NON_EPS_SERVICES_NOT_ALLOWED"); + sFailCauseMap.put(EPS_SERVICES_NOT_ALLOWED_IN_PLMN, "EPS_SERVICES_NOT_ALLOWED_IN_PLMN"); + sFailCauseMap.put(MSC_TEMPORARILY_NOT_REACHABLE, "MSC_TEMPORARILY_NOT_REACHABLE"); + sFailCauseMap.put(CS_DOMAIN_NOT_AVAILABLE, "CS_DOMAIN_NOT_AVAILABLE"); + sFailCauseMap.put(ESM_FAILURE, "ESM_FAILURE"); + sFailCauseMap.put(MAC_FAILURE, "MAC_FAILURE"); + sFailCauseMap.put(SYNCHRONIZATION_FAILURE, "SYNCHRONIZATION_FAILURE"); + sFailCauseMap.put(UE_SECURITY_CAPABILITIES_MISMATCH, "UE_SECURITY_CAPABILITIES_MISMATCH"); + sFailCauseMap.put(SECURITY_MODE_REJECTED, "SECURITY_MODE_REJECTED"); + sFailCauseMap.put(UNACCEPTABLE_NON_EPS_AUTHENTICATION, + "UNACCEPTABLE_NON_EPS_AUTHENTICATION"); + sFailCauseMap.put(CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED, + "CS_FALLBACK_CALL_ESTABLISHMENT_NOT_ALLOWED"); + sFailCauseMap.put(NO_EPS_BEARER_CONTEXT_ACTIVATED, "NO_EPS_BEARER_CONTEXT_ACTIVATED"); + sFailCauseMap.put(INVALID_EMM_STATE, "INVALID_EMM_STATE"); + sFailCauseMap.put(NAS_LAYER_FAILURE, "NAS_LAYER_FAILURE"); + sFailCauseMap.put(MULTIPLE_PDP_CALL_NOT_ALLOWED, "MULTIPLE_PDP_CALL_NOT_ALLOWED"); + sFailCauseMap.put(EMBMS_NOT_ENABLED, "EMBMS_NOT_ENABLED"); + sFailCauseMap.put(IRAT_HANDOVER_FAILED, "IRAT_HANDOVER_FAILED"); + sFailCauseMap.put(EMBMS_REGULAR_DEACTIVATION, "EMBMS_REGULAR_DEACTIVATION"); + sFailCauseMap.put(TEST_LOOPBACK_REGULAR_DEACTIVATION, "TEST_LOOPBACK_REGULAR_DEACTIVATION"); + sFailCauseMap.put(LOWER_LAYER_REGISTRATION_FAILURE, "LOWER_LAYER_REGISTRATION_FAILURE"); + sFailCauseMap.put(DATA_PLAN_EXPIRED, "DATA_PLAN_EXPIRED"); + sFailCauseMap.put(UMTS_HANDOVER_TO_IWLAN, "UMTS_HANDOVER_TO_IWLAN"); + sFailCauseMap.put(EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY, + "EVDO_CONNECTION_DENY_BY_GENERAL_OR_NETWORK_BUSY"); + sFailCauseMap.put(EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE, + "EVDO_CONNECTION_DENY_BY_BILLING_OR_AUTHENTICATION_FAILURE"); + sFailCauseMap.put(EVDO_HDR_CHANGED, "EVDO_HDR_CHANGED"); + sFailCauseMap.put(EVDO_HDR_EXITED, "EVDO_HDR_EXITED"); + sFailCauseMap.put(EVDO_HDR_NO_SESSION, "EVDO_HDR_NO_SESSION"); + sFailCauseMap.put(EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL, + "EVDO_USING_GPS_FIX_INSTEAD_OF_HDR_CALL"); + sFailCauseMap.put(EVDO_HDR_CONNECTION_SETUP_TIMEOUT, "EVDO_HDR_CONNECTION_SETUP_TIMEOUT"); + sFailCauseMap.put(FAILED_TO_ACQUIRE_COLOCATED_HDR, "FAILED_TO_ACQUIRE_COLOCATED_HDR"); + sFailCauseMap.put(OTASP_COMMIT_IN_PROGRESS, "OTASP_COMMIT_IN_PROGRESS"); + sFailCauseMap.put(NO_HYBRID_HDR_SERVICE, "NO_HYBRID_HDR_SERVICE"); + sFailCauseMap.put(HDR_NO_LOCK_GRANTED, "HDR_NO_LOCK_GRANTED"); + sFailCauseMap.put(DBM_OR_SMS_IN_PROGRESS, "DBM_OR_SMS_IN_PROGRESS"); + sFailCauseMap.put(HDR_FADE, "HDR_FADE"); + sFailCauseMap.put(HDR_ACCESS_FAILURE, "HDR_ACCESS_FAILURE"); + sFailCauseMap.put(UNSUPPORTED_1X_PREV, "UNSUPPORTED_1X_PREV"); + sFailCauseMap.put(LOCAL_END, "LOCAL_END"); + sFailCauseMap.put(NO_SERVICE, "NO_SERVICE"); + sFailCauseMap.put(FADE, "FADE"); + sFailCauseMap.put(NORMAL_RELEASE, "NORMAL_RELEASE"); + sFailCauseMap.put(ACCESS_ATTEMPT_ALREADY_IN_PROGRESS, "ACCESS_ATTEMPT_ALREADY_IN_PROGRESS"); + sFailCauseMap.put(REDIRECTION_OR_HANDOFF_IN_PROGRESS, "REDIRECTION_OR_HANDOFF_IN_PROGRESS"); + sFailCauseMap.put(EMERGENCY_MODE, "EMERGENCY_MODE"); + sFailCauseMap.put(PHONE_IN_USE, "PHONE_IN_USE"); + sFailCauseMap.put(INVALID_MODE, "INVALID_MODE"); + sFailCauseMap.put(INVALID_SIM_STATE, "INVALID_SIM_STATE"); + sFailCauseMap.put(NO_COLLOCATED_HDR, "NO_COLLOCATED_HDR"); + sFailCauseMap.put(UE_IS_ENTERING_POWERSAVE_MODE, "UE_IS_ENTERING_POWERSAVE_MODE"); + sFailCauseMap.put(DUAL_SWITCH, "DUAL_SWITCH"); + sFailCauseMap.put(PPP_TIMEOUT, "PPP_TIMEOUT"); + sFailCauseMap.put(PPP_AUTH_FAILURE, "PPP_AUTH_FAILURE"); + sFailCauseMap.put(PPP_OPTION_MISMATCH, "PPP_OPTION_MISMATCH"); + sFailCauseMap.put(PPP_PAP_FAILURE, "PPP_PAP_FAILURE"); + sFailCauseMap.put(PPP_CHAP_FAILURE, "PPP_CHAP_FAILURE"); + sFailCauseMap.put(PPP_CLOSE_IN_PROGRESS, "PPP_CLOSE_IN_PROGRESS"); + sFailCauseMap.put(LIMITED_TO_IPV4, "LIMITED_TO_IPV4"); + sFailCauseMap.put(LIMITED_TO_IPV6, "LIMITED_TO_IPV6"); + sFailCauseMap.put(VSNCP_TIMEOUT, "VSNCP_TIMEOUT"); + sFailCauseMap.put(VSNCP_GEN_ERROR, "VSNCP_GEN_ERROR"); + sFailCauseMap.put(VSNCP_APN_UNATHORIZED, "VSNCP_APN_UNATHORIZED"); + sFailCauseMap.put(VSNCP_PDN_LIMIT_EXCEEDED, "VSNCP_PDN_LIMIT_EXCEEDED"); + sFailCauseMap.put(VSNCP_NO_PDN_GATEWAY_ADDRESS, "VSNCP_NO_PDN_GATEWAY_ADDRESS"); + sFailCauseMap.put(VSNCP_PDN_GATEWAY_UNREACHABLE, "VSNCP_PDN_GATEWAY_UNREACHABLE"); + sFailCauseMap.put(VSNCP_PDN_GATEWAY_REJECT, "VSNCP_PDN_GATEWAY_REJECT"); + sFailCauseMap.put(VSNCP_INSUFFICIENT_PARAMETERS, "VSNCP_INSUFFICIENT_PARAMETERS"); + sFailCauseMap.put(VSNCP_RESOURCE_UNAVAILABLE, "VSNCP_RESOURCE_UNAVAILABLE"); + sFailCauseMap.put(VSNCP_ADMINISTRATIVELY_PROHIBITED, "VSNCP_ADMINISTRATIVELY_PROHIBITED"); + sFailCauseMap.put(VSNCP_PDN_ID_IN_USE, "VSNCP_PDN_ID_IN_USE"); + sFailCauseMap.put(VSNCP_SUBSCRIBER_LIMITATION, "VSNCP_SUBSCRIBER_LIMITATION"); + sFailCauseMap.put(VSNCP_PDN_EXISTS_FOR_THIS_APN, "VSNCP_PDN_EXISTS_FOR_THIS_APN"); + sFailCauseMap.put(VSNCP_RECONNECT_NOT_ALLOWED, "VSNCP_RECONNECT_NOT_ALLOWED"); + sFailCauseMap.put(IPV6_PREFIX_UNAVAILABLE, "IPV6_PREFIX_UNAVAILABLE"); + sFailCauseMap.put(HANDOFF_PREFERENCE_CHANGED, "HANDOFF_PREFERENCE_CHANGED"); sFailCauseMap.put(OEM_DCFAILCAUSE_1, "OEM_DCFAILCAUSE_1"); sFailCauseMap.put(OEM_DCFAILCAUSE_2, "OEM_DCFAILCAUSE_2"); sFailCauseMap.put(OEM_DCFAILCAUSE_3, "OEM_DCFAILCAUSE_3"); diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 8d148c36f3e7..0e695309fce2 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -140,15 +140,19 @@ public class ApnSetting implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface AuthType {} - // Possible values for protocol. - /** Protocol type for IP. */ + // Possible values for protocol which is defined in TS 27.007 section 10.1.1. + /** Internet protocol. */ public static final int PROTOCOL_IP = 0; - /** Protocol type for IPV6. */ + /** Internet protocol, version 6. */ public static final int PROTOCOL_IPV6 = 1; - /** Protocol type for IPV4V6. */ + /** Virtual PDP type introduced to handle dual IP stack UE capability. */ public static final int PROTOCOL_IPV4V6 = 2; - /** Protocol type for PPP. */ + /** Point to point protocol. */ public static final int PROTOCOL_PPP = 3; + /** Transfer of Non-IP data to external packet data network. */ + public static final int PROTOCOL_NON_IP = 4; + /** Transfer of Unstructured data to the Data Network via N6. */ + public static final int PROTOCOL_UNSTRUCTURED = 5; /** @hide */ @IntDef(prefix = { "PROTOCOL_" }, value = { @@ -156,6 +160,8 @@ public class ApnSetting implements Parcelable { PROTOCOL_IPV6, PROTOCOL_IPV4V6, PROTOCOL_PPP, + PROTOCOL_NON_IP, + PROTOCOL_UNSTRUCTURED, }) @Retention(RetentionPolicy.SOURCE) public @interface ProtocolType {} @@ -217,11 +223,15 @@ public class ApnSetting implements Parcelable { PROTOCOL_STRING_MAP.put("IPV6", PROTOCOL_IPV6); PROTOCOL_STRING_MAP.put("IPV4V6", PROTOCOL_IPV4V6); PROTOCOL_STRING_MAP.put("PPP", PROTOCOL_PPP); + PROTOCOL_STRING_MAP.put("NON-IP", PROTOCOL_NON_IP); + PROTOCOL_STRING_MAP.put("UNSTRUCTURED", PROTOCOL_UNSTRUCTURED); PROTOCOL_INT_MAP = new ArrayMap<Integer, String>(); PROTOCOL_INT_MAP.put(PROTOCOL_IP, "IP"); PROTOCOL_INT_MAP.put(PROTOCOL_IPV6, "IPV6"); PROTOCOL_INT_MAP.put(PROTOCOL_IPV4V6, "IPV4V6"); PROTOCOL_INT_MAP.put(PROTOCOL_PPP, "PPP"); + PROTOCOL_INT_MAP.put(PROTOCOL_NON_IP, "NON-IP"); + PROTOCOL_INT_MAP.put(PROTOCOL_UNSTRUCTURED, "UNSTRUCTURED"); MVNO_TYPE_STRING_MAP = new ArrayMap<String, Integer>(); MVNO_TYPE_STRING_MAP.put("spn", MVNO_TYPE_SPN); diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index 25f51333350b..294c79ba57a2 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -52,8 +52,7 @@ public final class DataCallResponse implements Parcelable { * @param status Data call fail cause. 0 indicates no error. * @param suggestedRetryTime The suggested data retry time in milliseconds. * @param cid The unique id of the data connection. - * @param active Data connection active status. 0 = inactive, 1 = active/physical link down, - * 2 = active/physical link up. + * @param active Data connection active status. 0 = inactive, 1 = dormant, 2 = active. * @param type The connection protocol, should be one of the PDP_type values in TS 27.007 * section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP". * @param ifname The network interface name. @@ -124,7 +123,7 @@ public final class DataCallResponse implements Parcelable { public int getCallId() { return mCid; } /** - * @return 0 = inactive, 1 = active/physical link down, 2 = active/physical link up. + * @return 0 = inactive, 1 = dormant, 2 = active. */ public int getActive() { return mActive; } diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 0e5c71d6ef90..0fa1b41d4b16 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -129,6 +129,66 @@ public class EuiccManager { "android.telephony.euicc.action.RESOLVE_ERROR"; /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * enable or disable a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and + * {@link #EXTRA_ENABLE_SUBSCRIPTION}. + * + * <p>Unlike {@link #switchToSubscription(int, PendingIntent)}, using this action allows the + * underlying eUICC service (i.e. the LPA app) to control the UI experience during this + * operation. The action is received by the Telephony framework, which in turn selects and + * launches an appropriate LPA activity to present UI to the user. For example, the activity may + * show a confirmation dialog, a progress dialog, or an error dialog when necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED"; + + /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * delete a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID}. + * + * <p>Unlike {@link #deleteSubscription(int, PendingIntent)}, using this action allows the + * underlying eUICC service (i.e. the LPA app) to control the UI experience during this + * operation. The action is received by the Telephony framework, which in turn selects and + * launches an appropriate LPA activity to present UI to the user. For example, the activity may + * show a confirmation dialog, a progress dialog, or an error dialog when necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; + + /** + * Intent action sent by system apps (such as the Settings app) to the Telephony framework to + * rename a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and + * {@link #EXTRA_SUBSCRIPTION_NICKNAME}. + * + * <p>Unlike {@link #updateSubscriptionNickname(int, String, PendingIntent)}, using this action + * allows the the underlying eUICC service (i.e. the LPA app) to control the UI experience + * during this operation. The action is received by the Telephony framework, which in turn + * selects and launches an appropriate LPA activity to present UI to the user. For example, the + * activity may show a confirmation dialog, a progress dialog, or an error dialog when + * necessary. + * + * <p>The launched activity will immediately finish with + * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false. + * + * @hide + */ + @SystemApi + public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = + "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED"; + + /** * Result code for an operation indicating that the operation succeeded. */ public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0; @@ -219,6 +279,37 @@ public class EuiccManager { "android.telephony.euicc.extra.FORCE_PROVISION"; /** + * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED}, + * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and + * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SUBSCRIPTION_ID = + "android.telephony.euicc.extra.SUBSCRIPTION_ID"; + + /** + * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean + * value of whether to enable or disable the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_ENABLE_SUBSCRIPTION = + "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION"; + + /** + * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new + * nickname for the targeted subscription. + * + * @hide + */ + @SystemApi + public static final String EXTRA_SUBSCRIPTION_NICKNAME = + "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME"; + + /** * Optional meta-data attribute for a carrier app providing an icon to use to represent the * carrier. If not provided, the app's launcher icon will be used as a fallback. */ diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index a181bc387d36..1110790c373f 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -32,7 +32,6 @@ package android.test.mock { public class MockContext extends android.content.Context { ctor public MockContext(); - method public boolean bindIsolatedService(android.content.Intent, android.content.ServiceConnection, int, String); method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int); method public int checkCallingOrSelfPermission(String); method public int checkCallingOrSelfUriPermission(android.net.Uri, int); @@ -81,7 +80,6 @@ package android.test.mock { method public java.io.File getNoBackupFilesDir(); method public java.io.File getObbDir(); method public java.io.File[] getObbDirs(); - method public String getOpPackageName(); method public String getPackageCodePath(); method public android.content.pm.PackageManager getPackageManager(); method public String getPackageName(); @@ -137,7 +135,6 @@ package android.test.mock { method public boolean stopService(android.content.Intent); method public void unbindService(android.content.ServiceConnection); method public void unregisterReceiver(android.content.BroadcastReceiver); - method public void updateServiceGroup(android.content.ServiceConnection, int, int); } @Deprecated public class MockCursor implements android.database.Cursor { diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk index ee2ec0a80b03..979d13ac9405 100644 --- a/tests/DexLoggerIntegrationTests/Android.mk +++ b/tests/DexLoggerIntegrationTests/Android.mk @@ -29,6 +29,35 @@ include $(BUILD_JAVA_LIBRARY) dexloggertest_jar := $(LOCAL_BUILT_MODULE) +# Also build a native library that the test app can dynamically load + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DexLoggerNativeTestLibrary +LOCAL_MULTILIB := first +LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) +LOCAL_SDK_VERSION := 28 +LOCAL_NDK_STL_VARIANT := c++_static + +include $(BUILD_SHARED_LIBRARY) + +dexloggertest_so := $(LOCAL_BUILT_MODULE) + +# And a standalone native executable that we can exec. + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DexLoggerNativeExecutable +LOCAL_SRC_FILES := src/cpp/test_executable.cpp + +include $(BUILD_EXECUTABLE) + +dexloggertest_executable := $(LOCAL_BUILT_MODULE) + # Build the test app itself include $(CLEAR_VARS) @@ -37,14 +66,18 @@ LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := DexLoggerIntegrationTests LOCAL_SDK_VERSION := current LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_CERTIFICATE := platform +LOCAL_CERTIFICATE := shared LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm) LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ truth-prebuilt \ -# This gets us the javalib.jar built by DexLoggerTestLibrary above. -LOCAL_JAVA_RESOURCE_FILES := $(dexloggertest_jar) +# This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various +# native binaries. +LOCAL_JAVA_RESOURCE_FILES := \ + $(dexloggertest_jar) \ + $(dexloggertest_so) \ + $(dexloggertest_executable) include $(BUILD_PACKAGE) diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java index 75ee0896c23a..d68769b378b9 100644 --- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java +++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java @@ -17,6 +17,7 @@ package com.android.server.pm.dex; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import android.app.UiAutomation; import android.content.Context; @@ -25,6 +26,7 @@ import android.os.SystemClock; import android.support.test.InstrumentationRegistry; import android.support.test.filters.LargeTest; import android.util.EventLog; +import android.util.EventLog.Event; import dalvik.system.DexClassLoader; @@ -65,14 +67,13 @@ public final class DexLoggerIntegrationTests { // Event log tag used for SNET related events private static final int SNET_TAG = 0x534e4554; - // Subtag used to distinguish dynamic code loading events - private static final String DCL_SUBTAG = "dcl"; + // Subtags used to distinguish dynamic code loading events + private static final String DCL_DEX_SUBTAG = "dcl"; + private static final String DCL_NATIVE_SUBTAG = "dcln"; - // All the tags we care about - private static final int[] TAG_LIST = new int[] { SNET_TAG }; - - // This is {@code DynamicCodeLoggingService#JOB_ID} - private static final int DYNAMIC_CODE_LOGGING_JOB_ID = 2030028; + // These are job IDs from DynamicCodeLoggingService + private static final int IDLE_LOGGING_JOB_ID = 2030028; + private static final int AUDIT_WATCHING_JOB_ID = 203142925; private static Context sContext; private static int sMyUid; @@ -89,15 +90,20 @@ public final class DexLoggerIntegrationTests { // Without this the first test passes and others don't - we don't see new events in the // log. The exact reason is unclear. EventLog.writeEvent(SNET_TAG, "Dummy event"); + + // Audit log messages are throttled by the kernel (at the request of logd) to 5 per + // second, so running the tests too quickly in sequence means we lose some and get + // spurious failures. Sigh. + SystemClock.sleep(1000); } @Test - public void testDexLoggerGeneratesEvents() throws Exception { - File privateCopyFile = fileForJar("copied.jar"); + public void testDexLoggerGeneratesEvents_standardClassLoader() throws Exception { + File privateCopyFile = privateFile("copied.jar"); // Obtained via "echo -n copied.jar | sha256sum" String expectedNameHash = "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; - String expectedContentHash = copyAndHashJar(privateCopyFile); + String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); // Feed the jar to a class loader and make sure it contains what we expect. ClassLoader parentClassLoader = sContext.getClass().getClassLoader(); @@ -107,18 +113,18 @@ public final class DexLoggerIntegrationTests { // And make sure we log events about it long previousEventNanos = mostRecentEventTimeNanos(); - runDexLogger(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); - assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash); + assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG, + expectedNameHash, expectedContentHash); } @Test - public void testDexLoggerGeneratesEvents_unknownClassLoader() throws Exception { - File privateCopyFile = fileForJar("copied2.jar"); + File privateCopyFile = privateFile("copied2.jar"); String expectedNameHash = "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93"; - String expectedContentHash = copyAndHashJar(privateCopyFile); + String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); // This time make sure an unknown class loader is an ancestor of the class loader we use. ClassLoader knownClassLoader = sContext.getClass().getClassLoader(); @@ -129,22 +135,185 @@ public final class DexLoggerIntegrationTests { // And make sure we log events about it long previousEventNanos = mostRecentEventTimeNanos(); - runDexLogger(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_nativeLibrary() throws Exception { + File privateCopyFile = privateFile("copied.so"); + String expectedNameHash = + "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E"; + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile); + + System.load(privateCopyFile.toString()); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); - assertDclLoggedSince(previousEventNanos, expectedNameHash, expectedContentHash); + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); } - private static File fileForJar(String name) { - return new File(sContext.getDir("jars", Context.MODE_PRIVATE), name); + @Test + public void testDexLoggerGeneratesEvents_nativeLibrary_escapedName() throws Exception { + // A file name with a space will be escaped in the audit log; verify we un-escape it + // correctly. + File privateCopyFile = privateFile("second copy.so"); + String expectedNameHash = + "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA"; + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile); + + System.load(privateCopyFile.toString()); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); } - private static String copyAndHashJar(File copyTo) throws Exception { + @Test + public void testDexLoggerGeneratesEvents_nativeExecutable() throws Exception { + File privateCopyFile = privateFile("test_executable"); + String expectedNameHash = + "3FBEC3F925A132D18F347F11AE9A5BB8DE1238828F8B4E064AA86EB68BD46DCF"; + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile); + assertThat(privateCopyFile.setExecutable(true)).isTrue(); + + Process process = Runtime.getRuntime().exec(privateCopyFile.toString()); + int exitCode = process.waitFor(); + assertThat(exitCode).isEqualTo(0); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_spoofed_validFile() throws Exception { + File privateCopyFile = privateFile("spoofed"); + + String expectedContentHash = + copyAndHashResource("/DexLoggerNativeExecutable", privateCopyFile); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + privateCopyFile + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testDexLoggerGeneratesEvents_spoofed_pathTraversal() throws Exception { + File privateDir = privateFile("x").getParentFile(); + + // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root + File pathTraversalToRoot = privateDir; + File root = new File("/"); + while (!privateDir.equals(root)) { + pathTraversalToRoot = new File(pathTraversalToRoot, ".."); + privateDir = privateDir.getParentFile(); + } + + File spoofedFile = new File(pathTraversalToRoot, "dev/urandom"); + + assertWithMessage("Expected " + spoofedFile + " to be readable") + .that(spoofedFile.canRead()).isTrue(); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + spoofedFile + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then trigger generating DCL events + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash); + } + + @Test + public void testDexLoggerGeneratesEvents_spoofed_otherAppFile() throws Exception { + File ourPath = sContext.getDatabasePath("android_pay"); + File targetPath = new File(ourPath.toString() + .replace("com.android.frameworks.dexloggertest", "com.google.android.gms")); + + assertWithMessage("Expected " + targetPath + " to not be readable") + .that(targetPath.canRead()).isFalse(); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + targetPath + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then trigger generating DCL events + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash); + } + + private static File privateFile(String name) { + return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name); + } + + private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception { MessageDigest hasher = MessageDigest.getInstance("SHA-256"); // Copy the jar from our Java resources to a private data directory Class<?> thisClass = DexLoggerIntegrationTests.class; - try (InputStream input = thisClass.getResourceAsStream("/javalib.jar"); - OutputStream output = new FileOutputStream(copyTo)) { + try (InputStream input = thisClass.getResourceAsStream(resourcePath); + OutputStream output = new FileOutputStream(copyTo)) { byte[] buffer = new byte[1024]; while (true) { int numRead = input.read(buffer); @@ -166,24 +335,18 @@ public final class DexLoggerIntegrationTests { return formatter.toString(); } - private static long mostRecentEventTimeNanos() throws Exception { - List<EventLog.Event> events = new ArrayList<>(); - - EventLog.readEvents(TAG_LIST, events); - return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); - } - - private static void runDexLogger() throws Exception { - // This forces {@code DynamicCodeLoggingService} to start now. - runCommand("cmd jobscheduler run -f android " + DYNAMIC_CODE_LOGGING_JOB_ID); + private static void runDynamicCodeLoggingJob(int jobId) throws Exception { + // This forces the DynamicCodeLoggingService job to start now. + runCommand("cmd jobscheduler run -f android " + jobId); // Wait for the job to have run. long startTime = SystemClock.elapsedRealtime(); while (true) { String response = runCommand( - "cmd jobscheduler get-job-state android " + DYNAMIC_CODE_LOGGING_JOB_ID); + "cmd jobscheduler get-job-state android " + jobId); if (!response.contains("pending") && !response.contains("active")) { break; } + // Don't wait forever - if it's taken > 10s then something is very wrong. if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) { throw new AssertionError("Job has not completed: " + response); } @@ -208,37 +371,68 @@ public final class DexLoggerIntegrationTests { return response.toString("UTF-8"); } - private static void assertDclLoggedSince(long previousEventNanos, String expectedNameHash, - String expectedContentHash) throws Exception { - List<EventLog.Event> events = new ArrayList<>(); - EventLog.readEvents(TAG_LIST, events); - int found = 0; - for (EventLog.Event event : events) { + private static long mostRecentEventTimeNanos() throws Exception { + List<Event> events = readSnetEvents(); + return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); + } + + private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag, + String expectedNameHash, String expectedContentHash) throws Exception { + List<String> messages = + findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash); + + assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1); + assertThat(messages.get(0)).endsWith(expectedContentHash); + } + + private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag, + String expectedNameHash) throws Exception { + List<String> messages = + findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash); + + assertWithMessage("Expected no matching log entries").that(messages).isEmpty(); + } + + private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag, + String expectedNameHash) throws Exception { + List<String> messages = new ArrayList<>(); + + for (Event event : readSnetEvents()) { if (event.getTimeNanos() <= previousEventNanos) { continue; } - Object[] data = (Object[]) event.getData(); + + Object data = event.getData(); + if (!(data instanceof Object[])) { + continue; + } + Object[] fields = (Object[]) data; // We only care about DCL events that we generated. - String subTag = (String) data[0]; - if (!DCL_SUBTAG.equals(subTag)) { + String subTag = (String) fields[0]; + if (!expectedSubTag.equals(subTag)) { continue; } - int uid = (int) data[1]; + int uid = (int) fields[1]; if (uid != sMyUid) { continue; } - String message = (String) data[2]; + String message = (String) fields[2]; if (!message.startsWith(expectedNameHash)) { continue; } - assertThat(message).endsWith(expectedContentHash); - ++found; + messages.add(message); + //assertThat(message).endsWith(expectedContentHash); } + return messages; + } - assertThat(found).isEqualTo(1); + private static List<Event> readSnetEvents() throws Exception { + List<Event> events = new ArrayList<>(); + EventLog.readEvents(new int[] { SNET_TAG }, events); + return events; } /** diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp index 62a8c4866f07..060888310b51 100644 --- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl +++ b/tests/DexLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.testing.alarmservice; +#include "jni.h" -interface Alarm { - int prepare(); - int setAlarmAndWait(long timeoutMills); - int done(); -}
\ No newline at end of file +extern "C" jint JNI_OnLoad(JavaVM* /* vm */, void* /* reserved */) +{ + return JNI_VERSION_1_6; +} diff --git a/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp new file mode 100644 index 000000000000..ad025e696dec --- /dev/null +++ b/tests/DexLoggerIntegrationTests/src/cpp/test_executable.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +int main() { + // This program just has to run, it doesn't need to do anything. So we don't. + return 0; +} diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index c2e735e184b0..ec6f4b55d3ea 100644 --- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -38,7 +38,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -470,7 +469,7 @@ public class RollbackTest { * Test that app user data is rolled back. * TODO: Stop ignoring this test once user data rollback is supported. */ - @Ignore @Test + @Test public void testUserDataRollback() throws Exception { try { RollbackTestUtils.adoptShellPermissionIdentity( @@ -479,9 +478,9 @@ public class RollbackTest { Manifest.permission.MANAGE_ROLLBACKS); RollbackTestUtils.uninstall(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppV1.apk", false); + RollbackTestUtils.install("RollbackTestAppAv1.apk", false); processUserData(TEST_APP_A); - RollbackTestUtils.install("RollbackTestAppV2.apk", true); + RollbackTestUtils.install("RollbackTestAppAv2.apk", true); processUserData(TEST_APP_A); RollbackManager rm = RollbackTestUtils.getRollbackManager(); diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java new file mode 100644 index 000000000000..032e52666970 --- /dev/null +++ b/tests/net/java/android/net/DnsPacketTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsPacketTest { + private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag, + int qCount, int aCount, int nsCount, int arCount) { + assertEquals(header.id, id); + assertEquals(header.flags, flag); + assertEquals(header.getSectionCount(DnsPacket.QDSECTION), qCount); + assertEquals(header.getSectionCount(DnsPacket.ANSECTION), aCount); + assertEquals(header.getSectionCount(DnsPacket.NSSECTION), nsCount); + assertEquals(header.getSectionCount(DnsPacket.ARSECTION), arCount); + } + + private void assertSectionParses(DnsPacket.DnsSection section, String dname, + int dtype, int dclass, int ttl, byte[] rr) { + assertEquals(section.dName, dname); + assertEquals(section.nsType, dtype); + assertEquals(section.nsClass, dclass); + assertEquals(section.ttl, ttl); + assertTrue(Arrays.equals(section.getRR(), rr)); + } + + class TestDnsPacket extends DnsPacket { + TestDnsPacket(byte[] data) throws ParseException { + super(data); + } + + public DnsHeader getHeader() { + return mHeader; + } + public List<DnsSection> getSectionList(int secType) { + return mSections[secType]; + } + } + + @Test + public void testNullDisallowed() { + try { + new TestDnsPacket(null); + fail("Exception not thrown for null byte array"); + } catch (DnsPacket.ParseException e) { + } + } + + @Test + public void testV4Answer() throws Exception { + final byte[] v4blob = new byte[] { + /* Header */ + 0x55, 0x66, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x01, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x01, 0x2b, /* TTL */ + 0x00, 0x04, /* Data length */ + (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v4blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0); + + // Section part + List<DnsPacket.DnsSection> qdSectionList = + packet.getSectionList(DnsPacket.QDSECTION); + assertEquals(qdSectionList.size(), 1); + assertSectionParses(qdSectionList.get(0), "www.google.com", 1, 1, 0, null); + + List<DnsPacket.DnsSection> anSectionList = + packet.getSectionList(DnsPacket.ANSECTION); + assertEquals(anSectionList.size(), 1); + assertSectionParses(anSectionList.get(0), "www.google.com", 1, 1, 0x12b, + new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 }); + } + + @Test + public void testV6Answer() throws Exception { + final byte[] v6blob = new byte[] { + /* Header */ + 0x77, 0x22, /* Transaction ID */ + (byte) 0x81, (byte) 0x80, /* Flags */ + 0x00, 0x01, /* Questions */ + 0x00, 0x01, /* Answer RRs */ + 0x00, 0x00, /* Authority RRs */ + 0x00, 0x00, /* Additional RRs */ + /* Queries */ + 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + /* Answers */ + (byte) 0xc0, 0x0c, /* Name */ + 0x00, 0x1c, /* Type */ + 0x00, 0x01, /* Class */ + 0x00, 0x00, 0x00, 0x37, /* TTL */ + 0x00, 0x10, /* Data length */ + 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */ + }; + TestDnsPacket packet = new TestDnsPacket(v6blob); + + // Header part + assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0); + + // Section part + List<DnsPacket.DnsSection> qdSectionList = + packet.getSectionList(DnsPacket.QDSECTION); + assertEquals(qdSectionList.size(), 1); + assertSectionParses(qdSectionList.get(0), "www.google.com", 28, 1, 0, null); + + List<DnsPacket.DnsSection> anSectionList = + packet.getSectionList(DnsPacket.ANSECTION); + assertEquals(anSectionList.size(), 1); + assertSectionParses(anSectionList.get(0), "www.google.com", 28, 1, 0x37, + new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 }); + } +} diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/tests/net/java/android/net/ip/IpClientTest.java index a2dcfef50a49..5110ce1830b3 100644 --- a/tests/net/java/android/net/ip/IpClientTest.java +++ b/tests/net/java/android/net/ip/IpClientTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.content.Context; import android.content.res.Resources; +import android.net.ConnectivityManager; import android.net.INetd; import android.net.IpPrefix; import android.net.LinkAddress; @@ -82,6 +83,7 @@ public class IpClientTest { private static final int TEST_TIMEOUT_MS = 400; @Mock private Context mContext; + @Mock private ConnectivityManager mCm; @Mock private INetworkManagementService mNMService; @Mock private INetd mNetd; @Mock private Resources mResources; @@ -98,6 +100,9 @@ public class IpClientTest { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); + when(mContext.getSystemServiceName(ConnectivityManager.class)) + .thenReturn(Context.CONNECTIVITY_SERVICE); + when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm); when(mContext.getResources()).thenReturn(mResources); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); @@ -122,13 +127,14 @@ public class IpClientTest { private IpClient makeIpClient(String ifname) throws Exception { setTestInterfaceParams(ifname); final IpClient ipc = new IpClient(mContext, ifname, mCb, mDependecies); - verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(ifname); - verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(ifname); + verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(ifname, false); + verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(ifname); ArgumentCaptor<BaseNetworkObserver> arg = ArgumentCaptor.forClass(BaseNetworkObserver.class); verify(mNMService, times(1)).registerObserver(arg.capture()); mObserver = arg.getValue(); reset(mNMService); + reset(mNetd); // Verify IpClient doesn't call onLinkPropertiesChange() when it starts. verify(mCb, never()).onLinkPropertiesChange(any()); reset(mCb); @@ -200,8 +206,8 @@ public class IpClientTest { verify(mCb, never()).onProvisioningFailure(any()); ipc.shutdown(); - verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(iface); - verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(iface); + verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false); + verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); } @@ -251,8 +257,8 @@ public class IpClientTest { verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want)); ipc.shutdown(); - verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(iface); - verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(iface); + verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false); + verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); } diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java index e65585f8ff0f..e3b5ddf6f4cf 100644 --- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java +++ b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java @@ -16,11 +16,10 @@ package android.net.ip; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.when; +import android.content.Context; import android.net.util.InterfaceParams; import android.net.util.SharedLog; import android.os.Handler; @@ -45,6 +44,7 @@ public class IpReachabilityMonitorTest { @Mock IpReachabilityMonitor.Callback mCallback; @Mock IpReachabilityMonitor.Dependencies mDependencies; @Mock SharedLog mLog; + @Mock Context mContext; Handler mHandler; @Before @@ -56,7 +56,8 @@ public class IpReachabilityMonitorTest { IpReachabilityMonitor makeMonitor() { final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null); - return new IpReachabilityMonitor(ifParams, mHandler, mLog, mCallback, null, mDependencies); + return new IpReachabilityMonitor( + mContext, ifParams, mHandler, mLog, mCallback, false, mDependencies); } @Test diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java index c3162af1868d..80aac047a723 100644 --- a/tests/net/java/android/net/ip/IpServerTest.java +++ b/tests/net/java/android/net/ip/IpServerTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -47,6 +48,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.net.INetd; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; import android.net.IpPrefix; @@ -92,6 +94,7 @@ public class IpServerTest { private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000; @Mock private INetworkManagementService mNMService; + @Mock private INetd mNetd; @Mock private INetworkStatsService mStatsService; @Mock private IpServer.Callback mCallback; @Mock private InterfaceConfiguration mInterfaceConfiguration; @@ -112,16 +115,6 @@ public class IpServerTest { } private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception { - mIpServer = new IpServer( - IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, - mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies); - mIpServer.start(); - // Starting the state machine always puts us in a consistent state and notifies - // the rest of the world that we've changed from an unknown to available state. - mLooper.dispatchAll(); - reset(mNMService, mStatsService, mCallback); - when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); - doAnswer(inv -> { final IDhcpServerCallbacks cb = inv.getArgument(2); new Thread(() -> { @@ -135,6 +128,17 @@ public class IpServerTest { }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any()); when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon); when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS); + when(mDependencies.getNetdService()).thenReturn(mNetd); + + mIpServer = new IpServer( + IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, + mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies); + mIpServer.start(); + // Starting the state machine always puts us in a consistent state and notifies + // the rest of the world that we've changed from an unknown to available state. + mLooper.dispatchAll(); + reset(mNMService, mStatsService, mCallback); + when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); when(mRaDaemon.start()).thenReturn(true); } @@ -223,9 +227,9 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_BLUETOOTH, null); dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNMService, mStatsService, mCallback); + InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback); inOrder.verify(mNMService).untetherInterface(IFACE_NAME); - inOrder.verify(mNMService).setInterfaceConfig(eq(IFACE_NAME), any()); + inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( @@ -318,12 +322,12 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNMService, mStatsService, mCallback); + InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback); inOrder.verify(mStatsService).forceUpdate(); inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNMService).untetherInterface(IFACE_NAME); - inOrder.verify(mNMService).setInterfaceConfig(eq(IFACE_NAME), any()); + inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( diff --git a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java index 6f711c0b5743..b6d01dbc1cac 100644 --- a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java +++ b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java @@ -48,61 +48,52 @@ public class LinkPropertiesParcelableUtilTest { private LinkProperties mLinkProperties; private static final String TEST_LINKPROPS_IFACE = "TEST_IFACE"; - private static final String TEST_STACKED_LINK_1_IFACE = "TEST_STACKED_IFACE_1"; - private static final String TEST_STACKED_LINK_2_IFACE = "TEST_STACKED_IFACE_2"; @Before public void setUp() { - mLinkProperties = makeLinkProperties(TEST_LINKPROPS_IFACE); - mLinkProperties.addStackedLink(makeLinkProperties(TEST_STACKED_LINK_1_IFACE)); - mLinkProperties.addStackedLink(makeLinkProperties(TEST_STACKED_LINK_2_IFACE)); - } - - private static LinkProperties makeLinkProperties(String iface) { - final LinkProperties lp = new LinkProperties(); - lp.setInterfaceName(iface); - lp.setLinkAddresses(Arrays.asList( + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(TEST_LINKPROPS_IFACE); + mLinkProperties.setLinkAddresses(Arrays.asList( new LinkAddress(InetAddresses.parseNumericAddress("192.168.0.42"), 16), new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::7"), 42))); - lp.setDnsServers(Arrays.asList( + mLinkProperties.setDnsServers(Arrays.asList( InetAddresses.parseNumericAddress("2001:db8::42"), InetAddresses.parseNumericAddress("192.168.1.1") )); - lp.setValidatedPrivateDnsServers(Arrays.asList( + mLinkProperties.setValidatedPrivateDnsServers(Arrays.asList( InetAddresses.parseNumericAddress("2001:db8::43"), InetAddresses.parseNumericAddress("192.168.42.43") )); - lp.setPcscfServers(Arrays.asList( + mLinkProperties.setPcscfServers(Arrays.asList( InetAddresses.parseNumericAddress("2001:db8::47"), InetAddresses.parseNumericAddress("192.168.42.47") )); - lp.setUsePrivateDns(true); - lp.setPrivateDnsServerName("test.example.com"); - lp.setDomains("test1.example.com,test2.example.com"); - lp.addRoute(new RouteInfo( + mLinkProperties.setUsePrivateDns(true); + mLinkProperties.setPrivateDnsServerName("test.example.com"); + mLinkProperties.setDomains("test1.example.com,test2.example.com"); + mLinkProperties.addRoute(new RouteInfo( new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::44"), 45), InetAddresses.parseNumericAddress("2001:db8::45"), - iface, + TEST_LINKPROPS_IFACE, RouteInfo.RTN_UNICAST )); - lp.addRoute(new RouteInfo( + mLinkProperties.addRoute(new RouteInfo( new IpPrefix(InetAddresses.parseNumericAddress("192.168.44.45"), 16), InetAddresses.parseNumericAddress("192.168.45.1"), - iface, + TEST_LINKPROPS_IFACE, RouteInfo.RTN_THROW )); - lp.setHttpProxy(new ProxyInfo("test3.example.com", 8000, + mLinkProperties.setHttpProxy(new ProxyInfo("test3.example.com", 8000, "excl1.example.com,excl2.example.com")); - lp.setMtu(5000); - lp.setTcpBufferSizes("1,2,3,4,5,6"); - lp.setNat64Prefix(new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::48"), 96)); + mLinkProperties.setMtu(5000); + mLinkProperties.setTcpBufferSizes("1,2,3,4,5,6"); + mLinkProperties.setNat64Prefix( + new IpPrefix(InetAddresses.parseNumericAddress("2001:db8::48"), 96)); // Verify that this test does not miss any new field added later. // If any added field is not included in LinkProperties#equals, assertLinkPropertiesEquals // must also be updated. assertFieldCountEquals(14, LinkProperties.class); - - return lp; } @Test @@ -186,7 +177,7 @@ public class LinkPropertiesParcelableUtilTest { private static void assertLinkPropertiesEquals(LinkProperties expected, LinkProperties actual) { assertEquals(expected, actual); - // LinkProperties equals() does not include stacked links - assertEquals(expected.getStackedLinks(), actual.getStackedLinks()); + // Equality on stacked links is not tested as they should not be passed to processes using + // LinkPropertiesParcelable. } } diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index 1ea83c2bbb6b..b6356076db60 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -803,13 +803,14 @@ public class TetheringTest { sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); mLooper.dispatchAll(); - // We verify get/set called thrice here: once for setup and twice during - // teardown because all events happen over the course of the single + // We verify get/set called thrice here: twice for setup (on NMService) and once during + // teardown (on Netd) because all events happen over the course of the single // dispatchAll() above. Note that once the IpServer IPv4 address config // code is refactored the two calls during shutdown will revert to one. verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, times(3)) + verify(mNMService, times(2)) .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); + verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName))); verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED); diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 9bf758797ed2..0b74d878f069 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -57,7 +57,6 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.ConnectivityManager; -import android.net.IConnectivityManager; import android.net.IpPrefix; import android.net.LinkProperties; import android.net.Network; @@ -97,7 +96,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -240,6 +238,30 @@ public class VpnTest { } @Test + public void testGetAlwaysAndOnGetLockDown() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + + // Default state. + assertFalse(vpn.getAlwaysOn()); + assertFalse(vpn.getLockdown()); + + // Set always-on without lockdown. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false)); + assertTrue(vpn.getAlwaysOn()); + assertFalse(vpn.getLockdown()); + + // Set always-on with lockdown. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true)); + assertTrue(vpn.getAlwaysOn()); + assertTrue(vpn.getLockdown()); + + // Remove always-on configuration. + assertTrue(vpn.setAlwaysOnPackage(null, false)); + assertFalse(vpn.getAlwaysOn()); + assertFalse(vpn.getLockdown()); + } + + @Test public void testLockdownChangingPackage() throws Exception { final Vpn vpn = createVpn(primaryUser.id); final UidRange user = UidRange.createForUser(primaryUser.id); diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java index 94bcd28bf009..f2ecef95b599 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java @@ -27,10 +27,14 @@ import static org.mockito.Mockito.doReturn; import android.content.Context; import android.net.ipmemorystore.Blob; import android.net.ipmemorystore.IOnBlobRetrievedListener; +import android.net.ipmemorystore.IOnL2KeyResponseListener; import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; +import android.net.ipmemorystore.IOnSameNetworkResponseListener; import android.net.ipmemorystore.IOnStatusListener; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.NetworkAttributesParcelable; +import android.net.ipmemorystore.SameL3NetworkResponse; +import android.net.ipmemorystore.SameL3NetworkResponseParcelable; import android.net.ipmemorystore.Status; import android.net.ipmemorystore.StatusParcelable; import android.os.IBinder; @@ -53,7 +57,6 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; -import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -65,6 +68,15 @@ public class IpMemoryStoreServiceTest { private static final String TEST_CLIENT_ID = "testClientId"; private static final String TEST_DATA_NAME = "testData"; + private static final int FAKE_KEY_COUNT = 20; + private static final String[] FAKE_KEYS; + static { + FAKE_KEYS = new String[FAKE_KEY_COUNT]; + for (int i = 0; i < FAKE_KEYS.length; ++i) { + FAKE_KEYS[i] = "fakeKey" + i; + } + } + @Mock private Context mMockContext; private File mDbFile; @@ -130,8 +142,8 @@ public class IpMemoryStoreServiceTest { final OnNetworkAttributesRetrievedListener functor) { return new IOnNetworkAttributesRetrieved() { @Override - public void onL2KeyResponse(final StatusParcelable status, final String l2Key, - final NetworkAttributesParcelable attributes) + public void onNetworkAttributesRetrieved(final StatusParcelable status, + final String l2Key, final NetworkAttributesParcelable attributes) throws RemoteException { functor.onNetworkAttributesRetrieved(new Status(status), l2Key, null == attributes ? null : new NetworkAttributes(attributes)); @@ -144,6 +156,47 @@ public class IpMemoryStoreServiceTest { }; } + /** Helper method to make an IOnSameNetworkResponseListener */ + private interface OnSameNetworkResponseListener { + void onSameNetworkResponse(Status status, SameL3NetworkResponse answer); + } + private IOnSameNetworkResponseListener onSameResponse( + final OnSameNetworkResponseListener functor) { + return new IOnSameNetworkResponseListener() { + @Override + public void onSameNetworkResponse(final StatusParcelable status, + final SameL3NetworkResponseParcelable sameL3Network) + throws RemoteException { + functor.onSameNetworkResponse(new Status(status), + null == sameL3Network ? null : new SameL3NetworkResponse(sameL3Network)); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + + /** Helper method to make an IOnL2KeyResponseListener */ + private interface OnL2KeyResponseListener { + void onL2KeyResponse(Status status, String key); + } + private IOnL2KeyResponseListener onL2KeyResponse(final OnL2KeyResponseListener functor) { + return new IOnL2KeyResponseListener() { + @Override + public void onL2KeyResponse(final StatusParcelable status, final String key) + throws RemoteException { + functor.onL2KeyResponse(new Status(status), key); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + // Helper method to factorize some boilerplate private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) { final CountDownLatch latch = new CountDownLatch(1); @@ -155,24 +208,28 @@ public class IpMemoryStoreServiceTest { } } + // Helper methods to factorize more boilerplate + private void storeAttributes(final String l2Key, final NetworkAttributes na) { + storeAttributes("Did not complete storing attributes", l2Key, na); + } + private void storeAttributes(final String timeoutMessage, final String l2Key, + final NetworkAttributes na) { + doLatched(timeoutMessage, latch -> mService.storeNetworkAttributes(l2Key, na.toParcelable(), + onStatus(status -> { + assertTrue("Store not successful : " + status.resultCode, status.isSuccess()); + latch.countDown(); + }))); + } + @Test - public void testNetworkAttributes() { + public void testNetworkAttributes() throws UnknownHostException { final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); - try { - na.setAssignedV4Address( - (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4})); - } catch (UnknownHostException e) { /* Can't happen */ } + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); na.setGroupHint("hint1"); na.setMtu(219); - final String l2Key = UUID.randomUUID().toString(); + final String l2Key = FAKE_KEYS[0]; NetworkAttributes attributes = na.build(); - doLatched("Did not complete storing attributes", latch -> - mService.storeNetworkAttributes(l2Key, attributes.toParcelable(), - onStatus(status -> { - assertTrue("Store status not successful : " + status.resultCode, - status.isSuccess()); - latch.countDown(); - }))); + storeAttributes(l2Key, attributes); doLatched("Did not complete retrieving attributes", latch -> mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved( @@ -185,14 +242,10 @@ public class IpMemoryStoreServiceTest { }))); final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder(); - try { - na.setDnsAddresses(Arrays.asList( - new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); - } catch (UnknownHostException e) { /* Still can't happen */ } + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); final NetworkAttributes attributes2 = na2.build(); - doLatched("Did not complete storing attributes 2", latch -> - mService.storeNetworkAttributes(l2Key, attributes2.toParcelable(), - onStatus(status -> latch.countDown()))); + storeAttributes("Did not complete storing attributes 2", l2Key, attributes2); doLatched("Did not complete retrieving attributes 2", latch -> mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved( @@ -268,7 +321,7 @@ public class IpMemoryStoreServiceTest { public void testPrivateData() { final Blob b = new Blob(); b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 }; - final String l2Key = UUID.randomUUID().toString(); + final String l2Key = FAKE_KEYS[0]; doLatched("Did not complete storing private data", latch -> mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b, onStatus(status -> { @@ -302,12 +355,139 @@ public class IpMemoryStoreServiceTest { } @Test - public void testFindL2Key() { - // TODO : implement this + public void testFindL2Key() throws UnknownHostException { + final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); + na.setGroupHint("hint0"); + storeAttributes(FAKE_KEYS[0], na.build()); + + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")})); + na.setMtu(219); + storeAttributes(FAKE_KEYS[1], na.build()); + na.setMtu(null); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); + na.setGroupHint("hint1"); + storeAttributes(FAKE_KEYS[2], na.build()); + na.setMtu(219); + storeAttributes(FAKE_KEYS[3], na.build()); + na.setMtu(240); + storeAttributes(FAKE_KEYS[4], na.build()); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8")); + storeAttributes(FAKE_KEYS[5], na.build()); + + // Matches key 5 exactly + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[5], key); + }))); + + // MTU matches key 4 but v4 address matches key 5. The latter is stronger. + na.setMtu(240); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[5], key); + }))); + + // Closest to key 3 (indeed, identical) + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setMtu(219); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + }))); + + // Group hint alone must not be strong enough to override the rest + na.setGroupHint("hint0"); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + }))); + + // Still closest to key 3, though confidence is lower + na.setGroupHint("hint1"); + na.setDnsAddresses(null); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[3], key); + }))); + + // But changing the MTU makes this closer to key 4 + na.setMtu(240); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(FAKE_KEYS[4], key); + }))); + + // MTU alone not strong enough to make this group-close + na.setGroupHint(null); + na.setDnsAddresses(null); + na.setAssignedV4Address(null); + doLatched("Did not finish finding L2Key", latch -> + mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertNull(key); + }))); + } + + private void assertNetworksSameness(final String key1, final String key2, final int sameness) { + doLatched("Did not finish evaluating sameness", latch -> + mService.isSameNetwork(key1, key2, onSameResponse((status, answer) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(sameness, answer.getNetworkSameness()); + }))); } @Test - public void testIsSameNetwork() { - // TODO : implement this + public void testIsSameNetwork() throws UnknownHostException { + final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); + na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + na.setGroupHint("hint1"); + na.setMtu(219); + na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6"))); + + storeAttributes(FAKE_KEYS[0], na.build()); + // 0 and 1 have identical attributes + storeAttributes(FAKE_KEYS[1], na.build()); + + // Hopefully only the MTU being different still means it's the same network + na.setMtu(200); + storeAttributes(FAKE_KEYS[2], na.build()); + + // Hopefully different MTU, assigned V4 address and grouphint make a different network, + // even with identical DNS addresses + na.setAssignedV4Address(null); + na.setGroupHint("hint2"); + storeAttributes(FAKE_KEYS[3], na.build()); + + assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[1], SameL3NetworkResponse.NETWORK_SAME); + assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME); + assertNetworksSameness(FAKE_KEYS[1], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME); + assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[3], SameL3NetworkResponse.NETWORK_DIFFERENT); + assertNetworksSameness(FAKE_KEYS[0], "neverInsertedKey", + SameL3NetworkResponse.NETWORK_NEVER_CONNECTED); + + doLatched("Did not finish evaluating sameness", latch -> + mService.isSameNetwork(null, null, onSameResponse((status, answer) -> { + assertFalse("Retrieve network sameness suspiciously successful : " + + status.resultCode, status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + assertNull(answer); + }))); } } diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java new file mode 100644 index 000000000000..fe19eee4594c --- /dev/null +++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.net.ipmemorystore; + +import static org.junit.Assert.assertEquals; + +import android.net.ipmemorystore.NetworkAttributes; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.Arrays; + +/** Unit tests for {@link NetworkAttributes}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NetworkAttributesTest { + private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_"; + private static final float EPSILON = 0.0001f; + + // This is running two tests to make sure the total weight is the sum of all weights. To be + // sure this is not fireproof, but you'd kind of need to do it on purpose to pass. + @Test + public void testTotalWeight() throws IllegalAccessException, UnknownHostException { + // Make sure that TOTAL_WEIGHT is equal to the sum of the fields starting with WEIGHT_ + float sum = 0f; + final Field[] fieldList = NetworkAttributes.class.getDeclaredFields(); + for (final Field field : fieldList) { + if (!field.getName().startsWith(WEIGHT_FIELD_NAME_PREFIX)) continue; + field.setAccessible(true); + sum += (float) field.get(null); + } + assertEquals(sum, NetworkAttributes.TOTAL_WEIGHT, EPSILON); + + // Use directly the constructor with all attributes, and make sure that when compared + // to itself the score is a clean 1.0f. + final NetworkAttributes na = + new NetworkAttributes( + (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}), + "some hint", + Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}), + Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})), + 98); + assertEquals(1.0f, na.getNetworkGroupSamenessConfidence(na), EPSILON); + } +} diff --git a/tests/utils/SleepUtils/AlarmService/Android.mk b/tests/utils/SleepUtils/AlarmService/Android.mk deleted file mode 100644 index 9022f03b7cf2..000000000000 --- a/tests/utils/SleepUtils/AlarmService/Android.mk +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (C) 2013 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests -LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_SRC_FILES += \ - src/com/android/testing/alarmservice/Alarm.aidl -LOCAL_PACKAGE_NAME := SleepUtilsAlarmService -LOCAL_SDK_VERSION := 7 -include $(BUILD_PACKAGE) diff --git a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml b/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml deleted file mode 100644 index 1b6de39c91da..000000000000 --- a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the - Apache License, Version 2.0 (the "License"); you may not use this file except - in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed - under the License is distributed on an "AS IS" BASIS, WITHOUT 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.testing.alarmservice" > - - <uses-sdk android:minSdkVersion="7" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> - - <application android:label="Sleep Utils Alarm Service"> - <service android:name=".AlarmService" - android:label="Sleep Utils Alarm Service" - android:exported="true" - android:enabled="true"> - <intent-filter> - <action android:name="com.android.testing.ALARM_SERVICE" /> - </intent-filter> - </service> - <receiver android:name=".WakeUpCall"> - <intent-filter> - <action android:name="com.android.testing.alarmservice.WAKEUP" /> - </intent-filter> - </receiver> - </application> -</manifest> diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java deleted file mode 100644 index 122d55deb3e5..000000000000 --- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.testing.alarmservice; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.RemoteException; -import android.os.SystemClock; -import android.util.Log; - -import com.android.testing.alarmservice.Alarm.Stub; - -public class AlarmImpl extends Stub { - - private static final String LOG_TAG = AlarmImpl.class.getSimpleName(); - - private Context mContext; - - public AlarmImpl(Context context) { - super(); - mContext = context; - } - - @Override - public int prepare() throws RemoteException { - WakeUpController.getController().getWakeLock().acquire(); - Log.d(LOG_TAG, "AlarmService prepared, wake lock acquired"); - return 0; - } - - @Override - public int setAlarmAndWait(long timeoutMills) throws RemoteException { - // calculate when device should be waken up - long atTime = SystemClock.elapsedRealtime() + timeoutMills; - AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, wakupIntent, 0); - // set alarm, which will be delivered in form of the wakeupIntent - am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi); - Log.d(LOG_TAG, String.format("Alarm set: %d, giving up wake lock", atTime)); - Object lock = WakeUpController.getController().getWakeSync(); - // release wakelock and wait for the lock to be poked from the broadcast receiver - WakeUpController.getController().getWakeLock().release(); - // does not really matter if device enters suspend before we start waiting on lock - synchronized (lock) { - try { - lock.wait(); - } catch (InterruptedException e) { - } - } - Log.d(LOG_TAG, String.format("Alarm triggered, done waiting")); - return 0; - } - - @Override - public int done() throws RemoteException { - WakeUpController.getController().getWakeLock().release(); - return 0; - } - -} diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java deleted file mode 100644 index 576a1cfcfbaf..000000000000 --- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.testing.alarmservice; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; - -public class AlarmService extends Service { - - private AlarmImpl mAlarmImpl = null; - static Context sContext; - - @Override - public void onCreate() { - super.onCreate(); - sContext = this; - } - - @Override - public IBinder onBind(Intent intent) { - return getAlarmImpl(); - } - - private AlarmImpl getAlarmImpl() { - if (mAlarmImpl == null) { - mAlarmImpl = new AlarmImpl(this); - } - return mAlarmImpl; - } - - @Override - public void onDestroy() { - sContext = null; - super.onDestroy(); - } -} diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java deleted file mode 100644 index f4bb4dba4cda..000000000000 --- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.testing.alarmservice; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -/** - * The receiver for the alarm we set - * - */ -public class WakeUpCall extends BroadcastReceiver { - - public static final String WAKEUP_CALL = "com.android.testing.alarmservice.WAKEUP"; - - @Override - public void onReceive(Context context, Intent intent) { - // we acquire wakelock without release because user is supposed to manually release it - WakeUpController.getController().getWakeLock().acquire(); - Object lock = WakeUpController.getController().getWakeSync(); - synchronized (lock) { - // poke the lock so the service side can be woken from waiting on the lock - lock.notifyAll(); - } - } - -} diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java deleted file mode 100644 index 478371f61f36..000000000000 --- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.testing.alarmservice; - -import android.content.Context; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.util.Log; - -/** - * A singleton used for controlling and sharing of states/wakelocks - * - */ -public class WakeUpController { - - private static final String LOG_TAG = WakeUpController.class.getName(); - private static WakeUpController mController = null; - private WakeLock mWakeLock = null; - private Object mWakeSync = new Object(); - - private WakeUpController() { - Log.i(LOG_TAG, "Created instance: 0x" + Integer.toHexString(this.hashCode())); - } - - public static synchronized WakeUpController getController() { - if (mController == null) { - mController = new WakeUpController(); - } - return mController; - } - - public WakeLock getWakeLock() { - if (mWakeLock == null) { - PowerManager pm = - (PowerManager) AlarmService.sContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "testing-alarmservice"); - Log.i(LOG_TAG, "Create wakelock: 0x" + Integer.toHexString(mWakeLock.hashCode())); - } - return mWakeLock; - } - - public Object getWakeSync() { - return mWakeSync; - } -} diff --git a/tests/utils/SleepUtils/Android.mk b/tests/utils/SleepUtils/Android.mk deleted file mode 100644 index 0e65e2255c5b..000000000000 --- a/tests/utils/SleepUtils/Android.mk +++ /dev/null @@ -1,2 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/tests/utils/SleepUtils/README b/tests/utils/SleepUtils/README deleted file mode 100644 index bfe07da4a389..000000000000 --- a/tests/utils/SleepUtils/README +++ /dev/null @@ -1,23 +0,0 @@ -This folder contains utils to properly perform timed suspend and wakeup. - -AlarmService - a service that client can bind to and perform: -1) holding wakelock (singleton to this service) -2) setting alarm for a specified period and releasing the wakelock; service - call will block until alarm has been triggered and the wakelock is held -3) releasing the wakelock - -SleepHelper - a self instrumentation meant as a convenient way to trigger -the service functions from command line. Corresponding to service function -above, supported operations are: -1) holding wakelock -am instrument -w -e command prepare \ - com.android.testing.sleephelper/.SetAlarm - -2) setting alarm and wait til triggered -am instrument -w -e command set_wait \ - -e param <time in ms> com.android.testing.sleephelper/.SetAlarm -Note: for the function to work properly, "-w" parameter is required - -3) releasing wakelock -am instrument -w -e command done \ - com.android.testing.sleephelper/.SetAlarm diff --git a/tests/utils/SleepUtils/SleepHelper/Android.mk b/tests/utils/SleepUtils/SleepHelper/Android.mk deleted file mode 100644 index f8267fd530ed..000000000000 --- a/tests/utils/SleepUtils/SleepHelper/Android.mk +++ /dev/null @@ -1,29 +0,0 @@ -# -# Copyright (C) 2013 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -# Only compile source java files in this apk. -LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_SRC_FILES += \ - ../AlarmService/src/com/android/testing/alarmservice/Alarm.aidl -LOCAL_SDK_VERSION := 7 -LOCAL_PACKAGE_NAME := SleepUtilsSleepHelper - -include $(BUILD_PACKAGE) diff --git a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml b/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml deleted file mode 100644 index 0f1d49185169..000000000000 --- a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the - Apache License, Version 2.0 (the "License"); you may not use this file except - in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed - under the License is distributed on an "AS IS" BASIS, WITHOUT 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.testing.sleephelper"> - - <uses-sdk android:minSdkVersion="7" /> - <instrumentation android:label="Sleep Helper" - android:name="com.android.testing.sleephelper.SetAlarm" - android:targetPackage="com.android.testing.sleephelper" /> - - <application android:label="Sleep Utils Sleep Helper"> - <uses-library android:name="android.test.runner" /> - </application> -</manifest> diff --git a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java b/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java deleted file mode 100644 index b558741da908..000000000000 --- a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.testing.sleephelper; - -import android.app.Activity; -import android.app.Instrumentation; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.Debug; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.testing.alarmservice.Alarm; - -public class SetAlarm extends Instrumentation { - - private static final String COMMAND = "command"; - private static final String PARAM = "param"; - private static final String CMD_PREPARE = "prepare"; - private static final String CMD_SET = "set_wait"; - private static final String CMD_DONE = "done"; - private static final String SERVICE_ACTION = "com.android.testing.ALARM_SERVICE"; - private static final String SERVICE_PKG = "com.android.testing.alarmservice"; - private static final String LOG_TAG = SetAlarm.class.getSimpleName(); - - private Alarm mAlarmService = null; - private Bundle mArgs = null; - private String mCommand = null; - private Intent mServceIntent = new Intent(SERVICE_ACTION).setPackage(SERVICE_PKG); - - private ServiceConnection mConn = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) { - Log.d(LOG_TAG, "Service disconnected."); - mAlarmService = null; - errorFinish("service disconnected"); - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - Log.d(LOG_TAG, "Service connected."); - mAlarmService = Alarm.Stub.asInterface(service); - handleCommands(); - } - }; - - - private void handleCommands() { - if (CMD_PREPARE.equals(mCommand)) { - callPrepare(); - } else if (CMD_SET.equals(mCommand)) { - String paramString = mArgs.getString(PARAM); - if (paramString == null) { - errorFinish("argument expected for alarm time"); - } - long timeout = -1; - try { - timeout = Long.parseLong(paramString); - } catch (NumberFormatException nfe) { - errorFinish("a number argument is expected"); - } - callSetAndWait(timeout); - } else if (CMD_DONE.equals(mCommand)) { - callDone(); - } else { - errorFinish("Unrecognized command: " + mCommand); - } - finish(Activity.RESULT_OK, new Bundle()); - } - - @Override - public void onCreate(Bundle arguments) { - super.onCreate(arguments); - mCommand = arguments.getString(COMMAND); - if ("true".equals(arguments.getString("debug"))) { - Debug.waitForDebugger(); - } - if (mCommand == null) { - errorFinish("No command specified"); - } - mArgs = arguments; - connectToAlarmService(); - } - - private void errorFinish(String msg) { - Bundle ret = new Bundle(); - ret.putString("error", msg); - finish(Activity.RESULT_CANCELED, ret); - } - - private void connectToAlarmService() { - // start the service with an intent, this ensures the service keeps running after unbind - ComponentName cn = getContext().startService(mServceIntent); - if (cn == null) { - errorFinish("failed to start service"); - } - if (!getContext().bindService(mServceIntent, mConn, Context.BIND_AUTO_CREATE)) { - errorFinish("failed to bind service"); - } - } - - private void callPrepare() { - try { - mAlarmService.prepare(); - } catch (RemoteException e) { - errorFinish("RemoteExeption in prepare()"); - } finally { - getContext().unbindService(mConn); - } - } - - private void callDone() { - try { - mAlarmService.done(); - } catch (RemoteException e) { - errorFinish("RemoteExeption in prepare()"); - } finally { - getContext().unbindService(mConn); - } - // explicitly stop the service (started in prepare()) so that the service is now free - // to be reclaimed - getContext().stopService(mServceIntent); - } - - private void callSetAndWait(long timeoutMills) { - try { - mAlarmService.setAlarmAndWait(timeoutMills); - } catch (RemoteException e) { - errorFinish("RemoteExeption in setAlarmAndWait()"); - } finally { - getContext().unbindService(mConn); - } - } -} diff --git a/tests/utils/SleepUtils/WakeLoopService/Android.mk b/tests/utils/SleepUtils/WakeLoopService/Android.mk deleted file mode 100644 index a8a944b215d2..000000000000 --- a/tests/utils/SleepUtils/WakeLoopService/Android.mk +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (C) 2014 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests -LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_PACKAGE_NAME := WakeupLoopService -LOCAL_SDK_VERSION := 7 -include $(BUILD_PACKAGE) diff --git a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml b/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml deleted file mode 100644 index a7028c4f671e..000000000000 --- a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 The Android Open Source Project Licensed under the - Apache License, Version 2.0 (the "License"); you may not use this file except - in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed - under the License is distributed on an "AS IS" BASIS, WITHOUT 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="android.test.wakeuploop" > - - <uses-sdk android:minSdkVersion="7" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - - <application android:label="Auto Wakeup Loop"> - <service android:name=".WakeLoopService" - android:label="Wakup Loop Service" - android:exported="true" - android:enabled="true"> - <intent-filter> - <action android:name="android.test.wakeuploop.WAKEUP_SERVICE" /> - </intent-filter> - </service> - <receiver android:name=".WakeUpCall"> - <intent-filter> - <action android:name="android.test.wakeuploop.WAKEUP" /> - </intent-filter> - </receiver> - </application> -</manifest> diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java deleted file mode 100644 index c8b075b4a90f..000000000000 --- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.test.wakeuploop; - -import android.util.Log; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; - -public class FileUtil { - - private static FileUtil sInst = null; - private static DateFormat sDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); - - private FileUtil() {}; - - public static FileUtil get() { - if (sInst == null) { - sInst = new FileUtil(); - } - return sInst; - } - - public void writeDateToFile(File file) { - try { - FileOutputStream fos = new FileOutputStream(file); - fos.write(sDateFormat.format(new Date()).getBytes()); - fos.write('\n'); - fos.flush(); - fos.close(); - } catch (IOException ioe) { - Log.e("FileUtil", "exception writing date to file", ioe); - } - } -} diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java deleted file mode 100644 index 4f557b8786f4..000000000000 --- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.test.wakeuploop; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Environment; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.SystemClock; -import android.util.Log; - -import java.io.File; - -public class WakeLoopService extends Service { - - private static final String LOG_TAG = WakeLoopService.class.getSimpleName(); - static final String WAKEUP_INTERNAL = "WAKEUP_INTERVAL"; - static final String MAX_LOOP = "MAX_LOOP"; - static final String STOP_CALLBACK = "STOP_CALLBACK"; - static final String THIS_LOOP = "THIS_LOOP"; - static final int MSG_STOP_SERVICE = 0xd1ed1e; - - private final Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - if (msg.what == MSG_STOP_SERVICE) { - stopSelf(); - } else { - super.handleMessage(msg); - } - }; - }; - - @Override - public IBinder onBind(Intent intent) { - // no binding, just start via intent - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // get wakeup interval from intent - long wakeupInterval = intent.getLongExtra(WAKEUP_INTERNAL, 0); - long maxLoop = intent.getLongExtra(MAX_LOOP, 0); - - if (wakeupInterval == 0) { - // stop and error - Log.e(LOG_TAG, "No wakeup interval specified, not starting the service"); - stopSelf(); - return START_NOT_STICKY; - } - FileUtil.get().writeDateToFile(new File(Environment.getExternalStorageDirectory(), - "wakeup-loop-start.txt")); - Log.d(LOG_TAG, String.format("WakeLoop: STARTED interval = %d, total loop = %d", - wakeupInterval, maxLoop)); - // calculate when device should be waken up - long atTime = SystemClock.elapsedRealtime() + wakeupInterval; - AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE); - Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL) - .putExtra(WAKEUP_INTERNAL, wakeupInterval) - .putExtra(MAX_LOOP, maxLoop) - .putExtra(THIS_LOOP, 0L) - .putExtra(STOP_CALLBACK, new Messenger(mHandler)); - PendingIntent pi = PendingIntent.getBroadcast(this, 0, wakupIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - // set alarm, which will be delivered in form of the wakeupIntent - am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi); - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - Log.d(LOG_TAG, "WakeLoop: STOPPED"); - // cancel alarms first - Intent intent = new Intent(WakeUpCall.WAKEUP_CALL) - .putExtra(WakeUpCall.CANCEL, "true"); - sendBroadcast(intent); - } -} diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java deleted file mode 100644 index 8347bbf0c1c7..000000000000 --- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.test.wakeuploop; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Environment; -import android.os.Message; -import android.os.Messenger; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.os.RemoteException; -import android.os.SystemClock; -import android.util.Log; - -import java.io.File; - -/** - * The receiver for the alarm we set - * - */ -public class WakeUpCall extends BroadcastReceiver { - private static final String LOG_TAG = WakeUpCall.class.getSimpleName(); - static final String WAKEUP_CALL = "android.test.wakeuploop.WAKEUP"; - static final String CANCEL = "CANCEL"; - - @Override - public void onReceive(Context context, Intent intent) { - AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - boolean cancel = intent.hasExtra(CANCEL); - if (!cancel) { - long maxLoop = intent.getLongExtra(WakeLoopService.MAX_LOOP, 0); - long wakeupInterval = intent.getLongExtra(WakeLoopService.WAKEUP_INTERNAL, 0); - long thisLoop = intent.getLongExtra(WakeLoopService.THIS_LOOP, -1); - Log.d(LOG_TAG, String.format("incoming: interval = %d, max loop = %d, this loop = %d", - wakeupInterval, maxLoop, thisLoop)); - if (thisLoop == -1) { - Log.e(LOG_TAG, "no valid loop count received, trying to stop service"); - stopService(intent); - return; - } - if (wakeupInterval == 0) { - Log.e(LOG_TAG, "no valid wakeup interval received, trying to stop service"); - stopService(intent); - return; - } - thisLoop++; - Log.d(LOG_TAG, String.format("WakeLoop - iteration %d of %d", thisLoop, maxLoop)); - if (thisLoop == maxLoop) { - // when maxLoop is 0, we loop forever, so not checking that case - // here - Log.d(LOG_TAG, "reached max loop count, stopping service"); - stopService(intent); - return; - } - screenOn(context); - FileUtil.get().writeDateToFile( - new File(Environment.getExternalStorageDirectory(), "wakeup-loop.txt")); - // calculate when device should be waken up - long atTime = SystemClock.elapsedRealtime() + wakeupInterval; - intent.putExtra(WakeLoopService.THIS_LOOP, thisLoop); - PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - // set alarm, which will be delivered in form of the wakeupIntent - am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi); - } else { - // cancel alarms - Log.d(LOG_TAG, "cancelling future alarms on request"); - am.cancel(PendingIntent.getBroadcast(context, 0, intent, 0)); - } - } - - private void stopService(Intent i) { - Messenger msgr = i.getParcelableExtra(WakeLoopService.STOP_CALLBACK); - if (msgr == null) { - Log.e(LOG_TAG, "no stop service callback found, cannot stop"); - } else { - Message msg = new Message(); - msg.what = WakeLoopService.MSG_STOP_SERVICE; - try { - msgr.send(msg); - } catch (RemoteException e) { - Log.e(LOG_TAG, "ignored remoted exception while attempting to stop service", e); - } - } - } - - private void screenOn(Context context) { - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - @SuppressWarnings("deprecation") - WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | - PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG); - wl.acquire(500); - } -} diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java new file mode 100644 index 000000000000..d0350aff5ef5 --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.test.filters; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; + +/** + * JUnit filter to select tests. + * + * <p>This filter selects tests specified by package name, class name, and method name. With this + * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the + * restriction that prevents using the package and the class options can be mitigated. + * + * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2. \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * Note that the ending {@code .} in package name is mandatory. + * + * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option. + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA,package2.ClassB \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * <p><b>Select out test methods from Java classes:</b> + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * + * Those options can be used simultaneously. For example + * <pre> + * adb shell am instrument -w \ + * -e filter com.android.test.filters.SelectTest \ + * -e selectTest package1.,package2.classA,package3.ClassB#methodZ \ + * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner + * </pre> + * will select out all tests in package1, all tests in classA, and ClassB#methodZ test. + * + * <p>Note that when this option is specified with either {@code -e package} or {@code -e class} + * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage}, + * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected + * with this SelectTest option. + * + * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely + * logs to logcat while parsing {@code -e selectTest} option. + */ +public class SelectTest extends Filter { + + private static final String TAG = SelectTest.class.getSimpleName(); + + @VisibleForTesting + static final String OPTION_SELECT_TEST = "selectTest"; + @VisibleForTesting + static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose"; + + private static final String ARGUMENT_ITEM_SEPARATOR = ","; + private static final String PACKAGE_NAME_SEPARATOR = "."; + private static final String METHOD_SEPARATOR = "#"; + + @Nullable + private final PackageSet mPackageSet; + + /** + * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}. + * + * @param testArgs instrumentation test arguments. + */ + public SelectTest(@NonNull Bundle testArgs) { + mPackageSet = parseSelectTest(testArgs); + } + + @Override + public boolean shouldRun(Description description) { + if (mPackageSet == null) { + // Accept all tests because this filter is disabled. + return true; + } + String testClassName = description.getClassName(); + String testMethodName = description.getMethodName(); + return mPackageSet.accept(testClassName, testMethodName); + } + + @Override + public String describe() { + return OPTION_SELECT_TEST + "=" + mPackageSet; + } + + /** + * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}. + * + * <p>This method is intended to be used at constructor of extended {@link Filter} class. + * + * @param testArgs instrumentation test arguments. + * @param selectTests array of class name to be selected to run. + * @return modified instrumentation test arguments. + */ + @NonNull + protected static Bundle addSelectTest( + @NonNull Bundle testArgs, @NonNull String... selectTests) { + if (selectTests.length == 0) { + return testArgs; + } + testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests))); + return testArgs; + } + + /** + * Parse {@code -e selectTest} argument. + * @param testArgs instrumentation test arguments. + * @return {@link PackageSet} that will filter tests. Returns {@code null} when no + * {@code -e selectTest} option is specified, thus this filter gets disabled. + */ + @Nullable + private static PackageSet parseSelectTest(Bundle testArgs) { + final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST); + if (selectTestArgs == null) { + Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified"); + return null; + } + + final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE)); + final PackageSet packageSet = new PackageSet(verbose); + for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) { + packageSet.add(selectTestArg); + } + return packageSet; + } + + private static String getPackageName(String selectTestArg) { + int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR); + return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos); + } + + @Nullable + private static String getClassName(String selectTestArg) { + if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) { + return null; + } + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos); + } + + @Nullable + private static String getMethodName(String selectTestArg) { + int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR); + return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1); + } + + /** Package level filter */ + private static class PackageSet { + private final boolean mVerbose; + /** + * Java package name to {@link ClassSet} map. To represent package filtering, a map value + * can be {@code null}. + */ + private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>(); + + PackageSet(boolean verbose) { + mVerbose = verbose; + } + + void add(final String selectTestArg) { + final String packageName = getPackageName(selectTestArg); + final String className = getClassName(selectTestArg); + + if (className == null) { + ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering. + if (mVerbose) { + logging("Select package " + selectTestArg, classSet != null, + "; supersede " + classSet); + } + return; + } + + ClassSet classSet = mClassSetMap.get(packageName); + if (classSet == null) { + if (mClassSetMap.containsKey(packageName)) { + if (mVerbose) { + logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true, + " ignore " + selectTestArg); + } + return; + } + classSet = new ClassSet(mVerbose); + mClassSetMap.put(packageName, classSet); + } + classSet.add(selectTestArg); + } + + boolean accept(String className, @Nullable String methodName) { + String packageName = getPackageName(className); + if (!mClassSetMap.containsKey(packageName)) { + return false; + } + ClassSet classSet = mClassSetMap.get(packageName); + return classSet == null || classSet.accept(className, methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String packageName : mClassSetMap.keySet()) { + ClassSet classSet = mClassSetMap.get(packageName); + joiner.add(classSet == null + ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString()); + } + return joiner.toString(); + } + } + + /** Class level filter */ + private static class ClassSet { + private final boolean mVerbose; + /** + * Java class name to set of method names map. To represent class filtering, a map value + * can be {@code null}. + */ + private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>(); + + ClassSet(boolean verbose) { + mVerbose = verbose; + } + + void add(String selectTestArg) { + final String className = getClassName(selectTestArg); + final String methodName = getMethodName(selectTestArg); + + if (methodName == null) { + Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering. + if (mVerbose) { + logging("Select class " + selectTestArg, methodSet != null, + "; supersede " + toString(className, methodSet)); + } + return; + } + + Set<String> methodSet = mMethodSetMap.get(className); + if (methodSet == null) { + if (mMethodSetMap.containsKey(className)) { + if (mVerbose) { + logging("Select class " + className, true, "; ignore " + selectTestArg); + } + return; + } + methodSet = new LinkedHashSet<>(); + mMethodSetMap.put(className, methodSet); + } + + methodSet.add(methodName); + if (mVerbose) { + logging("Select method " + selectTestArg, false, null); + } + } + + boolean accept(String className, @Nullable String methodName) { + if (!mMethodSetMap.containsKey(className)) { + return false; + } + Set<String> methodSet = mMethodSetMap.get(className); + return methodName == null || methodSet == null || methodSet.contains(methodName); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String className : mMethodSetMap.keySet()) { + joiner.add(toString(className, mMethodSetMap.get(className))); + } + return joiner.toString(); + } + + private static String toString(String className, @Nullable Set<String> methodSet) { + if (methodSet == null) { + return className; + } + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String methodName : methodSet) { + joiner.add(className + METHOD_SEPARATOR + methodName); + } + return joiner.toString(); + } + } + + private static void logging(String infoLog, boolean isWarning, String warningLog) { + if (isWarning) { + Log.w(TAG, infoLog + warningLog); + } else { + Log.i(TAG, infoLog); + } + } + + private static String join(Collection<String> list) { + StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR); + for (String text : list) { + joiner.add(text); + } + return joiner.toString(); + } +} diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java new file mode 100644 index 000000000000..a6b0102a511f --- /dev/null +++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.test.filters; + +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST; +import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Bundle; +import android.util.ArraySet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; + +public class SelectTestTests { + + private static final String PACKAGE_A = "packageA."; + private static final String PACKAGE_B = "packageB."; + private static final String PACKAGE_C = "packageC."; + private static final String CLASS_A1 = PACKAGE_A + "Class1"; + private static final String CLASS_A2 = PACKAGE_A + "Class2"; + private static final String CLASS_B3 = PACKAGE_B + "Class3"; + private static final String CLASS_B4 = PACKAGE_B + "Class4"; + private static final String CLASS_C5 = PACKAGE_C + "Class5"; + private static final String CLASS_C6 = PACKAGE_C + "Class6"; + private static final String METHOD_A1K = CLASS_A1 + "#methodK"; + private static final String METHOD_A1L = CLASS_A1 + "#methodL"; + private static final String METHOD_A2M = CLASS_A2 + "#methodM"; + private static final String METHOD_A2N = CLASS_A2 + "#methodN"; + private static final String METHOD_B3P = CLASS_B3 + "#methodP"; + private static final String METHOD_B3Q = CLASS_B3 + "#methodQ"; + private static final String METHOD_B4R = CLASS_B4 + "#methodR"; + private static final String METHOD_B4S = CLASS_B4 + "#methodS"; + private static final String METHOD_C5W = CLASS_C5 + "#methodW"; + private static final String METHOD_C5X = CLASS_C5 + "#methodX"; + private static final String METHOD_C6Y = CLASS_C6 + "#methodY"; + private static final String METHOD_C6Z = CLASS_C6 + "#methodZ"; + + private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K); + private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L); + private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M); + private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N); + private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P); + private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q); + private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R); + private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S); + private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W); + private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X); + private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y); + private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z); + private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L); + private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N); + private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q); + private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S); + private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X); + private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z); + private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2); + private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4); + private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6); + private static final Set<Description> TEST_ALL = + merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C); + + private SelectTestBuilder mBuilder; + + @Before + public void setUp() { + mBuilder = new SelectTestBuilder(); + } + + private static class SelectTestBuilder { + private final Bundle mTestArgs = new Bundle(); + + Filter build() { + mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString()); + return new SelectTest(mTestArgs); + } + + SelectTestBuilder withSelectTest(String... selectTestArgs) { + putTestOption(OPTION_SELECT_TEST, selectTestArgs); + return this; + } + + private void putTestOption(String option, String... args) { + if (args.length > 0) { + StringJoiner joiner = new StringJoiner(","); + for (String arg : args) { + joiner.add(arg); + } + mTestArgs.putString(option, joiner.toString()); + } + } + } + + private static Set<Description> methodTest(String testName) { + int methodSep = testName.indexOf("#"); + String className = testName.substring(0, methodSep); + String methodName = testName.substring(methodSep + 1); + final Set<Description> tests = new ArraySet<>(); + tests.add(Description.createSuiteDescription(className)); + tests.add(Description.createTestDescription(className, methodName)); + return Collections.unmodifiableSet(tests); + } + + @SafeVarargs + private static Set<Description> merge(Set<Description>... testSpecs) { + final Set<Description> merged = new LinkedHashSet<>(); + for (Set<Description> testSet : testSpecs) { + merged.addAll(testSet); + } + return Collections.unmodifiableSet(merged); + } + + @SafeVarargs + private static void acceptTests(Filter filter, Set<Description>... testSpecs) { + final Set<Description> accepts = merge(testSpecs); + for (Description test : TEST_ALL) { + if (accepts.contains(test)) { + assertTrue("accept " + test, filter.shouldRun(test)); + } else { + assertFalse("reject " + test, filter.shouldRun(test)); + } + } + } + + @Test + public void testFilterDisabled() { + final Filter filter = mBuilder.build(); + acceptTests(filter, TEST_ALL); + } + + @Test + public void testSelectPackage() { + final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } + + @Test + public void testSelectClass() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3); + } + + @Test + public void testSelectMethod() { + final Filter filter = mBuilder + .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndPackage() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5); + } + + @Test + public void testSelectMethodAndPackage() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W); + } + + @Test + public void testSelectMethodAndClass() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build(); + acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P); + } + + @Test + public void testSelectClassAndSamePackage() { + final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, PACKAGE_A, + CLASS_C5, CLASS_C6, PACKAGE_C).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndSameClass() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, METHOD_A1L, METHOD_A2M, CLASS_A1, + CLASS_B3, METHOD_B3P, METHOD_B3Q, METHOD_B4R).build(); + acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R); + } + + @Test + public void testSelectMethodAndSamePackage() { + final Filter filter = mBuilder.withSelectTest(METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C); + } + + @Test + public void testSelectMethodAndClassAndPackage() { + final Filter filter = mBuilder.withSelectTest( + METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A, + PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build(); + acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B); + } +} diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 92beb4eb7ce4..0512bdc5bf72 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -469,16 +469,12 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, return false; } - // Read the file as a string - char buffer_2[data->size()]; - memcpy(&buffer_2, data->data(), data->size()); - StringPiece content(buffer_2, data->size()); - BigBuffer crunched_png_buffer(4096); io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer); // Ensure that we only keep the chunks we care about if we end up // using the original PNG instead of the crunched one. + const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size()); PngChunkFilter png_chunk_filter(content); std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter); if (!image) { diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 8c00cebf6c59..1fd45e72f1e8 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -2084,6 +2084,8 @@ public class WifiManager { public static final int WIFI_FEATURE_LOW_LATENCY = 0x40000000; // Low Latency modes /** @hide */ public static final int WIFI_FEATURE_DPP = 0x80000000; // DPP (Easy-Connect) + /** @hide */ + public static final long WIFI_FEATURE_P2P_RAND_MAC = 0x100000000L; // Random P2P MAC private long getSupportedFeatures() { try { @@ -3717,10 +3719,8 @@ public class WifiManager { * @param SSID, in the format of WifiConfiguration's SSID. * @hide */ - @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.NETWORK_SETTINGS, - android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK }) public void disableEphemeralNetwork(String SSID) { diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java index aa1669ee6d94..52ee7423c8fb 100644 --- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java @@ -28,6 +28,7 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.util.Objects; @@ -50,12 +51,24 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements */ private final int mOriginalRequestorUid; + /** + * The package name of the app that requested a specific wifi network using + * {@link WifiNetworkSpecifier}. + * + * Will only be filled when the device connects to a wifi network as a result of a + * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device + * auto-connected to a wifi network. + */ + private final String mOriginalRequestorPackageName; + public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration, - int originalRequestorUid) { + int originalRequestorUid, + @Nullable String originalRequestorPackageName) { checkNotNull(wifiConfiguration); mWifiConfiguration = wifiConfiguration; mOriginalRequestorUid = originalRequestorUid; + mOriginalRequestorPackageName = originalRequestorPackageName; } /** @@ -67,7 +80,9 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) { WifiConfiguration wifiConfiguration = in.readParcelable(null); int originalRequestorUid = in.readInt(); - return new WifiNetworkAgentSpecifier(wifiConfiguration, originalRequestorUid); + String originalRequestorPackageName = in.readString(); + return new WifiNetworkAgentSpecifier( + wifiConfiguration, originalRequestorUid, originalRequestorPackageName); } @Override @@ -85,6 +100,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelable(mWifiConfiguration, flags); dest.writeInt(mOriginalRequestorUid); + dest.writeString(mOriginalRequestorPackageName); } @Override @@ -137,6 +153,9 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements if (ns.requestorUid != this.mOriginalRequestorUid) { return false; } + if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) { + return false; + } return true; } @@ -146,7 +165,8 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements mWifiConfiguration.SSID, mWifiConfiguration.BSSID, mWifiConfiguration.allowedKeyManagement, - mOriginalRequestorUid); + mOriginalRequestorUid, + mOriginalRequestorPackageName); } @Override @@ -162,7 +182,9 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements && Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID) && Objects.equals(this.mWifiConfiguration.allowedKeyManagement, lhs.mWifiConfiguration.allowedKeyManagement) - && mOriginalRequestorUid == lhs.mOriginalRequestorUid; + && mOriginalRequestorUid == lhs.mOriginalRequestorUid + && TextUtils.equals(mOriginalRequestorPackageName, + lhs.mOriginalRequestorPackageName); } @Override @@ -172,6 +194,7 @@ public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements .append(", SSID=").append(mWifiConfiguration.SSID) .append(", BSSID=").append(mWifiConfiguration.BSSID) .append(", mOriginalRequestorUid=").append(mOriginalRequestorUid) + .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName) .append("]"); return sb.toString(); } diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java index ecee5ff0161d..42d43934e229 100644 --- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java +++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityThread; import android.net.MacAddress; import android.net.NetworkRequest; import android.net.NetworkSpecifier; @@ -586,7 +587,8 @@ public class WifiNetworkConfigBuilder { mSsidPatternMatcher, mBssidPatternMatcher, buildWifiConfiguration(), - Process.myUid()); + Process.myUid(), + ActivityThread.currentApplication().getApplicationContext().getOpPackageName()); } /** @@ -648,7 +650,8 @@ public class WifiNetworkConfigBuilder { buildWifiConfiguration(), mIsAppInteractionRequired, mIsUserInteractionRequired, - Process.myUid()); + Process.myUid(), + ActivityThread.currentApplication().getApplicationContext().getOpPackageName()); } } diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java index 6e4eeef4dd55..a5f4675f3956 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java @@ -25,6 +25,7 @@ import android.net.NetworkSpecifier; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; +import android.text.TextUtils; import android.util.Pair; import java.util.Objects; @@ -63,18 +64,25 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc */ public final int requestorUid; + /** + * The package name of the app initializing this network specifier. + */ + public final String requestorPackageName; + public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher, @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher, @NonNull WifiConfiguration wifiConfiguration, - int requestorUid) { + int requestorUid, @NonNull String requestorPackageName) { checkNotNull(ssidPatternMatcher); checkNotNull(bssidPatternMatcher); checkNotNull(wifiConfiguration); + checkNotNull(requestorPackageName); this.ssidPatternMatcher = ssidPatternMatcher; this.bssidPatternMatcher = bssidPatternMatcher; this.wifiConfiguration = wifiConfiguration; this.requestorUid = requestorUid; + this.requestorPackageName = requestorPackageName; } public static final Creator<WifiNetworkSpecifier> CREATOR = @@ -88,8 +96,9 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc Pair.create(baseAddress, mask); WifiConfiguration wifiConfiguration = in.readParcelable(null); int requestorUid = in.readInt(); + String requestorPackageName = in.readString(); return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher, - wifiConfiguration, requestorUid); + wifiConfiguration, requestorUid, requestorPackageName); } @Override @@ -110,6 +119,7 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc dest.writeParcelable(bssidPatternMatcher.second, flags); dest.writeParcelable(wifiConfiguration, flags); dest.writeInt(requestorUid); + dest.writeString(requestorPackageName); } @Override @@ -136,7 +146,7 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc ssidPatternMatcher.getType(), bssidPatternMatcher, wifiConfiguration.allowedKeyManagement, - requestorUid); + requestorUid, requestorPackageName); } @Override @@ -156,7 +166,8 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc lhs.bssidPatternMatcher) && Objects.equals(this.wifiConfiguration.allowedKeyManagement, lhs.wifiConfiguration.allowedKeyManagement) - && requestorUid == lhs.requestorUid; + && requestorUid == lhs.requestorUid + && TextUtils.equals(requestorPackageName, lhs.requestorPackageName); } @Override @@ -168,6 +179,7 @@ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parc .append(", SSID=").append(wifiConfiguration.SSID) .append(", BSSID=").append(wifiConfiguration.BSSID) .append(", requestorUid=").append(requestorUid) + .append(", requestorPackageName=").append(requestorPackageName) .append("]") .toString(); } diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index 3c90eb763e81..6b05dfc1f774 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -18,8 +18,10 @@ package android.net.wifi; import static com.android.internal.util.Preconditions.checkNotNull; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.util.List; import java.util.Objects; @@ -58,17 +60,25 @@ public final class WifiNetworkSuggestion implements Parcelable { */ public final int suggestorUid; + /** + * The package name of the process initializing this network suggestion. + * @hide + */ + public final String suggestorPackageName; + /** @hide */ - public WifiNetworkSuggestion(WifiConfiguration wifiConfiguration, + public WifiNetworkSuggestion(@NonNull WifiConfiguration wifiConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, - int suggestorUid) { + int suggestorUid, @NonNull String suggestorPackageName) { checkNotNull(wifiConfiguration); + checkNotNull(suggestorPackageName); this.wifiConfiguration = wifiConfiguration; this.isAppInteractionRequired = isAppInteractionRequired; this.isUserInteractionRequired = isUserInteractionRequired; this.suggestorUid = suggestorUid; + this.suggestorPackageName = suggestorPackageName; } public static final Creator<WifiNetworkSuggestion> CREATOR = @@ -79,7 +89,8 @@ public final class WifiNetworkSuggestion implements Parcelable { in.readParcelable(null), // wifiConfiguration in.readBoolean(), // isAppInteractionRequired in.readBoolean(), // isUserInteractionRequired - in.readInt() // suggestorUid + in.readInt(), // suggestorUid + in.readString() // suggestorPackageName ); } @@ -100,12 +111,13 @@ public final class WifiNetworkSuggestion implements Parcelable { dest.writeBoolean(isAppInteractionRequired); dest.writeBoolean(isUserInteractionRequired); dest.writeInt(suggestorUid); + dest.writeString(suggestorPackageName); } @Override public int hashCode() { return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, - wifiConfiguration.allowedKeyManagement, suggestorUid); + wifiConfiguration.allowedKeyManagement, suggestorUid, suggestorPackageName); } /** @@ -124,7 +136,8 @@ public final class WifiNetworkSuggestion implements Parcelable { && Objects.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) && Objects.equals(this.wifiConfiguration.allowedKeyManagement, lhs.wifiConfiguration.allowedKeyManagement) - && suggestorUid == lhs.suggestorUid; + && suggestorUid == lhs.suggestorUid + && TextUtils.equals(suggestorPackageName, lhs.suggestorPackageName); } @Override @@ -135,6 +148,7 @@ public final class WifiNetworkSuggestion implements Parcelable { .append(", isAppInteractionRequired=").append(isAppInteractionRequired) .append(", isUserInteractionRequired=").append(isUserInteractionRequired) .append(", suggestorUid=").append(suggestorUid) + .append(", suggestorPackageName=").append(suggestorPackageName) .append("]"); return sb.toString(); } diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java index 1ee874a9698b..1d499b6654d0 100644 --- a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java +++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java @@ -65,9 +65,9 @@ public abstract class ProvisioningCallback { public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7; /** - * The reason code for provisioning failure due to invalid server url. + * The reason code for provisioning failure due to invalid web url format for an OSU web page. */ - public static final int OSU_FAILURE_INVALID_SERVER_URL = 8; + public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8; /** * The reason code for provisioning failure when a command received is not the expected command diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java index 2258e4dd79e5..e6eece85cb19 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java @@ -38,6 +38,8 @@ import org.junit.Test; public class WifiNetworkAgentSpecifierTest { private static final int TEST_UID = 5; private static final int TEST_UID_1 = 8; + private static final String TEST_PACKAGE = "com.test"; + private static final String TEST_PACKAGE_1 = "com.test.1"; private static final String TEST_SSID = "Test123"; private static final String TEST_SSID_PATTERN = "Test"; private static final String TEST_SSID_1 = "456test"; @@ -104,14 +106,14 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( wifiConfiguration1, - TEST_UID); + TEST_UID, TEST_PACKAGE); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( wifiConfiguration2, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertFalse(specifier2.equals(specifier1)); } @@ -128,14 +130,14 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( wifiConfiguration1, - TEST_UID); + TEST_UID, TEST_PACKAGE); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.SSID = TEST_SSID_1; WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( wifiConfiguration2, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertFalse(specifier2.equals(specifier1)); } @@ -152,14 +154,14 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkAgentSpecifier specifier1 = new WifiNetworkAgentSpecifier( wifiConfiguration1, - TEST_UID); + TEST_UID, TEST_PACKAGE); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1); wifiConfiguration2.BSSID = TEST_BSSID_1; WifiNetworkAgentSpecifier specifier2 = new WifiNetworkAgentSpecifier( wifiConfiguration2, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertFalse(specifier2.equals(specifier1)); } @@ -214,7 +216,7 @@ public class WifiNetworkAgentSpecifierTest { ssidPattern, bssidPattern, wificonfigurationNetworkSpecifier, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -243,7 +245,7 @@ public class WifiNetworkAgentSpecifierTest { ssidPattern, bssidPattern, wificonfigurationNetworkSpecifier, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -272,7 +274,7 @@ public class WifiNetworkAgentSpecifierTest { ssidPattern, bssidPattern, wificonfigurationNetworkSpecifier, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -292,7 +294,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( wifiConfigurationNetworkAgent, - TEST_UID); + TEST_UID, TEST_PACKAGE); PatternMatcher ssidPattern = new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); @@ -305,7 +307,7 @@ public class WifiNetworkAgentSpecifierTest { ssidPattern, bssidPattern, wificonfigurationNetworkSpecifier, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -325,7 +327,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( wifiConfigurationNetworkAgent, - TEST_UID); + TEST_UID, TEST_PACKAGE); PatternMatcher ssidPattern = new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB); @@ -339,7 +341,7 @@ public class WifiNetworkAgentSpecifierTest { ssidPattern, bssidPattern, wificonfigurationNetworkSpecifier, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -359,7 +361,7 @@ public class WifiNetworkAgentSpecifierTest { WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier = new WifiNetworkAgentSpecifier( wifiConfigurationNetworkAgent, - TEST_UID); + TEST_UID, TEST_PACKAGE); PatternMatcher ssidPattern = new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX); @@ -373,7 +375,7 @@ public class WifiNetworkAgentSpecifierTest { ssidPattern, bssidPattern, wificonfigurationNetworkSpecifier, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -401,7 +403,7 @@ public class WifiNetworkAgentSpecifierTest { ssidPattern, bssidPattern, wificonfigurationNetworkSpecifier, - TEST_UID); + TEST_UID, TEST_PACKAGE); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -430,7 +432,7 @@ public class WifiNetworkAgentSpecifierTest { ssidPattern, bssidPattern, wificonfigurationNetworkSpecifier, - TEST_UID_1); + TEST_UID_1, TEST_PACKAGE_1); assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier)); assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier)); @@ -446,7 +448,8 @@ public class WifiNetworkAgentSpecifierTest { } private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() { - return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID); + return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID, + TEST_PACKAGE); } } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java index 2a8df8dba1d2..fce247f729e0 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java @@ -38,6 +38,7 @@ import org.junit.Test; @SmallTest public class WifiNetworkSpecifierTest { private static final int TEST_UID = 5; + private static final String TEST_PACKAGE_NAME = "com.test"; private static final String TEST_SSID = "Test123"; private static final String TEST_BSSID_OUI_BASE_ADDRESS = "12:12:12:00:00:00"; private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00"; @@ -56,7 +57,7 @@ public class WifiNetworkSpecifierTest { Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); Parcel parcelW = Parcel.obtain(); specifier.writeToParcel(parcelW, 0); @@ -88,7 +89,7 @@ public class WifiNetworkSpecifierTest { Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); assertTrue(specifier.satisfiedBy(null)); assertTrue(specifier.satisfiedBy(new MatchAllNetworkSpecifier())); @@ -111,14 +112,14 @@ public class WifiNetworkSpecifierTest { Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); assertTrue(specifier2.satisfiedBy(specifier1)); } @@ -140,7 +141,7 @@ public class WifiNetworkSpecifierTest { Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration1, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); WifiConfiguration wifiConfiguration2 = new WifiConfiguration(); wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); @@ -149,7 +150,7 @@ public class WifiNetworkSpecifierTest { Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration2, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); assertFalse(specifier2.satisfiedBy(specifier1)); } @@ -171,14 +172,14 @@ public class WifiNetworkSpecifierTest { Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); assertFalse(specifier2.satisfiedBy(specifier1)); } @@ -200,13 +201,42 @@ public class WifiNetworkSpecifierTest { Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), MacAddress.fromString(TEST_BSSID_OUI_MASK)), wifiConfiguration, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS), wifiConfiguration, - TEST_UID); + TEST_UID, TEST_PACKAGE_NAME); + + assertFalse(specifier2.satisfiedBy(specifier1)); + } + + /** + * Validate NetworkSpecifier matching. + * a) Create network specifier 1 for WPA_PSK network + * b) Create network specifier 2 with different package name . + * c) Ensure that the specifier 2 is not satisfied by specifier 1. + */ + @Test + public void testWifiNetworkSpecifierDoesNotSatisfyWhenPackageNameDifferent() { + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY; + + WifiNetworkSpecifier specifier1 = + new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), + Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), + MacAddress.fromString(TEST_BSSID_OUI_MASK)), + wifiConfiguration, + TEST_UID, TEST_PACKAGE_NAME); + + WifiNetworkSpecifier specifier2 = + new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL), + Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS), + MacAddress.fromString(TEST_BSSID_OUI_MASK)), + wifiConfiguration, + TEST_UID, TEST_PACKAGE_NAME + "blah"); assertFalse(specifier2.satisfiedBy(specifier1)); } diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java index 31f501ff85c1..5f76055d8640 100644 --- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java +++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java @@ -29,6 +29,10 @@ import org.junit.Test; */ @SmallTest public class WifiNetworkSuggestionTest { + private static final int TEST_UID = 45677; + private static final int TEST_UID_OTHER = 45673; + private static final String TEST_PACKAGE_NAME = "com.test.packagename"; + private static final String TEST_PACKAGE_NAME_OTHER = "com.test.packagenameother"; private static final String TEST_SSID = "\"Test123\""; private static final String TEST_BSSID = "12:12:12:12:12:12"; private static final String TEST_SSID_1 = "\"Test1234\""; @@ -43,7 +47,7 @@ public class WifiNetworkSuggestionTest { configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, false, true, 0); + new WifiNetworkSuggestion(configuration, false, true, TEST_UID, TEST_PACKAGE_NAME); Parcel parcelW = Parcel.obtain(); suggestion.writeToParcel(parcelW, 0); @@ -77,14 +81,16 @@ public class WifiNetworkSuggestionTest { configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, true, false, 0); + new WifiNetworkSuggestion(configuration, true, false, TEST_UID, + TEST_PACKAGE_NAME); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID; configuration1.BSSID = TEST_BSSID; configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration1, false, true, 0); + new WifiNetworkSuggestion(configuration1, false, true, TEST_UID, + TEST_PACKAGE_NAME); assertEquals(suggestion, suggestion1); } @@ -99,13 +105,15 @@ public class WifiNetworkSuggestionTest { configuration.SSID = TEST_SSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, false, false, 0); + new WifiNetworkSuggestion(configuration, false, false, TEST_UID, + TEST_PACKAGE_NAME); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID_1; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration1, false, false, 0); + new WifiNetworkSuggestion(configuration1, false, false, TEST_UID, + TEST_PACKAGE_NAME); assertNotEquals(suggestion, suggestion1); } @@ -121,13 +129,15 @@ public class WifiNetworkSuggestionTest { configuration.BSSID = TEST_BSSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, false, false, 0); + new WifiNetworkSuggestion(configuration, false, false, TEST_UID, + TEST_PACKAGE_NAME); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID; configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration1, false, false, 0); + new WifiNetworkSuggestion(configuration1, false, false, TEST_UID, + TEST_PACKAGE_NAME); assertNotEquals(suggestion, suggestion1); } @@ -142,13 +152,15 @@ public class WifiNetworkSuggestionTest { configuration.SSID = TEST_SSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, false, false, 0); + new WifiNetworkSuggestion(configuration, false, false, TEST_UID, + TEST_PACKAGE_NAME); WifiConfiguration configuration1 = new WifiConfiguration(); configuration1.SSID = TEST_SSID; configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration1, false, false, 0); + new WifiNetworkSuggestion(configuration1, false, false, TEST_UID, + TEST_PACKAGE_NAME); assertNotEquals(suggestion, suggestion1); } @@ -163,10 +175,31 @@ public class WifiNetworkSuggestionTest { configuration.SSID = TEST_SSID; configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); WifiNetworkSuggestion suggestion = - new WifiNetworkSuggestion(configuration, false, false, 0); + new WifiNetworkSuggestion(configuration, false, false, TEST_UID, + TEST_PACKAGE_NAME); WifiNetworkSuggestion suggestion1 = - new WifiNetworkSuggestion(configuration, false, false, 1); + new WifiNetworkSuggestion(configuration, false, false, TEST_UID_OTHER, + TEST_PACKAGE_NAME); + + assertNotEquals(suggestion, suggestion1); + } + + /** + * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same + * SSID, BSSID and key mgmt, but different package name. + */ + @Test + public void testWifiNetworkSuggestionEqualsFailsWhenPackageNameIsDifferent() { + WifiConfiguration configuration = new WifiConfiguration(); + configuration.SSID = TEST_SSID; + configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + WifiNetworkSuggestion suggestion = + new WifiNetworkSuggestion(configuration, false, false, TEST_UID, TEST_PACKAGE_NAME); + + WifiNetworkSuggestion suggestion1 = + new WifiNetworkSuggestion(configuration, false, false, TEST_UID, + TEST_PACKAGE_NAME_OTHER); assertNotEquals(suggestion, suggestion1); } |