diff options
403 files changed, 22761 insertions, 5881 deletions
diff --git a/Android.bp b/Android.bp index 15befaef84ae..980aa045dc79 100644 --- a/Android.bp +++ b/Android.bp @@ -104,6 +104,11 @@ java_defaults { "core/java/android/app/backup/IRestoreObserver.aidl", "core/java/android/app/backup/IRestoreSession.aidl", "core/java/android/app/backup/ISelectBackupTransportCallback.aidl", + "core/java/android/app/contentsuggestions/IClassificationsCallback.aidl", + "core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl", + "core/java/android/app/contentsuggestions/ISelectionsCallback.aidl", + "core/java/android/app/prediction/IPredictionCallback.aidl", + "core/java/android/app/prediction/IPredictionManager.aidl", "core/java/android/app/role/IOnRoleHoldersChangedListener.aidl", "core/java/android/app/role/IRoleManager.aidl", "core/java/android/app/role/IRoleManagerCallback.aidl", @@ -273,6 +278,7 @@ java_defaults { "core/java/android/rolecontrollerservice/IRoleControllerService.aidl", ":keystore_aidl", "core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl", + "core/java/android/service/appprediction/IPredictionService.aidl", "core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl", "core/java/android/service/autofill/augmented/IFillCallback.aidl", "core/java/android/service/autofill/IAutoFillService.aidl", @@ -282,6 +288,7 @@ java_defaults { "core/java/android/service/carrier/ICarrierService.aidl", "core/java/android/service/carrier/ICarrierMessagingCallback.aidl", "core/java/android/service/carrier/ICarrierMessagingService.aidl", + "core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl", "core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl", "core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl", "core/java/android/service/euicc/IEraseSubscriptionsCallback.aidl", @@ -593,7 +600,7 @@ java_defaults { "telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl", "telephony/java/com/android/internal/telephony/ISms.aidl", "telephony/java/com/android/internal/telephony/ISub.aidl", - "telephony/java/com/android/internal/telephony/IAns.aidl", + "telephony/java/com/android/internal/telephony/IOns.aidl", "telephony/java/com/android/internal/telephony/ITelephony.aidl", "telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl", "telephony/java/com/android/internal/telephony/IWapPushManager.aidl", @@ -731,6 +738,7 @@ java_defaults { "android.hardware.contexthub-V1.0-java", "android.hardware.health-V1.0-java-constants", "android.hardware.thermal-V1.0-java-constants", + "android.hardware.thermal-V1.0-java", "android.hardware.thermal-V1.1-java", "android.hardware.thermal-V2.0-java", "android.hardware.tv.input-V1.0-java-constants", @@ -745,6 +753,7 @@ java_defaults { "android.hardware.radio-V1.3-java", "android.hardware.radio-V1.4-java", "android.hardware.usb.gadget-V1.0-java", + "networkstack-aidl-interfaces-java", "netd_aidl_interface-java", "devicepolicyprotosnano", ], @@ -872,11 +881,16 @@ aidl_interface { name: "networkstack-aidl-interfaces", local_include_dir: "core/java", srcs: [ + "core/java/android/net/INetworkMonitor.aidl", + "core/java/android/net/INetworkMonitorCallbacks.aidl", + "core/java/android/net/IIpMemoryStore.aidl", "core/java/android/net/INetworkStackConnector.aidl", "core/java/android/net/INetworkStackStatusCallback.aidl", + "core/java/android/net/PrivateDnsConfigParcel.aidl", "core/java/android/net/dhcp/DhcpServingParamsParcel.aidl", "core/java/android/net/dhcp/IDhcpServer.aidl", "core/java/android/net/dhcp/IDhcpServerCallbacks.aidl", + "core/java/android/net/ipmemorystore/**/*.aidl", ], api_dir: "aidl/networkstack", } diff --git a/api/current.txt b/api/current.txt index 7e0c33cfb657..8ee83daeb1dc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7379,7 +7379,9 @@ package android.app.role { method public boolean isRoleHeld(java.lang.String); field public static final java.lang.String ROLE_BROWSER = "android.app.role.BROWSER"; field public static final java.lang.String ROLE_DIALER = "android.app.role.DIALER"; + field public static final java.lang.String ROLE_EMERGENCY = "android.app.role.EMERGENCY"; field public static final java.lang.String ROLE_GALLERY = "android.app.role.GALLERY"; + field public static final java.lang.String ROLE_HOME = "android.app.role.HOME"; field public static final java.lang.String ROLE_MUSIC = "android.app.role.MUSIC"; field public static final java.lang.String ROLE_SMS = "android.app.role.SMS"; } @@ -26076,6 +26078,27 @@ package android.media { field public static final int URI_COLUMN_INDEX = 2; // 0x2 } + public final class Session2Command implements android.os.Parcelable { + ctor public Session2Command(int); + ctor public Session2Command(java.lang.String, android.os.Bundle); + method public int describeContents(); + method public int getCommandCode(); + method public java.lang.String getCustomCommand(); + method public android.os.Bundle getExtras(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0 + field public static final android.os.Parcelable.Creator<android.media.Session2Command> CREATOR; + field public static final int RESULT_ERROR_UNKNOWN_ERROR = -1; // 0xffffffff + field public static final int RESULT_INFO_SKIPPED = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + } + + public static final class Session2Command.Result { + ctor public Session2Command.Result(int, android.os.Bundle); + method public int getResultCode(); + method public android.os.Bundle getResultData(); + } + public class SoundPool { ctor public deprecated SoundPool(int, int, int); method public final void autoPause(); @@ -27121,6 +27144,21 @@ package android.media.projection { package android.media.session { + public final class ControllerCallbackLink implements android.os.Parcelable { + method public int describeContents(); + method public android.os.IBinder getBinder(); + method public void notifyEvent(java.lang.String, android.os.Bundle); + method public void notifyExtrasChanged(android.os.Bundle); + method public void notifyMetadataChanged(android.media.MediaMetadata); + method public void notifyPlaybackStateChanged(android.media.session.PlaybackState); + method public void notifyQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>); + method public void notifyQueueTitleChanged(java.lang.CharSequence); + method public void notifySessionDestroyed(); + method public void notifyVolumeInfoChanged(android.media.session.MediaController.PlaybackInfo); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.media.session.ControllerCallbackLink> CREATOR; + } + public final class MediaController { ctor public MediaController(android.content.Context, android.media.session.MediaSession.Token); method public void adjustVolume(int, int); @@ -27355,6 +27393,36 @@ package android.media.session { method public android.media.session.PlaybackState.CustomAction.Builder setExtras(android.os.Bundle); } + public final class SessionCallbackLink implements android.os.Parcelable { + method public int describeContents(); + method public android.os.IBinder getBinder(); + method public void notifyAdjustVolume(java.lang.String, int, int, android.media.session.ControllerCallbackLink, int); + method public void notifyCommand(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle, android.os.ResultReceiver); + method public void notifyCustomAction(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle); + method public void notifyFastForward(java.lang.String, int, int, android.media.session.ControllerCallbackLink); + method public void notifyMediaButton(java.lang.String, int, int, android.content.Intent, int, android.os.ResultReceiver); + method public void notifyMediaButtonFromController(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent); + method public void notifyNext(java.lang.String, int, int, android.media.session.ControllerCallbackLink); + method public void notifyPause(java.lang.String, int, int, android.media.session.ControllerCallbackLink); + method public void notifyPlay(java.lang.String, int, int, android.media.session.ControllerCallbackLink); + method public void notifyPlayFromMediaId(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle); + method public void notifyPlayFromSearch(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle); + method public void notifyPlayFromUri(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle); + method public void notifyPrepare(java.lang.String, int, int, android.media.session.ControllerCallbackLink); + method public void notifyPrepareFromMediaId(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle); + method public void notifyPrepareFromSearch(java.lang.String, int, int, android.media.session.ControllerCallbackLink, java.lang.String, android.os.Bundle); + method public void notifyPrepareFromUri(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle); + method public void notifyPrevious(java.lang.String, int, int, android.media.session.ControllerCallbackLink); + method public void notifyRate(java.lang.String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating); + method public void notifyRewind(java.lang.String, int, int, android.media.session.ControllerCallbackLink); + method public void notifySeekTo(java.lang.String, int, int, android.media.session.ControllerCallbackLink, long); + method public void notifySetVolumeTo(java.lang.String, int, int, android.media.session.ControllerCallbackLink, int); + method public void notifySkipToTrack(java.lang.String, int, int, android.media.session.ControllerCallbackLink, long); + method public void notifyStop(java.lang.String, int, int, android.media.session.ControllerCallbackLink); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.media.session.SessionCallbackLink> CREATOR; + } + } package android.media.tv { @@ -29682,9 +29750,6 @@ package android.net.wifi { method public void setReferenceCounted(boolean); } - public static abstract class WifiManager.NetworkSuggestionsStatusCode implements java.lang.annotation.Annotation { - } - public class WifiManager.WifiLock { method public void acquire(); method public boolean isHeld(); @@ -34374,6 +34439,8 @@ package android.os { method public java.util.ArrayList<java.lang.String> createStringArrayList(); method public <T> T[] createTypedArray(android.os.Parcelable.Creator<T>); method public <T> java.util.ArrayList<T> createTypedArrayList(android.os.Parcelable.Creator<T>); + method public <T extends android.os.Parcelable> android.util.ArrayMap<java.lang.String, T> createTypedArrayMap(android.os.Parcelable.Creator<T>); + method public <T extends android.os.Parcelable> android.util.SparseArray<T> createTypedSparseArray(android.os.Parcelable.Creator<T>); method public int dataAvail(); method public int dataCapacity(); method public int dataPosition(); @@ -34415,7 +34482,7 @@ package android.os { method public java.io.Serializable readSerializable(); method public android.util.Size readSize(); method public android.util.SizeF readSizeF(); - method public android.util.SparseArray readSparseArray(java.lang.ClassLoader); + method public <T> android.util.SparseArray<T> readSparseArray(java.lang.ClassLoader); method public android.util.SparseBooleanArray readSparseBooleanArray(); method public java.lang.String readString(); method public void readStringArray(java.lang.String[]); @@ -34461,7 +34528,7 @@ package android.os { method public void writeSerializable(java.io.Serializable); method public void writeSize(android.util.Size); method public void writeSizeF(android.util.SizeF); - method public void writeSparseArray(android.util.SparseArray<java.lang.Object>); + method public <T> void writeSparseArray(android.util.SparseArray<T>); method public void writeSparseBooleanArray(android.util.SparseBooleanArray); method public void writeString(java.lang.String); method public void writeStringArray(java.lang.String[]); @@ -34469,8 +34536,10 @@ package android.os { method public void writeStrongBinder(android.os.IBinder); method public void writeStrongInterface(android.os.IInterface); method public <T extends android.os.Parcelable> void writeTypedArray(T[], int); + method public <T extends android.os.Parcelable> void writeTypedArrayMap(android.util.ArrayMap<java.lang.String, T>, int); method public <T extends android.os.Parcelable> void writeTypedList(java.util.List<T>); method public <T extends android.os.Parcelable> void writeTypedObject(T, int); + method public <T extends android.os.Parcelable> void writeTypedSparseArray(android.util.SparseArray<T>, int); method public void writeValue(java.lang.Object); field public static final android.os.Parcelable.Creator<java.lang.String> STRING_CREATOR; } @@ -42662,6 +42731,7 @@ package android.telecom { method public static java.lang.String capabilitiesToString(int); method public android.telecom.PhoneAccountHandle getAccountHandle(); method public int getCallCapabilities(); + method public int getCallDirection(); method public android.telecom.CallIdentification getCallIdentification(); method public int getCallProperties(); method public java.lang.String getCallerDisplayName(); @@ -42698,6 +42768,9 @@ package android.telecom { field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000 field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int DIRECTION_INCOMING = 0; // 0x0 + field public static final int DIRECTION_OUTGOING = 1; // 0x1 + field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 @@ -44853,6 +44926,7 @@ package android.telephony.emergency { method public java.util.List<java.lang.Integer> getEmergencyNumberSources(); method public java.util.List<java.lang.Integer> getEmergencyServiceCategories(); method public int getEmergencyServiceCategoryBitmask(); + method public java.util.List<java.lang.String> getEmergencyUrns(); method public java.lang.String getMnc(); method public java.lang.String getNumber(); method public boolean isFromSources(int); @@ -44906,6 +44980,7 @@ package android.telephony.euicc { method public boolean isEnabled(); method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException; method public void switchToSubscription(int, android.app.PendingIntent); + method public void updateSubscriptionNickname(int, java.lang.String, android.app.PendingIntent); field public static final java.lang.String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; field public static final java.lang.String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE"; field public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2; // 0x2 @@ -49118,6 +49193,7 @@ package android.view { method public float getAxisValue(int); method public float getAxisValue(int, int); method public int getButtonState(); + method public int getClassification(); method public int getDeviceId(); method public long getDownTime(); method public int getEdgeFlags(); @@ -49269,6 +49345,9 @@ package android.view { field public static final int BUTTON_STYLUS_PRIMARY = 32; // 0x20 field public static final int BUTTON_STYLUS_SECONDARY = 64; // 0x40 field public static final int BUTTON_TERTIARY = 4; // 0x4 + field public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1; // 0x1 + field public static final int CLASSIFICATION_DEEP_PRESS = 2; // 0x2 + field public static final int CLASSIFICATION_NONE = 0; // 0x0 field public static final android.os.Parcelable.Creator<android.view.MotionEvent> CREATOR; field public static final int EDGE_BOTTOM = 2; // 0x2 field public static final int EDGE_LEFT = 4; // 0x4 @@ -52319,6 +52398,8 @@ package android.view.contentcapture { method public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext); method public final void destroy(); method public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId(); + method public android.view.autofill.AutofillId newAutofillId(android.view.autofill.AutofillId, int); + method public final android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int); method public final void notifyViewAppeared(android.view.ViewStructure); method public final void notifyViewDisappeared(android.view.autofill.AutofillId); method public final void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int); diff --git a/api/system-current.txt b/api/system-current.txt index 79648a9c98e4..a4b39782433f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -86,11 +86,13 @@ package android { field public static final java.lang.String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY"; field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final java.lang.String MANAGE_APP_OPS_RESTRICTIONS = "android.permission.MANAGE_APP_OPS_RESTRICTIONS"; + field public static final java.lang.String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS"; field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS"; field public static final java.lang.String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL"; field public static final java.lang.String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE"; field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES"; field public static final java.lang.String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE"; + field public static final java.lang.String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS"; field public static final java.lang.String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; @@ -284,10 +286,11 @@ package android.app { } public class AppOpsManager { - method public java.util.List<android.app.AppOpsManager.HistoricalPackageOps> getAllHistoricPackagesOps(java.lang.String[], long, long); - method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long); + method public void getHistoricalOps(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>); method public static java.lang.String[] getOpStrs(); - method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]); + method public deprecated java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]); + method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, java.lang.String...); + method public java.util.List<android.app.AppOpsManager.PackageOps> getPackagesForOps(java.lang.String[]); method public static int opToDefaultMode(java.lang.String); method public static java.lang.String opToPermission(java.lang.String); method public void setMode(java.lang.String, int, java.lang.String, int); @@ -343,7 +346,7 @@ package android.app { field public static final int UID_STATE_TOP = 1; // 0x1 } - public static final class AppOpsManager.HistoricalOpEntry implements android.os.Parcelable { + public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable { method public int describeContents(); method public long getAccessCount(int); method public long getAccessDuration(int); @@ -353,23 +356,43 @@ package android.app { method public long getForegroundAccessCount(); method public long getForegroundAccessDuration(); method public long getForegroundRejectCount(); - method public java.lang.String getOp(); + method public java.lang.String getOpName(); method public long getRejectCount(int); method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOpEntry> CREATOR; + field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOp> CREATOR; + } + + public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable { + method public int describeContents(); + method public long getBeginTimeMillis(); + method public long getEndTimeMillis(); + method public int getUidCount(); + method public android.app.AppOpsManager.HistoricalUidOps getUidOps(int); + method public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR; } public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable { method public int describeContents(); - method public android.app.AppOpsManager.HistoricalOpEntry getEntry(java.lang.String); - method public android.app.AppOpsManager.HistoricalOpEntry getEntryAt(int); - method public int getEntryCount(); + method public android.app.AppOpsManager.HistoricalOp getOp(java.lang.String); + method public android.app.AppOpsManager.HistoricalOp getOpAt(int); + method public int getOpCount(); method public java.lang.String getPackageName(); - method public int getUid(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalPackageOps> CREATOR; } + public static final class AppOpsManager.HistoricalUidOps implements android.os.Parcelable { + method public int describeContents(); + method public int getPackageCount(); + method public android.app.AppOpsManager.HistoricalPackageOps getPackageOps(java.lang.String); + method public android.app.AppOpsManager.HistoricalPackageOps getPackageOpsAt(int); + method public int getUid(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR; + } + public static final class AppOpsManager.OpEntry implements android.os.Parcelable { method public int describeContents(); method public int getDuration(); @@ -848,6 +871,73 @@ package android.app.backup { } +package android.app.contentsuggestions { + + public final class ClassificationsRequest implements android.os.Parcelable { + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR; + } + + public static final class ClassificationsRequest.Builder { + ctor public ClassificationsRequest.Builder(java.util.List<android.app.contentsuggestions.ContentSelection>); + method public android.app.contentsuggestions.ClassificationsRequest build(); + method public android.app.contentsuggestions.ClassificationsRequest.Builder setExtras(android.os.Bundle); + } + + public final class ContentClassification implements android.os.Parcelable { + ctor public ContentClassification(java.lang.String, android.os.Bundle); + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public java.lang.String getId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR; + } + + public final class ContentSelection implements android.os.Parcelable { + ctor public ContentSelection(java.lang.String, android.os.Bundle); + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public java.lang.String getId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR; + } + + public final class ContentSuggestionsManager { + method public void classifyContentSelections(android.app.contentsuggestions.ClassificationsRequest, java.util.concurrent.Executor, android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback); + method public void notifyInteraction(java.lang.String, android.os.Bundle); + method public void provideContextImage(int, android.os.Bundle); + method public void suggestContentSelections(android.app.contentsuggestions.SelectionsRequest, java.util.concurrent.Executor, android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback); + } + + public static abstract interface ContentSuggestionsManager.ClassificationsCallback { + method public abstract void onContentClassificationsAvailable(int, java.util.List<android.app.contentsuggestions.ContentClassification>); + } + + public static abstract interface ContentSuggestionsManager.SelectionsCallback { + method public abstract void onContentSelectionsAvailable(int, java.util.List<android.app.contentsuggestions.ContentSelection>); + } + + public final class SelectionsRequest implements android.os.Parcelable { + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public android.graphics.Point getInterestPoint(); + method public int getTaskId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR; + } + + public static final class SelectionsRequest.Builder { + ctor public SelectionsRequest.Builder(int); + method public android.app.contentsuggestions.SelectionsRequest build(); + method public android.app.contentsuggestions.SelectionsRequest.Builder setExtras(android.os.Bundle); + method public android.app.contentsuggestions.SelectionsRequest.Builder setInterestPoint(android.graphics.Point); + } + +} + package android.app.job { public abstract class JobScheduler { @@ -856,6 +946,87 @@ package android.app.job { } +package android.app.prediction { + + public final class AppPredictionContext implements android.os.Parcelable { + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public java.lang.String getPackageName(); + method public int getPredictedTargetCount(); + method public java.lang.String getUiSurface(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.prediction.AppPredictionContext> CREATOR; + } + + public static final class AppPredictionContext.Builder { + method public android.app.prediction.AppPredictionContext build(); + method public android.app.prediction.AppPredictionContext.Builder setExtras(android.os.Bundle); + method public android.app.prediction.AppPredictionContext.Builder setPredictedTargetCount(int); + method public android.app.prediction.AppPredictionContext.Builder setUiSurface(java.lang.String); + } + + public final class AppPredictionManager { + method public android.app.prediction.AppPredictor createAppPredictionSession(android.app.prediction.AppPredictionContext); + } + + public final class AppPredictionSessionId implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.prediction.AppPredictionSessionId> CREATOR; + } + + public final class AppPredictor { + method public void destroy(); + method public void notifyAppTargetEvent(android.app.prediction.AppTargetEvent); + method public void notifyLocationShown(java.lang.String, java.util.List<android.app.prediction.AppTargetId>); + method public void registerPredictionUpdates(java.util.concurrent.Executor, android.app.prediction.AppPredictor.Callback); + method public void requestPredictionUpdate(); + method public void sortTargets(java.util.List<android.app.prediction.AppTarget>, java.util.concurrent.Executor, java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>); + method public void unregisterPredictionUpdates(android.app.prediction.AppPredictor.Callback); + } + + public static abstract interface AppPredictor.Callback { + method public abstract void onTargetsAvailable(java.util.List<android.app.prediction.AppTarget>); + } + + public final class AppTarget implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getClassName(); + method public android.app.prediction.AppTargetId getId(); + method public java.lang.String getPackageName(); + method public int getRank(); + method public android.content.pm.ShortcutInfo getShortcutInfo(); + method public android.os.UserHandle getUser(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.prediction.AppTarget> CREATOR; + } + + public final class AppTargetEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getAction(); + method public java.lang.String getLaunchLocation(); + method public android.app.prediction.AppTarget getTarget(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int ACTION_DISMISS = 2; // 0x2 + field public static final int ACTION_LAUNCH = 1; // 0x1 + field public static final int ACTION_PIN = 3; // 0x3 + field public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetEvent> CREATOR; + } + + public static final class AppTargetEvent.Builder { + ctor public AppTargetEvent.Builder(android.app.prediction.AppTarget, int); + method public android.app.prediction.AppTargetEvent build(); + method public android.app.prediction.AppTargetEvent.Builder setLaunchLocation(java.lang.String); + } + + public final class AppTargetId implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.prediction.AppTargetId> CREATOR; + } + +} + package android.app.role { public abstract interface OnRoleHoldersChangedListener { @@ -1042,7 +1213,9 @@ package android.content { method public abstract void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle); method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); method public void startActivityAsUser(android.content.Intent, android.os.UserHandle); + field public static final java.lang.String APP_PREDICTION_SERVICE = "app_prediction"; field public static final java.lang.String BACKUP_SERVICE = "backup"; + field public static final java.lang.String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final java.lang.String CONTEXTHUB_SERVICE = "contexthub"; field public static final java.lang.String EUICC_CARD_SERVICE = "euicc_card"; field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control"; @@ -1110,6 +1283,7 @@ package android.content { field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID"; field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; + field public static final java.lang.String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME"; field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON"; field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; @@ -1128,6 +1302,7 @@ package android.content { package android.content.pm { public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { + method public boolean isEncryptionAware(); method public boolean isInstantApp(); field public java.lang.String credentialProtectedDataDir; field public int targetSandboxVersion; @@ -1548,6 +1723,10 @@ package android.hardware.display { field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessCorrection> CREATOR; } + public final class ColorDisplayManager { + method public boolean setSaturationLevel(int); + } + public final class DisplayManager { method public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats(); method public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration(); @@ -1556,7 +1735,7 @@ package android.hardware.display { method public android.util.Pair<float[], float[]> getMinimumBrightnessCurve(); method public android.graphics.Point getStableDisplaySize(); method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); - method public void setSaturationLevel(float); + method public deprecated void setSaturationLevel(float); } } @@ -2954,7 +3133,7 @@ package android.location { method public void setLocationControllerExtraPackage(java.lang.String); method public void setLocationControllerExtraPackageEnabled(boolean); method public void setLocationEnabledForUser(boolean, android.os.UserHandle); - method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle); + method public deprecated boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle); method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback); } @@ -3214,6 +3393,22 @@ package android.media.audiopolicy { package android.media.session { + public final class ControllerCallbackLink implements android.os.Parcelable { + ctor public ControllerCallbackLink(android.media.session.ControllerCallbackLink.CallbackStub); + } + + public static abstract class ControllerCallbackLink.CallbackStub { + ctor public ControllerCallbackLink.CallbackStub(); + method public void onEvent(java.lang.String, android.os.Bundle); + method public void onExtrasChanged(android.os.Bundle); + method public void onMetadataChanged(android.media.MediaMetadata); + method public void onPlaybackStateChanged(android.media.session.PlaybackState); + method public void onQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>); + method public void onQueueTitleChanged(java.lang.CharSequence); + method public void onSessionDestroyed(); + method public void onVolumeInfoChanged(android.media.session.MediaController.PlaybackInfo); + } + public final class MediaSessionManager { method public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler); method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler); @@ -5146,6 +5341,24 @@ package android.security.keystore.recovery { } +package android.service.appprediction { + + public abstract class AppPredictionService extends android.app.Service { + ctor public AppPredictionService(); + method public abstract void onAppTargetEvent(android.app.prediction.AppPredictionSessionId, android.app.prediction.AppTargetEvent); + method public final android.os.IBinder onBind(android.content.Intent); + method public void onCreatePredictionSession(android.app.prediction.AppPredictionContext, android.app.prediction.AppPredictionSessionId); + method public void onDestroyPredictionSession(android.app.prediction.AppPredictionSessionId); + method public abstract void onLocationShown(android.app.prediction.AppPredictionSessionId, java.lang.String, java.util.List<android.app.prediction.AppTargetId>); + method public abstract void onRequestPredictionUpdate(android.app.prediction.AppPredictionSessionId); + method public abstract void onSortAppTargets(android.app.prediction.AppPredictionSessionId, java.util.List<android.app.prediction.AppTarget>, android.os.CancellationSignal, java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>); + method public void onStartPredictionUpdates(); + method public void onStopPredictionUpdates(); + method public final void updatePredictions(android.app.prediction.AppPredictionSessionId, java.util.List<android.app.prediction.AppTarget>); + } + +} + package android.service.autofill { public abstract class AutofillFieldClassificationService extends android.app.Service { @@ -5256,6 +5469,7 @@ package android.service.contentcapture { method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId); method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId); method public void onDisconnected(); + method public void onUserDataRemovalRequest(android.view.contentcapture.UserDataRemovalRequest); method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean); method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>); method public final void setPackageContentCaptureEnabled(java.lang.String, boolean); @@ -5273,6 +5487,19 @@ package android.service.contentcapture { } +package android.service.contentsuggestions { + + public abstract class ContentSuggestionsService extends android.app.Service { + ctor public ContentSuggestionsService(); + method public abstract void classifyContentSelections(android.app.contentsuggestions.ClassificationsRequest, android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback); + method public abstract void notifyInteraction(java.lang.String, android.os.Bundle); + method public abstract void processContextImage(int, android.graphics.Bitmap, android.os.Bundle); + method public abstract void suggestContentSelections(android.app.contentsuggestions.SelectionsRequest, android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.contentsuggestions.ContentSuggestionsService"; + } + +} + package android.service.euicc { public final class DownloadSubscriptionResult implements android.os.Parcelable { @@ -6117,6 +6344,18 @@ package android.telephony { field public static final int WIFI_LOST = 59; // 0x3b } + public final class LteVopsSupportInfo implements android.os.Parcelable { + ctor public LteVopsSupportInfo(int, int); + method public int describeContents(); + method public int getEmcBearerSupport(); + method public int getVopsSupport(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.LteVopsSupportInfo> CREATOR; + field public static final int LTE_STATUS_NOT_AVAILABLE = 1; // 0x1 + field public static final int LTE_STATUS_NOT_SUPPORTED = 3; // 0x3 + field public static final int LTE_STATUS_SUPPORTED = 2; // 0x2 + } + public class MbmsDownloadSession implements java.lang.AutoCloseable { field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_ACTION = "android.telephony.action.EmbmsDownload"; } @@ -6383,6 +6622,7 @@ package android.telephony { public class SubscriptionInfo implements android.os.Parcelable { method public java.util.List<android.telephony.UiccAccessRule> getAccessRules(); method public int getCardId(); + method public int getProfileClass(); } public class SubscriptionManager { @@ -6392,6 +6632,11 @@ package android.telephony { method public void setDefaultDataSubId(int); method public void setDefaultSmsSubId(int); field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI; + field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff + field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2 + field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1 + field public static final int PROFILE_CLASS_TESTING = 0; // 0x0 + field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff field public static final android.net.Uri VT_ENABLED_CONTENT_URI; field public static final android.net.Uri WFC_ENABLED_CONTENT_URI; field public static final android.net.Uri WFC_MODE_CONTENT_URI; @@ -6853,6 +7098,7 @@ package android.telephony.ims { method public static int getCallTypeFromVideoState(int); method public int getEmergencyCallRouting(); method public int getEmergencyServiceCategories(); + method public java.util.List<java.lang.String> getEmergencyUrns(); method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile(); method public int getRestrictCause(); method public int getServiceType(); @@ -6867,6 +7113,7 @@ package android.telephony.ims { method public void setCallRestrictCause(int); method public void setEmergencyCallRouting(int); method public void setEmergencyServiceCategories(int); + method public void setEmergencyUrns(java.util.List<java.lang.String>); method public void updateCallExtras(android.telephony.ims.ImsCallProfile); method public void updateCallType(android.telephony.ims.ImsCallProfile); method public void updateMediaProfile(android.telephony.ims.ImsCallProfile); diff --git a/api/test-current.txt b/api/test-current.txt index ae3c1e027f51..575875dddad7 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -89,18 +89,26 @@ package android.app { } public class AppOpsManager { - method public java.util.List<android.app.AppOpsManager.HistoricalPackageOps> getAllHistoricPackagesOps(java.lang.String[], long, long); - method public android.app.AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int, java.lang.String, java.lang.String[], long, long); + method public void addHistoricalOps(android.app.AppOpsManager.HistoricalOps); + method public void clearHistory(); + method public void getHistoricalOps(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>); + method public void getHistoricalOpsFromDiskRaw(int, java.lang.String, java.lang.String[], long, long, java.util.concurrent.Executor, java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>); method public static int getNumOps(); method public static java.lang.String[] getOpStrs(); method public boolean isOperationActive(int, int, java.lang.String); + method public void offsetHistory(long); method public static java.lang.String opToPermission(int); method public static int permissionToOpCode(java.lang.String); + method public void resetHistoryParameters(); + method public void setHistoryParameters(int, long, int); method public void setMode(int, int, java.lang.String, int); method public void setUidMode(java.lang.String, int, int); method public void startWatchingActive(int[], android.app.AppOpsManager.OnOpActiveChangedListener); method public void stopWatchingActive(android.app.AppOpsManager.OnOpActiveChangedListener); method public static int strOpToOp(java.lang.String); + field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0 + field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1 + field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2 field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; @@ -144,11 +152,18 @@ package android.app { field public static final java.lang.String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms"; field public static final java.lang.String OPSTR_WRITE_SMS = "android:write_sms"; field public static final java.lang.String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper"; + field public static final int OP_COARSE_LOCATION = 0; // 0x0 field public static final int OP_RECORD_AUDIO = 27; // 0x1b field public static final int OP_SYSTEM_ALERT_WINDOW = 24; // 0x18 + field public static final int UID_STATE_BACKGROUND = 4; // 0x4 + field public static final int UID_STATE_CACHED = 5; // 0x5 + field public static final int UID_STATE_FOREGROUND = 3; // 0x3 + field public static final int UID_STATE_FOREGROUND_SERVICE = 2; // 0x2 + field public static final int UID_STATE_PERSISTENT = 0; // 0x0 + field public static final int UID_STATE_TOP = 1; // 0x1 } - public static final class AppOpsManager.HistoricalOpEntry implements android.os.Parcelable { + public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable { method public int describeContents(); method public long getAccessCount(int); method public long getAccessDuration(int); @@ -158,23 +173,48 @@ package android.app { method public long getForegroundAccessCount(); method public long getForegroundAccessDuration(); method public long getForegroundRejectCount(); - method public java.lang.String getOp(); + method public java.lang.String getOpName(); method public long getRejectCount(int); method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOpEntry> CREATOR; + field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOp> CREATOR; + } + + public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable { + ctor public AppOpsManager.HistoricalOps(long, long); + method public int describeContents(); + method public long getBeginTimeMillis(); + method public long getEndTimeMillis(); + method public int getUidCount(); + method public android.app.AppOpsManager.HistoricalUidOps getUidOps(int); + method public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(int); + method public void increaseAccessCount(int, int, java.lang.String, int, long); + method public void increaseAccessDuration(int, int, java.lang.String, int, long); + method public void increaseRejectCount(int, int, java.lang.String, int, long); + method public void offsetBeginAndEndTime(long); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR; } public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable { method public int describeContents(); - method public android.app.AppOpsManager.HistoricalOpEntry getEntry(java.lang.String); - method public android.app.AppOpsManager.HistoricalOpEntry getEntryAt(int); - method public int getEntryCount(); + method public android.app.AppOpsManager.HistoricalOp getOp(java.lang.String); + method public android.app.AppOpsManager.HistoricalOp getOpAt(int); + method public int getOpCount(); method public java.lang.String getPackageName(); - method public int getUid(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalPackageOps> CREATOR; } + public static final class AppOpsManager.HistoricalUidOps implements android.os.Parcelable { + method public int describeContents(); + method public int getPackageCount(); + method public android.app.AppOpsManager.HistoricalPackageOps getPackageOps(java.lang.String); + method public android.app.AppOpsManager.HistoricalPackageOps getPackageOpsAt(int); + method public int getUid(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalUidOps> CREATOR; + } + public static abstract interface AppOpsManager.OnOpActiveChangedListener { method public abstract void onOpActiveChanged(int, int, java.lang.String, boolean); } diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp index 49187475ccdb..4f88127f8af1 100644 --- a/cmds/idmap2/idmap2/Scan.cpp +++ b/cmds/idmap2/idmap2/Scan.cpp @@ -39,6 +39,17 @@ using android::idmap2::ZipFile; using android::idmap2::utils::FindFiles; namespace { + +struct InputOverlay { + bool operator<(InputOverlay const& rhs) const { + return priority < rhs.priority || (priority == rhs.priority && apk_path < rhs.apk_path); + } + + std::string apk_path; // NOLINT(misc-non-private-member-variables-in-classes) + std::string idmap_path; // NOLINT(misc-non-private-member-variables-in-classes) + int priority; // NOLINT(misc-non-private-member-variables-in-classes) +}; + std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::string>& dirs, bool recursive, std::ostream& out_error) { const auto predicate = [](unsigned char type, const std::string& path) -> bool { @@ -58,6 +69,7 @@ std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::st } return std::make_unique<std::vector<std::string>>(paths.cbegin(), paths.cend()); } + } // namespace bool Scan(const std::vector<std::string>& args, std::ostream& out_error) { @@ -87,7 +99,7 @@ bool Scan(const std::vector<std::string>& args, std::ostream& out_error) { return false; } - std::vector<std::string> interesting_apks; + std::vector<InputOverlay> interesting_apks; for (const std::string& path : *apk_paths) { std::unique_ptr<const ZipFile> zip = ZipFile::Open(path); if (!zip) { @@ -132,27 +144,29 @@ bool Scan(const std::vector<std::string>& args, std::ostream& out_error) { continue; } + // Sort the static overlays in ascending priority order + std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, path); + InputOverlay input{path, idmap_path, priority}; interesting_apks.insert( - std::lower_bound(interesting_apks.begin(), interesting_apks.end(), path), path); + std::lower_bound(interesting_apks.begin(), interesting_apks.end(), input), input); } std::stringstream stream; - for (const auto& apk : interesting_apks) { - const std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, apk); + for (const auto& overlay : interesting_apks) { std::stringstream dev_null; - if (!Verify(std::vector<std::string>({"--idmap-path", idmap_path}), dev_null) && + if (!Verify(std::vector<std::string>({"--idmap-path", overlay.idmap_path}), dev_null) && !Create(std::vector<std::string>({ "--target-apk-path", target_apk_path, "--overlay-apk-path", - apk, + overlay.apk_path, "--idmap-path", - idmap_path, + overlay.idmap_path, }), out_error)) { return false; } - stream << idmap_path << std::endl; + stream << overlay.idmap_path << std::endl; } std::cout << stream.str(); diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh index 560ccb692bb1..ad9830b8a096 100755 --- a/cmds/idmap2/static-checks.sh +++ b/cmds/idmap2/static-checks.sh @@ -70,7 +70,15 @@ function _bpfmt() function _cpplint() { local cpplint="${ANDROID_BUILD_TOP}/tools/repohooks/tools/cpplint.py" - $cpplint --quiet $cpp_files + local output="$($cpplint --quiet $cpp_files 2>&1 >/dev/null | grep -v \ + -e 'Found C system header after C++ system header.' \ + -e 'Unknown NOLINT error category: misc-non-private-member-variables-in-classes' \ + )" + if [[ "$output" ]]; then + echo "$output" + return 1 + fi + return 0 } function _parse_args() diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java index 6788f7d45071..d160b73cc223 100644 --- a/cmds/media/src/com/android/commands/media/Media.java +++ b/cmds/media/src/com/android/commands/media/Media.java @@ -19,12 +19,12 @@ package com.android.commands.media; import android.app.ActivityManager; import android.content.Context; -import android.content.pm.ParceledListSlice; import android.media.MediaMetadata; +import android.media.session.ControllerCallbackLink; import android.media.session.ISessionController; -import android.media.session.ISessionControllerCallback; import android.media.session.ISessionManager; import android.media.session.MediaController.PlaybackInfo; +import android.media.session.MediaSession.QueueItem; import android.media.session.PlaybackState; import android.os.Bundle; import android.os.HandlerThread; @@ -178,13 +178,7 @@ public class Media extends BaseCommand { KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD)); } - class ControllerMonitor extends ISessionControllerCallback.Stub { - private final ISessionController mController; - - public ControllerMonitor(ISessionController controller) { - mController = controller; - } - + class ControllerCallbackStub extends ControllerCallbackLink.CallbackStub { @Override public void onSessionDestroyed() { System.out.println("onSessionDestroyed. Enter q to quit."); @@ -208,25 +202,35 @@ public class Media extends BaseCommand { } @Override - public void onQueueChanged(ParceledListSlice queue) throws RemoteException { + public void onQueueChanged(List<QueueItem> queue) { System.out.println("onQueueChanged, " - + (queue == null ? "null queue" : " size=" + queue.getList().size())); + + (queue == null ? "null queue" : " size=" + queue.size())); } @Override - public void onQueueTitleChanged(CharSequence title) throws RemoteException { + public void onQueueTitleChanged(CharSequence title) { System.out.println("onQueueTitleChange " + title); } @Override - public void onExtrasChanged(Bundle extras) throws RemoteException { + public void onExtrasChanged(Bundle extras) { System.out.println("onExtrasChanged " + extras); } @Override - public void onVolumeInfoChanged(PlaybackInfo info) throws RemoteException { + public void onVolumeInfoChanged(PlaybackInfo info) { System.out.println("onVolumeInfoChanged " + info); } + } + + private class ControllerMonitor { + private final ISessionController mController; + private final ControllerCallbackLink mControllerCallbackLink; + + ControllerMonitor(ISessionController controller) { + mController = controller; + mControllerCallbackLink = new ControllerCallbackLink(new ControllerCallbackStub()); + } void printUsageMessage() { try { @@ -244,7 +248,7 @@ public class Media extends BaseCommand { @Override protected void onLooperPrepared() { try { - mController.registerCallbackListener(PACKAGE_NAME, ControllerMonitor.this); + mController.registerCallbackListener(PACKAGE_NAME, mControllerCallbackLink); } catch (RemoteException e) { System.out.println("Error registering monitor callback"); } @@ -287,7 +291,7 @@ public class Media extends BaseCommand { } finally { cbThread.getLooper().quit(); try { - mController.unregisterCallbackListener(this); + mController.unregisterCallbackListener(mControllerCallbackLink); } catch (Exception e) { // ignoring } diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index fa3be26a96f7..f3478673cb49 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -216,7 +216,7 @@ message Atom { NumFingerprints num_fingerprints = 10031; DiskIo disk_io = 10032; PowerProfile power_profile = 10033; - ProcStats proc_stats_pkg_proc = 10034; + ProcStatsPkgProc proc_stats_pkg_proc = 10034; ProcessCpuTime process_cpu_time = 10035; NativeProcessMemoryState native_process_memory_state = 10036; CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037; @@ -3272,6 +3272,13 @@ message ProcStats { optional ProcessStatsSectionProto proc_stats_section = 1; } +/** + * Pulled from ProcessStatsService.java + */ +message ProcStatsPkgProc { + optional ProcessStatsSectionProto proc_stats_section = 1; +} + message PowerProfileProto { optional double cpu_suspend = 1; diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index e0e1b7202347..ed22df52b87d 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -490,7 +490,6 @@ Landroid/location/ILocationManager$Stub;->asInterface(Landroid/os/IBinder;)Landr Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I Landroid/location/ILocationManager;->getAllProviders()Ljava/util/List; Landroid/location/ILocationManager;->getNetworkProviderPackage()Ljava/lang/String; -Landroid/location/ILocationManager;->reportLocation(Landroid/location/Location;Z)V Landroid/location/INetInitiatedListener$Stub;-><init>()V Landroid/location/INetInitiatedListener;->sendNiResponse(II)Z Landroid/location/LocationManager$ListenerTransport;-><init>(Landroid/location/LocationManager;Landroid/location/LocationListener;Landroid/os/Looper;)V diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index fdb71bbb93d7..c89848e2a14f 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -121,6 +121,7 @@ import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.AutofillClient; import android.view.autofill.AutofillPopupWindow; import android.view.autofill.IAutofillWindowPresenter; +import android.view.contentcapture.ContentCaptureContext; import android.view.contentcapture.ContentCaptureManager; import android.widget.AdapterView; import android.widget.Toast; @@ -1045,14 +1046,19 @@ public class Activity extends ContextThemeWrapper private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) { final ContentCaptureManager cm = getContentCaptureManager(); - if (cm == null || !cm.isContentCaptureEnabled()) { + 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 - cm.onActivityStarted(mToken, getComponentName()); + 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_FLUSH: cm.flush(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 8faf08a77355..7767f0491a16 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -271,6 +271,13 @@ public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage final H mH = new H(); final Executor mExecutor = new HandlerExecutor(mH); + /** + * Maps from activity token to local record of running activities in this process. + * + * This variable is readable if the code is running in activity thread or holding {@link + * #mResourcesManager}. It's only writable if the code is running in activity thread and holding + * {@link #mResourcesManager}. + */ @UnsupportedAppUsage final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); /** The activities to be truly destroyed (not include relaunch). */ @@ -434,6 +441,10 @@ public final class ActivityThread extends ClientTransactionHandler { Configuration newConfig; Configuration createdConfig; Configuration overrideConfig; + // Used to save the last reported configuration from server side so that activity + // configuration transactions can always use the latest configuration. + @GuardedBy("this") + private Configuration mPendingOverrideConfig; // Used for consolidating configs before sending on to Activity. private Configuration tmpConfig = new Configuration(); // Callback used for updating activity override config. @@ -3064,7 +3075,12 @@ public final class ActivityThread extends ClientTransactionHandler { } r.setState(ON_CREATE); - mActivities.put(r.token, r); + // updatePendingActivityConfiguration() reads from mActivities to update + // ActivityClientRecord which runs in a different thread. Protect modifications to + // mActivities to avoid race. + synchronized (mResourcesManager) { + mActivities.put(r.token, r); + } } catch (SuperNotCalledException e) { throw e; @@ -4639,7 +4655,12 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_DESTROY); } schedulePurgeIdler(); - mActivities.remove(token); + // updatePendingActivityConfiguration() reads from mActivities to update + // ActivityClientRecord which runs in a different thread. Protect modifications to + // mActivities to avoid race. + synchronized (mResourcesManager) { + mActivities.remove(token); + } StrictMode.decrementExpectedActivityCount(activityClass); return r; } @@ -5382,6 +5403,26 @@ public final class ActivityThread extends ClientTransactionHandler { } } + @Override + public void updatePendingActivityConfiguration(IBinder activityToken, + Configuration overrideConfig) { + final ActivityClientRecord r; + synchronized (mResourcesManager) { + r = mActivities.get(activityToken); + } + + if (r == null) { + if (DEBUG_CONFIGURATION) { + Slog.w(TAG, "Not found target activity to update its pending config."); + } + return; + } + + synchronized (r) { + r.mPendingOverrideConfig = overrideConfig; + } + } + /** * Handle new activity configuration and/or move to a different display. * @param activityToken Target activity token. @@ -5401,6 +5442,24 @@ public final class ActivityThread extends ClientTransactionHandler { final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY && displayId != r.activity.getDisplayId(); + synchronized (r) { + if (r.mPendingOverrideConfig != null + && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) { + overrideConfig = r.mPendingOverrideConfig; + } + r.mPendingOverrideConfig = null; + } + + if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig) + && !movedToDifferentDisplay) { + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Activity already handled newer configuration so drop this" + + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig=" + + r.overrideConfig); + } + return; + } + // Perform updates. r.overrideConfig = overrideConfig; final ViewRootImpl viewRoot = r.activity.mDecor != null diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl index 9329fbc83376..224030202e08 100644 --- a/core/java/android/app/AppOpsManager.aidl +++ b/core/java/android/app/AppOpsManager.aidl @@ -19,5 +19,7 @@ package android.app; parcelable AppOpsManager.PackageOps; parcelable AppOpsManager.OpEntry; +parcelable AppOpsManager.HistoricalOp; +parcelable AppOpsManager.HistoricalOps; parcelable AppOpsManager.HistoricalPackageOps; -parcelable AppOpsManager.HistoricalOpEntry; +parcelable AppOpsManager.HistoricalUidOps; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 78fe0024b0b0..e155fe201f98 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -36,26 +36,33 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserManager; import android.provider.Settings; import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; +import android.util.SparseArray; import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * API for interacting with "application operation" tracking. @@ -106,6 +113,51 @@ public class AppOpsManager { static IBinder sToken; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = { + HISTORICAL_MODE_DISABLED, + HISTORICAL_MODE_ENABLED_ACTIVE, + HISTORICAL_MODE_ENABLED_PASSIVE + }) + public @interface HistoricalMode {} + + /** + * Mode in which app op history is completely disabled. + * @hide + */ + @TestApi + public static final int HISTORICAL_MODE_DISABLED = 0; + + /** + * Mode in which app op history is enabled and app ops performed by apps would + * be tracked. This is the mode in which the feature is completely enabled. + * @hide + */ + @TestApi + public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; + + /** + * Mode in which app op history is enabled but app ops performed by apps would + * not be tracked and the only way to add ops to the history is via explicit calls + * to dedicated APIs. This mode is useful for testing to allow full control of + * the historical content. + * @hide + */ + @TestApi + public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "MODE_" }, value = { + MODE_ALLOWED, + MODE_IGNORED, + MODE_ERRORED, + MODE_DEFAULT, + MODE_FOREGROUND + }) + public @interface Mode {} + /** * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is * allowed to perform the given operation. @@ -159,7 +211,6 @@ public class AppOpsManager { "foreground", // MODE_FOREGROUND }; - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "UID_STATE_" }, value = { @@ -173,9 +224,16 @@ public class AppOpsManager { public @interface UidState {} /** + * Invalid UID state. + * @hide + */ + public static final int UID_STATE_INVALID = -1; + + /** * Metrics about an op when its uid is persistent. * @hide */ + @TestApi @SystemApi public static final int UID_STATE_PERSISTENT = 0; @@ -183,6 +241,7 @@ public class AppOpsManager { * Metrics about an op when its uid is at the top. * @hide */ + @TestApi @SystemApi public static final int UID_STATE_TOP = 1; @@ -190,6 +249,7 @@ public class AppOpsManager { * Metrics about an op when its uid is running a foreground service. * @hide */ + @TestApi @SystemApi public static final int UID_STATE_FOREGROUND_SERVICE = 2; @@ -203,6 +263,7 @@ public class AppOpsManager { * Metrics about an op when its uid is in the foreground for any other reasons. * @hide */ + @TestApi @SystemApi public static final int UID_STATE_FOREGROUND = 3; @@ -210,6 +271,7 @@ public class AppOpsManager { * Metrics about an op when its uid is in the background for any reason. * @hide */ + @TestApi @SystemApi public static final int UID_STATE_BACKGROUND = 4; @@ -217,6 +279,7 @@ public class AppOpsManager { * Metrics about an op when its uid is cached. * @hide */ + @TestApi @SystemApi public static final int UID_STATE_CACHED = 5; @@ -237,7 +300,7 @@ public class AppOpsManager { @UnsupportedAppUsage public static final int OP_NONE = -1; /** @hide Access to coarse location information. */ - @UnsupportedAppUsage + @TestApi public static final int OP_COARSE_LOCATION = 0; /** @hide Access to fine location information. */ @UnsupportedAppUsage @@ -1645,6 +1708,9 @@ public class AppOpsManager { } } + /** @hide */ + public static final String KEY_HISTORICAL_OPS = "historical_ops"; + /** * Retrieve the op switch that controls the given operation. * @hide @@ -1731,7 +1797,7 @@ public class AppOpsManager { * Retrieve the default mode for the operation. * @hide */ - public static int opToDefaultMode(int op) { + public static @Mode int opToDefaultMode(int op) { // STOPSHIP b/118520006: Hardcode the default values once the feature is stable. switch (op) { // SMS permissions @@ -1795,7 +1861,7 @@ public class AppOpsManager { * Retrieve the human readable mode. * @hide */ - public static String modeToName(int mode) { + public static String modeToName(@Mode int mode) { if (mode >= 0 && mode < MODE_NAMES.length) { return MODE_NAMES[mode]; } @@ -1885,7 +1951,7 @@ public class AppOpsManager { @SystemApi public static final class OpEntry implements Parcelable { private final int mOp; - private final int mMode; + private final @Mode int mMode; private final long[] mTimes; private final long[] mRejectTimes; private final int mDuration; @@ -1896,7 +1962,7 @@ public class AppOpsManager { /** * @hide */ - public OpEntry(int op, int mode, long time, long rejectTime, int duration, + public OpEntry(int op, @Mode int mode, long time, long rejectTime, int duration, int proxyUid, String proxyPackage) { mOp = op; mMode = mode; @@ -1913,7 +1979,7 @@ public class AppOpsManager { /** * @hide */ - public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration, + public OpEntry(int op, @Mode int mode, long[] times, long[] rejectTimes, int duration, boolean running, int proxyUid, String proxyPackage) { mOp = op; mMode = mode; @@ -1930,7 +1996,7 @@ public class AppOpsManager { /** * @hide */ - public OpEntry(int op, int mode, long[] times, long[] rejectTimes, int duration, + public OpEntry(int op, @Mode int mode, long[] times, long[] rejectTimes, int duration, int proxyUid, String proxyPackage) { this(op, mode, times, rejectTimes, duration, duration == -1, proxyUid, proxyPackage); } @@ -1953,7 +2019,7 @@ public class AppOpsManager { /** * Return this entry's current mode, such as {@link #MODE_ALLOWED}. */ - public int getMode() { + public @Mode int getMode() { return mMode; } @@ -2086,83 +2152,813 @@ public class AppOpsManager { }; } + /** @hide */ + public interface HistoricalOpsVisitor { + void visitHistoricalOps(@NonNull HistoricalOps ops); + void visitHistoricalUidOps(@NonNull HistoricalUidOps ops); + void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops); + void visitHistoricalOp(@NonNull HistoricalOp ops); + } + /** - * This class represents historical app op information about a package. The history - * is aggregated information about ops for a certain amount of time such - * as the times the op was accessed, the times the op was rejected, the total - * duration the app op has been accessed. + * This class represents historical app op state of all UIDs for a given time interval. * * @hide */ @TestApi @SystemApi - public static final class HistoricalPackageOps implements Parcelable { - private final int mUid; - private final @NonNull String mPackageName; - private final @NonNull List<HistoricalOpEntry> mEntries; + public static final class HistoricalOps implements Parcelable { + private long mBeginTimeMillis; + private long mEndTimeMillis; + private @Nullable SparseArray<HistoricalUidOps> mHistoricalUidOps; + + /** @hide */ + @TestApi + public HistoricalOps(long beginTimeMillis, long endTimeMillis) { + Preconditions.checkState(beginTimeMillis <= endTimeMillis); + mBeginTimeMillis = beginTimeMillis; + mEndTimeMillis = endTimeMillis; + } + + /** @hide */ + public HistoricalOps(@NonNull HistoricalOps other) { + mBeginTimeMillis = other.mBeginTimeMillis; + mEndTimeMillis = other.mEndTimeMillis; + Preconditions.checkState(mBeginTimeMillis <= mEndTimeMillis); + if (other.mHistoricalUidOps != null) { + final int opCount = other.getUidCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalUidOps origOps = other.getUidOpsAt(i); + final HistoricalUidOps clonedOps = new HistoricalUidOps(origOps); + if (mHistoricalUidOps == null) { + mHistoricalUidOps = new SparseArray<>(opCount); + } + mHistoricalUidOps.put(clonedOps.getUid(), clonedOps); + } + } + } + + private HistoricalOps(Parcel parcel) { + mBeginTimeMillis = parcel.readLong(); + mEndTimeMillis = parcel.readLong(); + final int[] uids = parcel.createIntArray(); + if (!ArrayUtils.isEmpty(uids)) { + final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable( + HistoricalOps.class.getClassLoader()); + final List<HistoricalUidOps> uidOps = (listSlice != null) + ? listSlice.getList() : null; + if (uidOps == null) { + return; + } + for (int i = 0; i < uids.length; i++) { + if (mHistoricalUidOps == null) { + mHistoricalUidOps = new SparseArray<>(); + } + mHistoricalUidOps.put(uids[i], uidOps.get(i)); + } + } + } /** + * Splice a piece from the beginning of these ops. + * + * @param splicePoint The fraction of the data to be spliced off. + * * @hide */ - public HistoricalPackageOps(int uid, @NonNull String packageName) { - mUid = uid; - mPackageName = packageName; - mEntries = new ArrayList<>(); + public @NonNull HistoricalOps spliceFromBeginning(double splicePoint) { + return splice(splicePoint, true); } - HistoricalPackageOps(@NonNull Parcel parcel) { - mUid = parcel.readInt(); - mPackageName = parcel.readString(); - mEntries = parcel.createTypedArrayList(HistoricalOpEntry.CREATOR); + /** + * Splice a piece from the end of these ops. + * + * @param fractionToRemove The fraction of the data to be spliced off. + * + * @hide + */ + public @NonNull HistoricalOps spliceFromEnd(double fractionToRemove) { + return splice(fractionToRemove, false); } /** + * Splice a piece from the beginning or end of these ops. + * + * @param fractionToRemove The fraction of the data to be spliced off. + * @param beginning Whether to splice off the beginning or the end. + * + * @return The spliced off part. + * * @hide */ - public void addEntry(@NonNull HistoricalOpEntry entry) { - mEntries.add(entry); + private @Nullable HistoricalOps splice(double fractionToRemove, boolean beginning) { + final long spliceBeginTimeMills; + final long spliceEndTimeMills; + if (beginning) { + spliceBeginTimeMills = mBeginTimeMillis; + spliceEndTimeMills = (long) (mBeginTimeMillis + + getDurationMillis() * fractionToRemove); + mBeginTimeMillis = spliceEndTimeMills; + } else { + spliceBeginTimeMills = (long) (mEndTimeMillis + - getDurationMillis() * fractionToRemove); + spliceEndTimeMills = mEndTimeMillis; + mEndTimeMillis = spliceBeginTimeMills; + } + + HistoricalOps splice = null; + final int uidCount = getUidCount(); + for (int i = 0; i < uidCount; i++) { + final HistoricalUidOps origOps = getUidOpsAt(i); + final HistoricalUidOps spliceOps = origOps.splice(fractionToRemove); + if (spliceOps != null) { + if (splice == null) { + splice = new HistoricalOps(spliceBeginTimeMills, spliceEndTimeMills); + } + if (splice.mHistoricalUidOps == null) { + splice.mHistoricalUidOps = new SparseArray<>(); + } + splice.mHistoricalUidOps.put(spliceOps.getUid(), spliceOps); + } + } + return splice; } /** - * Gets the package name which the data represents. + * Merge the passed ops into the current ones. The time interval is a + * union of the current and passed in one and the passed in data is + * folded into the data of this instance. * - * @return The package name which the data represents. + * @hide */ - public @NonNull String getPackageName() { - return mPackageName; + public void merge(@NonNull HistoricalOps other) { + mBeginTimeMillis = Math.min(mBeginTimeMillis, other.mBeginTimeMillis); + mEndTimeMillis = Math.max(mEndTimeMillis, other.mEndTimeMillis); + final int uidCount = other.getUidCount(); + for (int i = 0; i < uidCount; i++) { + final HistoricalUidOps otherUidOps = other.getUidOpsAt(i); + final HistoricalUidOps thisUidOps = getUidOps(otherUidOps.getUid()); + if (thisUidOps != null) { + thisUidOps.merge(otherUidOps); + } else { + if (mHistoricalUidOps == null) { + mHistoricalUidOps = new SparseArray<>(); + } + mHistoricalUidOps.put(otherUidOps.getUid(), otherUidOps); + } + } + } + + /** + * AppPermissionUsage the ops to leave only the data we filter for. + * + * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all. + * @param packageName Package to filter for or null for all. + * @param opNames Ops to filter for or null for all. + * @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all. + * @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all. + * + * @hide + */ + public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames, + long beginTimeMillis, long endTimeMillis) { + final long durationMillis = getDurationMillis(); + mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis); + mEndTimeMillis = Math.min(mEndTimeMillis, endTimeMillis); + final double scaleFactor = Math.min((double) (endTimeMillis - beginTimeMillis) + / (double) durationMillis, 1); + final int uidCount = getUidCount(); + for (int i = uidCount - 1; i >= 0; i--) { + final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i); + if (uid != Process.INVALID_UID && uid != uidOp.getUid()) { + mHistoricalUidOps.removeAt(i); + } else { + uidOp.filter(packageName, opNames, scaleFactor); + } + } + } + + /** @hide */ + public boolean isEmpty() { + if (getBeginTimeMillis() >= getEndTimeMillis()) { + return true; + } + final int uidCount = getUidCount(); + for (int i = uidCount - 1; i >= 0; i--) { + final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i); + if (!uidOp.isEmpty()) { + return false; + } + } + return true; + } + + /** @hide */ + public long getDurationMillis() { + return mEndTimeMillis - mBeginTimeMillis; + } + + /** @hide */ + @TestApi + public void increaseAccessCount(int opCode, int uid, @NonNull String packageName, + @UidState int uidState, long increment) { + getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode, + packageName, uidState, increment); + } + + /** @hide */ + @TestApi + public void increaseRejectCount(int opCode, int uid, @NonNull String packageName, + @UidState int uidState, long increment) { + getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode, + packageName, uidState, increment); + } + + /** @hide */ + @TestApi + public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName, + @UidState int uidState, long increment) { + getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode, + packageName, uidState, increment); + } + + /** @hide */ + @TestApi + public void offsetBeginAndEndTime(long offsetMillis) { + mBeginTimeMillis += offsetMillis; + mEndTimeMillis += offsetMillis; + } + + /** @hide */ + public void setBeginAndEndTime(long beginTimeMillis, long endTimeMillis) { + mBeginTimeMillis = beginTimeMillis; + mEndTimeMillis = endTimeMillis; + } + + /** @hide */ + public void setBeginTime(long beginTimeMillis) { + mBeginTimeMillis = beginTimeMillis; + } + + /** @hide */ + public void setEndTime(long endTimeMillis) { + mEndTimeMillis = endTimeMillis; + } + + /** + * @return The beginning of the interval in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + */ + public long getBeginTimeMillis() { + return mBeginTimeMillis; + } + + /** + * @return The end of the interval in milliseconds since + * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). + */ + public long getEndTimeMillis() { + return mEndTimeMillis; + } + + /** + * Gets number of UIDs with historical ops. + * + * @return The number of UIDs with historical ops. + * + * @see #getUidOpsAt(int) + */ + public int getUidCount() { + if (mHistoricalUidOps == null) { + return 0; + } + return mHistoricalUidOps.size(); + } + + /** + * Gets the historical UID ops at a given index. + * + * @param index The index. + * + * @return The historical UID ops at the given index. + * + * @see #getUidCount() + */ + public @NonNull HistoricalUidOps getUidOpsAt(int index) { + if (mHistoricalUidOps == null) { + throw new IndexOutOfBoundsException(); + } + return mHistoricalUidOps.valueAt(index); + } + + /** + * Gets the historical UID ops for a given UID. + * + * @param uid The UID. + * + * @return The historical ops for the UID. + */ + public @Nullable HistoricalUidOps getUidOps(int uid) { + if (mHistoricalUidOps == null) { + return null; + } + return mHistoricalUidOps.get(uid); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeLong(mBeginTimeMillis); + parcel.writeLong(mEndTimeMillis); + if (mHistoricalUidOps != null) { + final int uidCount = mHistoricalUidOps.size(); + parcel.writeInt(uidCount); + for (int i = 0; i < uidCount; i++) { + parcel.writeInt(mHistoricalUidOps.keyAt(i)); + } + final List<HistoricalUidOps> opsList = new ArrayList<>(uidCount); + for (int i = 0; i < uidCount; i++) { + opsList.add(mHistoricalUidOps.valueAt(i)); + } + parcel.writeParcelable(new ParceledListSlice<>(opsList), flags); + } else { + parcel.writeInt(-1); + } + } + + /** + * Accepts a visitor to traverse the ops tree. + * + * @param visitor The visitor. + * + * @hide + */ + public void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalOps(this); + final int uidCount = getUidCount(); + for (int i = 0; i < uidCount; i++) { + getUidOpsAt(i).accept(visitor); + } + } + + private @NonNull HistoricalUidOps getOrCreateHistoricalUidOps(int uid) { + if (mHistoricalUidOps == null) { + mHistoricalUidOps = new SparseArray<>(); + } + HistoricalUidOps historicalUidOp = mHistoricalUidOps.get(uid); + if (historicalUidOp == null) { + historicalUidOp = new HistoricalUidOps(uid); + mHistoricalUidOps.put(uid, historicalUidOp); + } + return historicalUidOp; } /** - * Gets the UID which the data represents. + * @return Rounded value up at the 0.5 boundary. * - * @return The UID which the data represents. + * @hide + */ + public static double round(double value) { + final BigDecimal decimalScale = new BigDecimal(value); + return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalOps other = (HistoricalOps) obj; + if (mBeginTimeMillis != other.mBeginTimeMillis) { + return false; + } + if (mEndTimeMillis != other.mEndTimeMillis) { + return false; + } + if (mHistoricalUidOps == null) { + if (other.mHistoricalUidOps != null) { + return false; + } + } else if (!mHistoricalUidOps.equals(other.mHistoricalUidOps)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = (int) (mBeginTimeMillis ^ (mBeginTimeMillis >>> 32)); + result = 31 * result + mHistoricalUidOps.hashCode(); + return result; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[from:" + + mBeginTimeMillis + " to:" + mEndTimeMillis + "]"; + } + + public static final Creator<HistoricalOps> CREATOR = new Creator<HistoricalOps>() { + @Override + public @NonNull HistoricalOps createFromParcel(@NonNull Parcel parcel) { + return new HistoricalOps(parcel); + } + + @Override + public @NonNull HistoricalOps[] newArray(int size) { + return new HistoricalOps[size]; + } + }; + } + + /** + * This class represents historical app op state for a UID. + * + * @hide + */ + @TestApi + @SystemApi + public static final class HistoricalUidOps implements Parcelable { + private final int mUid; + private @Nullable ArrayMap<String, HistoricalPackageOps> mHistoricalPackageOps; + + /** @hide */ + public HistoricalUidOps(int uid) { + mUid = uid; + } + + private HistoricalUidOps(@NonNull HistoricalUidOps other) { + mUid = other.mUid; + final int opCount = other.getPackageCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalPackageOps origOps = other.getPackageOpsAt(i); + final HistoricalPackageOps cloneOps = new HistoricalPackageOps(origOps); + if (mHistoricalPackageOps == null) { + mHistoricalPackageOps = new ArrayMap<>(opCount); + } + mHistoricalPackageOps.put(cloneOps.getPackageName(), cloneOps); + } + } + + private HistoricalUidOps(@NonNull Parcel parcel) { + // No arg check since we always read from a trusted source. + mUid = parcel.readInt(); + mHistoricalPackageOps = parcel.createTypedArrayMap(HistoricalPackageOps.CREATOR); + } + + private @Nullable HistoricalUidOps splice(double fractionToRemove) { + HistoricalUidOps splice = null; + final int packageCount = getPackageCount(); + for (int i = 0; i < packageCount; i++) { + final HistoricalPackageOps origOps = getPackageOpsAt(i); + final HistoricalPackageOps spliceOps = origOps.splice(fractionToRemove); + if (spliceOps != null) { + if (splice == null) { + splice = new HistoricalUidOps(mUid); + } + if (splice.mHistoricalPackageOps == null) { + splice.mHistoricalPackageOps = new ArrayMap<>(); + } + splice.mHistoricalPackageOps.put(spliceOps.getPackageName(), spliceOps); + } + } + return splice; + } + + private void merge(@NonNull HistoricalUidOps other) { + final int packageCount = other.getPackageCount(); + for (int i = 0; i < packageCount; i++) { + final HistoricalPackageOps otherPackageOps = other.getPackageOpsAt(i); + final HistoricalPackageOps thisPackageOps = getPackageOps( + otherPackageOps.getPackageName()); + if (thisPackageOps != null) { + thisPackageOps.merge(otherPackageOps); + } else { + if (mHistoricalPackageOps == null) { + mHistoricalPackageOps = new ArrayMap<>(); + } + mHistoricalPackageOps.put(otherPackageOps.getPackageName(), otherPackageOps); + } + } + } + + private void filter(@Nullable String packageName, @Nullable String[] opNames, + double fractionToRemove) { + final int packageCount = getPackageCount(); + for (int i = packageCount - 1; i >= 0; i--) { + final HistoricalPackageOps packageOps = getPackageOpsAt(i); + if (packageName != null && !packageName.equals(packageOps.getPackageName())) { + mHistoricalPackageOps.removeAt(i); + } else { + packageOps.filter(opNames, fractionToRemove); + } + } + } + + private boolean isEmpty() { + final int packageCount = getPackageCount(); + for (int i = packageCount - 1; i >= 0; i--) { + final HistoricalPackageOps packageOps = mHistoricalPackageOps.valueAt(i); + if (!packageOps.isEmpty()) { + return false; + } + } + return true; + } + + private void increaseAccessCount(int opCode, @NonNull String packageName, + @UidState int uidState, long increment) { + getOrCreateHistoricalPackageOps(packageName).increaseAccessCount( + opCode, uidState, increment); + } + + private void increaseRejectCount(int opCode, @NonNull String packageName, + @UidState int uidState, long increment) { + getOrCreateHistoricalPackageOps(packageName).increaseRejectCount( + opCode, uidState, increment); + } + + private void increaseAccessDuration(int opCode, @NonNull String packageName, + @UidState int uidState, long increment) { + getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration( + opCode, uidState, increment); + } + + /** + * @return The UID for which the data is related. */ public int getUid() { return mUid; } /** - * Gets number historical app op entries. + * Gets number of packages with historical ops. + * + * @return The number of packages with historical ops. + * + * @see #getPackageOpsAt(int) + */ + public int getPackageCount() { + if (mHistoricalPackageOps == null) { + return 0; + } + return mHistoricalPackageOps.size(); + } + + /** + * Gets the historical package ops at a given index. + * + * @param index The index. * - * @return The number historical app op entries. + * @return The historical package ops at the given index. * - * @see #getEntryAt(int) + * @see #getPackageCount() */ - public int getEntryCount() { - return mEntries.size(); + public @NonNull HistoricalPackageOps getPackageOpsAt(int index) { + if (mHistoricalPackageOps == null) { + throw new IndexOutOfBoundsException(); + } + return mHistoricalPackageOps.valueAt(index); } /** - * Gets the historical at a given index. + * Gets the historical package ops for a given package. + * + * @param packageName The package. + * + * @return The historical ops for the package. + */ + public @Nullable HistoricalPackageOps getPackageOps(@NonNull String packageName) { + if (mHistoricalPackageOps == null) { + return null; + } + return mHistoricalPackageOps.get(packageName); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mUid); + parcel.writeTypedArrayMap(mHistoricalPackageOps, flags); + } + + private void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalUidOps(this); + final int packageCount = getPackageCount(); + for (int i = 0; i < packageCount; i++) { + getPackageOpsAt(i).accept(visitor); + } + } + + private @NonNull HistoricalPackageOps getOrCreateHistoricalPackageOps( + @NonNull String packageName) { + if (mHistoricalPackageOps == null) { + mHistoricalPackageOps = new ArrayMap<>(); + } + HistoricalPackageOps historicalPackageOp = mHistoricalPackageOps.get(packageName); + if (historicalPackageOp == null) { + historicalPackageOp = new HistoricalPackageOps(packageName); + mHistoricalPackageOps.put(packageName, historicalPackageOp); + } + return historicalPackageOp; + } + + + public static final Creator<HistoricalUidOps> CREATOR = new Creator<HistoricalUidOps>() { + @Override + public @NonNull HistoricalUidOps createFromParcel(@NonNull Parcel parcel) { + return new HistoricalUidOps(parcel); + } + + @Override + public @NonNull HistoricalUidOps[] newArray(int size) { + return new HistoricalUidOps[size]; + } + }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalUidOps other = (HistoricalUidOps) obj; + if (mUid != other.mUid) { + return false; + } + if (mHistoricalPackageOps == null) { + if (other.mHistoricalPackageOps != null) { + return false; + } + } else if (!mHistoricalPackageOps.equals(other.mHistoricalPackageOps)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = mUid; + result = 31 * result + (mHistoricalPackageOps != null + ? mHistoricalPackageOps.hashCode() : 0); + return result; + } + } + + /** + * This class represents historical app op information about a package. + * + * @hide + */ + @TestApi + @SystemApi + public static final class HistoricalPackageOps implements Parcelable { + private final @NonNull String mPackageName; + private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps; + + /** @hide */ + public HistoricalPackageOps(@NonNull String packageName) { + mPackageName = packageName; + } + + private HistoricalPackageOps(@NonNull HistoricalPackageOps other) { + mPackageName = other.mPackageName; + final int opCount = other.getOpCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalOp origOp = other.getOpAt(i); + final HistoricalOp cloneOp = new HistoricalOp(origOp); + if (mHistoricalOps == null) { + mHistoricalOps = new ArrayMap<>(opCount); + } + mHistoricalOps.put(cloneOp.getOpName(), cloneOp); + } + } + + private HistoricalPackageOps(@NonNull Parcel parcel) { + mPackageName = parcel.readString(); + mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR); + } + + private @Nullable HistoricalPackageOps splice(double fractionToRemove) { + HistoricalPackageOps splice = null; + final int opCount = getOpCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalOp origOps = getOpAt(i); + final HistoricalOp spliceOps = origOps.splice(fractionToRemove); + if (spliceOps != null) { + if (splice == null) { + splice = new HistoricalPackageOps(mPackageName); + } + if (splice.mHistoricalOps == null) { + splice.mHistoricalOps = new ArrayMap<>(); + } + splice.mHistoricalOps.put(spliceOps.getOpName(), spliceOps); + } + } + return splice; + } + + private void merge(@NonNull HistoricalPackageOps other) { + final int opCount = other.getOpCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalOp otherOp = other.getOpAt(i); + final HistoricalOp thisOp = getOp(otherOp.getOpName()); + if (thisOp != null) { + thisOp.merge(otherOp); + } else { + if (mHistoricalOps == null) { + mHistoricalOps = new ArrayMap<>(); + } + mHistoricalOps.put(otherOp.getOpName(), otherOp); + } + } + } + + private void filter(@Nullable String[] opNames, double scaleFactor) { + final int opCount = getOpCount(); + for (int i = opCount - 1; i >= 0; i--) { + final HistoricalOp op = mHistoricalOps.valueAt(i); + if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) { + mHistoricalOps.removeAt(i); + } else { + op.filter(scaleFactor); + } + } + } + + private boolean isEmpty() { + final int opCount = getOpCount(); + for (int i = opCount - 1; i >= 0; i--) { + final HistoricalOp op = mHistoricalOps.valueAt(i); + if (!op.isEmpty()) { + return false; + } + } + return true; + } + + private void increaseAccessCount(int opCode, @UidState int uidState, long increment) { + getOrCreateHistoricalOp(opCode).increaseAccessCount(uidState, increment); + } + + private void increaseRejectCount(int opCode, @UidState int uidState, long increment) { + getOrCreateHistoricalOp(opCode).increaseRejectCount(uidState, increment); + } + + private void increaseAccessDuration(int opCode, @UidState int uidState, long increment) { + getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, increment); + } + + /** + * Gets the package name which the data represents. + * + * @return The package name which the data represents. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + /** + * Gets number historical app ops. + * + * @return The number historical app ops. + * + * @see #getOpAt(int) + */ + public int getOpCount() { + if (mHistoricalOps == null) { + return 0; + } + return mHistoricalOps.size(); + } + + /** + * Gets the historical op at a given index. * * @param index The index to lookup. * - * @return The entry at the given index. + * @return The op at the given index. * - * @see #getEntryCount() + * @see #getOpCount() */ - public @NonNull HistoricalOpEntry getEntryAt(int index) { - return mEntries.get(index); + public @NonNull HistoricalOp getOpAt(int index) { + if (mHistoricalOps == null) { + throw new IndexOutOfBoundsException(); + } + return mHistoricalOps.valueAt(index); } /** @@ -2172,15 +2968,11 @@ public class AppOpsManager { * * @return The historical entry for that op name. */ - public @Nullable HistoricalOpEntry getEntry(@NonNull String opName) { - final int entryCount = mEntries.size(); - for (int i = 0; i < entryCount; i++) { - final HistoricalOpEntry entry = mEntries.get(i); - if (entry.getOp().equals(opName)) { - return entry; - } + public @Nullable HistoricalOp getOp(@NonNull String opName) { + if (mHistoricalOps == null) { + return null; } - return null; + return mHistoricalOps.get(opName); } @Override @@ -2190,9 +2982,29 @@ public class AppOpsManager { @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mUid); parcel.writeString(mPackageName); - parcel.writeTypedList(mEntries, flags); + parcel.writeTypedArrayMap(mHistoricalOps, flags); + } + + private void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalPackageOps(this); + final int opCount = getOpCount(); + for (int i = 0; i < opCount; i++) { + getOpAt(i).accept(visitor); + } + } + + private @NonNull HistoricalOp getOrCreateHistoricalOp(int opCode) { + if (mHistoricalOps == null) { + mHistoricalOps = new ArrayMap<>(); + } + final String opStr = sOpToString[opCode]; + HistoricalOp op = mHistoricalOps.get(opStr); + if (op == null) { + op = new HistoricalOp(opCode); + mHistoricalOps.put(opStr, op); + } + return op; } public static final Creator<HistoricalPackageOps> CREATOR = @@ -2207,49 +3019,177 @@ public class AppOpsManager { return new HistoricalPackageOps[size]; } }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalPackageOps other = (HistoricalPackageOps) obj; + if (!mPackageName.equals(other.mPackageName)) { + return false; + } + if (mHistoricalOps == null) { + if (other.mHistoricalOps != null) { + return false; + } + } else if (!mHistoricalOps.equals(other.mHistoricalOps)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = mPackageName != null ? mPackageName.hashCode() : 0; + result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0); + return result; + } } /** - * This class represents historical information about an app op. The history - * is aggregated information about the op for a certain amount of time such - * as the times the op was accessed, the times the op was rejected, the total - * duration the app op has been accessed. + * This class represents historical information about an app op. * * @hide */ @TestApi @SystemApi - public static final class HistoricalOpEntry implements Parcelable { + public static final class HistoricalOp implements Parcelable { private final int mOp; - private final long[] mAccessCount; - private final long[] mRejectCount; - private final long[] mAccessDuration; + private @Nullable long[] mAccessCount; + private @Nullable long[] mRejectCount; + private @Nullable long[] mAccessDuration; - /** - * @hide - */ - public HistoricalOpEntry(int op) { + /** @hide */ + public HistoricalOp(int op) { mOp = op; mAccessCount = new long[_NUM_UID_STATE]; mRejectCount = new long[_NUM_UID_STATE]; mAccessDuration = new long[_NUM_UID_STATE]; } - HistoricalOpEntry(@NonNull Parcel parcel) { + private HistoricalOp(@NonNull HistoricalOp other) { + mOp = other.mOp; + if (other.mAccessCount != null) { + System.arraycopy(other.mAccessCount, 0, getOrCreateAccessCount(), + 0, other.mAccessCount.length); + } + if (other.mRejectCount != null) { + System.arraycopy(other.mRejectCount, 0, getOrCreateRejectCount(), + 0, other.mRejectCount.length); + } + if (other.mAccessDuration != null) { + System.arraycopy(other.mAccessDuration, 0, getOrCreateAccessDuration(), + 0, other.mAccessDuration.length); + } + } + + private HistoricalOp(@NonNull Parcel parcel) { mOp = parcel.readInt(); mAccessCount = parcel.createLongArray(); mRejectCount = parcel.createLongArray(); mAccessDuration = parcel.createLongArray(); } - /** - * @hide - */ - public void addEntry(@UidState int uidState, long accessCount, - long rejectCount, long accessDuration) { - mAccessCount[uidState] = accessCount; - mRejectCount[uidState] = rejectCount; - mAccessDuration[uidState] = accessDuration; + private void filter(double scaleFactor) { + scale(mAccessCount, scaleFactor); + scale(mRejectCount, scaleFactor); + scale(mAccessDuration, scaleFactor); + } + + private boolean isEmpty() { + return !hasData(mAccessCount) + && !hasData(mRejectCount) + && !hasData(mAccessDuration); + } + + private boolean hasData(@NonNull long[] array) { + for (long value : array) { + if (value != 0) { + return true; + } + } + return false; + } + + private @Nullable HistoricalOp splice(double fractionToRemove) { + HistoricalOp splice = null; + if (mAccessCount != null) { + for (int i = 0; i < _NUM_UID_STATE; i++) { + final long spliceAccessCount = Math.round( + mAccessCount[i] * fractionToRemove); + if (spliceAccessCount > 0) { + if (splice == null) { + splice = new HistoricalOp(mOp); + } + splice.getOrCreateAccessCount()[i] = spliceAccessCount; + mAccessCount[i] -= spliceAccessCount; + } + } + } + + if (mRejectCount != null) { + for (int i = 0; i < _NUM_UID_STATE; i++) { + final long spliceRejectCount = Math.round( + mRejectCount[i] * fractionToRemove); + + if (spliceRejectCount > 0) { + if (splice == null) { + splice = new HistoricalOp(mOp); + } + splice.getOrCreateRejectCount()[i] = spliceRejectCount; + mRejectCount[i] -= spliceRejectCount; + } + } + } + + if (mAccessDuration != null) { + for (int i = 0; i < _NUM_UID_STATE; i++) { + final long spliceAccessDuration = Math.round( + mAccessDuration[i] * fractionToRemove); + if (spliceAccessDuration > 0) { + if (splice == null) { + splice = new HistoricalOp(mOp); + } + splice.getOrCreateAccessDuration()[i] = spliceAccessDuration; + mAccessDuration[i] -= spliceAccessDuration; + } + } + } + return splice; + } + + private void merge(@NonNull HistoricalOp other) { + if (other.mAccessCount != null) { + for (int i = 0; i < _NUM_UID_STATE; i++) { + getOrCreateAccessCount()[i] += other.mAccessCount[i]; + } + } + if (other.mRejectCount != null) { + for (int i = 0; i < _NUM_UID_STATE; i++) { + getOrCreateRejectCount()[i] += other.mRejectCount[i]; + } + } + if (other.mAccessDuration != null) { + for (int i = 0; i < _NUM_UID_STATE; i++) { + getOrCreateAccessDuration()[i] += other.mAccessDuration[i]; + } + } + } + + private void increaseAccessCount(@UidState int uidState, long increment) { + getOrCreateAccessCount()[uidState] += increment; + } + + private void increaseRejectCount(@UidState int uidState, long increment) { + getOrCreateRejectCount()[uidState] += increment; + } + + private void increaseAccessDuration(@UidState int uidState, long increment) { + getOrCreateAccessDuration()[uidState] += increment; } /** @@ -2257,10 +3197,15 @@ public class AppOpsManager { * * @return The op name. */ - public @NonNull String getOp() { + public @NonNull String getOpName() { return sOpToString[mOp]; } + /** @hide */ + public int getOpCode() { + return mOp; + } + /** * Gets the number times the op was accessed (performed) in the foreground. * @@ -2270,6 +3215,9 @@ public class AppOpsManager { * @see #getAccessCount(int) */ public long getForegroundAccessCount() { + if (mAccessCount == null) { + return 0; + } return sum(mAccessCount, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1); } @@ -2282,6 +3230,9 @@ public class AppOpsManager { * @see #getAccessCount(int) */ public long getBackgroundAccessCount() { + if (mAccessCount == null) { + return 0; + } return sum(mAccessCount, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE); } @@ -2299,6 +3250,9 @@ public class AppOpsManager { * @see #getBackgroundAccessCount() */ public long getAccessCount(@UidState int uidState) { + if (mAccessCount == null) { + return 0; + } return mAccessCount[uidState]; } @@ -2311,6 +3265,9 @@ public class AppOpsManager { * @see #getRejectCount(int) */ public long getForegroundRejectCount() { + if (mRejectCount == null) { + return 0; + } return sum(mRejectCount, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1); } @@ -2323,6 +3280,9 @@ public class AppOpsManager { * @see #getRejectCount(int) */ public long getBackgroundRejectCount() { + if (mRejectCount == null) { + return 0; + } return sum(mRejectCount, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE); } @@ -2340,6 +3300,9 @@ public class AppOpsManager { * @see #getBackgroundRejectCount() */ public long getRejectCount(@UidState int uidState) { + if (mRejectCount == null) { + return 0; + } return mRejectCount[uidState]; } @@ -2352,6 +3315,9 @@ public class AppOpsManager { * @see #getAccessDuration(int) */ public long getForegroundAccessDuration() { + if (mAccessDuration == null) { + return 0; + } return sum(mAccessDuration, UID_STATE_PERSISTENT, UID_STATE_LAST_NON_RESTRICTED + 1); } @@ -2364,6 +3330,9 @@ public class AppOpsManager { * @see #getAccessDuration(int) */ public long getBackgroundAccessDuration() { + if (mAccessDuration == null) { + return 0; + } return sum(mAccessDuration, UID_STATE_LAST_NON_RESTRICTED + 1, _NUM_UID_STATE); } @@ -2381,6 +3350,9 @@ public class AppOpsManager { * @see #getBackgroundAccessDuration() */ public long getAccessDuration(@UidState int uidState) { + if (mAccessDuration == null) { + return 0; + } return mAccessDuration[uidState]; } @@ -2397,34 +3369,29 @@ public class AppOpsManager { parcel.writeLongArray(mAccessDuration); } - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; - } - final HistoricalOpEntry otherInstance = (HistoricalOpEntry) other; - if (mOp != otherInstance.mOp) { - return false; - } - if (!Arrays.equals(mAccessCount, otherInstance.mAccessCount)) { - return false; + private void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalOp(this); + } + + private @NonNull long[] getOrCreateAccessCount() { + if (mAccessCount == null) { + mAccessCount = new long[_NUM_UID_STATE]; } - if (!Arrays.equals(mRejectCount, otherInstance.mRejectCount)) { - return false; + return mAccessCount; + } + + private @NonNull long[] getOrCreateRejectCount() { + if (mRejectCount == null) { + mRejectCount = new long[_NUM_UID_STATE]; } - return Arrays.equals(mAccessDuration, otherInstance.mAccessDuration); + return mRejectCount; } - @Override - public int hashCode() { - int result = mOp; - result = 31 * result + Arrays.hashCode(mAccessCount); - result = 31 * result + Arrays.hashCode(mRejectCount); - result = 31 * result + Arrays.hashCode(mAccessDuration); - return result; + private @NonNull long[] getOrCreateAccessDuration() { + if (mAccessDuration == null) { + mAccessDuration = new long[_NUM_UID_STATE]; + } + return mAccessDuration; } /** @@ -2444,17 +3411,62 @@ public class AppOpsManager { return totalCount; } - public static final Creator<HistoricalOpEntry> CREATOR = new Creator<HistoricalOpEntry>() { + /** + * Multiplies the entries in the array with the passed in scale factor and + * rounds the result at up 0.5 boundary. + * + * @param data The data to scale. + * @param scaleFactor The scale factor. + */ + private static void scale(@NonNull long[] data, double scaleFactor) { + if (data != null) { + for (int i = 0; i < _NUM_UID_STATE; i++) { + data[i] = (long) HistoricalOps.round((double) data[i] * scaleFactor); + } + } + } + + public static final Creator<HistoricalOp> CREATOR = new Creator<HistoricalOp>() { @Override - public @NonNull HistoricalOpEntry createFromParcel(@NonNull Parcel source) { - return new HistoricalOpEntry(source); + public @NonNull HistoricalOp createFromParcel(@NonNull Parcel source) { + return new HistoricalOp(source); } @Override - public @NonNull HistoricalOpEntry[] newArray(int size) { - return new HistoricalOpEntry[size]; + public @NonNull HistoricalOp[] newArray(int size) { + return new HistoricalOp[size]; } }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalOp other = (HistoricalOp) obj; + if (mOp != other.mOp) { + return false; + } + if (!Arrays.equals(mAccessCount, other.mAccessCount)) { + return false; + } + if (!Arrays.equals(mRejectCount, other.mRejectCount)) { + return false; + } + return Arrays.equals(mAccessDuration, other.mAccessDuration); + } + + @Override + public int hashCode() { + int result = mOp; + result = 31 * result + Arrays.hashCode(mAccessCount); + result = 31 * result + Arrays.hashCode(mRejectCount); + result = 31 * result + Arrays.hashCode(mAccessDuration); + return result; + } } /** @@ -2520,6 +3532,24 @@ public class AppOpsManager { * @param ops The set of operations you are interested in, or null if you want all of them. * @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) + public @NonNull List<AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[] ops) { + final int opCount = ops.length; + final int[] opCodes = new int[opCount]; + for (int i = 0; i < opCount; i++) { + opCodes[i] = sOpStrToOp.get(ops[i]); + } + final List<AppOpsManager.PackageOps> result = getPackagesForOps(opCodes); + return (result != null) ? result : Collections.emptyList(); + } + + /** + * Retrieve current operation state for all applications. + * + * @param ops The set of operations you are interested in, or null if you want all of them. + * @hide + */ @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) @UnsupportedAppUsage public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) { @@ -2536,11 +3566,17 @@ public class AppOpsManager { * @param uid The uid of the application of interest. * @param packageName The name of the application of interest. * @param ops The set of operations you are interested in, or null if you want all of them. + * + * @deprecated The int op codes are not stable and you should use the string based op + * names which are stable and namespaced. Use + * {@link #getOpsForPackage(int, String, String...)})}. + * * @hide */ + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) - public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) { + public List<PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) { try { return mService.getOpsForPackage(uid, packageName, ops); } catch (RemoteException e) { @@ -2549,7 +3585,49 @@ public class AppOpsManager { } /** - * Retrieve historical app op stats for a package. + * Retrieve current operation state for one application. The UID and the + * package must match. + * + * @param uid The uid of the application of interest. + * @param packageName The name of the application of interest. + * @param ops The set of operations you are interested in, or null if you want all of them. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) + public @NonNull List<AppOpsManager.PackageOps> getOpsForPackage(int uid, + @NonNull String packageName, @Nullable String... ops) { + int[] opCodes = null; + if (ops != null) { + opCodes = new int[ops.length]; + for (int i = 0; i < ops.length; i++) { + opCodes[i] = strOpToOp(ops[i]); + } + } + try { + final List<PackageOps> result = mService.getOpsForPackage(uid, packageName, opCodes); + if (result == null) { + return Collections.emptyList(); + } + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve historical app op stats for a period. + * + * <p>Historical data can be obtained + * for a specific package by specifying the <code>packageName</code> argument, + * for a specific UID if specifying the <code>uid</code> argument, for a + * specific package in a UID by specifying the <code>packageName</code> + * and the <code>uid</code> arguments, for all packages by passing + * {@link android.os.Process#INVALID_UID} and <code>null</code> for the + * <code>uid</code> and <code>packageName</code> arguments, respectively. + * Similarly, you can specify the <code>opNames</code> argument to get + * data only for these ops or <code>null</code> for all ops. * * @param uid The UID to query for. * @param packageName The package to query for. @@ -2558,10 +3636,12 @@ public class AppOpsManager { * negative. * @param endTimeMillis The end of the interval in milliseconds since * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after - * {@code beginTimeMillis}. + * {@code beginTimeMillis}. Pass {@link Long#MAX_VALUE} to get the most recent + * history including ops that happen while this call is in flight. * @param opNames The ops to query for. Pass {@code null} for all ops. - * - * @return The historical ops or {@code null} if there are no ops for this package. + * @param executor Executor on which to run the callback. If <code>null</code> + * the callback is executed on the default executor running on the main thread. + * @param callback Callback on which to deliver the result. * * @throws IllegalArgumentException If any of the argument contracts is violated. * @@ -2570,47 +3650,78 @@ public class AppOpsManager { @TestApi @SystemApi @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) - public @Nullable HistoricalPackageOps getHistoricalPackagesOps( - int uid, @NonNull String packageName, @Nullable String[] opNames, - long beginTimeMillis, long endTimeMillis) { + public void getHistoricalOps(int uid, @Nullable String packageName, + @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @NonNull Executor executor, @NonNull Consumer<HistoricalOps> callback) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); try { - return mService.getHistoricalPackagesOps(uid, packageName, opNames, - beginTimeMillis, endTimeMillis); + mService.getHistoricalOps(uid, packageName, opNames, beginTimeMillis, endTimeMillis, + new RemoteCallback((result) -> { + final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.accept(ops)); + } finally { + Binder.restoreCallingIdentity(identity); + } + })); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Retrieve historical app op stats for all packages. + * Retrieve historical app op stats for a period. + * + * <p>Historical data can be obtained + * for a specific package by specifying the <code>packageName</code> argument, + * for a specific UID if specifying the <code>uid</code> argument, for a + * specific package in a UID by specifying the <code>packageName</code> + * and the <code>uid</code> arguments, for all packages by passing + * {@link android.os.Process#INVALID_UID} and <code>null</code> for the + * <code>uid</code> and <code>packageName</code> arguments, respectively. + * Similarly, you can specify the <code>opNames</code> argument to get + * data only for these ops or <code>null</code> for all ops. + * <p> + * This method queries only the on disk state and the returned ops are raw, + * which is their times are relative to the history start as opposed to the + * epoch start. * + * @param uid The UID to query for. + * @param packageName The package to query for. * @param beginTimeMillis The beginning of the interval in milliseconds since - * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be non - * negative. + * history start. History time grows as one goes into the past. * @param endTimeMillis The end of the interval in milliseconds since - * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after + * history start. History time grows as one goes into the past. Must be after * {@code beginTimeMillis}. * @param opNames The ops to query for. Pass {@code null} for all ops. - * - * @return The historical ops or an empty list if there are no ops for any package. + * @param executor Executor on which to run the callback. If <code>null</code> + * the callback is executed on the default executor running on the main thread. + * @param callback Callback on which to deliver the result. * * @throws IllegalArgumentException If any of the argument contracts is violated. * * @hide */ @TestApi - @SystemApi @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) - public @NonNull List<HistoricalPackageOps> getAllHistoricPackagesOps( - @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis) { + public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName, + @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @Nullable Executor executor, @NonNull Consumer<HistoricalOps> callback) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); try { - @SuppressWarnings("unchecked") - final ParceledListSlice<HistoricalPackageOps> payload = - mService.getAllHistoricalPackagesOps(opNames, beginTimeMillis, endTimeMillis); - if (payload != null) { - return payload.getList(); - } - return Collections.emptyList(); + mService.getHistoricalOpsFromDiskRaw(uid, packageName, opNames, beginTimeMillis, + endTimeMillis, new RemoteCallback((result) -> { + final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.accept(ops)); + } finally { + Binder.restoreCallingIdentity(identity); + } + })); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2627,7 +3738,7 @@ public class AppOpsManager { * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) - public void setUidMode(int code, int uid, int mode) { + public void setUidMode(int code, int uid, @Mode int mode) { try { mService.setUidMode(code, uid, mode); } catch (RemoteException e) { @@ -2648,7 +3759,7 @@ public class AppOpsManager { @SystemApi @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) - public void setUidMode(String appOp, int uid, int mode) { + public void setUidMode(String appOp, int uid, @Mode int mode) { try { mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode); } catch (RemoteException e) { @@ -2680,7 +3791,7 @@ public class AppOpsManager { /** @hide */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) - public void setMode(int code, int uid, String packageName, int mode) { + public void setMode(int code, int uid, String packageName, @Mode int mode) { try { mService.setMode(code, uid, packageName, mode); } catch (RemoteException e) { @@ -2701,7 +3812,7 @@ public class AppOpsManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) - public void setMode(String op, int uid, String packageName, int mode) { + public void setMode(String op, int uid, String packageName, @Mode int mode) { try { mService.setMode(strOpToOp(op), uid, packageName, mode); } catch (RemoteException e) { @@ -2722,7 +3833,7 @@ public class AppOpsManager { */ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) @UnsupportedAppUsage - public void setRestriction(int code, @AttributeUsage int usage, int mode, + public void setRestriction(int code, @AttributeUsage int usage, @Mode int mode, String[] exceptionPackages) { try { final int uid = Binder.getCallingUid(); @@ -3507,6 +4618,104 @@ public class AppOpsManager { } /** + * Configures the app ops persistence for testing. + * + * @param mode The mode in which the historical registry operates. + * @param baseSnapshotInterval The base interval on which we would be persisting a snapshot of + * the historical data. The history is recursive where every subsequent step encompasses + * {@code compressionStep} longer interval with {@code compressionStep} distance between + * snapshots. + * @param compressionStep The compression step in every iteration. + * + * @see #HISTORICAL_MODE_DISABLED + * @see #HISTORICAL_MODE_ENABLED_ACTIVE + * @see #HISTORICAL_MODE_ENABLED_PASSIVE + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void setHistoryParameters(@HistoricalMode int mode, long baseSnapshotInterval, + int compressionStep) { + try { + mService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Offsets the history by the given duration. + * + * @param offsetMillis The offset duration. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void offsetHistory(long offsetMillis) { + try { + mService.offsetHistory(offsetMillis); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds ops to the history directly. This could be useful for testing especially + * when the historical registry operates in {@link #HISTORICAL_MODE_ENABLED_PASSIVE} + * mode. + * + * @param ops The ops to add to the history. + * + * @see #setHistoryParameters(int, long, int) + * @see #HISTORICAL_MODE_ENABLED_PASSIVE + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void addHistoricalOps(@NonNull HistoricalOps ops) { + try { + mService.addHistoricalOps(ops); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Resets the app ops persistence for testing. + * + * @see #setHistoryParameters(int, long, int) + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void resetHistoryParameters() { + try { + mService.resetHistoryParameters(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Clears all app ops history. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void clearHistory() { + try { + mService.clearHistory(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns all supported operation names. * @hide */ @@ -3538,4 +4747,64 @@ public class AppOpsManager { } return time; } + + /** @hide */ + public static String uidStateToString(@UidState int uidState) { + switch (uidState) { + case UID_STATE_PERSISTENT: { + return "UID_STATE_PERSISTENT"; + } + case UID_STATE_TOP: { + return "UID_STATE_TOP"; + } + case UID_STATE_FOREGROUND_SERVICE: { + return "UID_STATE_FOREGROUND_SERVICE"; + } + case UID_STATE_FOREGROUND: { + return "UID_STATE_FOREGROUND"; + } + case UID_STATE_BACKGROUND: { + return "UID_STATE_BACKGROUND"; + } + case UID_STATE_CACHED: { + return "UID_STATE_CACHED"; + } + default: { + return "UNKNOWN"; + } + } + } + + /** @hide */ + public static int parseHistoricalMode(@NonNull String mode) { + switch (mode) { + case "HISTORICAL_MODE_ENABLED_ACTIVE": { + return HISTORICAL_MODE_ENABLED_ACTIVE; + } + case "HISTORICAL_MODE_ENABLED_PASSIVE": { + return HISTORICAL_MODE_ENABLED_PASSIVE; + } + default: { + return HISTORICAL_MODE_DISABLED; + } + } + } + + /** @hide */ + public static String historicalModeToString(@HistoricalMode int mode) { + switch (mode) { + case HISTORICAL_MODE_DISABLED: { + return "HISTORICAL_MODE_DISABLED"; + } + case HISTORICAL_MODE_ENABLED_ACTIVE: { + return "HISTORICAL_MODE_ENABLED_ACTIVE"; + } + case HISTORICAL_MODE_ENABLED_PASSIVE: { + return "HISTORICAL_MODE_ENABLED_PASSIVE"; + } + default: { + return "UNKNOWN"; + } + } + } } diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index b5295d15ee8d..07dbb6bee9fd 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -124,6 +124,10 @@ public abstract class ClientTransactionHandler { /** Restart the activity after it was stopped. */ public abstract void performRestartActivity(IBinder token, boolean start); + /** Set pending activity configuration in case it will be updated by other transaction item. */ + public abstract void updatePendingActivityConfiguration(IBinder activityToken, + Configuration overrideConfig); + /** Deliver activity (override) configuration change. */ public abstract void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig, int displayId); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 9ddf4bd0870c..d827f6cb9654 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -21,8 +21,11 @@ import android.accounts.IAccountManager; import android.app.ContextImpl.ServiceInitializationState; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; +import android.app.contentsuggestions.ContentSuggestionsManager; +import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.job.IJobScheduler; import android.app.job.JobScheduler; +import android.app.prediction.AppPredictionManager; import android.app.role.RoleManager; import android.app.slice.SliceManager; import android.app.timedetector.TimeDetector; @@ -95,8 +98,10 @@ import android.net.ConnectivityThread; import android.net.EthernetManager; import android.net.IConnectivityManager; import android.net.IEthernetManager; +import android.net.IIpMemoryStore; import android.net.IIpSecService; import android.net.INetworkPolicyManager; +import android.net.IpMemoryStore; import android.net.IpSecManager; import android.net.NetworkPolicyManager; import android.net.NetworkScoreManager; @@ -120,6 +125,7 @@ import android.net.wifi.rtt.WifiRttManager; import android.nfc.NfcManager; import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.BugreportManager; import android.os.Build; import android.os.DeviceIdleManager; import android.os.DropBoxManager; @@ -127,6 +133,7 @@ import android.os.HardwarePropertiesManager; import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; import android.os.IDeviceIdleController; +import android.os.IDumpstate; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; @@ -321,10 +328,21 @@ final class SystemServiceRegistry { registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class, new StaticServiceFetcher<NetworkStack>() { - @Override - public NetworkStack createService() { - return new NetworkStack(); - }}); + @Override + public NetworkStack createService() { + return new NetworkStack(); + }}); + + registerService(Context.IP_MEMORY_STORE_SERVICE, IpMemoryStore.class, + new CachedServiceFetcher<IpMemoryStore>() { + @Override + public IpMemoryStore createService(final ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.IP_MEMORY_STORE_SERVICE); + IIpMemoryStore service = IIpMemoryStore.Stub.asInterface(b); + return new IpMemoryStore(ctx, service); + }}); registerService(Context.IPSEC_SERVICE, IpSecManager.class, new CachedServiceFetcher<IpSecManager>() { @@ -1069,6 +1087,16 @@ final class SystemServiceRegistry { return new IncidentManager(ctx); }}); + registerService(Context.BUGREPORT_SERVICE, BugreportManager.class, + new CachedServiceFetcher<BugreportManager>() { + @Override + public BugreportManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow(Context.BUGREPORT_SERVICE); + return new BugreportManager(ctx.getOuterContext(), + IDumpstate.Stub.asInterface(b)); + }}); + registerService(Context.AUTOFILL_MANAGER_SERVICE, AutofillManager.class, new CachedServiceFetcher<AutofillManager>() { @Override @@ -1095,6 +1123,29 @@ final class SystemServiceRegistry { return null; }}); + registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class, + new CachedServiceFetcher<AppPredictionManager>() { + @Override + public AppPredictionManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return new AppPredictionManager(ctx); + } + }); + + registerService(Context.CONTENT_SUGGESTIONS_SERVICE, + ContentSuggestionsManager.class, + new CachedServiceFetcher<ContentSuggestionsManager>() { + @Override + public ContentSuggestionsManager createService(ContextImpl ctx) { + // No throw as this is an optional service + IBinder b = ServiceManager.getService( + Context.CONTENT_SUGGESTIONS_SERVICE); + IContentSuggestionsManager service = + IContentSuggestionsManager.Stub.asInterface(b); + return new ContentSuggestionsManager(service); + } + }); + registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() { @Override public VrManager createService(ContextImpl ctx) throws ServiceNotFoundException { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 7da67d9e14e4..9247486dff40 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6815,8 +6815,12 @@ public class DevicePolicyManager { } /** - * Returns the list of input methods permitted by the device or profiles - * owners of the current user. (*Not* calling user, due to a limitation in InputMethodManager.) + * Returns the list of input methods permitted by the device or profiles owners. + * + * <p>On {@link android.os.Build.VERSION_CODES#Q} and later devices, this method returns the + * result for the calling user.</p> + * + * <p>On Android P and prior devices, this method returns the result for the current user.</p> * * <p>Null means all input methods are allowed, if a non-null list is returned * it will contain the intersection of the permitted lists for any device or profile diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index cc4d4b1ae7ec..7d03f00611d4 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -1699,7 +1699,8 @@ public class AssistStructure implements Parcelable { @Override public void setVisibility(int visibility) { - mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_VISIBILITY_MASK) | visibility; + mNode.mFlags = (mNode.mFlags & ~ViewNode.FLAGS_VISIBILITY_MASK) + | (visibility & ViewNode.FLAGS_VISIBILITY_MASK); } @Override diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl b/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl new file mode 100644 index 000000000000..a66413becc29 --- /dev/null +++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +parcelable ClassificationsRequest; diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java new file mode 100644 index 000000000000..3f715186abfb --- /dev/null +++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * @hide + */ +@SystemApi +public final class ClassificationsRequest implements Parcelable { + @NonNull + private final List<ContentSelection> mSelections; + @Nullable + private final Bundle mExtras; + + private ClassificationsRequest(@NonNull List<ContentSelection> selections, + @Nullable Bundle extras) { + mSelections = selections; + mExtras = extras; + } + + /** + * Return request selections. + */ + public List<ContentSelection> getSelections() { + return mSelections; + } + + /** + * Return the request extras. + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(mSelections); + dest.writeBundle(mExtras); + } + + public static final Creator<ClassificationsRequest> CREATOR = + new Creator<ClassificationsRequest>() { + @Override + public ClassificationsRequest createFromParcel(Parcel source) { + return new ClassificationsRequest( + source.createTypedArrayList(ContentSelection.CREATOR), + source.readBundle()); + } + + @Override + public ClassificationsRequest[] newArray(int size) { + return new ClassificationsRequest[size]; + } + }; + + /** + * A builder for classifications request events. + * @hide + */ + @SystemApi + public static final class Builder { + + private final List<ContentSelection> mSelections; + private Bundle mExtras; + + public Builder(@NonNull List<ContentSelection> selections) { + mSelections = selections; + } + + /** + * Sets the request extras. + */ + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Builds a new request instance. + */ + public ClassificationsRequest build() { + return new ClassificationsRequest(mSelections, mExtras); + } + } +} diff --git a/core/java/android/app/contentsuggestions/ContentClassification.aidl b/core/java/android/app/contentsuggestions/ContentClassification.aidl new file mode 100644 index 000000000000..f67b2c8cea36 --- /dev/null +++ b/core/java/android/app/contentsuggestions/ContentClassification.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +parcelable ContentClassification; diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java new file mode 100644 index 000000000000..f18520f1053c --- /dev/null +++ b/core/java/android/app/contentsuggestions/ContentClassification.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +@SystemApi +public final class ContentClassification implements Parcelable { + @NonNull + private final String mClassificationId; + @NonNull + private final Bundle mExtras; + + public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) { + mClassificationId = classificationId; + mExtras = extras; + } + + /** + * Return the classification id. + */ + public String getId() { + return mClassificationId; + } + + /** + * Return the classification extras. + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mClassificationId); + dest.writeBundle(mExtras); + } + + public static final Creator<ContentClassification> CREATOR = + new Creator<ContentClassification>() { + @Override + public ContentClassification createFromParcel(Parcel source) { + return new ContentClassification( + source.readString(), + source.readBundle()); + } + + @Override + public ContentClassification[] newArray(int size) { + return new ContentClassification[size]; + } + }; +} diff --git a/core/java/android/app/contentsuggestions/ContentSelection.aidl b/core/java/android/app/contentsuggestions/ContentSelection.aidl new file mode 100644 index 000000000000..e626d69278a4 --- /dev/null +++ b/core/java/android/app/contentsuggestions/ContentSelection.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +parcelable ContentSelection; diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java new file mode 100644 index 000000000000..a8917f782e83 --- /dev/null +++ b/core/java/android/app/contentsuggestions/ContentSelection.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +@SystemApi +public final class ContentSelection implements Parcelable { + @NonNull + private final String mSelectionId; + @NonNull + private final Bundle mExtras; + + public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) { + mSelectionId = selectionId; + mExtras = extras; + } + + /** + * Return the selection id. + */ + public String getId() { + return mSelectionId; + } + + /** + * Return the selection extras. + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mSelectionId); + dest.writeBundle(mExtras); + } + + public static final Creator<ContentSelection> CREATOR = + new Creator<ContentSelection>() { + @Override + public ContentSelection createFromParcel(Parcel source) { + return new ContentSelection( + source.readString(), + source.readBundle()); + } + + @Override + public ContentSelection[] newArray(int size) { + return new ContentSelection[size]; + } + }; +} diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java new file mode 100644 index 000000000000..b4d89774cc3b --- /dev/null +++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Binder; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * When provided with content from an app, can suggest selections and classifications of that + * content. + * + * <p>The content is mainly a snapshot of a running task, the selections will be text and image + * selections with that image content. These mSelections can then be classified to find actions and + * entities on those selections. + * + * <p>Only accessible to blessed components such as Overview. + * + * @hide + */ +@SystemApi +public final class ContentSuggestionsManager { + private static final String TAG = ContentSuggestionsManager.class.getSimpleName(); + + @Nullable + private final IContentSuggestionsManager mService; + + /** @hide */ + public ContentSuggestionsManager(@Nullable IContentSuggestionsManager service) { + mService = service; + } + + /** + * Hints to the system that a new context image for the provided task should be sent to the + * system content suggestions service. + * + * @param taskId of the task to snapshot. + * @param imageContextRequestExtras sent with with request to provide implementation specific + * extra information. + */ + public void provideContextImage(int taskId, @NonNull Bundle imageContextRequestExtras) { + if (mService == null) { + Log.e(TAG, "provideContextImage called, but no ContentSuggestionsManager configured"); + return; + } + + try { + mService.provideContextImage(taskId, imageContextRequestExtras); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Suggest content selections, based on the provided task id and optional + * location on screen provided in the request. Called after provideContextImage(). + * The result can be passed to + * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)} + * to classify actions and entities on these selections. + * + * @param request containing the task and point location. + * @param callbackExecutor to execute the provided callback on. + * @param callback to receive the selections. + */ + public void suggestContentSelections( + @NonNull SelectionsRequest request, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull SelectionsCallback callback) { + if (mService == null) { + Log.e(TAG, + "suggestContentSelections called, but no ContentSuggestionsManager configured"); + return; + } + + try { + mService.suggestContentSelections( + request, new SelectionsCallbackWrapper(callback, callbackExecutor)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Classify actions and entities in content selections, as returned from + * suggestContentSelections. Note these selections may be modified by the + * caller before being passed here. + * + * @param request containing the selections to classify. + * @param callbackExecutor to execute the provided callback on. + * @param callback to receive the classifications. + */ + public void classifyContentSelections( + @NonNull ClassificationsRequest request, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull ClassificationsCallback callback) { + if (mService == null) { + Log.e(TAG, "classifyContentSelections called, " + + "but no ContentSuggestionsManager configured"); + return; + } + + try { + mService.classifyContentSelections( + request, new ClassificationsCallbackWrapper(callback, callbackExecutor)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Report telemetry for interaction with suggestions / classifications. + * + * @param requestId the id for the associated interaction + * @param interaction to report back to the system content suggestions service. + */ + public void notifyInteraction(@NonNull String requestId, @NonNull Bundle interaction) { + if (mService == null) { + Log.e(TAG, "notifyInteraction called, but no ContentSuggestionsManager configured"); + return; + } + + try { + mService.notifyInteraction(requestId, interaction); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Callback to receive content selections from + * {@link #suggestContentSelections(SelectionsRequest, Executor, SelectionsCallback)}. + */ + public interface SelectionsCallback { + /** + * Async callback called when the content suggestions service has selections available. + * These can be modified and sent back to the manager for classification. The contents of + * the selection is implementation dependent. + * + * @param statusCode as defined by the implementation of content suggestions service. + * @param selections not {@code null}, but can be size {@code 0}. + */ + void onContentSelectionsAvailable( + int statusCode, @NonNull List<ContentSelection> selections); + } + + /** + * Callback to receive classifications from + * {@link #classifyContentSelections(ClassificationsRequest, Executor, ClassificationsCallback)} + */ + public interface ClassificationsCallback { + /** + * Async callback called when the content suggestions service has classified selections. The + * contents of the classification is implementation dependent. + * + * @param statusCode as defined by the implementation of content suggestions service. + * @param classifications not {@code null}, but can be size {@code 0}. + */ + void onContentClassificationsAvailable(int statusCode, + @NonNull List<ContentClassification> classifications); + } + + private static class SelectionsCallbackWrapper extends ISelectionsCallback.Stub { + private final SelectionsCallback mCallback; + private final Executor mExecutor; + + SelectionsCallbackWrapper( + @NonNull SelectionsCallback callback, @NonNull Executor executor) { + mCallback = callback; + mExecutor = executor; + } + + @Override + public void onContentSelectionsAvailable( + int statusCode, List<ContentSelection> selections) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mCallback.onContentSelectionsAvailable(statusCode, selections)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private static final class ClassificationsCallbackWrapper extends + IClassificationsCallback.Stub { + private final ClassificationsCallback mCallback; + private final Executor mExecutor; + + ClassificationsCallbackWrapper(@NonNull ClassificationsCallback callback, + @NonNull Executor executor) { + mCallback = callback; + mExecutor = executor; + } + + @Override + public void onContentClassificationsAvailable( + int statusCode, List<ContentClassification> classifications) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mCallback.onContentClassificationsAvailable(statusCode, classifications)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } +} diff --git a/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl b/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl new file mode 100644 index 000000000000..69f5d5595986 --- /dev/null +++ b/core/java/android/app/contentsuggestions/IClassificationsCallback.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +import android.app.contentsuggestions.ContentClassification; + +/** @hide */ +oneway interface IClassificationsCallback { + void onContentClassificationsAvailable( + int statusCode, in List<ContentClassification> classifications); +} diff --git a/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl new file mode 100644 index 000000000000..24f5ad866635 --- /dev/null +++ b/core/java/android/app/contentsuggestions/IContentSuggestionsManager.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +import android.app.contentsuggestions.IClassificationsCallback; +import android.app.contentsuggestions.ISelectionsCallback; +import android.app.contentsuggestions.ClassificationsRequest; +import android.app.contentsuggestions.SelectionsRequest; +import android.os.Bundle; + +/** @hide */ +oneway interface IContentSuggestionsManager { + void provideContextImage( + int taskId, + in Bundle imageContextRequestExtras); + void suggestContentSelections( + in SelectionsRequest request, + in ISelectionsCallback callback); + void classifyContentSelections( + in ClassificationsRequest request, + in IClassificationsCallback callback); + void notifyInteraction(in String requestId, in Bundle interaction); +} diff --git a/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl b/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl new file mode 100644 index 000000000000..19525832a4cd --- /dev/null +++ b/core/java/android/app/contentsuggestions/ISelectionsCallback.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +import android.app.contentsuggestions.ContentSelection; +import android.os.Bundle; + +/** @hide */ +oneway interface ISelectionsCallback { + void onContentSelectionsAvailable(int statusCode, in List<ContentSelection> selections); +} diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.aidl b/core/java/android/app/contentsuggestions/SelectionsRequest.aidl new file mode 100644 index 000000000000..f5ce7c31bd94 --- /dev/null +++ b/core/java/android/app/contentsuggestions/SelectionsRequest.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +parcelable SelectionsRequest; diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java new file mode 100644 index 000000000000..16f3e6b35aa4 --- /dev/null +++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contentsuggestions; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.graphics.Point; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +@SystemApi +public final class SelectionsRequest implements Parcelable { + private final int mTaskId; + @Nullable + private final Point mInterestPoint; + @Nullable + private final Bundle mExtras; + + private SelectionsRequest(int taskId, @Nullable Point interestPoint, @Nullable Bundle extras) { + mTaskId = taskId; + mInterestPoint = interestPoint; + mExtras = extras; + } + + /** + * Return the request task id. + */ + public int getTaskId() { + return mTaskId; + } + + /** + * Return the request point of interest. + */ + public Point getInterestPoint() { + return mInterestPoint; + } + + /** + * Return the request extras. + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mTaskId); + dest.writeTypedObject(mInterestPoint, flags); + dest.writeBundle(mExtras); + } + + public static final Creator<SelectionsRequest> CREATOR = + new Creator<SelectionsRequest>() { + @Override + public SelectionsRequest createFromParcel(Parcel source) { + return new SelectionsRequest( + source.readInt(), source.readTypedObject(Point.CREATOR), source.readBundle()); + } + + @Override + public SelectionsRequest[] newArray(int size) { + return new SelectionsRequest[size]; + } + }; + + /** + * A builder for selections requests events. + * @hide + */ + @SystemApi + public static final class Builder { + + private final int mTaskId; + private Point mInterestPoint; + private Bundle mExtras; + + public Builder(int taskId) { + mTaskId = taskId; + } + + /** + * Sets the request extras. + */ + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Sets the request interest point. + */ + public Builder setInterestPoint(@NonNull Point interestPoint) { + mInterestPoint = interestPoint; + return this; + } + + /** + * Builds a new request instance. + */ + public SelectionsRequest build() { + return new SelectionsRequest(mTaskId, mInterestPoint, mExtras); + } + } +} diff --git a/core/java/android/app/prediction/AppPredictionContext.aidl b/core/java/android/app/prediction/AppPredictionContext.aidl new file mode 100644 index 000000000000..5767bf4dbc9a --- /dev/null +++ b/core/java/android/app/prediction/AppPredictionContext.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.prediction; + +parcelable AppPredictionContext; diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java new file mode 100644 index 000000000000..87ccb660e5bd --- /dev/null +++ b/core/java/android/app/prediction/AppPredictionContext.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.prediction; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * TODO(b/111701043): Add java docs + * + * @hide + */ +@SystemApi +public final class AppPredictionContext implements Parcelable { + + private final int mPredictedTargetCount; + @NonNull + private final String mUiSurface; + @NonNull + private final String mPackageName; + @Nullable + private final Bundle mExtras; + + private AppPredictionContext(@NonNull String uiSurface, int numPredictedTargets, + @NonNull String packageName, @Nullable Bundle extras) { + mUiSurface = uiSurface; + mPredictedTargetCount = numPredictedTargets; + mPackageName = packageName; + mExtras = extras; + } + + private AppPredictionContext(Parcel parcel) { + mUiSurface = parcel.readString(); + mPredictedTargetCount = parcel.readInt(); + mPackageName = parcel.readString(); + mExtras = parcel.readBundle(); + } + + public String getUiSurface() { + return mUiSurface; + } + + public int getPredictedTargetCount() { + return mPredictedTargetCount; + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + @Nullable + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mUiSurface); + dest.writeInt(mPredictedTargetCount); + dest.writeString(mPackageName); + dest.writeBundle(mExtras); + } + + /** + * @see Parcelable.Creator + */ + public static final Parcelable.Creator<AppPredictionContext> CREATOR = + new Parcelable.Creator<AppPredictionContext>() { + public AppPredictionContext createFromParcel(Parcel parcel) { + return new AppPredictionContext(parcel); + } + + public AppPredictionContext[] newArray(int size) { + return new AppPredictionContext[size]; + } + }; + + /** + * A builder for app prediction contexts. + * @hide + */ + @SystemApi + public static final class Builder { + + @NonNull + private final String mPackageName; + + private int mPredictedTargetCount; + @Nullable + private String mUiSurface; + @Nullable + private Bundle mExtras; + + /** + * @hide + */ + public Builder(@NonNull Context context) { + mPackageName = context.getPackageName(); + } + + + /** + * Sets the number of prediction targets as a hint. + */ + public Builder setPredictedTargetCount(int predictedTargetCount) { + mPredictedTargetCount = predictedTargetCount; + return this; + } + + /** + * Sets the UI surface. + */ + public Builder setUiSurface(@Nullable String uiSurface) { + mUiSurface = uiSurface; + return this; + } + + /** + * Sets the extras. + */ + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Builds a new context instance. + */ + public AppPredictionContext build() { + return new AppPredictionContext(mUiSurface, mPredictedTargetCount, mPackageName, + mExtras); + } + } +} diff --git a/core/java/android/app/prediction/AppPredictionManager.java b/core/java/android/app/prediction/AppPredictionManager.java new file mode 100644 index 000000000000..f8578d4533ef --- /dev/null +++ b/core/java/android/app/prediction/AppPredictionManager.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.prediction; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; + +import com.android.internal.util.Preconditions; + +/** + * TODO (b/111701043) : Add java doc + * @hide + */ +@SystemApi +public final class AppPredictionManager { + + private final Context mContext; + + /** + * @hide + */ + public AppPredictionManager(Context context) { + mContext = Preconditions.checkNotNull(context); + } + + /** + * Creates a new app prediction session. + */ + public AppPredictor createAppPredictionSession( + @NonNull AppPredictionContext predictionContext) { + return new AppPredictor(mContext, predictionContext); + } +} diff --git a/core/java/android/app/prediction/AppPredictionSessionId.aidl b/core/java/android/app/prediction/AppPredictionSessionId.aidl new file mode 100644 index 000000000000..e8295261ae2a --- /dev/null +++ b/core/java/android/app/prediction/AppPredictionSessionId.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.prediction; + +parcelable AppPredictionSessionId; diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java new file mode 100644 index 000000000000..1d7308ec2404 --- /dev/null +++ b/core/java/android/app/prediction/AppPredictionSessionId.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.prediction; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * TODO (b/111701043) : Add java doc + * + * @hide + */ +@SystemApi +public final class AppPredictionSessionId implements Parcelable { + + private final String mId; + + /** + * @hide + */ + public AppPredictionSessionId(@NonNull String id) { + mId = id; + } + + private AppPredictionSessionId(Parcel p) { + mId = p.readString(); + } + + @Override + public boolean equals(Object o) { + if (!getClass().equals(o != null ? o.getClass() : null)) return false; + + AppPredictionSessionId other = (AppPredictionSessionId) o; + return mId.equals(other.mId); + } + + @Override + public @NonNull String toString() { + return mId; + } + + @Override + public int hashCode() { + // Ensure that the id has a consistent hash + return mId.hashCode(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + } + + /** + * @see Parcelable.Creator + */ + public static final Parcelable.Creator<AppPredictionSessionId> CREATOR = + new Parcelable.Creator<AppPredictionSessionId>() { + public AppPredictionSessionId createFromParcel(Parcel parcel) { + return new AppPredictionSessionId(parcel); + } + + public AppPredictionSessionId[] newArray(int size) { + return new AppPredictionSessionId[size]; + } + }; +} diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java new file mode 100644 index 000000000000..2ddbd08c7477 --- /dev/null +++ b/core/java/android/app/prediction/AppPredictor.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.prediction; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.prediction.IPredictionCallback.Stub; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArrayMap; +import android.util.Log; + +import dalvik.system.CloseGuard; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +/** + * TODO (b/111701043) : Add java doc + * + * <p> + * Usage: <pre> {@code + * + * class MyActivity { + * private AppPredictor mClient + * + * void onCreate() { + * mClient = new AppPredictor(...) + * } + * + * void onStart() { + * mClient.requestPredictionUpdate(); + * } + * + * void onDestroy() { + * mClient.close(); + * } + * + * }</pre> + * + * @hide + */ +@SystemApi +public final class AppPredictor { + + private static final String TAG = AppPredictor.class.getSimpleName(); + + + private final IPredictionManager mPredictionManager; + private final CloseGuard mCloseGuard = CloseGuard.get(); + private final AtomicBoolean mIsClosed = new AtomicBoolean(false); + + private final AppPredictionSessionId mSessionId; + private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>(); + + /** + * Creates a new Prediction client. + * <p> + * The caller should call {@link AppPredictor#destroy()} to dispose the client once it + * no longer used. + * + * @param predictionContext The prediction context + */ + AppPredictor(@NonNull Context context, @NonNull AppPredictionContext predictionContext) { + IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE); + mPredictionManager = IPredictionManager.Stub.asInterface(b); + mSessionId = new AppPredictionSessionId( + context.getPackageName() + ":" + UUID.randomUUID().toString()); + try { + mPredictionManager.createPredictionSession(predictionContext, mSessionId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to create predictor", e); + return; + } + + mCloseGuard.open("close"); + } + + /** + * Notifies the prediction service of an app target event. + */ + public void notifyAppTargetEvent(@NonNull AppTargetEvent event) { + try { + mPredictionManager.notifyAppTargetEvent(mSessionId, event); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify app target event", e); + } + } + + /** + * Notifies the prediction service when the targets in a launch location are shown to the user. + */ + public void notifyLocationShown(@NonNull String launchLocation, + @NonNull List<AppTargetId> targetIds) { + try { + mPredictionManager.notifyLocationShown(mSessionId, launchLocation, + new ParceledListSlice<>(targetIds)); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify location shown event", e); + } + } + + /** + * Requests the prediction service provide continuous updates of App predictions via the + * provided callback, until the given callback is unregistered. + * + * @see Callback#onTargetsAvailable(List) + */ + public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull AppPredictor.Callback callback) { + if (mRegisteredCallbacks.containsKey(callback)) { + // Skip if this callback is already registered + return; + } + try { + final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor, + callback::onTargetsAvailable); + mPredictionManager.registerPredictionUpdates(mSessionId, callbackWrapper); + mRegisteredCallbacks.put(callback, callbackWrapper); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register for prediction updates", e); + } + } + + /** + * Requests the prediction service to stop providing continuous updates to the provided + * callback until the callback is re-registered. + */ + public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) { + if (!mRegisteredCallbacks.containsKey(callback)) { + // Skip if this callback was never registered + return; + } + try { + final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback); + mPredictionManager.unregisterPredictionUpdates(mSessionId, callbackWrapper); + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister for prediction updates", e); + } + } + + /** + * Requests the prediction service to dispatch a new set of App predictions via the provided + * callback. + * + * @see Callback#onTargetsAvailable(List) + */ + public void requestPredictionUpdate() { + try { + mPredictionManager.requestPredictionUpdate(mSessionId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to request prediction update", e); + } + } + + /** + * Returns a new list of AppTargets sorted based on prediction rank or {@code null} if the + * ranker is not available. + */ + @Nullable + public void sortTargets(@NonNull List<AppTarget> targets, + @NonNull Executor callbackExecutor, @NonNull Consumer<List<AppTarget>> callback) { + try { + mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets), + new CallbackWrapper(callbackExecutor, callback)); + } catch (RemoteException e) { + Log.e(TAG, "Failed to sort targets", e); + } + } + + /** + * Destroys the client and unregisters the callback. Any method on this class after this call + * with throw {@link IllegalStateException}. + * + * TODO(b/111701043): Add state check in other methods. + */ + public void destroy() { + if (!mIsClosed.getAndSet(true)) { + mCloseGuard.close(); + + // Do destroy; + try { + mPredictionManager.onDestroyPredictionSession(mSessionId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify app target event", e); + } + } + } + + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + destroy(); + } finally { + super.finalize(); + } + } + + /** + * Callback for receiving prediction updates. + */ + public interface Callback { + + /** + * Called when a new set of predicted app targets are available. + * @param targets Sorted list of predicted targets + */ + void onTargetsAvailable(@NonNull List<AppTarget> targets); + } + + static class CallbackWrapper extends Stub { + + private final Consumer<List<AppTarget>> mCallback; + private final Executor mExecutor; + + CallbackWrapper(@NonNull Executor callbackExecutor, + @NonNull Consumer<List<AppTarget>> callback) { + mCallback = callback; + mExecutor = callbackExecutor; + } + + @Override + public void onResult(ParceledListSlice result) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.accept(result.getList())); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } +} diff --git a/core/java/android/app/prediction/AppTarget.aidl b/core/java/android/app/prediction/AppTarget.aidl new file mode 100644 index 000000000000..e4e2bc2aaaf5 --- /dev/null +++ b/core/java/android/app/prediction/AppTarget.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.prediction; + +parcelable AppTarget; diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java new file mode 100644 index 000000000000..99c1c44866fe --- /dev/null +++ b/core/java/android/app/prediction/AppTarget.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.prediction; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.pm.ShortcutInfo; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; + +import com.android.internal.util.Preconditions; + +/** + * A representation of a launchable target. + * @hide + */ +@SystemApi +public final class AppTarget implements Parcelable { + + private final AppTargetId mId; + private final String mPackageName; + private final String mClassName; + private final UserHandle mUser; + + private final ShortcutInfo mShortcutInfo; + + private int mRank; + + /** + * @hide + */ + public AppTarget(@NonNull AppTargetId id, @NonNull String packageName, + @Nullable String className, @NonNull UserHandle user) { + mId = id; + mShortcutInfo = null; + + mPackageName = Preconditions.checkNotNull(packageName); + mClassName = className; + mUser = Preconditions.checkNotNull(user); + } + + /** + * @hide + */ + public AppTarget(@NonNull AppTargetId id, @NonNull ShortcutInfo shortcutInfo) { + mId = id; + mShortcutInfo = Preconditions.checkNotNull(shortcutInfo); + + mPackageName = mShortcutInfo.getPackage(); + mUser = mShortcutInfo.getUserHandle(); + mClassName = null; + } + + private AppTarget(Parcel parcel) { + mId = parcel.readTypedObject(AppTargetId.CREATOR); + mShortcutInfo = parcel.readTypedObject(ShortcutInfo.CREATOR); + if (mShortcutInfo == null) { + mPackageName = parcel.readString(); + mClassName = parcel.readString(); + mUser = UserHandle.of(parcel.readInt()); + } else { + mPackageName = mShortcutInfo.getPackage(); + mUser = mShortcutInfo.getUserHandle(); + mClassName = null; + } + mRank = parcel.readInt(); + } + + /** + * Returns the target id. + */ + @NonNull + public AppTargetId getId() { + return mId; + } + + /** + * Returns the class name for the app target. + */ + @Nullable + public String getClassName() { + return mClassName; + } + + /** + * Returns the package name for the app target. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the user for the app target. + */ + @NonNull + public UserHandle getUser() { + return mUser; + } + + /** + * Returns the shortcut info for the target. + */ + @Nullable + public ShortcutInfo getShortcutInfo() { + return mShortcutInfo; + } + + /** + * Sets the rank of the for the target. + * @hide + */ + public void setRank(int rank) { + mRank = rank; + } + + public int getRank() { + return mRank; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedObject(mId, flags); + dest.writeTypedObject(mShortcutInfo, flags); + if (mShortcutInfo == null) { + dest.writeString(mPackageName); + dest.writeString(mClassName); + dest.writeInt(mUser.getIdentifier()); + } + dest.writeInt(mRank); + } + + /** + * @see Parcelable.Creator + */ + public static final Parcelable.Creator<AppTarget> CREATOR = + new Parcelable.Creator<AppTarget>() { + public AppTarget createFromParcel(Parcel parcel) { + return new AppTarget(parcel); + } + + public AppTarget[] newArray(int size) { + return new AppTarget[size]; + } + }; +} diff --git a/core/java/android/app/prediction/AppTargetEvent.aidl b/core/java/android/app/prediction/AppTargetEvent.aidl new file mode 100644 index 000000000000..ebed2dadbee6 --- /dev/null +++ b/core/java/android/app/prediction/AppTargetEvent.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.prediction; + +parcelable AppTargetEvent; diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java new file mode 100644 index 000000000000..18317e1bf99d --- /dev/null +++ b/core/java/android/app/prediction/AppTargetEvent.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.prediction; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A representation of an app target event. + * @hide + */ +@SystemApi +public final class AppTargetEvent implements Parcelable { + + /** + * @hide + */ + @IntDef({ACTION_LAUNCH, ACTION_DISMISS, ACTION_PIN}) + @Retention(RetentionPolicy.SOURCE) + public @interface ActionType {} + + /** + * Event type constant indicating an app target has been launched. + */ + public static final int ACTION_LAUNCH = 1; + + /** + * Event type constant indicating an app target has been dismissed. + */ + public static final int ACTION_DISMISS = 2; + + /** + * Event type constant indicating an app target has been pinned. + */ + public static final int ACTION_PIN = 3; + + private final AppTarget mTarget; + private final String mLocation; + private final int mAction; + + private AppTargetEvent(@Nullable AppTarget target, @Nullable String location, + @ActionType int actionType) { + mTarget = target; + mLocation = location; + mAction = actionType; + } + + private AppTargetEvent(Parcel parcel) { + mTarget = parcel.readParcelable(null); + mLocation = parcel.readString(); + mAction = parcel.readInt(); + } + + /** + * Returns the app target. + */ + @Nullable + public AppTarget getTarget() { + return mTarget; + } + + /** + * Returns the launch location. + */ + @NonNull + public String getLaunchLocation() { + return mLocation; + } + + /** + * Returns the action type. + */ + @NonNull + public int getAction() { + return mAction; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mTarget, 0); + dest.writeString(mLocation); + dest.writeInt(mAction); + } + + /** + * @see Creator + */ + public static final Creator<AppTargetEvent> CREATOR = + new Creator<AppTargetEvent>() { + public AppTargetEvent createFromParcel(Parcel parcel) { + return new AppTargetEvent(parcel); + } + + public AppTargetEvent[] newArray(int size) { + return new AppTargetEvent[size]; + } + }; + + /** + * A builder for app target events. + * @hide + */ + @SystemApi + public static final class Builder { + private AppTarget mTarget; + private String mLocation; + private @ActionType int mAction; + + public Builder(@Nullable AppTarget target, @ActionType int actionType) { + mTarget = target; + mAction = actionType; + } + + /** + * Sets the launch location. + */ + public Builder setLaunchLocation(String location) { + mLocation = location; + return this; + } + + /** + * Builds a new event instance. + */ + public AppTargetEvent build() { + return new AppTargetEvent(mTarget, mLocation, mAction); + } + } +} diff --git a/core/java/android/app/prediction/AppTargetId.aidl b/core/java/android/app/prediction/AppTargetId.aidl new file mode 100644 index 000000000000..bf69eeaca691 --- /dev/null +++ b/core/java/android/app/prediction/AppTargetId.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.prediction; + +parcelable AppTargetId; diff --git a/core/java/android/app/prediction/AppTargetId.java b/core/java/android/app/prediction/AppTargetId.java new file mode 100644 index 000000000000..0b8fb47377d2 --- /dev/null +++ b/core/java/android/app/prediction/AppTargetId.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app.prediction; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The id for a prediction target. + * @hide + */ +@SystemApi +public final class AppTargetId implements Parcelable { + + @NonNull + private final String mId; + + /** + * @hide + */ + public AppTargetId(@NonNull String id) { + mId = id; + } + + private AppTargetId(Parcel parcel) { + mId = parcel.readString(); + } + + /** + * Returns the id. + * @hide + */ + @NonNull + public String getId() { + return mId; + } + + @Override + public boolean equals(Object o) { + if (!getClass().equals(o != null ? o.getClass() : null)) return false; + + AppTargetId other = (AppTargetId) o; + return mId.equals(other.mId); + } + + @Override + public int hashCode() { + // Ensure that the id has a consistent hash + return mId.hashCode(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + } + + /** + * @see Creator + */ + public static final Creator<AppTargetId> CREATOR = + new Creator<AppTargetId>() { + public AppTargetId createFromParcel(Parcel parcel) { + return new AppTargetId(parcel); + } + + public AppTargetId[] newArray(int size) { + return new AppTargetId[size]; + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/core/java/android/app/prediction/IPredictionCallback.aidl index 065222762b33..f6f241e5560d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java +++ b/core/java/android/app/prediction/IPredictionCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 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. @@ -14,21 +14,14 @@ * limitations under the License. */ -package com.android.systemui.statusbar; +package android.app.prediction; -import com.android.internal.statusbar.StatusBarIcon; - -import java.util.ArrayList; -import java.util.List; +import android.content.pm.ParceledListSlice; /** - * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them - * in a linear layout + * @hide */ -public class StatusBarIconContainer { - private final List<StatusBarIcon> mIcons = new ArrayList<>(); +oneway interface IPredictionCallback { - public StatusBarIconContainer(List<StatusBarIcon> icons) { - mIcons.addAll(icons); - } + void onResult(in ParceledListSlice result); } diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl new file mode 100644 index 000000000000..114a1ffb0eb2 --- /dev/null +++ b/core/java/android/app/prediction/IPredictionManager.aidl @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.prediction; + +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.IPredictionCallback; +import android.content.pm.ParceledListSlice; + +/** + * @hide + */ +interface IPredictionManager { + + void createPredictionSession(in AppPredictionContext context, + in AppPredictionSessionId sessionId); + + void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event); + + void notifyLocationShown(in AppPredictionSessionId sessionId, in String launchLocation, + in ParceledListSlice targetIds); + + void sortAppTargets(in AppPredictionSessionId sessionId, in ParceledListSlice targets, + in IPredictionCallback callback); + + void registerPredictionUpdates(in AppPredictionSessionId sessionId, + in IPredictionCallback callback); + + void unregisterPredictionUpdates(in AppPredictionSessionId sessionId, + in IPredictionCallback callback); + + void requestPredictionUpdate(in AppPredictionSessionId sessionId); + + void onDestroyPredictionSession(in AppPredictionSessionId sessionId); +} diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl index 0c9b41bf8d68..2964fbc0084b 100644 --- a/core/java/android/app/role/IRoleManager.aidl +++ b/core/java/android/app/role/IRoleManager.aidl @@ -50,4 +50,6 @@ interface IRoleManager { boolean removeRoleHolderFromController(in String roleName, in String packageName); List<String> getHeldRolesFromController(in String packageName); + + String getDefaultSmsPackage(int userId); } diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index 2d630a61a1c3..27581fc0088a 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -19,6 +19,7 @@ package android.app.role; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -68,6 +69,8 @@ public final class RoleManager { /** * The name of the dialer role. + * + * @see Intent#ACTION_DIAL */ public static final String ROLE_DIALER = "android.app.role.DIALER"; @@ -100,6 +103,75 @@ public final class RoleManager { public static final String ROLE_MUSIC = "android.app.role.MUSIC"; /** + * The name of the home role. + * + * @see Intent#CATEGORY_HOME + */ + public static final String ROLE_HOME = "android.app.role.HOME"; + + /** + * The name of the emergency role + * + * @see android.telephony.TelephonyManager#ACTION_EMERGENCY_ASSISTANCE + */ + public static final String ROLE_EMERGENCY = "android.app.role.EMERGENCY"; + + /** + * The name of the car mode dialer app role. + * <p> + * Similar to the {@link #ROLE_DIALER dialer} role, this role determines which app is + * responsible for showing the user interface for ongoing calls on the device. It is only used + * when the device is in car mode. + * + * @see #ROLE_DIALER + * @see android.app.UiModeManager#ACTION_ENTER_CAR_MODE + * @see android.telecom.InCallService + * + * TODO: STOPSHIP: Make name of required roles public API + * @hide + */ + public static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP"; + + /** + * The name of the proxy calling role. + * <p> + * A proxy calling app provides a means to re-write the phone number for an outgoing call to + * place the call through a proxy calling service. + * + * @see android.telecom.CallRedirectionService + * + * TODO: STOPSHIP: Make name of required roles public API + * @hide + */ + public static final String ROLE_PROXY_CALLING_APP = "android.app.role.PROXY_CALLING_APP"; + + /** + * The name of the call screening and caller id role. + * + * @see android.telecom.CallScreeningService + * + * TODO: STOPSHIP: Make name of required roles public API + * @hide + */ + public static final String ROLE_CALL_SCREENING_APP = "android.app.role.CALL_SCREENING_APP"; + + /** + * The name of the call companion app role. + * <p> + * A call companion app provides no user interface for calls, but will be bound to by Telecom + * when there are active calls on the device. Companion apps for wearable devices are an + * acceptable use-case. + * <p> + * Multiple apps may hold this role at the same time. + * + * @see android.telecom.InCallService + * + * TODO: STOPSHIP: Make name of required roles public API + * @hide + */ + public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP"; + + /** * The action used to request user approval of a role for an application. * * @hide @@ -542,7 +614,6 @@ public final class RoleManager { } } - /** * Returns the list of all roles that the given package is currently holding * @@ -563,6 +634,22 @@ public final class RoleManager { } } + /** + * Allows getting the role holder for {@link #ROLE_SMS} without + * {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}, as required by + * {@link android.provider.Telephony.Sms#getDefaultSmsPackage(Context)} + * + * @hide + */ + @Nullable + public String getDefaultSmsPackage(@UserIdInt int userId) { + try { + return mService.getDefaultSmsPackage(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private static class RoleManagerCallbackDelegate extends IRoleManagerCallback.Stub { @NonNull diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java index a2b7d5809c4d..8ee9e5381b86 100644 --- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -36,6 +36,11 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem { private Configuration mConfiguration; @Override + public void preExecute(android.app.ClientTransactionHandler client, IBinder token) { + client.updatePendingActivityConfiguration(token, mConfiguration); + } + + @Override public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index eb7be6f8a6b0..cefc700d372a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -337,6 +337,15 @@ public abstract class Context { public static final int BIND_ADJUST_BELOW_PERCEPTIBLE = 0x0100; /** + * @hide Flag for {@link #bindService}: the service being bound to represents a + * protected system component, so must have association restrictions applied to it. + * That is, a system config must have one or more allow-association tags limiting + * which packages it can interact with. If it does not have any such association + * restrictions, a default empty set will be created. + */ + public static final int BIND_RESTRICT_ASSOCIATIONS = 0x00200000; + + /** * @hide Flag for {@link #bindService}: allows binding to a service provided * by an instant app. Note that the caller may not have access to the instant * app providing the service which is a violation of the instant app sandbox. @@ -3093,6 +3102,7 @@ public abstract class Context { VIBRATOR_SERVICE, //@hide: STATUS_BAR_SERVICE, CONNECTIVITY_SERVICE, + //@hide: IP_MEMORY_STORE_SERVICE, IPSEC_SERVICE, //@hide: UPDATE_LOCK_SERVICE, //@hide: NETWORKMANAGEMENT_SERVICE, @@ -3630,6 +3640,14 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.net.IpMemoryStore} to store and read information about + * known networks. + * @hide + */ + public static final String IP_MEMORY_STORE_SERVICE = "ipmemorystore"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a * {@link android.net.IpSecManager} for encrypting Sockets or Networks with * IPSec. * @@ -3975,6 +3993,24 @@ public abstract class Context { public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture"; /** + * Used for getting content selections and classifications for task snapshots. + * + * @hide + * @see #getSystemService(String) + */ + @SystemApi + public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; + + /** + * Official published name of the app prediction service. + * + * @hide + * @see #getSystemService(String) + */ + @SystemApi + public static final String APP_PREDICTION_SERVICE = "app_prediction"; + + /** * Use with {@link #getSystemService(String)} to access the * {@link com.android.server.voiceinteraction.SoundTriggerService}. * @@ -4427,6 +4463,16 @@ public abstract class Context { public static final String STATS_MANAGER = "stats"; /** + * Service to capture a bugreport. + * @see #getSystemService(String) + * @see android.os.BugreportManager + * @hide + */ + // TODO: Expose API when the implementation is more complete. + // @SystemApi + public static final String BUGREPORT_SERVICE = "bugreport"; + + /** * Use with {@link #getSystemService(String)} to retrieve a {@link * android.content.om.OverlayManager} for managing overlay packages. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index de810e8a02e7..e37126b8c2e7 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1953,16 +1953,35 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; /** + * Intent extra: The name of a permission group. + * <p> + * Type: String + * </p> + * + * @hide + */ + @SystemApi + public static final String EXTRA_PERMISSION_GROUP_NAME = + "android.intent.extra.PERMISSION_GROUP_NAME"; + + /** * Activity action: Launch UI to review app uses of permissions. * <p> * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission name - * that will be displayed by the launched UI. + * that will be displayed by the launched UI. Do not pass both this and + * {@link #EXTRA_PERMISSION_GROUP_NAME} . + * </p> + * <p> + * Input: {@link #EXTRA_PERMISSION_GROUP_NAME} specifies the permission group name + * that will be displayed by the launched UI. Do not pass both this and + * {@link #EXTRA_PERMISSION_NAME}. * </p> * <p> * Output: Nothing. * </p> * * @see #EXTRA_PERMISSION_NAME + * @see #EXTRA_PERMISSION_GROUP_NAME * * @hide */ diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 2978058b2848..576466f21bcb 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1866,7 +1866,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0; } - /** @hide */ + /** + * Check whether the application is encryption aware. + * + * @see #isDirectBootAware() + * @see #isPartiallyDirectBootAware() + * + * @hide + */ + @SystemApi public boolean isEncryptionAware() { return isDirectBootAware() || isPartiallyDirectBootAware(); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 2b266b730485..c9846510be4f 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1621,7 +1621,7 @@ public class PackageParser { } final AttributeSet attrs = parser; - return parseApkLite(apkPath, parser, attrs, signingDetails); + return parseApkLite(apkPath, parser, attrs, signingDetails, flags); } catch (XmlPullParserException | IOException | RuntimeException e) { Slog.w(TAG, "Failed to parse " + apkPath, e); @@ -1708,7 +1708,7 @@ public class PackageParser { } private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs, - SigningDetails signingDetails) + SigningDetails signingDetails, int flags) throws IOException, XmlPullParserException, PackageParserException { final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs); @@ -1716,11 +1716,12 @@ public class PackageParser { int versionCode = 0; int versionCodeMajor = 0; int revisionCode = 0; + int targetSdkVersion = 0; boolean coreApp = false; boolean debuggable = false; boolean multiArch = false; boolean use32bitAbi = false; - boolean extractNativeLibs = true; + Boolean extractNativeLibsProvided = null; boolean isolatedSplits = false; boolean isFeatureSplit = false; boolean isSplitRequired = false; @@ -1785,7 +1786,8 @@ public class PackageParser { use32bitAbi = attrs.getAttributeBooleanValue(i, false); } if ("extractNativeLibs".equals(attr)) { - extractNativeLibs = attrs.getAttributeBooleanValue(i, true); + extractNativeLibsProvided = Boolean.valueOf( + attrs.getAttributeBooleanValue(i, true)); } if ("preferCodeIntegrity".equals(attr)) { preferCodeIntegrity = attrs.getAttributeBooleanValue(i, false); @@ -1803,9 +1805,51 @@ public class PackageParser { PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "<uses-split> tag requires 'android:name' attribute"); } + } else if (TAG_USES_SDK.equals(parser.getName())) { + final String[] errorMsg = new String[1]; + Pair<Integer, Integer> versions = deriveSdkVersions(new AbstractVersionsAccessor() { + @Override public String getMinSdkVersionCode() { + return getAttributeAsString("minSdkVersion"); + } + + @Override public int getMinSdkVersion() { + return getAttributeAsInt("minSdkVersion"); + } + + @Override public String getTargetSdkVersionCode() { + return getAttributeAsString("targetSdkVersion"); + } + + @Override public int getTargetSdkVersion() { + return getAttributeAsInt("targetSdkVersion"); + } + + private String getAttributeAsString(String name) { + return attrs.getAttributeValue(ANDROID_RESOURCES, name); + } + + private int getAttributeAsInt(String name) { + try { + return attrs.getAttributeIntValue(ANDROID_RESOURCES, name, -1); + } catch (NumberFormatException e) { + return -1; + } + } + }, flags, errorMsg); + + if (versions == null) { + throw new PackageParserException( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, errorMsg[0]); + } + + targetSdkVersion = versions.second; } } + final boolean extractNativeLibsDefault = targetSdkVersion < Build.VERSION_CODES.Q; + final boolean extractNativeLibs = (extractNativeLibsProvided != null) + ? extractNativeLibsProvided : extractNativeLibsDefault; + if (preferCodeIntegrity && extractNativeLibs) { throw new PackageParserException( PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, @@ -2215,65 +2259,60 @@ public class PackageParser { } else if (tagName.equals(TAG_USES_SDK)) { if (SDK_VERSION > 0) { - sa = res.obtainAttributes(parser, - com.android.internal.R.styleable.AndroidManifestUsesSdk); - - int minVers = 1; - String minCode = null; - int targetVers = 0; - String targetCode = null; - - TypedValue val = sa.peekValue( - com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion); - if (val != null) { - if (val.type == TypedValue.TYPE_STRING && val.string != null) { - minCode = val.string.toString(); - } else { - // If it's not a string, it's an integer. - minVers = val.data; - } - } - - val = sa.peekValue( - com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion); - if (val != null) { - if (val.type == TypedValue.TYPE_STRING && val.string != null) { - targetCode = val.string.toString(); - if (minCode == null) { - minCode = targetCode; - } - } else { - // If it's not a string, it's an integer. - targetVers = val.data; - } - } else { - targetVers = minVers; - targetCode = minCode; - } - - sa.recycle(); - - final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode, - SDK_VERSION, SDK_CODENAMES, outError); - if (minSdkVersion < 0) { + sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk); + final TypedArray saFinal = sa; + Pair<Integer, Integer> versions = deriveSdkVersions( + new AbstractVersionsAccessor() { + @Override public String getMinSdkVersionCode() { + return getAttributeAsString( + R.styleable.AndroidManifestUsesSdk_minSdkVersion); + } + + @Override public int getMinSdkVersion() { + return getAttributeAsInt( + R.styleable.AndroidManifestUsesSdk_minSdkVersion); + } + + @Override public String getTargetSdkVersionCode() { + return getAttributeAsString( + R.styleable.AndroidManifestUsesSdk_targetSdkVersion); + } + + @Override public int getTargetSdkVersion() { + return getAttributeAsInt( + R.styleable.AndroidManifestUsesSdk_targetSdkVersion); + } + + private String getAttributeAsString(int index) { + TypedValue val = saFinal.peekValue(index); + if (val != null && val.type == TypedValue.TYPE_STRING + && val.string != null) { + return val.string.toString(); + } + return null; + } + + private int getAttributeAsInt(int index) { + TypedValue val = saFinal.peekValue(index); + if (val != null && val.type != TypedValue.TYPE_STRING) { + // If it's not a string, it's an integer. + return val.data; + } + return -1; + } + }, flags, outError); + + if (versions == null) { mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; return null; } - boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0; - final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers, - targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch); - if (targetSdkVersion < 0) { - mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; - return null; - } + pkg.applicationInfo.minSdkVersion = versions.first; + pkg.applicationInfo.targetSdkVersion = versions.second; - pkg.applicationInfo.minSdkVersion = minSdkVersion; - pkg.applicationInfo.targetSdkVersion = targetSdkVersion; + sa.recycle(); } - XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals(TAG_SUPPORT_SCREENS)) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestSupportsScreens); @@ -2675,6 +2714,67 @@ public class PackageParser { return -1; } + private interface AbstractVersionsAccessor { + /** Returns minimum SDK version code string, or null if absent. */ + String getMinSdkVersionCode(); + + /** Returns minimum SDK version code, or -1 if absent. */ + int getMinSdkVersion(); + + /** Returns target SDK version code string, or null if absent. */ + String getTargetSdkVersionCode(); + + /** Returns target SDK version code, or -1 if absent. */ + int getTargetSdkVersion(); + } + + private static @Nullable Pair<Integer, Integer> deriveSdkVersions( + @NonNull AbstractVersionsAccessor accessor, int flags, String[] outError) { + int minVers = 1; + String minCode = null; + int targetVers = 0; + String targetCode = null; + + String code = accessor.getMinSdkVersionCode(); + int version = accessor.getMinSdkVersion(); + // Check integer first since code is almost never a null string (e.g. "28"). + if (version >= 0) { + minVers = version; + } else if (code != null) { + minCode = code; + } + + code = accessor.getTargetSdkVersionCode(); + version = accessor.getTargetSdkVersion(); + // Check integer first since code is almost never a null string (e.g. "28"). + if (version >= 0) { + targetVers = version; + } else if (code != null) { + targetCode = code; + if (minCode == null) { + minCode = targetCode; + } + } else { + targetVers = minVers; + targetCode = minCode; + } + + final int minSdkVersion = computeMinSdkVersion(minVers, minCode, + SDK_VERSION, SDK_CODENAMES, outError); + if (minSdkVersion < 0) { + return null; + } + + boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0; + final int targetSdkVersion = computeTargetSdkVersion(targetVers, + targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch); + if (targetSdkVersion < 0) { + return null; + } + + return Pair.create(minSdkVersion, targetSdkVersion); + } + /** * Computes the minSdkVersion to use at runtime. If the package is not * compatible with this platform, populates {@code outError[0]} with an diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index be6ed51e3c89..74dd08fc1d6b 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -29,8 +29,11 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import android.annotation.UnsupportedAppUsage; import android.os.BaseBundle; +import android.os.Debug; import android.os.PersistableBundle; import android.util.ArraySet; +import android.util.DebugUtils; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -43,6 +46,9 @@ import java.util.Objects; * @hide */ public class PackageUserState { + private static final boolean DEBUG = false; + private static final String LOG_TAG = "PackageUserState"; + public long ceDataInode; public boolean installed; public boolean stopped; @@ -132,12 +138,12 @@ public class PackageUserState { final boolean isSystemApp = componentInfo.applicationInfo.isSystemApp(); final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0; if (!isAvailable(flags) - && !(isSystemApp && matchUninstalled)) return false; - if (!isEnabled(componentInfo, flags)) return false; + && !(isSystemApp && matchUninstalled)) return reportIfDebug(false, flags); + if (!isEnabled(componentInfo, flags)) return reportIfDebug(false, flags); if ((flags & MATCH_SYSTEM_ONLY) != 0) { if (!isSystemApp) { - return false; + return reportIfDebug(false, flags); } } @@ -145,7 +151,16 @@ public class PackageUserState { && !componentInfo.directBootAware; final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0) && componentInfo.directBootAware; - return matchesUnaware || matchesAware; + return reportIfDebug(matchesUnaware || matchesAware, flags); + } + + private boolean reportIfDebug(boolean result, int flags) { + if (DEBUG && !result) { + Slog.i(LOG_TAG, "No match!; flags: " + + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " " + + Debug.getCaller()); + } + return result; } /** diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index a8c3b889421b..6e519c1863e8 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -475,6 +475,14 @@ public abstract class RegisteredServicesCache<V> { final List<ResolveInfo> resolveInfos = queryIntentServices(userId); for (ResolveInfo resolveInfo : resolveInfos) { try { + // if this package is not one of those changedUids, we don't need to scan it, + // since nothing in it changed, so save a call to parseServiceInfo, which + // can cause a large amount of the package apk to be loaded into memory. + // if this is the initial scan, changedUids will be null, and containsUid will + // trivially return true, and will call parseServiceInfo + if (!containsUid(changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) { + continue; + } ServiceInfo<V> info = parseServiceInfo(resolveInfo); if (info == null) { Log.w(TAG, "Unable to load service info " + resolveInfo.toString()); diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index b238d778f55a..f652f85c153b 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -73,6 +73,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public static final String KEY_NEGATIVE_TEXT = "negative_text"; + /** + * @hide + */ + public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation"; /** * Error/help message will show for this amount of time. @@ -215,6 +219,30 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Optional: A hint to the system to require user confirmation after a biometric has been + * authenticated. For example, implicit modalities like Face and Iris authentication are + * passive, meaning they don't require an explicit user action to complete. When set to + * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt + * will require confirmation by default. + * + * A typical use case for not requiring confirmation would be for low-risk transactions, + * such as re-authenticating a recently authenticated application. A typical use case for + * requiring confirmation would be for authorizing a purchase. + * + * Note that this is a hint to the system. The system may choose to ignore the flag. For + * example, if the user disables implicit authentication in Settings, or if it does not + * apply to a modality (e.g. Fingerprint). When ignored, the system will default to + * requiring confirmation. + * + * @param requireConfirmation + * @hide + */ + public Builder setRequireConfirmation(boolean requireConfirmation) { + mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} * @throws IllegalArgumentException if any of the required fields are not set. diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index dd782ec3a42f..0cf2d18e5c04 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -16,7 +16,9 @@ package android.hardware.display; +import android.Manifest; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.IBinder; @@ -31,6 +33,7 @@ import com.android.internal.R; * * @hide */ +@SystemApi @SystemService(Context.COLOR_DISPLAY_SERVICE) public final class ColorDisplayManager { @@ -48,13 +51,29 @@ public final class ColorDisplayManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean isDeviceColorManaged() { return mManager.isDeviceColorManaged(); } /** + * Set the level of color saturation to apply to the display. + * + * @param saturationLevel 0-100 (inclusive), where 100 is full saturation + * @return whether the saturation level change was applied successfully + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setSaturationLevel(int saturationLevel) { + return mManager.setSaturationLevel(saturationLevel); + } + + /** * Returns {@code true} if Night Display is supported by the device. + * + * @hide */ public static boolean isNightDisplayAvailable(Context context) { return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable); @@ -62,6 +81,8 @@ public final class ColorDisplayManager { /** * Returns {@code true} if display white balance is supported by the device. + * + * @hide */ public static boolean isDisplayWhiteBalanceAvailable(Context context) { return context.getResources().getBoolean(R.bool.config_displayWhiteBalanceAvailable); @@ -99,5 +120,13 @@ public final class ColorDisplayManager { throw e.rethrowFromSystemServer(); } } + + boolean setSaturationLevel(int saturationLevel) { + try { + return mCdm.setSaturationLevel(saturationLevel); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 82e765dee447..44b653c314ad 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -563,11 +563,16 @@ public final class DisplayManager { * 0 produces a grayscale image, 1 is normal. * * @hide + * @deprecated use {@link ColorDisplayManager#setSaturationLevel(int)}. */ @SystemApi @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float level) { - mGlobal.setSaturationLevel(level); + if (level < 0f || level > 1f) { + throw new IllegalArgumentException("Saturation level must be between 0 and 1"); + } + final ColorDisplayManager cdm = mContext.getSystemService(ColorDisplayManager.class); + cdm.setSaturationLevel(Math.round(level * 100f)); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 7304ab4aa1df..cda8498c4f01 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -394,17 +394,6 @@ public final class DisplayManagerGlobal { } } - /** - * Set the level of color saturation to apply to the display. - */ - public void setSaturationLevel(float level) { - try { - mDm.setSaturationLevel(level); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, String name, int width, int height, int densityDpi, Surface surface, int flags, VirtualDisplay.Callback callback, Handler handler, String uniqueId) { diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl index f7865899812a..81b82c64461f 100644 --- a/core/java/android/hardware/display/IColorDisplayManager.aidl +++ b/core/java/android/hardware/display/IColorDisplayManager.aidl @@ -19,4 +19,6 @@ package android.hardware.display; /** @hide */ interface IColorDisplayManager { boolean isDeviceColorManaged(); + + boolean setSaturationLevel(int saturationLevel); }
\ No newline at end of file diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index b57599724ad5..2d81cdfec179 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -66,9 +66,6 @@ interface IDisplayManager { // Requires CONFIGURE_DISPLAY_COLOR_MODE void requestColorMode(int displayId, int colorMode); - // Requires CONTROL_DISPLAY_SATURATION - void setSaturationLevel(float level); - // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate // MediaProjection token for certain combinations of flags. int createVirtualDisplay(in IVirtualDisplayCallback callback, diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 1630b0603f3a..c9a7830d50f5 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -163,8 +163,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan mAuthenticationCallback = callback; mCryptoObject = crypto; long sessionId = crypto != null ? crypto.getOpId() : 0; - mService.authenticate(mToken, sessionId, mServiceReceiver, flags, - mContext.getOpPackageName()); + mService.authenticate(mToken, sessionId, mContext.getUserId(), mServiceReceiver, + flags, mContext.getOpPackageName()); } catch (RemoteException e) { Log.w(TAG, "Remote exception while authenticating: ", e); if (callback != null) { diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index a1c88f81e3e7..f67760a8fafc 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -27,7 +27,7 @@ import android.hardware.face.Face; */ interface IFaceService { // Authenticate the given sessionId with a face - void authenticate(IBinder token, long sessionId, + void authenticate(IBinder token, long sessionId, int userid, IFaceServiceReceiver receiver, int flags, String opPackageName); // This method prepares the service to start authenticating, but doesn't start authentication. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 436b4a15e7c1..abc00feeb212 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2051,6 +2051,16 @@ public class ConnectivityManager { return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); } + /** @hide */ + public NetworkRequest getDefaultRequest() { + try { + // This is not racy as the default request is final in ConnectivityService. + return mService.getDefaultRequest(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /* TODO: These permissions checks don't belong in client-side code. Move them to * services.jar, possibly in com.android.server.net. */ diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index e7d441df82a6..da5d96e49d02 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -167,6 +167,8 @@ interface IConnectivityManager int getMultipathPreference(in Network Network); + NetworkRequest getDefaultRequest(); + int getRestoreDefaultNetworkDelay(int networkType); boolean addVpnAddress(String address, int prefixLength); diff --git a/core/java/android/net/IIpMemoryStore.aidl b/core/java/android/net/IIpMemoryStore.aidl new file mode 100644 index 000000000000..6f88dec8dee9 --- /dev/null +++ b/core/java/android/net/IIpMemoryStore.aidl @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.ipmemorystore.Blob; +import android.net.ipmemorystore.NetworkAttributesParcelable; +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; + +/** {@hide} */ +oneway interface IIpMemoryStore { + /** + * Store network attributes for a given L2 key. + * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to + * calling findL2Key with the attributes and storing in the returned value. + * + * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2 + * key and only care about grouping can pass a unique ID here like the ones + * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low + * relevance of such a network will lead to it being evicted soon if it's not + * refreshed. Use findL2Key to try and find a similar L2Key to these attributes. + * @param attributes The attributes for this network. + * @param listener A listener that will be invoked to inform of the completion of this call, + * or null if the client is not interested in learning about success/failure. + * @return (through the listener) The L2 key. This is useful if the L2 key was not specified. + * If the call failed, the L2 key will be null. + */ + void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes, + IOnStatusListener listener); + + /** + * Store a binary blob associated with an L2 key and a name. + * + * @param l2Key The L2 key for this network. + * @param clientId The ID of the client. + * @param name The name of this data. + * @param data The data to store. + * @param listener A listener to inform of the completion of this call, or null if the client + * is not interested in learning about success/failure. + * @return (through the listener) A status to indicate success or failure. + */ + void storeBlob(String l2Key, String clientId, String name, in Blob data, + IOnStatusListener listener); + + /** + * Returns the best L2 key associated with the attributes. + * + * This will find a record that would be in the same group as the passed attributes. This is + * useful to choose the key for storing a sample or private data when the L2 key is not known. + * If multiple records are group-close to these attributes, the closest match is returned. + * If multiple records have the same closeness, the one with the smaller (unicode codepoint + * order) L2 key is returned. + * If no record matches these attributes, null is returned. + * + * @param attributes The attributes of the network to find. + * @param listener The listener that will be invoked to return the answer. + * @return (through the listener) The L2 key if one matched, or null. + */ + void findL2Key(in NetworkAttributesParcelable attributes, IOnL2KeyResponseListener listener); + + /** + * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point + * to the same L3 network. Group-closeness is used to determine this. + * + * @param l2Key1 The key for the first network. + * @param l2Key2 The key for the second network. + * @param listener The listener that will be invoked to return the answer. + * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence. + */ + void isSameNetwork(String l2Key1, String l2Key2, IOnSameNetworkResponseListener listener); + + /** + * Retrieve the network attributes for a key. + * If no record is present for this key, this will return null attributes. + * + * @param l2Key The key of the network to query. + * @param listener The listener that will be invoked to return the answer. + * @return (through the listener) The network attributes and the L2 key associated with + * the query. + */ + void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrieved listener); + + /** + * Retrieve previously stored private data. + * If no data was stored for this L2 key and name this will return null. + * + * @param l2Key The L2 key. + * @param clientId The id of the client that stored this data. + * @param name The name of the data. + * @param listener The listener that will be invoked to return the answer. + * @return (through the listener) The private data (or null), with the L2 key + * and the name of the data associated with the query. + */ + void retrieveBlob(String l2Key, String clientId, String name, + IOnBlobRetrievedListener listener); +} diff --git a/core/java/android/net/INetworkMonitor.aidl b/core/java/android/net/INetworkMonitor.aidl new file mode 100644 index 000000000000..41f969acad6f --- /dev/null +++ b/core/java/android/net/INetworkMonitor.aidl @@ -0,0 +1,45 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +package android.net; + +import android.net.PrivateDnsConfigParcel; + +/** @hide */ +oneway interface INetworkMonitor { + // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. + // The network should be used as a default internet connection. It was found to be: + // 1. a functioning network providing internet access, or + // 2. a captive portal and the user decided to use it as is. + const int NETWORK_TEST_RESULT_VALID = 0; + + // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. + // The network should not be used as a default internet connection. It was found to be: + // 1. a captive portal and the user is prompted to sign-in, or + // 2. a captive portal and the user did not want to use it, or + // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed). + const int NETWORK_TEST_RESULT_INVALID = 1; + + void start(); + void launchCaptivePortalApp(); + void forceReevaluation(int uid); + void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config); + void notifyDnsResponse(int returnCode); + void notifySystemReady(); + void notifyNetworkConnected(); + void notifyNetworkDisconnected(); + void notifyLinkPropertiesChanged(); + void notifyNetworkCapabilitiesChanged(); +}
\ No newline at end of file diff --git a/core/java/android/net/INetworkMonitorCallbacks.aidl b/core/java/android/net/INetworkMonitorCallbacks.aidl new file mode 100644 index 000000000000..0bc25750129b --- /dev/null +++ b/core/java/android/net/INetworkMonitorCallbacks.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.INetworkMonitor; +import android.net.PrivateDnsConfigParcel; + +/** @hide */ +oneway interface INetworkMonitorCallbacks { + void onNetworkMonitorCreated(in INetworkMonitor networkMonitor); + void notifyNetworkTested(int testResult, @nullable String redirectUrl); + void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config); + void showProvisioningNotification(String action); + void hideProvisioningNotification(); +}
\ No newline at end of file diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl index be0dc07f4b23..2df8ab7ec198 100644 --- a/core/java/android/net/INetworkStackConnector.aidl +++ b/core/java/android/net/INetworkStackConnector.aidl @@ -15,6 +15,7 @@ */ package android.net; +import android.net.INetworkMonitorCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; @@ -22,4 +23,5 @@ import android.net.dhcp.IDhcpServerCallbacks; oneway interface INetworkStackConnector { void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params, in IDhcpServerCallbacks cb); + void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb); }
\ No newline at end of file diff --git a/core/java/android/net/IpMemoryStore.java b/core/java/android/net/IpMemoryStore.java new file mode 100644 index 000000000000..b35f09743877 --- /dev/null +++ b/core/java/android/net/IpMemoryStore.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemService; +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.os.RemoteException; + +import com.android.internal.util.Preconditions; + +/** + * The interface for system components to access the IP memory store. + * @see com.android.server.net.ipmemorystore.IpMemoryStoreService + * @hide + */ +@SystemService(Context.IP_MEMORY_STORE_SERVICE) +public class IpMemoryStore { + @NonNull final Context mContext; + @NonNull final IIpMemoryStore mService; + + public IpMemoryStore(@NonNull final Context context, @NonNull final IIpMemoryStore service) { + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IIpMemoryStore"); + } + + /** + * Store network attributes for a given L2 key. + * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to + * calling findL2Key with the attributes and storing in the returned value. + * + * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2 + * key and only care about grouping can pass a unique ID here like the ones + * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low + * relevance of such a network will lead to it being evicted soon if it's not + * refreshed. Use findL2Key to try and find a similar L2Key to these attributes. + * @param attributes The attributes for this network. + * @param listener A listener that will be invoked to inform of the completion of this call, + * or null if the client is not interested in learning about success/failure. + * Through the listener, returns the L2 key. This is useful if the L2 key was not specified. + * If the call failed, the L2 key will be null. + */ + public void storeNetworkAttributes(@NonNull final String l2Key, + @NonNull final NetworkAttributes attributes, + @Nullable final IOnStatusListener listener) { + try { + mService.storeNetworkAttributes(l2Key, attributes.toParcelable(), listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Store a binary blob associated with an L2 key and a name. + * + * @param l2Key The L2 key for this network. + * @param clientId The ID of the client. + * @param name The name of this data. + * @param data The data to store. + * @param listener A listener to inform of the completion of this call, or null if the client + * is not interested in learning about success/failure. + * Through the listener, returns a status to indicate success or failure. + */ + public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId, + @NonNull final String name, @NonNull final Blob data, + @Nullable final IOnStatusListener listener) { + try { + mService.storeBlob(l2Key, clientId, name, data, listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the best L2 key associated with the attributes. + * + * This will find a record that would be in the same group as the passed attributes. This is + * useful to choose the key for storing a sample or private data when the L2 key is not known. + * If multiple records are group-close to these attributes, the closest match is returned. + * If multiple records have the same closeness, the one with the smaller (unicode codepoint + * order) L2 key is returned. + * If no record matches these attributes, null is returned. + * + * @param attributes The attributes of the network to find. + * @param listener The listener that will be invoked to return the answer. + * Through the listener, returns the L2 key if one matched, or null. + */ + public void findL2Key(@NonNull final NetworkAttributes attributes, + @NonNull final IOnL2KeyResponseListener listener) { + try { + mService.findL2Key(attributes.toParcelable(), listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point + * to the same L3 network. Group-closeness is used to determine this. + * + * @param l2Key1 The key for the first network. + * @param l2Key2 The key for the second network. + * @param listener The listener that will be invoked to return the answer. + * Through the listener, a SameL3NetworkResponse containing the answer and confidence. + */ + public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2, + @NonNull final IOnSameNetworkResponseListener listener) { + try { + mService.isSameNetwork(l2Key1, l2Key2, listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve the network attributes for a key. + * If no record is present for this key, this will return null attributes. + * + * @param l2Key The key of the network to query. + * @param listener The listener that will be invoked to return the answer. + * Through the listener, returns the network attributes and the L2 key associated with + * the query. + */ + public void retrieveNetworkAttributes(@NonNull final String l2Key, + @NonNull final IOnNetworkAttributesRetrieved listener) { + try { + mService.retrieveNetworkAttributes(l2Key, listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve previously stored private data. + * If no data was stored for this L2 key and name this will return null. + * + * @param l2Key The L2 key. + * @param clientId The id of the client that stored this data. + * @param name The name of the data. + * @param listener The listener that will be invoked to return the answer. + * Through the listener, returns the private data (or null), with the L2 key + * and the name of the data associated with the query. + */ + public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId, + @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) { + try { + mService.retrieveBlob(l2Key, clientId, name, listener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index d4a0ec632383..2eac6de56346 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -48,14 +48,16 @@ import java.util.ArrayList; public class NetworkStack { private static final String TAG = NetworkStack.class.getSimpleName(); + public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack"; + @NonNull @GuardedBy("mPendingNetStackRequests") - private final ArrayList<NetworkStackRequest> mPendingNetStackRequests = new ArrayList<>(); + private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>(); @Nullable @GuardedBy("mPendingNetStackRequests") private INetworkStackConnector mConnector; - private interface NetworkStackRequest { + private interface NetworkStackCallback { void onNetworkStackConnected(INetworkStackConnector connector); } @@ -77,6 +79,21 @@ public class NetworkStack { }); } + /** + * Create a NetworkMonitor. + * + * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks. + */ + public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeNetworkMonitor(network.netId, name, cb); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + private class NetworkStackConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -96,14 +113,14 @@ public class NetworkStack { ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */, DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); - final ArrayList<NetworkStackRequest> requests; + final ArrayList<NetworkStackCallback> requests; synchronized (mPendingNetStackRequests) { requests = new ArrayList<>(mPendingNetStackRequests); mPendingNetStackRequests.clear(); mConnector = connector; } - for (NetworkStackRequest r : requests) { + for (NetworkStackCallback r : requests) { r.onNetworkStackConnected(connector); } } @@ -124,7 +141,8 @@ public class NetworkStack { "com.android.server.NetworkStackService", true /* initialize */, context.getClassLoader()); - connector = (IBinder) service.getMethod("makeConnector").invoke(null); + connector = (IBinder) service.getMethod("makeConnector", Context.class) + .invoke(null, context); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { Slog.wtf(TAG, "Could not create network stack connector from NetworkStackService"); // TODO: crash/reboot system here ? @@ -153,7 +171,7 @@ public class NetworkStack { } // TODO: use this method to obtain the connector when implementing network stack operations - private void requestConnector(@NonNull NetworkStackRequest request) { + private void requestConnector(@NonNull NetworkStackCallback request) { // TODO: PID check. if (Binder.getCallingUid() != Process.SYSTEM_UID) { // Don't even attempt to obtain the connector and give a nice error message diff --git a/core/java/android/net/PrivateDnsConfigParcel.aidl b/core/java/android/net/PrivateDnsConfigParcel.aidl new file mode 100644 index 000000000000..b52fce643302 --- /dev/null +++ b/core/java/android/net/PrivateDnsConfigParcel.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +parcelable PrivateDnsConfigParcel { + String hostname; + String[] ips; +} diff --git a/core/java/android/net/ipmemorystore/Blob.aidl b/core/java/android/net/ipmemorystore/Blob.aidl new file mode 100644 index 000000000000..9dbef117f8a4 --- /dev/null +++ b/core/java/android/net/ipmemorystore/Blob.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +/** + * A blob of data opaque to the memory store. The client mutates this at its own risk, + * and it is strongly suggested to never do it at all and treat this as immutable. + * {@hide} + */ +parcelable Blob { + byte[] data; +} diff --git a/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl new file mode 100644 index 000000000000..4926feb06e55 --- /dev/null +++ b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import android.net.ipmemorystore.Blob; +import android.net.ipmemorystore.StatusParcelable; + +/** {@hide} */ +oneway interface IOnBlobRetrievedListener { + /** + * Private data was retrieved for the L2 key and name specified. + * Note this does not return the client ID, as clients are expected to only ever use one ID. + */ + void onBlobRetrieved(in StatusParcelable status, in String l2Key, in String name, + in Blob data); +} diff --git a/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl new file mode 100644 index 000000000000..dea0cc4e2586 --- /dev/null +++ b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import android.net.ipmemorystore.StatusParcelable; + +/** {@hide} */ +oneway interface IOnL2KeyResponseListener { + /** + * The operation completed with the specified L2 key. + */ + void onL2KeyResponse(in StatusParcelable status, in String l2Key); +} diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl new file mode 100644 index 000000000000..57f59a17cfe7 --- /dev/null +++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import android.net.ipmemorystore.NetworkAttributesParcelable; +import android.net.ipmemorystore.StatusParcelable; + +/** {@hide} */ +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, + in NetworkAttributesParcelable attributes); +} diff --git a/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl new file mode 100644 index 000000000000..294bd3bd4012 --- /dev/null +++ b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import android.net.ipmemorystore.SameL3NetworkResponseParcelable; +import android.net.ipmemorystore.StatusParcelable; + +/** {@hide} */ +oneway interface IOnSameNetworkResponseListener { + /** + * The memory store has come up with the answer to a query that was sent. + */ + void onSameNetworkResponse(in StatusParcelable status, + in SameL3NetworkResponseParcelable response); +} diff --git a/core/java/android/net/ipmemorystore/IOnStatusListener.aidl b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl new file mode 100644 index 000000000000..5d0750449ec5 --- /dev/null +++ b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import android.net.ipmemorystore.StatusParcelable; + +/** {@hide} */ +oneway interface IOnStatusListener { + /** + * The operation has completed with the specified status. + */ + void onComplete(in StatusParcelable status); +} diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java new file mode 100644 index 000000000000..d7e5b2761e58 --- /dev/null +++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * A POD object to represent attributes of a single L2 network entry. + * @hide + */ +public class NetworkAttributes { + private static final boolean DBG = true; + + // 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; + + // 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; + + // The list of DNS server addresses. + @Nullable + public final List<InetAddress> dnsAddresses; + + // The mtu on this network. + @Nullable + public final Integer mtu; + + NetworkAttributes( + @Nullable final Inet4Address assignedV4Address, + @Nullable final String groupHint, + @Nullable final List<InetAddress> dnsAddresses, + @Nullable final Integer mtu) { + if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative"); + this.assignedV4Address = assignedV4Address; + this.groupHint = groupHint; + this.dnsAddresses = null == dnsAddresses ? null : + Collections.unmodifiableList(new ArrayList<>(dnsAddresses)); + this.mtu = mtu; + } + + @VisibleForTesting + public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) { + // The call to the other constructor must be the first statement of this constructor, + // so everything has to be inline + this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address), + parcelable.groupHint, + blobArrayToInetAddressList(parcelable.dnsAddresses), + parcelable.mtu >= 0 ? parcelable.mtu : null); + } + + @Nullable + private static InetAddress getByAddressOrNull(@Nullable final byte[] address) { + try { + return InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + return null; + } + } + + @Nullable + private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) { + if (null == blobs) return null; + final ArrayList<InetAddress> list = new ArrayList<>(blobs.length); + for (final Blob b : blobs) { + final InetAddress addr = getByAddressOrNull(b.data); + if (null != addr) list.add(addr); + } + return list; + } + + @Nullable + private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) { + if (null == addresses) return null; + final ArrayList<Blob> blobs = new ArrayList<>(); + for (int i = 0; i < addresses.size(); ++i) { + final InetAddress addr = addresses.get(i); + if (null == addr) continue; + final Blob b = new Blob(); + b.data = addr.getAddress(); + blobs.add(b); + } + return blobs.toArray(new Blob[0]); + } + + /** Converts this NetworkAttributes to a parcelable object */ + @NonNull + public NetworkAttributesParcelable toParcelable() { + final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable(); + parcelable.assignedV4Address = + (null == assignedV4Address) ? null : assignedV4Address.getAddress(); + parcelable.groupHint = groupHint; + parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses); + parcelable.mtu = (null == mtu) ? -1 : mtu; + return parcelable; + } + + /** @hide */ + public static class Builder { + @Nullable + private Inet4Address mAssignedAddress; + @Nullable + private String mGroupHint; + @Nullable + private List<InetAddress> mDnsAddresses; + @Nullable + private Integer mMtu; + + /** + * Set the assigned address. + * @param assignedV4Address The assigned address. + * @return This builder. + */ + public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) { + mAssignedAddress = assignedV4Address; + return this; + } + + /** + * Set the group hint. + * @param groupHint The group hint. + * @return This builder. + */ + public Builder setGroupHint(@Nullable final String groupHint) { + mGroupHint = groupHint; + return this; + } + + /** + * Set the DNS addresses. + * @param dnsAddresses The DNS addresses. + * @return This builder. + */ + public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) { + if (DBG && null != dnsAddresses) { + // Parceling code crashes if one of the addresses is null, therefore validate + // them when running in debug. + for (final InetAddress address : dnsAddresses) { + if (null == address) throw new IllegalArgumentException("Null DNS address"); + } + } + this.mDnsAddresses = dnsAddresses; + return this; + } + + /** + * Set the MTU. + * @param mtu The MTU. + * @return This builder. + */ + public Builder setMtu(@Nullable final Integer mtu) { + if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative"); + mMtu = mtu; + return this; + } + + /** + * Return the built NetworkAttributes object. + * @return The built NetworkAttributes object. + */ + public NetworkAttributes build() { + return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu); + } + } + + @Override + public boolean equals(@Nullable final Object o) { + if (!(o instanceof NetworkAttributes)) return false; + final NetworkAttributes other = (NetworkAttributes) o; + return Objects.equals(assignedV4Address, other.assignedV4Address) + && Objects.equals(groupHint, other.groupHint) + && Objects.equals(dnsAddresses, other.dnsAddresses) + && Objects.equals(mtu, other.mtu); + } + + @Override + public int hashCode() { + return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu); + } +} diff --git a/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl new file mode 100644 index 000000000000..0894d7260915 --- /dev/null +++ b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +// Blob[] is used to represent an array of byte[], as structured AIDL does not support arrays +// of arrays. +import android.net.ipmemorystore.Blob; + +/** + * An object to represent attributes of a single L2 network entry. + * See NetworkAttributes.java for a description of each field. The types used in this class + * are structured parcelable types instead of the richer types of the NetworkAttributes object, + * but they have the same purpose. The NetworkAttributes.java file also contains the code + * to convert the richer types to the parcelable types and back. + * @hide + */ +parcelable NetworkAttributesParcelable { + byte[] assignedV4Address; + String groupHint; + Blob[] dnsAddresses; + int mtu; +} diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java new file mode 100644 index 000000000000..0cb37e9f75b4 --- /dev/null +++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * An object representing the answer to a query whether two given L2 networks represent the + * same L3 network. Parcels as a SameL3NetworkResponseParceled object. + * @hide + */ +public class SameL3NetworkResponse { + @IntDef(prefix = "NETWORK_", + value = {NETWORK_SAME, NETWORK_DIFFERENT, NETWORK_NEVER_CONNECTED}) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkSameness {} + + /** + * Both L2 networks represent the same L3 network. + */ + public static final int NETWORK_SAME = 1; + + /** + * The two L2 networks represent a different L3 network. + */ + public static final int NETWORK_DIFFERENT = 2; + + /** + * The device has never connected to at least one of these two L2 networks, or data + * has been wiped. Therefore the device has never seen the L3 network behind at least + * one of these two L2 networks, and can't evaluate whether it's the same as the other. + */ + public static final int NETWORK_NEVER_CONNECTED = 3; + + /** + * The first L2 key specified in the query. + */ + @NonNull + public final String l2Key1; + + /** + * The second L2 key specified in the query. + */ + @NonNull + public final String l2Key2; + + /** + * A confidence value indicating whether the two L2 networks represent the same L3 network. + * + * If both L2 networks were known, this value will be between 0.0 and 1.0, with 0.0 + * representing complete confidence that the given L2 networks represent a different + * L3 network, and 1.0 representing complete confidence that the given L2 networks + * represent the same L3 network. + * If at least one of the L2 networks was not known, this value will be outside of the + * 0.0~1.0 range. + * + * Most apps should not be interested in this, and are encouraged to use the collapsing + * {@link #getNetworkSameness()} function below. + */ + public final float confidence; + + /** + * @return whether the two L2 networks represent the same L3 network. Either + * {@code NETWORK_SAME}, {@code NETWORK_DIFFERENT} or {@code NETWORK_NEVER_CONNECTED}. + */ + @NetworkSameness + public final int getNetworkSameness() { + if (confidence > 1.0 || confidence < 0.0) return NETWORK_NEVER_CONNECTED; + return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT; + } + + SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2, + final float confidence) { + this.l2Key1 = l2Key1; + this.l2Key2 = l2Key2; + this.confidence = confidence; + } + + /** Builds a SameL3NetworkResponse from a parcelable object */ + @VisibleForTesting + public SameL3NetworkResponse(@NonNull final SameL3NetworkResponseParcelable parceled) { + this(parceled.l2Key1, parceled.l2Key2, parceled.confidence); + } + + /** Converts this SameL3NetworkResponse to a parcelable object */ + @NonNull + public SameL3NetworkResponseParcelable toParcelable() { + final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable(); + parcelable.l2Key1 = l2Key1; + parcelable.l2Key2 = l2Key2; + parcelable.confidence = confidence; + return parcelable; + } + + // Note key1 and key2 have to match each other for this to return true. If + // key1 matches o.key2 and the other way around this returns false. + @Override + public boolean equals(@Nullable final Object o) { + if (!(o instanceof SameL3NetworkResponse)) return false; + final SameL3NetworkResponse other = (SameL3NetworkResponse) o; + return l2Key1.equals(other.l2Key1) && l2Key2.equals(other.l2Key2) + && confidence == other.confidence; + } + + @Override + public int hashCode() { + return Objects.hash(l2Key1, l2Key2, confidence); + } +} diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl new file mode 100644 index 000000000000..71966998a68a --- /dev/null +++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +/** {@hide} */ +parcelable SameL3NetworkResponseParcelable { + String l2Key1; + String l2Key2; + float confidence; +} diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java new file mode 100644 index 000000000000..5b016ec55ae1 --- /dev/null +++ b/core/java/android/net/ipmemorystore/Status.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import android.annotation.NonNull; + +/** + * A parcelable status representing the result of an operation. + * Parcels as StatusParceled. + * @hide + */ +public class Status { + public static final int SUCCESS = 0; + + public final int resultCode; + + public Status(final int resultCode) { + this.resultCode = resultCode; + } + + Status(@NonNull final StatusParcelable parcelable) { + this(parcelable.resultCode); + } + + /** Converts this Status to a parcelable object */ + @NonNull + public StatusParcelable toParcelable() { + final StatusParcelable parcelable = new StatusParcelable(); + parcelable.resultCode = resultCode; + return parcelable; + } + + public boolean isSuccess() { + return SUCCESS == resultCode; + } +} diff --git a/core/java/android/net/ipmemorystore/StatusParcelable.aidl b/core/java/android/net/ipmemorystore/StatusParcelable.aidl new file mode 100644 index 000000000000..fb36ef4a56ff --- /dev/null +++ b/core/java/android/net/ipmemorystore/StatusParcelable.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +/** {@hide} */ +parcelable StatusParcelable { + int resultCode; +} diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java new file mode 100644 index 000000000000..1343d24d0d94 --- /dev/null +++ b/core/java/android/os/BugreportManager.java @@ -0,0 +1,148 @@ +/* + * 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.os; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.content.Context; +import android.os.IBinder.DeathRecipient; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Class that provides a privileged API to capture and consume bugreports. + * + * @hide + */ +// TODO: Expose API when the implementation is more complete. +// @SystemApi +@SystemService(Context.BUGREPORT_SERVICE) +public class BugreportManager { + private final Context mContext; + private final IDumpstate mBinder; + + /** @hide */ + public BugreportManager(@NonNull Context context, IDumpstate binder) { + mContext = context; + mBinder = binder; + } + + /** + * An interface describing the listener for bugreport progress and status. + */ + public interface BugreportListener { + /** + * Called when there is a progress update. + * @param progress the progress in [0.0, 100.0] + */ + void onProgress(float progress); + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { + BUGREPORT_ERROR_INVALID_INPUT, + BUGREPORT_ERROR_RUNTIME + }) + + /** Possible error codes taking a bugreport can encounter */ + @interface BugreportErrorCode {} + + /** The input options were invalid */ + int BUGREPORT_ERROR_INVALID_INPUT = 1; + + /** A runtime error occured */ + int BUGREPORT_ERROR_RUNTIME = 2; + + /** + * Called when taking bugreport resulted in an error. + * + * @param errorCode the error that occurred. Possible values are + * {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}. + */ + void onError(@BugreportErrorCode int errorCode); + + /** + * Called when taking bugreport finishes successfully + * + * @param durationMs time capturing bugreport took in milliseconds + * @param title title for the bugreport; helpful in reminding the user why they took it + * @param description detailed description for the bugreport + */ + void onFinished(long durationMs, @NonNull String title, + @NonNull String description); + } + + /** + * Starts a bugreport asynchronously. + * + * @param bugreportFd file to write the bugreport. This should be opened in write-only, + * append mode. + * @param screenshotFd file to write the screenshot, if necessary. This should be opened + * in write-only, append mode. + * @param params options that specify what kind of a bugreport should be taken + * @param listener callback for progress and status updates + */ + @RequiresPermission(android.Manifest.permission.DUMP) + public void startBugreport(@NonNull FileDescriptor bugreportFd, + @Nullable FileDescriptor screenshotFd, + @NonNull BugreportParams params, @Nullable BugreportListener listener) { + // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. + DumpstateListener dsListener = new DumpstateListener(listener); + + try { + mBinder.startBugreport(bugreportFd, screenshotFd, params.getMode(), dsListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + // TODO(b/111441001) Connect up with BugreportListener methods. + private final class DumpstateListener extends IDumpstateListener.Stub + implements DeathRecipient { + private final BugreportListener mListener; + + DumpstateListener(@Nullable BugreportListener listener) { + mListener = listener; + } + + @Override + public void binderDied() { + // TODO(b/111441001): implement + } + + @Override + public void onProgressUpdated(int progress) throws RemoteException { + // TODO(b/111441001): implement + } + + @Override + public void onMaxProgressUpdated(int maxProgress) throws RemoteException { + // TODO(b/111441001): implement + } + + @Override + public void onSectionComplete(String title, int status, int size, int durationMs) + throws RemoteException { + // TODO(b/111441001): implement + } + } +} diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java new file mode 100644 index 000000000000..4e696aed1fa8 --- /dev/null +++ b/core/java/android/os/BugreportParams.java @@ -0,0 +1,90 @@ +/* + * 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.os; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Parameters that specify what kind of bugreport should be taken. + * + * @hide + */ +// TODO: Expose API when the implementation is more complete. +// @SystemApi +public final class BugreportParams { + private final int mMode; + + public BugreportParams(@BugreportMode int mode) { + mMode = mode; + } + + public int getMode() { + return mMode; + } + + /** + * Defines acceptable types of bugreports. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "BUGREPORT_MODE_" }, value = { + BUGREPORT_MODE_FULL, + BUGREPORT_MODE_INTERACTIVE, + BUGREPORT_MODE_REMOTE, + BUGREPORT_MODE_WEAR, + BUGREPORT_MODE_TELEPHONY, + BUGREPORT_MODE_WIFI + }) + public @interface BugreportMode {} + + /** + * Options for a bugreport without user interference (and hence causing less + * interference to the system), but includes all sections. + */ + public static final int BUGREPORT_MODE_FULL = IDumpstate.BUGREPORT_MODE_FULL; + + /** + * Options that allow user to monitor progress and enter additional data; might not + * include all sections. + */ + public static final int BUGREPORT_MODE_INTERACTIVE = IDumpstate.BUGREPORT_MODE_INTERACTIVE; + + /** + * Options for a bugreport requested remotely by administrator of the Device Owner app, + * not the device's user. + */ + public static final int BUGREPORT_MODE_REMOTE = IDumpstate.BUGREPORT_MODE_REMOTE; + + /** + * Options for a bugreport on a wearable device. + */ + public static final int BUGREPORT_MODE_WEAR = IDumpstate.BUGREPORT_MODE_WEAR; + + /** + * Options for a lightweight version of bugreport that only includes a few, urgent + * sections used to report telephony bugs. + */ + public static final int BUGREPORT_MODE_TELEPHONY = IDumpstate.BUGREPORT_MODE_TELEPHONY; + + /** + * Options for a lightweight bugreport that only includes a few sections related to + * Wifi. + */ + public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI; +} diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index cc6bb12a0894..b9cdcc096962 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -899,6 +899,33 @@ public final class Parcel { } /** + * Flatten an {@link ArrayMap} with string keys containing a particular object + * type into the parcel at the current dataPosition() and growing dataCapacity() + * if needed. The type of the objects in the array must be one that implements + * Parcelable. Only the raw data of the objects is written and not their type, + * so you must use the corresponding {@link #createTypedArrayMap(Parcelable.Creator)} + * + * @param val The map of objects to be written. + * @param parcelableFlags The parcelable flags to use. + * + * @see #createTypedArrayMap(Parcelable.Creator) + * @see Parcelable + */ + public <T extends Parcelable> void writeTypedArrayMap(@Nullable ArrayMap<String, T> val, + int parcelableFlags) { + if (val == null) { + writeInt(-1); + return; + } + final int count = val.size(); + writeInt(count); + for (int i = 0; i < count; i++) { + writeString(val.keyAt(i)); + writeTypedObject(val.valueAt(i), parcelableFlags); + } + } + + /** * Write an array set to the parcel. * * @param val The array set to write. @@ -1001,7 +1028,7 @@ public final class Parcel { * values are written using {@link #writeValue} and must follow the * specification there. */ - public final void writeSparseArray(@Nullable SparseArray<Object> val) { + public final <T> void writeSparseArray(@Nullable SparseArray<T> val) { if (val == null) { writeInt(-1); return; @@ -1400,6 +1427,34 @@ public final class Parcel { } /** + * Flatten a {@link SparseArray} containing a particular object type into the parcel + * at the current dataPosition() and growing dataCapacity() if needed. The + * type of the objects in the array must be one that implements Parcelable. + * Unlike the generic {@link #writeSparseArray(SparseArray)} method, however, only + * the raw data of the objects is written and not their type, so you must use the + * corresponding {@link #createTypedSparseArray(Parcelable.Creator)}. + * + * @param val The list of objects to be written. + * @param parcelableFlags The parcelable flags to use. + * + * @see #createTypedSparseArray(Parcelable.Creator) + * @see Parcelable + */ + public final <T extends Parcelable> void writeTypedSparseArray(@Nullable SparseArray<T> val, + int parcelableFlags) { + if (val == null) { + writeInt(-1); + return; + } + final int count = val.size(); + writeInt(count); + for (int i = 0; i < count; i++) { + writeInt(val.keyAt(i)); + writeTypedObject(val.valueAt(i), parcelableFlags); + } + } + + /** * @hide */ public <T extends Parcelable> void writeTypedList(@Nullable List<T> val, int parcelableFlags) { @@ -2369,7 +2424,7 @@ public final class Parcel { * Parcelables. */ @Nullable - public final SparseArray readSparseArray(@Nullable ClassLoader loader) { + public final <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) { int N = readInt(); if (N < 0) { return null; @@ -2466,6 +2521,62 @@ public final class Parcel { } /** + * Read into a new {@link SparseArray} items containing a particular object type + * that were written with {@link #writeTypedSparseArray(SparseArray, int)} at the + * current dataPosition(). The list <em>must</em> have previously been written + * via {@link #writeTypedSparseArray(SparseArray, int)} with the same object type. + * + * @param creator The creator to use when for instantiation. + * + * @return A newly created {@link SparseArray} containing objects with the same data + * as those that were previously written. + * + * @see #writeTypedSparseArray(SparseArray, int) + */ + public final @Nullable <T extends Parcelable> SparseArray<T> createTypedSparseArray( + @NonNull Parcelable.Creator<T> creator) { + final int count = readInt(); + if (count < 0) { + return null; + } + final SparseArray<T> array = new SparseArray<>(count); + for (int i = 0; i < count; i++) { + final int index = readInt(); + final T value = readTypedObject(creator); + array.append(index, value); + } + return array; + } + + /** + * Read into a new {@link ArrayMap} with string keys items containing a particular + * object type that were written with {@link #writeTypedArrayMap(ArrayMap, int)} at the + * current dataPosition(). The list <em>must</em> have previously been written + * via {@link #writeTypedArrayMap(ArrayMap, int)} with the same object type. + * + * @param creator The creator to use when for instantiation. + * + * @return A newly created {@link ArrayMap} containing objects with the same data + * as those that were previously written. + * + * @see #writeTypedArrayMap(ArrayMap, int) + */ + public final @Nullable <T extends Parcelable> ArrayMap<String, T> createTypedArrayMap( + @NonNull Parcelable.Creator<T> creator) { + final int count = readInt(); + if (count < 0) { + return null; + } + final ArrayMap<String, T> map = new ArrayMap<>(count); + for (int i = 0; i < count; i++) { + final String key = readString(); + final T value = readTypedObject(creator); + map.append(key, value); + } + return map; + } + + /** * Read and return a new ArrayList containing String objects from * the parcel that was written with {@link #writeStringList} at the * current dataPosition(). Returns null if the diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index c3e04894c6c9..0942d97df6c7 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1326,7 +1326,7 @@ public final class PowerManager { * @return Battery saver state data. * * @hide - * @see com.android.server.power.BatterySaverPolicy + * @see com.android.server.power.batterysaver.BatterySaverPolicy * @see PowerSaveState */ public PowerSaveState getPowerSaveState(@ServiceType int serviceType) { diff --git a/core/java/android/os/PowerSaveState.java b/core/java/android/os/PowerSaveState.java index de1128dfdef5..4918ad107763 100644 --- a/core/java/android/os/PowerSaveState.java +++ b/core/java/android/os/PowerSaveState.java @@ -27,7 +27,7 @@ public class PowerSaveState implements Parcelable { /** * Whether we should enable battery saver for this service. * - * @see com.android.server.power.BatterySaverPolicy + * @see com.android.server.power.batterysaver.BatterySaverPolicy */ public final boolean batterySaverEnabled; /** diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 980140675959..68d6d85775e4 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -16,6 +16,8 @@ package android.os; +import android.annotation.NonNull; + import com.android.internal.os.Zygote; import dalvik.annotation.optimization.FastNative; @@ -313,7 +315,7 @@ public final class Trace { * @param sectionName The name of the code section to appear in the trace. This may be at * most 127 Unicode code units long. */ - public static void beginSection(String sectionName) { + public static void beginSection(@NonNull String sectionName) { if (isTagEnabled(TRACE_TAG_APP)) { if (sectionName.length() > MAX_SECTION_NAME_LEN) { throw new IllegalArgumentException("sectionName is too long"); @@ -345,7 +347,7 @@ public final class Trace { * @param methodName The method name to appear in the trace. * @param cookie Unique identifier for distinguishing simultaneous events */ - public static void beginAsyncSection(String methodName, int cookie) { + public static void beginAsyncSection(@NonNull String methodName, int cookie) { asyncTraceBegin(TRACE_TAG_APP, methodName, cookie); } @@ -357,7 +359,7 @@ public final class Trace { * @param methodName The method name to appear in the trace. * @param cookie Unique identifier for distinguishing simultaneous events */ - public static void endAsyncSection(String methodName, int cookie) { + public static void endAsyncSection(@NonNull String methodName, int cookie) { asyncTraceEnd(TRACE_TAG_APP, methodName, cookie); } @@ -367,7 +369,7 @@ public final class Trace { * @param counterName The counter name to appear in the trace. * @param counterValue The counter value. */ - public static void setCounter(String counterName, long counterValue) { + public static void setCounter(@NonNull String counterName, long counterValue) { if (isTagEnabled(TRACE_TAG_APP)) { nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue); } diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index b5aeba0c5f16..226d1d0a1241 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -74,10 +74,13 @@ public abstract class Vibrator { private final String mPackageName; // The default vibration intensity level for haptic feedback. @VibrationIntensity - private final int mDefaultHapticFeedbackIntensity; + private int mDefaultHapticFeedbackIntensity; // The default vibration intensity level for notifications. @VibrationIntensity - private final int mDefaultNotificationVibrationIntensity; + private int mDefaultNotificationVibrationIntensity; + // The default vibration intensity level for ringtones. + @VibrationIntensity + private int mDefaultRingVibrationIntensity; /** * @hide to prevent subclassing from outside of the framework @@ -85,10 +88,7 @@ public abstract class Vibrator { public Vibrator() { mPackageName = ActivityThread.currentPackageName(); final Context ctx = ActivityThread.currentActivityThread().getSystemContext(); - mDefaultHapticFeedbackIntensity = loadDefaultIntensity(ctx, - com.android.internal.R.integer.config_defaultHapticFeedbackIntensity); - mDefaultNotificationVibrationIntensity = loadDefaultIntensity(ctx, - com.android.internal.R.integer.config_defaultNotificationVibrationIntensity); + loadVibrationIntensities(ctx); } /** @@ -96,10 +96,16 @@ public abstract class Vibrator { */ protected Vibrator(Context context) { mPackageName = context.getOpPackageName(); + loadVibrationIntensities(context); + } + + private void loadVibrationIntensities(Context context) { mDefaultHapticFeedbackIntensity = loadDefaultIntensity(context, com.android.internal.R.integer.config_defaultHapticFeedbackIntensity); mDefaultNotificationVibrationIntensity = loadDefaultIntensity(context, com.android.internal.R.integer.config_defaultNotificationVibrationIntensity); + mDefaultRingVibrationIntensity = loadDefaultIntensity(context, + com.android.internal.R.integer.config_defaultRingVibrationIntensity); } private int loadDefaultIntensity(Context ctx, int resId) { @@ -115,13 +121,20 @@ public abstract class Vibrator { } /** - * Get the default vibration intensity for notifications and ringtones. + * Get the default vibration intensity for notifications. * @hide */ public int getDefaultNotificationVibrationIntensity() { return mDefaultNotificationVibrationIntensity; } + /** Get the default vibration intensity for ringtones. + * @hide + */ + public int getDefaultRingVibrationIntensity() { + return mDefaultRingVibrationIntensity; + } + /** * Check whether the hardware has a vibrator. * diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 66e1c80fa31e..574ccaf8d1cc 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -86,7 +86,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.MemoryIntArray; -import android.view.textservice.TextServicesManager; +import android.view.inputmethod.InputMethodSystemProperty; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.ColorDisplayController; @@ -3377,6 +3377,22 @@ public final class Settings { */ public static final String NOTIFICATION_VIBRATION_INTENSITY = "notification_vibration_intensity"; + /** + * The intensity of ringtone vibrations, if configurable. + * + * Not all devices are capable of changing their vibration intensity; on these devices + * there will likely be no difference between the various vibration intensities except for + * intensity 0 (off) and the rest. + * + * <b>Values:</b><br/> + * 0 - Vibration is disabled<br/> + * 1 - Weak vibrations<br/> + * 2 - Medium vibrations<br/> + * 3 - Strong vibrations + * @hide + */ + public static final String RING_VIBRATION_INTENSITY = + "ring_vibration_intensity"; /** * The intensity of haptic feedback vibrations, if configurable. @@ -4246,6 +4262,7 @@ public final class Settings { ACCELEROMETER_ROTATION, SHOW_BATTERY_PERCENT, NOTIFICATION_VIBRATION_INTENSITY, + RING_VIBRATION_INTENSITY, HAPTIC_FEEDBACK_INTENSITY, DISPLAY_COLOR_MODE, ALARM_ALERT, @@ -4397,6 +4414,7 @@ public final class Settings { VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR); VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR); VALIDATORS.put(NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); + VALIDATORS.put(RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR); VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR); @@ -5030,10 +5048,6 @@ public final class Settings { public static boolean putStringForUser(@NonNull ContentResolver resolver, @NonNull String name, @Nullable String value, @Nullable String tag, boolean makeDefault, @UserIdInt int userHandle) { - if (LOCATION_MODE.equals(name)) { - // Map LOCATION_MODE to underlying location provider storage API - return setLocationModeForUser(resolver, Integer.parseInt(value), userHandle); - } if (MOVED_TO_GLOBAL.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure" + " to android.provider.Settings.Global"); @@ -5186,10 +5200,6 @@ public final class Settings { /** @hide */ @UnsupportedAppUsage public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { - if (LOCATION_MODE.equals(name)) { - // Map from to underlying location provider storage API to location mode - return getLocationModeForUser(cr, userHandle); - } String v = getStringForUser(cr, name, userHandle); try { return v != null ? Integer.parseInt(v) : def; @@ -5224,10 +5234,6 @@ public final class Settings { /** @hide */ public static int getIntForUser(ContentResolver cr, String name, int userHandle) throws SettingNotFoundException { - if (LOCATION_MODE.equals(name)) { - // Map from to underlying location provider storage API to location mode - return getLocationModeForUser(cr, userHandle); - } String v = getStringForUser(cr, name, userHandle); try { return Integer.parseInt(v); @@ -5810,9 +5816,8 @@ public final class Settings { * this value being present in settings.db or on ContentObserver notifications on the * corresponding Uri. * - * @deprecated use {@link #LOCATION_MODE} and - * {@link LocationManager#MODE_CHANGED_ACTION} (or - * {@link LocationManager#PROVIDERS_CHANGED_ACTION}) + * @deprecated Providers should not be controlled individually. See {@link #LOCATION_MODE} + * documentation for information on reading/writing location information. */ @Deprecated public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; @@ -5830,9 +5835,7 @@ public final class Settings { * notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION} * to receive changes in this value. * - * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To - * get the status of a location provider, use - * {@link LocationManager#isProviderEnabled(String)}. + * @deprecated To check location mode, use {@link LocationManager#isLocationEnabled()}. */ @Deprecated public static final String LOCATION_MODE = "location_mode"; @@ -5861,9 +5864,7 @@ public final class Settings { /** * Location access disabled. * - * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To - * get the status of a location provider, use - * {@link LocationManager#isProviderEnabled(String)}. + * @deprecated See {@link #LOCATION_MODE}. */ @Deprecated public static final int LOCATION_MODE_OFF = 0; @@ -5883,9 +5884,7 @@ public final class Settings { * with {@link android.location.Criteria#POWER_HIGH} may be downgraded to * {@link android.location.Criteria#POWER_MEDIUM}. * - * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To - * get the status of a location provider, use - * {@link LocationManager#isProviderEnabled(String)}. + * @deprecated See {@link #LOCATION_MODE}. */ @Deprecated public static final int LOCATION_MODE_BATTERY_SAVING = 2; @@ -5893,9 +5892,7 @@ public final class Settings { /** * Best-effort location computation allowed. * - * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To - * get the status of a location provider, use - * {@link LocationManager#isProviderEnabled(String)}. + * @deprecated See {@link #LOCATION_MODE}. */ @Deprecated public static final int LOCATION_MODE_HIGH_ACCURACY = 3; @@ -6019,6 +6016,15 @@ public final class Settings { "lock_screen_allow_remote_input"; /** + * Indicates which clock face to show on lock screen and AOD. + * @hide + */ + public static final String LOCK_SCREEN_CUSTOM_CLOCK_FACE = "lock_screen_custom_clock_face"; + + private static final Validator LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR = + ANY_STRING_VALIDATOR; + + /** * Set by the system to track if the user needs to see the call to action for * the lockscreen notification policy. * @hide @@ -7835,6 +7841,19 @@ public final class Settings { BOOLEAN_VALIDATOR; /** + * Whether or not face unlock always requires user confirmation, meaning {@link + * android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(boolean)} + * is always 'true'. This overrides the behavior that apps choose in the + * setRequireConfirmation API. + * @hide + */ + public static final String FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION = + "face_unlock_always_require_confirmation"; + + private static final Validator FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR = + BOOLEAN_VALIDATOR; + + /** * Whether the assist gesture should be enabled. * * @hide @@ -8431,6 +8450,7 @@ public final class Settings { AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, FACE_UNLOCK_KEYGUARD_ENABLED, FACE_UNLOCK_APP_ENABLED, + FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, ASSIST_GESTURE_WAKE_ENABLED, @@ -8448,6 +8468,7 @@ public final class Settings { HUSH_GESTURE_USED, IN_CALL_NOTIFICATION_ENABLED, LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + LOCK_SCREEN_CUSTOM_CLOCK_FACE, LOCK_SCREEN_SHOW_NOTIFICATIONS, ZEN_DURATION, SHOW_ZEN_UPGRADE_NOTIFICATION, @@ -8584,6 +8605,8 @@ public final class Settings { AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR); VALIDATORS.put(FACE_UNLOCK_KEYGUARD_ENABLED, FACE_UNLOCK_KEYGUARD_ENABLED_VALIDATOR); VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR); + VALIDATORS.put(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, + FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR); VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR); VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR); @@ -8624,6 +8647,7 @@ public final class Settings { VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR); VALIDATORS.put(NOTIFICATION_NEW_INTERRUPTION_MODEL, BOOLEAN_VALIDATOR); VALIDATORS.put(TRUST_AGENTS_EXTEND_UNLOCK, TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR); + VALIDATORS.put(LOCK_SCREEN_CUSTOM_CLOCK_FACE, LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR); VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR); } @@ -8652,14 +8676,14 @@ public final class Settings { CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED); CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION); CLONE_TO_MANAGED_PROFILE.add(ALLOWED_GEOLOCATION_ORIGINS); - CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD); CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES); - CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS); CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER); CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE); CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED); CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE); - if (TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER) { + if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) { + CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD); + CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS); CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER); CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER_SUBTYPE); } @@ -8778,84 +8802,6 @@ public final class Settings { userId); } } - - /** - * Thread-safe method for setting the location mode to one of - * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, - * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. - * Necessary because the mode is a composite of the underlying location provider - * settings. - * - * @param cr the content resolver to use - * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY} - * @param userId the userId for which to change mode - * @return true if the value was set, false on database errors - * - * @throws IllegalArgumentException if mode is not one of the supported values - * - * @deprecated To enable/disable location, use - * {@link LocationManager#setLocationEnabledForUser(boolean, int)}. - * To enable/disable a specific location provider, use - * {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}. - */ - @Deprecated - private static boolean setLocationModeForUser( - ContentResolver cr, int mode, int userId) { - synchronized (mLocationSettingsLock) { - boolean gps = false; - boolean network = false; - switch (mode) { - case LOCATION_MODE_OFF: - break; - case LOCATION_MODE_SENSORS_ONLY: - gps = true; - break; - case LOCATION_MODE_BATTERY_SAVING: - network = true; - break; - case LOCATION_MODE_HIGH_ACCURACY: - gps = true; - network = true; - break; - default: - throw new IllegalArgumentException("Invalid location mode: " + mode); - } - - boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser( - cr, LocationManager.NETWORK_PROVIDER, network, userId); - boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser( - cr, LocationManager.GPS_PROVIDER, gps, userId); - return gpsSuccess && nlpSuccess; - } - } - - /** - * Thread-safe method for reading the location mode, returns one of - * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, - * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. Necessary - * because the mode is a composite of the underlying location provider settings. - * - * @param cr the content resolver to use - * @param userId the userId for which to read the mode - * @return the location mode - */ - private static final int getLocationModeForUser(ContentResolver cr, int userId) { - synchronized (mLocationSettingsLock) { - boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser( - cr, LocationManager.GPS_PROVIDER, userId); - boolean networkEnabled = Settings.Secure.isLocationProviderEnabledForUser( - cr, LocationManager.NETWORK_PROVIDER, userId); - if (gpsEnabled && networkEnabled) { - return LOCATION_MODE_HIGH_ACCURACY; - } else if (gpsEnabled) { - return LOCATION_MODE_SENSORS_ONLY; - } else if (networkEnabled) { - return LOCATION_MODE_BATTERY_SAVING; - } else { - return LOCATION_MODE_OFF; - } - } - } } /** @@ -9423,8 +9369,7 @@ public final class Settings { "hdmi_control_auto_wakeup_enabled"; /** - * Whether TV or Audio System will also turn off other CEC devices when it goes to standby - * mode. + * Whether TV will also turn off other CEC devices when it goes to standby mode. * (0 = false, 1 = true) * * @hide @@ -9433,15 +9378,6 @@ public final class Settings { "hdmi_control_auto_device_off_enabled"; /** - * Whether Audio System will also turn off TV when it goes to standby mode. - * (0 = false, 1 = true) - * - * @hide - */ - public static final String HDMI_CONTROL_AUTO_TV_OFF_ENABLED = - "hdmi_control_auto_tv_off_enabled"; - - /** * If <b>true</b>, enables out-of-the-box execution for priv apps. * Default: false * Values: 0 = false, 1 = true @@ -11272,14 +11208,14 @@ public final class Settings { * quick_doze_enabled (boolean) * </pre> * @hide - * @see com.android.server.power.BatterySaverPolicy + * @see com.android.server.power.batterysaver.BatterySaverPolicy */ public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants"; /** * Battery Saver device specific settings * This is encoded as a key=value list, separated by commas. - * See {@link com.android.server.power.BatterySaverPolicy} for the details. + * See {@link com.android.server.power.batterysaver.BatterySaverPolicy} for the details. * * @hide */ @@ -14020,6 +13956,53 @@ public final class Settings { */ public static final String NATIVE_FLAGS_HEALTH_CHECK_ENABLED = "native_flags_health_check_enabled"; + + /** + * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls the mode + * in which the historical registry operates. + * + * @hide + */ + public static final String APPOP_HISTORY_MODE = "mode"; + + /** + * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls how long + * is the interval between snapshots in the base case i.e. the most recent + * part of the history. + * + * @hide + */ + public static final String APPOP_HISTORY_BASE_INTERVAL_MILLIS = "baseIntervalMillis"; + + /** + * Parameter for {@link #APPOP_HISTORY_PARAMETERS} that controls the base + * for the logarithmic step when building app op history. + * + * @hide + */ + public static final String APPOP_HISTORY_INTERVAL_MULTIPLIER = "intervalMultiplier"; + + /** + * Appop history parameters. These parameters are represented by + * a comma-delimited key-value list. + * + * The following strings are supported as keys: + * <pre> + * mode (int) + * baseIntervalMillis (long) + * intervalMultiplier (int) + * </pre> + * + * Ex: "enabled=true,baseIntervalMillis=1000,intervalMultiplier=10" + * + * @see #APPOP_HISTORY_MODE + * @see #APPOP_HISTORY_BASE_INTERVAL_MILLIS + * @see #APPOP_HISTORY_INTERVAL_MULTIPLIER + * + * @hide + */ + public static final String APPOP_HISTORY_PARAMETERS = + "appop_history_parameters"; } /** diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java new file mode 100644 index 000000000000..b77405affaec --- /dev/null +++ b/core/java/android/service/appprediction/AppPredictionService.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.appprediction; + +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.annotation.CallSuper; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.Service; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; +import android.app.prediction.IPredictionCallback; +import android.content.Intent; +import android.content.pm.ParceledListSlice; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.service.appprediction.IPredictionService.Stub; +import android.util.ArrayMap; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * TODO(b/111701043): Add java docs + * + * @hide + */ +@SystemApi +public abstract class AppPredictionService extends Service { + + private static final String TAG = "AppPredictionService"; + + /** + * The {@link Intent} that must be declared as handled by the service. + * TODO(b/111701043): Add any docs about permissions the service must hold + * + * @hide + */ + public static final String SERVICE_INTERFACE = + "android.service.appprediction.AppPredictionService"; + + private final ArrayMap<AppPredictionSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks = + new ArrayMap<>(); + private Handler mHandler; + + private final IPredictionService mInterface = new Stub() { + + @Override + public void onCreatePredictionSession(AppPredictionContext context, + AppPredictionSessionId sessionId) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::doCreatePredictionSession, + AppPredictionService.this, context, sessionId)); + } + + @Override + public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::onAppTargetEvent, + AppPredictionService.this, sessionId, event)); + } + + @Override + public void notifyLocationShown(AppPredictionSessionId sessionId, String launchLocation, + ParceledListSlice targetIds) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::onLocationShown, AppPredictionService.this, + sessionId, launchLocation, targetIds.getList())); + } + + @Override + public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets, + IPredictionCallback callback) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::onSortAppTargets, + AppPredictionService.this, sessionId, targets.getList(), null, + new CallbackWrapper(callback))); + } + + @Override + public void registerPredictionUpdates(AppPredictionSessionId sessionId, + IPredictionCallback callback) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::doRegisterPredictionUpdates, + AppPredictionService.this, sessionId, callback)); + } + + @Override + public void unregisterPredictionUpdates(AppPredictionSessionId sessionId, + IPredictionCallback callback) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::doUnregisterPredictionUpdates, + AppPredictionService.this, sessionId, callback)); + } + + @Override + public void requestPredictionUpdate(AppPredictionSessionId sessionId) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::doRequestPredictionUpdate, + AppPredictionService.this, sessionId)); + } + + @Override + public void onDestroyPredictionSession(AppPredictionSessionId sessionId) { + mHandler.sendMessage( + obtainMessage(AppPredictionService::doDestroyPredictionSession, + AppPredictionService.this, sessionId)); + } + }; + + @CallSuper + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(Looper.getMainLooper(), null, true); + } + + @Override + public final IBinder onBind(Intent intent) { + return mInterface.asBinder(); + } + + /** + * Called by a client app to indicate a target launch + */ + @MainThread + public abstract void onAppTargetEvent(@NonNull AppPredictionSessionId sessionId, + @NonNull AppTargetEvent event); + + /** + * Called by a client app to indication a particular location has been shown to the user. + */ + @MainThread + public abstract void onLocationShown(@NonNull AppPredictionSessionId sessionId, + @NonNull String launchLocation, @NonNull List<AppTargetId> targetIds); + + private void doCreatePredictionSession(@NonNull AppPredictionContext context, + @NonNull AppPredictionSessionId sessionId) { + mSessionCallbacks.put(sessionId, new ArrayList<>()); + onCreatePredictionSession(context, sessionId); + } + + /** + * Creates a new interaction session. + * + * @param context interaction context + * @param sessionId the session's Id + */ + public void onCreatePredictionSession(@NonNull AppPredictionContext context, + @NonNull AppPredictionSessionId sessionId) {} + + /** + * Called by the client app to request sorting of targets based on prediction rank. + * TODO(b/111701043): Implement CancellationSignal so caller can cancel a long running request + */ + @MainThread + public abstract void onSortAppTargets(@NonNull AppPredictionSessionId sessionId, + @NonNull List<AppTarget> targets, @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer<List<AppTarget>> callback); + + private void doRegisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId); + if (callbacks == null) { + Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId); + return; + } + + final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback); + if (wrapper == null) { + callbacks.add(new CallbackWrapper(callback)); + if (callbacks.size() == 1) { + onStartPredictionUpdates(); + } + } + } + + /** + * Called when any continuous prediction callback is registered. + */ + @MainThread + public void onStartPredictionUpdates() {} + + private void doUnregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId); + if (callbacks == null) { + Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId); + return; + } + + final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback); + if (wrapper != null) { + callbacks.remove(wrapper); + if (callbacks.isEmpty()) { + onStopPredictionUpdates(); + } + } + } + + /** + * Called when there are no longer any continuous prediction callbacks registered. + */ + @MainThread + public void onStopPredictionUpdates() {} + + private void doRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { + final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId); + if (callbacks != null && !callbacks.isEmpty()) { + onRequestPredictionUpdate(sessionId); + } + } + + /** + * Called by the client app to request target predictions. This method is only called if there + * are one or more prediction callbacks registered. + * TODO(b/111701043): Add java docs + * + * @see #updatePredictions(AppPredictionSessionId, List) + */ + @MainThread + public abstract void onRequestPredictionUpdate(@NonNull AppPredictionSessionId sessionId); + + private void doDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { + mSessionCallbacks.remove(sessionId); + onDestroyPredictionSession(sessionId); + } + + /** + * Destroys the interaction session. + * + * @param sessionId the id of the session to destroy + */ + @MainThread + public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {} + + /** + * Used by the prediction factory to send back results the client app. The can be called + * in response to {@link #onRequestPredictionUpdate(AppPredictionSessionId)} or proactively as + * a result of changes in predictions. + */ + public final void updatePredictions(@NonNull AppPredictionSessionId sessionId, + @NonNull List<AppTarget> targets) { + List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId); + if (callbacks != null) { + for (CallbackWrapper callback : callbacks) { + callback.accept(targets); + } + } + } + + /** + * Finds the callback wrapper for the given callback. + */ + private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks, + IPredictionCallback callback) { + for (int i = callbacks.size() - 1; i >= 0; i--) { + if (callbacks.get(i).isCallback(callback)) { + return callbacks.get(i); + } + } + return null; + } + + private static final class CallbackWrapper implements Consumer<List<AppTarget>>, + IBinder.DeathRecipient { + + private IPredictionCallback mCallback; + + CallbackWrapper(IPredictionCallback callback) { + mCallback = callback; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death: " + e); + } + } + + public boolean isCallback(@NonNull IPredictionCallback callback) { + return mCallback.equals(callback); + } + + @Override + public void accept(List<AppTarget> ts) { + try { + if (mCallback != null) { + mCallback.onResult(new ParceledListSlice(ts)); + } + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result:" + e); + } + } + + @Override + public void binderDied() { + mCallback = null; + } + } +} diff --git a/core/java/android/service/appprediction/IPredictionService.aidl b/core/java/android/service/appprediction/IPredictionService.aidl new file mode 100644 index 000000000000..3a6d1666f4b9 --- /dev/null +++ b/core/java/android/service/appprediction/IPredictionService.aidl @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.appprediction; + +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.IPredictionCallback; +import android.content.pm.ParceledListSlice; + +/** + * Interface from the system to a prediction service. + * + * @hide + */ +oneway interface IPredictionService { + + void onCreatePredictionSession(in AppPredictionContext context, + in AppPredictionSessionId sessionId); + + void notifyAppTargetEvent(in AppPredictionSessionId sessionId, in AppTargetEvent event); + + void notifyLocationShown(in AppPredictionSessionId sessionId, in String launchLocation, + in ParceledListSlice targetIds); + + void sortAppTargets(in AppPredictionSessionId sessionId, in ParceledListSlice targets, + in IPredictionCallback callback); + + void registerPredictionUpdates(in AppPredictionSessionId sessionId, + in IPredictionCallback callback); + + void unregisterPredictionUpdates(in AppPredictionSessionId sessionId, + in IPredictionCallback callback); + + void requestPredictionUpdate(in AppPredictionSessionId sessionId); + + void onDestroyPredictionSession(in AppPredictionSessionId sessionId); +} diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 26bf361a8acc..e5e028d22d97 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -42,6 +42,7 @@ import android.view.contentcapture.ContentCaptureSession; import android.view.contentcapture.ContentCaptureSessionId; import android.view.contentcapture.IContentCaptureDirectManager; import android.view.contentcapture.MainContentCaptureSession; +import android.view.contentcapture.UserDataRemovalRequest; import com.android.internal.os.IResultReceiver; @@ -165,7 +166,7 @@ public abstract class ContentCaptureService extends Service { */ public final void setContentCaptureWhitelist(@Nullable List<String> packages, @Nullable List<ComponentName> activities) { - //TODO(b/111276913): implement + //TODO(b/122595322): implement } /** @@ -176,7 +177,7 @@ public abstract class ContentCaptureService extends Service { */ public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity, boolean enabled) { - //TODO(b/111276913): implement + //TODO(b/122595322): implement } /** @@ -187,7 +188,7 @@ public abstract class ContentCaptureService extends Service { */ public final void setPackageContentCaptureEnabled(@NonNull String packageName, boolean enabled) { - //TODO(b/111276913): implement + //TODO(b/122595322): implement } /** @@ -196,7 +197,7 @@ public abstract class ContentCaptureService extends Service { */ @NonNull public final Set<ComponentName> getContentCaptureDisabledActivities() { - //TODO(b/111276913): implement + //TODO(b/122595322): implement return null; } @@ -206,7 +207,7 @@ public abstract class ContentCaptureService extends Service { */ @NonNull public final Set<String> getContentCaptureDisabledPackages() { - //TODO(b/111276913): implement + //TODO(b/122595322): implement return null; } @@ -255,6 +256,16 @@ public abstract class ContentCaptureService extends Service { if (VERBOSE) Log.v(TAG, "onContentCaptureEventsRequest(id=" + sessionId + ")"); onContentCaptureEventsRequest(sessionId, new ContentCaptureEventsRequest(event)); } + + /** + * Notifies the service that the app requested to remove data associated with the user. + * + * @param request the user data requested to be removed + */ + public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) { + if (VERBOSE) Log.v(TAG, "onUserDataRemovalRequest()"); + } + /** * Notifies the service of {@link SnapshotData snapshot data} associated with a session. * @@ -311,6 +322,14 @@ public abstract class ContentCaptureService extends Service { @NonNull String sessionId, int uid, IResultReceiver clientReceiver) { mSessionUids.put(sessionId, uid); onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId)); + + final int flags = context.getFlags(); + if ((flags & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0) { + setClientState(clientReceiver, ContentCaptureSession.STATE_DISABLED_BY_FLAG_SECURE, + mClientInterface.asBinder()); + return; + } + setClientState(clientReceiver, ContentCaptureSession.STATE_ACTIVE, mClientInterface.asBinder()); } @@ -392,15 +411,16 @@ public abstract class ContentCaptureService extends Service { } /** - * Sends the state of the {@link ContentCaptureManager} in the cleint app. + * Sends the state of the {@link ContentCaptureManager} in the client app. * * @param clientReceiver receiver in the client app. + * @param sessionState state of the session * @param binder handle to the {@code IContentCaptureDirectManager} object that resides in the * service. * @hide */ public static void setClientState(@NonNull IResultReceiver clientReceiver, - int sessionStatus, @Nullable IBinder binder) { + int sessionState, @Nullable IBinder binder) { try { final Bundle extras; if (binder != null) { @@ -409,7 +429,7 @@ public abstract class ContentCaptureService extends Service { } else { extras = null; } - clientReceiver.send(sessionStatus, extras); + clientReceiver.send(sessionState, extras); } catch (RemoteException e) { Slog.w(TAG, "Error async reporting result to client: " + e); } diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java new file mode 100644 index 000000000000..0da80397ec75 --- /dev/null +++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.contentsuggestions; + +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.app.contentsuggestions.ClassificationsRequest; +import android.app.contentsuggestions.ContentSuggestionsManager; +import android.app.contentsuggestions.IClassificationsCallback; +import android.app.contentsuggestions.ISelectionsCallback; +import android.app.contentsuggestions.SelectionsRequest; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.GraphicBuffer; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +/** + * @hide + */ +@SystemApi +public abstract class ContentSuggestionsService extends Service { + + private static final String TAG = ContentSuggestionsService.class.getSimpleName(); + + private Handler mHandler; + + /** + * The action for the intent used to define the content suggestions service. + */ + public static final String SERVICE_INTERFACE = + "android.service.contentsuggestions.ContentSuggestionsService"; + + private final IContentSuggestionsService mInterface = new IContentSuggestionsService.Stub() { + @Override + public void provideContextImage(int taskId, GraphicBuffer contextImage, + Bundle imageContextRequestExtras) { + mHandler.sendMessage( + obtainMessage(ContentSuggestionsService::processContextImage, + ContentSuggestionsService.this, taskId, + Bitmap.createHardwareBitmap(contextImage), + imageContextRequestExtras)); + } + + @Override + public void suggestContentSelections(SelectionsRequest request, + ISelectionsCallback callback) { + mHandler.sendMessage(obtainMessage(ContentSuggestionsService::suggestContentSelections, + ContentSuggestionsService.this, request, wrapSelectionsCallback(callback))); + + } + + @Override + public void classifyContentSelections(ClassificationsRequest request, + IClassificationsCallback callback) { + mHandler.sendMessage(obtainMessage(ContentSuggestionsService::classifyContentSelections, + ContentSuggestionsService.this, request, wrapClassificationCallback(callback))); + } + + @Override + public void notifyInteraction(String requestId, Bundle interaction) { + mHandler.sendMessage( + obtainMessage(ContentSuggestionsService::notifyInteraction, + ContentSuggestionsService.this, requestId, interaction)); + } + }; + + @CallSuper + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(Looper.getMainLooper(), null, true); + } + + /** @hide */ + @Override + public final IBinder onBind(Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return mInterface.asBinder(); + } + Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent); + return null; + } + + /** + * Called by the system to provide the snapshot for the task associated with the given + * {@param taskId}. + */ + public abstract void processContextImage( + int taskId, @Nullable Bitmap contextImage, @NonNull Bundle extras); + + /** + * Called by a client app to make a request for content selections. + */ + public abstract void suggestContentSelections(@NonNull SelectionsRequest request, + @NonNull ContentSuggestionsManager.SelectionsCallback callback); + + /** + * Called by a client app to classify the provided content selections. + */ + public abstract void classifyContentSelections(@NonNull ClassificationsRequest request, + @NonNull ContentSuggestionsManager.ClassificationsCallback callback); + + /** + * Called by a client app to report an interaction. + */ + public abstract void notifyInteraction(@NonNull String requestId, @NonNull Bundle interaction); + + private ContentSuggestionsManager.SelectionsCallback wrapSelectionsCallback( + ISelectionsCallback callback) { + return (statusCode, selections) -> { + try { + callback.onContentSelectionsAvailable(statusCode, selections); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + } + }; + } + + private ContentSuggestionsManager.ClassificationsCallback wrapClassificationCallback( + IClassificationsCallback callback) { + return ((statusCode, classifications) -> { + try { + callback.onContentClassificationsAvailable(statusCode, classifications); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + } + }); + } +} diff --git a/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl b/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl new file mode 100644 index 000000000000..1926478322c8 --- /dev/null +++ b/core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.contentsuggestions; + +import android.app.contentsuggestions.IClassificationsCallback; +import android.app.contentsuggestions.ISelectionsCallback; +import android.app.contentsuggestions.ClassificationsRequest; +import android.app.contentsuggestions.SelectionsRequest; +import android.graphics.GraphicBuffer; +import android.os.Bundle; + +/** + * Interface from the system to an implementation of a content suggestions service. + * + * @hide + */ +oneway interface IContentSuggestionsService { + void provideContextImage( + int taskId, + in GraphicBuffer contextImage, + in Bundle imageContextRequestExtras); + void suggestContentSelections( + in SelectionsRequest request, + in ISelectionsCallback callback); + void classifyContentSelections( + in ClassificationsRequest request, + in IClassificationsCallback callback); + void notifyInteraction(in String requestId, in Bundle interaction); +} diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 433483f717fa..7a586815199c 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; @@ -370,6 +371,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { /** * @return The color of the underline for that span, or 0 if there is no underline */ + @ColorInt public int getUnderlineColor() { // The order here should match what is used in updateDrawState final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java index af73a16e012a..265b0d303f7f 100644 --- a/core/java/android/util/DebugUtils.java +++ b/core/java/android/util/DebugUtils.java @@ -18,6 +18,7 @@ package android.util; import android.annotation.UnsupportedAppUsage; import android.os.Build; + import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -250,7 +251,7 @@ public class DebugUtils { if (value == 0 && flagsWasZero) { return constNameWithoutPrefix(prefix, field); } - if ((flags & value) != 0) { + if ((flags & value) == value) { flags &= ~value; res.append(constNameWithoutPrefix(prefix, field)).append('|'); } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 8b97e0e4a107..0edcb3d8eb6a 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -22,7 +22,9 @@ import android.provider.Settings; import android.text.TextUtils; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** * Util class to get feature flag information. @@ -37,8 +39,11 @@ public class FeatureFlagUtils { public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; public static final String SAFETY_HUB = "settings_safety_hub"; public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; + public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled"; private static final Map<String, String> DEFAULT_FLAGS; + private static final Set<String> OBSERVABLE_FLAGS; + static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("settings_audio_switcher", "true"); @@ -54,6 +59,10 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); DEFAULT_FLAGS.put(SAFETY_HUB, "false"); DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false"); + DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false"); + + OBSERVABLE_FLAGS = new HashSet<>(); + OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED); } /** @@ -90,6 +99,16 @@ public class FeatureFlagUtils { */ public static void setEnabled(Context context, String feature, boolean enabled) { SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false"); + + // Also update Settings.Global if needed so that we can observe it via observer. + if (OBSERVABLE_FLAGS.contains(feature)) { + setObservableFlag(context, feature, enabled); + } + } + + private static void setObservableFlag(Context context, String feature, boolean enabled) { + Settings.Global.putString( + context.getContentResolver(), feature, enabled ? "true" : "false"); } /** diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index a86abe517b6a..9d3552f71f1f 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -18,6 +18,9 @@ package android.view; import static android.view.Display.DEFAULT_DISPLAY; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.graphics.Matrix; @@ -30,6 +33,7 @@ import android.util.SparseArray; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import java.lang.annotation.Retention; import java.util.Objects; /** @@ -462,7 +466,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { /** * This flag indicates that the event has been generated by a gesture generator. It - * provides a hint to the GestureDector to not apply any touch slop. + * provides a hint to the GestureDetector to not apply any touch slop. * * @hide */ @@ -1391,6 +1395,42 @@ public final class MotionEvent extends InputEvent implements Parcelable { }; /** + * Classification constant: None. + * + * No additional information is available about the current motion event stream. + * + * @see #getClassification + */ + public static final int CLASSIFICATION_NONE = 0; + + /** + * Classification constant: Ambiguous gesture. + * + * The user's intent with respect to the current event stream is not yet determined. + * Gestural actions, such as scrolling, should be inhibited until the classification resolves + * to another value or the event stream ends. + * + * @see #getClassification + */ + public static final int CLASSIFICATION_AMBIGUOUS_GESTURE = 1; + + /** + * Classification constant: Deep press. + * + * The current event stream represents the user intentionally pressing harder on the screen. + * This classification type should be used to accelerate the long press behaviour. + * + * @see #getClassification + */ + public static final int CLASSIFICATION_DEEP_PRESS = 2; + + /** @hide */ + @Retention(SOURCE) + @IntDef(prefix = { "CLASSIFICATION" }, value = { + CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS}) + public @interface Classification {}; + + /** * Tool type constant: Unknown tool type. * This constant is used when the tool type is not known or is not relevant, * such as for a trackball or other non-pointing device. @@ -1478,7 +1518,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { private static native long nativeInitialize(long nativePtr, int deviceId, int source, int displayId, int action, int flags, int edgeFlags, - int metaState, int buttonState, + int metaState, int buttonState, @Classification int classification, float xOffset, float yOffset, float xPrecision, float yPrecision, long downTimeNanos, long eventTimeNanos, int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords); @@ -1548,6 +1588,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { @CriticalNative private static native void nativeSetButtonState(long nativePtr, int buttonState); @CriticalNative + private static native int nativeGetClassification(long nativePtr); + @CriticalNative private static native int nativeGetActionButton(long nativePtr); @CriticalNative private static native void nativeSetActionButton(long nativePtr, int actionButton); @@ -1648,7 +1690,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { MotionEvent ev = obtain(); ev.mNativePtr = nativeInitialize(ev.mNativePtr, deviceId, source, displayId, action, flags, edgeFlags, metaState, buttonState, - 0, 0, xPrecision, yPrecision, + CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision, downTime * NS_PER_MS, eventTime * NS_PER_MS, pointerCount, pointerProperties, pointerCoords); if (ev.mNativePtr == 0) { @@ -1833,7 +1875,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { ev.mNativePtr = nativeInitialize(ev.mNativePtr, deviceId, source, displayId, - action, 0, edgeFlags, metaState, 0, + action, 0, edgeFlags, metaState, 0 /*buttonState*/, CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision, downTime * NS_PER_MS, eventTime * NS_PER_MS, 1, pp, pc); @@ -2539,6 +2581,18 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** + * Returns the classification for the current gesture. + * The classification may change as more events become available for the same gesture. + * + * @see #CLASSIFICATION_NONE + * @see #CLASSIFICATION_AMBIGUOUS_GESTURE + * @see #CLASSIFICATION_DEEP_PRESS + */ + public @Classification int getClassification() { + return nativeGetClassification(mNativePtr); + } + + /** * Gets which button has been modified during a press or release action. * * For actions other than {@link #ACTION_BUTTON_PRESS} and {@link #ACTION_BUTTON_RELEASE} @@ -3172,7 +3226,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { /** * Adds all of the movement samples of the specified event to this one if * it is compatible. To be compatible, the event must have the same device id, - * source, display id, action, flags, pointer count, pointer properties. + * source, display id, action, flags, classification, pointer count, pointer properties. * * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events. * @@ -3194,7 +3248,9 @@ public final class MotionEvent extends InputEvent implements Parcelable { if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr) || nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr) || nativeGetDisplayId(mNativePtr) != nativeGetDisplayId(event.mNativePtr) - || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)) { + || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr) + || nativeGetClassification(mNativePtr) + != nativeGetClassification(event.mNativePtr)) { return false; } @@ -3282,7 +3338,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { nativeGetDisplayId(mNativePtr), nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr), nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr), - nativeGetButtonState(mNativePtr), + nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr), nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr), nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr), nativeGetDownTimeNanos(mNativePtr), @@ -3376,7 +3432,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { nativeGetDisplayId(mNativePtr), newAction, nativeGetFlags(mNativePtr), nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr), - nativeGetButtonState(mNativePtr), + nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr), nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr), nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr), nativeGetDownTimeNanos(mNativePtr), eventTimeNanos, @@ -3409,6 +3465,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { } appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState())); + appendUnless(classificationToString(CLASSIFICATION_NONE), msg, ", classification=", + classificationToString(getClassification())); appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState())); appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags())); appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags())); @@ -3547,6 +3605,26 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** + * Returns a string that represents the symbolic name of the specified classification. + * + * @param classification The classification type. + * @return The symbolic name of this classification. + * @hide + */ + public static String classificationToString(@Classification int classification) { + switch (classification) { + case CLASSIFICATION_NONE: + return "NONE"; + case CLASSIFICATION_AMBIGUOUS_GESTURE: + return "AMBIGUOUS_GESTURE"; + case CLASSIFICATION_DEEP_PRESS: + return "DEEP_PRESS"; + + } + return "NONE"; + } + + /** * Returns a string that represents the symbolic name of the specified tool type * such as "TOOL_TYPE_FINGER" or an equivalent numeric constant such as "42" if unknown. * diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index acad5d76f0b5..ff5120d09e25 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -58,7 +58,7 @@ import java.io.Closeable; /** * SurfaceControl - * @hide + * @hide */ public class SurfaceControl implements Parcelable { private static final String TAG = "SurfaceControl"; @@ -186,6 +186,7 @@ public class SurfaceControl implements Parcelable { /** * Surface creation flag: Surface is created hidden + * @hide */ @UnsupportedAppUsage public static final int HIDDEN = 0x00000004; @@ -196,7 +197,7 @@ public class SurfaceControl implements Parcelable { * from another process. In particular, screenshots and VNC servers will * be disabled, but other measures can take place, for instance the * surface might not be hardware accelerated. - * + * @hide */ public static final int SECURE = 0x00000080; @@ -220,7 +221,7 @@ public class SurfaceControl implements Parcelable { * pixel. * <p> * In some rare situations, a non pre-multiplied surface is preferable. - * + * @hide */ public static final int NON_PREMULTIPLIED = 0x00000100; @@ -240,6 +241,7 @@ public class SurfaceControl implements Parcelable { * </ul> * If the underlying buffer lacks an alpha channel, the OPAQUE flag is effectively * set automatically. + * @hide */ public static final int OPAQUE = 0x00000400; @@ -248,6 +250,7 @@ public class SurfaceControl implements Parcelable { * external display sink. If a hardware-protected path is not available, * then this surface will not be displayed on the external sink. * + * @hide */ public static final int PROTECTED_APP = 0x00000800; @@ -255,6 +258,7 @@ public class SurfaceControl implements Parcelable { /** * Surface creation flag: Window represents a cursor glyph. + * @hide */ public static final int CURSOR_WINDOW = 0x00002000; @@ -262,6 +266,7 @@ public class SurfaceControl implements Parcelable { * Surface creation flag: Creates a normal surface. * This is the default. * + * @hide */ public static final int FX_SURFACE_NORMAL = 0x00000000; @@ -271,6 +276,7 @@ public class SurfaceControl implements Parcelable { * in {@link #setAlpha}. It is an error to lock a Dim surface, since it * doesn't have a backing store. * + * @hide */ public static final int FX_SURFACE_DIM = 0x00020000; @@ -278,12 +284,14 @@ public class SurfaceControl implements Parcelable { * Surface creation flag: Creates a container surface. * This surface will have no buffers and will only be used * as a container for other surfaces, or for its InputInfo. + * @hide */ public static final int FX_SURFACE_CONTAINER = 0x00080000; /** * Mask used for FX values above. * + * @hide */ public static final int FX_SURFACE_MASK = 0x000F0000; @@ -309,12 +317,14 @@ public class SurfaceControl implements Parcelable { /** * Built-in physical display id: Main display. * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}. + * @hide */ public static final int BUILT_IN_DISPLAY_ID_MAIN = 0; /** * Built-in physical display id: Attached HDMI display. * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}. + * @hide */ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1; @@ -323,30 +333,35 @@ public class SurfaceControl implements Parcelable { /** * Display power mode off: used while blanking the screen. * Use only with {@link SurfaceControl#setDisplayPowerMode}. + * @hide */ public static final int POWER_MODE_OFF = 0; /** * Display power mode doze: used while putting the screen into low power mode. * Use only with {@link SurfaceControl#setDisplayPowerMode}. + * @hide */ public static final int POWER_MODE_DOZE = 1; /** * Display power mode normal: used while unblanking the screen. * Use only with {@link SurfaceControl#setDisplayPowerMode}. + * @hide */ public static final int POWER_MODE_NORMAL = 2; /** * Display power mode doze: used while putting the screen into a suspended * low power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}. + * @hide */ public static final int POWER_MODE_DOZE_SUSPEND = 3; /** * Display power mode on: used while putting the screen into a suspended * full power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}. + * @hide */ public static final int POWER_MODE_ON_SUSPEND = 4; @@ -366,6 +381,9 @@ public class SurfaceControl implements Parcelable { mNativeObject = nativeObject; } + /** + * @hide + */ public void copyFrom(SurfaceControl other) { mName = other.mName; mWidth = other.mWidth; @@ -375,6 +393,7 @@ public class SurfaceControl implements Parcelable { /** * Builder class for {@link SurfaceControl} objects. + * @hide */ public static class Builder { private SurfaceSession mSession; @@ -391,6 +410,7 @@ public class SurfaceControl implements Parcelable { * Begin building a SurfaceControl with a given {@link SurfaceSession}. * * @param session The {@link SurfaceSession} with which to eventually construct the surface. + * @hide */ public Builder(SurfaceSession session) { mSession = session; @@ -398,6 +418,7 @@ public class SurfaceControl implements Parcelable { /** * Construct a new {@link SurfaceControl} with the set parameters. + * @hide */ public SurfaceControl build() { if (mWidth < 0 || mHeight < 0) { @@ -416,6 +437,7 @@ 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; @@ -427,6 +449,7 @@ 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) { if (width < 0 || height < 0) { @@ -441,6 +464,7 @@ public class SurfaceControl implements Parcelable { /** * Set the pixel format of the controlled surface's buffers, using constants from * {@link android.graphics.PixelFormat}. + * @hide */ public Builder setFormat(@PixelFormat.Format int format) { mFormat = format; @@ -454,6 +478,7 @@ public class SurfaceControl implements Parcelable { * not be displayed. * * @param protectedContent Whether to require a protected sink. + * @hide */ public Builder setProtected(boolean protectedContent) { if (protectedContent) { @@ -469,6 +494,7 @@ public class SurfaceControl implements Parcelable { * will prevent the surfaces content from being copied by another process. In * particular screenshots and VNC servers will be disabled. This is however * not a complete prevention of readback as {@link #setProtected}. + * @hide */ public Builder setSecure(boolean secure) { if (secure) { @@ -501,6 +527,7 @@ 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 */ public Builder setOpaque(boolean opaque) { if (opaque) { @@ -519,6 +546,7 @@ public class SurfaceControl implements Parcelable { * of the parent. * * @param parent The parent control. + * @hide */ public Builder setParent(SurfaceControl parent) { mParent = parent; @@ -534,6 +562,7 @@ public class SurfaceControl implements Parcelable { * * @param windowType A window-type * @param ownerUid UID of the window owner. + * @hide */ public Builder setMetadata(int windowType, int ownerUid) { if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) { @@ -552,6 +581,7 @@ public class SurfaceControl implements Parcelable { * solid color (that is, solid before plane alpha). Currently that color is black. * * @param isColorLayer Whether to create a color layer. + * @hide */ public Builder setColorLayer(boolean isColorLayer) { if (isColorLayer) { @@ -573,6 +603,7 @@ public class SurfaceControl implements Parcelable { * as a parent of renderable layers. * * @param isContainerLayer Whether to create a container layer. + * @hide */ public Builder setContainerLayer(boolean isContainerLayer) { if (isContainerLayer) { @@ -592,6 +623,7 @@ public class SurfaceControl implements Parcelable { * * TODO: Finish conversion to individual builder methods? * @param flags The combined flags + * @hide */ public Builder setFlags(int flags) { mFlags = flags; @@ -660,9 +692,11 @@ public class SurfaceControl implements Parcelable { mCloseGuard.open("release"); } - // This is a transfer constructor, useful for transferring a live SurfaceControl native - // object to another Java wrapper which could have some different behavior, e.g. - // event logging. + /** This is a transfer constructor, useful for transferring a live SurfaceControl native + * object to another Java wrapper which could have some different behavior, e.g. + * event logging. + * @hide + */ public SurfaceControl(SurfaceControl other) { mName = other.mName; mWidth = other.mWidth; @@ -678,10 +712,16 @@ public class SurfaceControl implements Parcelable { mCloseGuard.open("release"); } + /** + * @hide + */ public SurfaceControl() { mCloseGuard.open("release"); } + /** + * @hide + */ public void readFromParcel(Parcel in) { if (in == null) { throw new IllegalArgumentException("source must not be null"); @@ -698,11 +738,17 @@ 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); @@ -735,6 +781,9 @@ public class SurfaceControl implements Parcelable { proto.end(token); } + /** + * @hide + */ public static final Creator<SurfaceControl> CREATOR = new Creator<SurfaceControl>() { public SurfaceControl createFromParcel(Parcel in) { @@ -746,6 +795,9 @@ public class SurfaceControl implements Parcelable { } }; + /** + * @hide + */ @Override protected void finalize() throws Throwable { try { @@ -764,6 +816,7 @@ 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 */ public void release() { if (mNativeObject != 0) { @@ -777,6 +830,7 @@ public class SurfaceControl implements Parcelable { * Free all server-side state associated with this surface and * release this object's reference. This method can only be * called from the process that created the service. + * @hide */ public void destroy() { if (mNativeObject != 0) { @@ -788,6 +842,7 @@ public class SurfaceControl implements Parcelable { /** * Disconnect any client still connected to the surface. + * @hide */ public void disconnect() { if (mNativeObject != 0) { @@ -800,6 +855,9 @@ public class SurfaceControl implements Parcelable { "mNativeObject is null. Have you called release() already?"); } + /** + * @hide + */ public boolean isValid() { return mNativeObject != 0; } @@ -809,7 +867,9 @@ public class SurfaceControl implements Parcelable { * needs to be inside open/closeTransaction block */ - /** start a transaction */ + /** start a transaction + * @hide + */ @UnsupportedAppUsage public static void openTransaction() { synchronized (SurfaceControl.class) { @@ -838,6 +898,7 @@ public class SurfaceControl implements Parcelable { * This clears the supplied transaction in an identical fashion to {@link Transaction#merge}. * <p> * This is a utility for interop with legacy-code and will go away with the Global Transaction. + * @hide */ @Deprecated public static void mergeToGlobalTransaction(Transaction t) { @@ -846,46 +907,69 @@ public class SurfaceControl implements Parcelable { } } - /** end a transaction */ + /** end a transaction + * @hide + */ @UnsupportedAppUsage public static void closeTransaction() { closeTransaction(false); } + /** + * @hide + */ public static void closeTransactionSync() { closeTransaction(true); } + /** + * @hide + */ public void deferTransactionUntil(IBinder handle, long frame) { synchronized(SurfaceControl.class) { sGlobalTransaction.deferTransactionUntil(this, handle, frame); } } + /** + * @hide + */ public void deferTransactionUntil(Surface barrier, long frame) { synchronized(SurfaceControl.class) { sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame); } } + /** + * @hide + */ public void reparentChildren(IBinder newParentHandle) { synchronized(SurfaceControl.class) { sGlobalTransaction.reparentChildren(this, newParentHandle); } } + /** + * @hide + */ public void reparent(IBinder newParentHandle) { synchronized(SurfaceControl.class) { sGlobalTransaction.reparent(this, newParentHandle); } } + /** + * @hide + */ public void detachChildren() { synchronized(SurfaceControl.class) { sGlobalTransaction.detachChildren(this); } } + /** + * @hide + */ public void setOverrideScalingMode(int scalingMode) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -893,16 +977,25 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public IBinder getHandle() { return nativeGetHandle(mNativeObject); } + /** + * @hide + */ public static void setAnimationTransaction() { synchronized (SurfaceControl.class) { sGlobalTransaction.setAnimationTransaction(); } } + /** + * @hide + */ @UnsupportedAppUsage public void setLayer(int zorder) { checkNotReleased(); @@ -911,6 +1004,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setRelativeLayer(SurfaceControl relativeTo, int zorder) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -918,6 +1014,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ @UnsupportedAppUsage public void setPosition(float x, float y) { checkNotReleased(); @@ -926,6 +1025,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setGeometryAppliesWithResize() { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -933,6 +1035,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setBufferSize(int w, int h) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -940,6 +1045,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ @UnsupportedAppUsage public void hide() { checkNotReleased(); @@ -948,6 +1056,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ @UnsupportedAppUsage public void show() { checkNotReleased(); @@ -956,6 +1067,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setTransparentRegionHint(Region region) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -963,24 +1077,39 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public boolean clearContentFrameStats() { checkNotReleased(); return nativeClearContentFrameStats(mNativeObject); } + /** + * @hide + */ public boolean getContentFrameStats(WindowContentFrameStats outStats) { checkNotReleased(); return nativeGetContentFrameStats(mNativeObject, outStats); } + /** + * @hide + */ public static boolean clearAnimationFrameStats() { return nativeClearAnimationFrameStats(); } + /** + * @hide + */ public static boolean getAnimationFrameStats(WindowAnimationFrameStats outStats) { return nativeGetAnimationFrameStats(outStats); } + /** + * @hide + */ public void setAlpha(float alpha) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -988,6 +1117,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setColor(@Size(3) float[] color) { checkNotReleased(); synchronized (SurfaceControl.class) { @@ -995,6 +1127,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -1007,6 +1142,7 @@ public class SurfaceControl implements Parcelable { * * @param matrix The matrix to apply. * @param float9 An array of 9 floats to be used to extract the values from the matrix. + * @hide */ public void setMatrix(Matrix matrix, float[] float9) { checkNotReleased(); @@ -1022,6 +1158,7 @@ public class SurfaceControl implements Parcelable { * Sets the color transform for the Surface. * @param matrix A float array with 9 values represents a 3x3 transform matrix * @param translation A float array with 3 values represents a translation vector + * @hide */ public void setColorTransform(@Size(9) float[] matrix, @Size(3) float[] translation) { checkNotReleased(); @@ -1037,6 +1174,7 @@ public class SurfaceControl implements Parcelable { * constrained by the size of its parent bounds. * * @param crop Bounds of the crop to apply. + * @hide */ public void setWindowCrop(Rect crop) { checkNotReleased(); @@ -1050,6 +1188,7 @@ public class SurfaceControl implements Parcelable { * * @param width width of crop rect * @param height height of crop rect + * @hide */ public void setWindowCrop(int width, int height) { checkNotReleased(); @@ -1062,6 +1201,7 @@ public class SurfaceControl implements Parcelable { * Sets the corner radius of a {@link SurfaceControl}. * * @param cornerRadius Corner radius in pixels. + * @hide */ public void setCornerRadius(float cornerRadius) { checkNotReleased(); @@ -1070,6 +1210,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setLayerStack(int layerStack) { checkNotReleased(); synchronized(SurfaceControl.class) { @@ -1077,6 +1220,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setOpaque(boolean isOpaque) { checkNotReleased(); @@ -1085,6 +1231,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public void setSecure(boolean isSecure) { checkNotReleased(); @@ -1093,18 +1242,27 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public int getWidth() { synchronized (mSizeLock) { return mWidth; } } + /** + * @hide + */ public int getHeight() { synchronized (mSizeLock) { return mHeight; } } + /** + * @hide + */ @Override public String toString() { return "Surface(name=" + mName + ")/@0x" + @@ -1120,38 +1278,85 @@ public class SurfaceControl implements Parcelable { * Describes the properties of a physical display known to surface flinger. */ public static final class PhysicalDisplayInfo { + /** + * @hide + */ @UnsupportedAppUsage public int width; + + /** + * @hide + */ @UnsupportedAppUsage public int height; + + /** + * @hide + */ @UnsupportedAppUsage public float refreshRate; + + /** + * @hide + */ @UnsupportedAppUsage public float density; + + /** + * @hide + */ @UnsupportedAppUsage public float xDpi; + + /** + * @hide + */ @UnsupportedAppUsage public float yDpi; + + /** + * @hide + */ @UnsupportedAppUsage public boolean secure; + + /** + * @hide + */ @UnsupportedAppUsage public long appVsyncOffsetNanos; + + /** + * @hide + */ @UnsupportedAppUsage public long presentationDeadlineNanos; + /** + * @hide + */ @UnsupportedAppUsage public PhysicalDisplayInfo() { } + /** + * @hide + */ public PhysicalDisplayInfo(PhysicalDisplayInfo other) { copyFrom(other); } + /** + * @hide + */ @Override public boolean equals(Object o) { return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o); } + /** + * @hide + */ public boolean equals(PhysicalDisplayInfo other) { return other != null && width == other.width @@ -1165,11 +1370,17 @@ public class SurfaceControl implements Parcelable { && presentationDeadlineNanos == other.presentationDeadlineNanos; } + /** + * @hide + */ @Override public int hashCode() { return 0; // don't care } + /** + * @hide + */ public void copyFrom(PhysicalDisplayInfo other) { width = other.width; height = other.height; @@ -1182,7 +1393,9 @@ public class SurfaceControl implements Parcelable { presentationDeadlineNanos = other.presentationDeadlineNanos; } - // For debugging purposes + /** + * @hide + */ @Override public String toString() { return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, " @@ -1192,6 +1405,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public static void setDisplayPowerMode(IBinder displayToken, int mode) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1199,6 +1415,9 @@ public class SurfaceControl implements Parcelable { nativeSetDisplayPowerMode(displayToken, mode); } + /** + * @hide + */ @UnsupportedAppUsage public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) { if (displayToken == null) { @@ -1207,6 +1426,9 @@ public class SurfaceControl implements Parcelable { return nativeGetDisplayConfigs(displayToken); } + /** + * @hide + */ public static int getActiveConfig(IBinder displayToken) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1253,6 +1475,9 @@ public class SurfaceControl implements Parcelable { } + /** + * @hide + */ public static boolean setActiveConfig(IBinder displayToken, int id) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1260,6 +1485,9 @@ public class SurfaceControl implements Parcelable { return nativeSetActiveConfig(displayToken, id); } + /** + * @hide + */ public static int[] getDisplayColorModes(IBinder displayToken) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1267,6 +1495,9 @@ public class SurfaceControl implements Parcelable { return nativeGetDisplayColorModes(displayToken); } + /** + * @hide + */ public static int getActiveColorMode(IBinder displayToken) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1274,6 +1505,9 @@ public class SurfaceControl implements Parcelable { return nativeGetActiveColorMode(displayToken); } + /** + * @hide + */ public static boolean setActiveColorMode(IBinder displayToken, int colorMode) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1281,6 +1515,9 @@ public class SurfaceControl implements Parcelable { return nativeSetActiveColorMode(displayToken, colorMode); } + /** + * @hide + */ @UnsupportedAppUsage public static void setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) { @@ -1290,6 +1527,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ @UnsupportedAppUsage public static void setDisplayLayerStack(IBinder displayToken, int layerStack) { synchronized (SurfaceControl.class) { @@ -1297,6 +1537,9 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ @UnsupportedAppUsage public static void setDisplaySurface(IBinder displayToken, Surface surface) { synchronized (SurfaceControl.class) { @@ -1304,12 +1547,18 @@ public class SurfaceControl implements Parcelable { } } + /** + * @hide + */ public static void setDisplaySize(IBinder displayToken, int width, int height) { synchronized (SurfaceControl.class) { sGlobalTransaction.setDisplaySize(displayToken, width, height); } } + /** + * @hide + */ public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1317,6 +1566,9 @@ public class SurfaceControl implements Parcelable { return nativeGetHdrCapabilities(displayToken); } + /** + * @hide + */ @UnsupportedAppUsage public static IBinder createDisplay(String name, boolean secure) { if (name == null) { @@ -1325,6 +1577,9 @@ public class SurfaceControl implements Parcelable { return nativeCreateDisplay(name, secure); } + /** + * @hide + */ @UnsupportedAppUsage public static void destroyDisplay(IBinder displayToken) { if (displayToken == null) { @@ -1333,6 +1588,9 @@ public class SurfaceControl implements Parcelable { nativeDestroyDisplay(displayToken); } + /** + * @hide + */ @UnsupportedAppUsage public static IBinder getBuiltInDisplay(int builtInDisplayId) { return nativeGetBuiltInDisplay(builtInDisplayId); @@ -1340,6 +1598,7 @@ public class SurfaceControl implements Parcelable { /** * @see SurfaceControl#screenshot(IBinder, Surface, Rect, int, int, boolean, int) + * @hide */ public static void screenshot(IBinder display, Surface consumer) { screenshot(display, consumer, new Rect(), 0, 0, false, 0); @@ -1350,6 +1609,7 @@ public class SurfaceControl implements Parcelable { * * @param consumer The {@link Surface} to take the screenshot into. * @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int) + * @hide */ public static void screenshot(IBinder display, Surface consumer, Rect sourceCrop, int width, int height, boolean useIdentityTransform, int rotation) { @@ -1368,6 +1628,7 @@ public class SurfaceControl implements Parcelable { /** * @see SurfaceControl#screenshot(Rect, int, int, boolean, int)} + * @hide */ @UnsupportedAppUsage public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) { @@ -1385,6 +1646,7 @@ public class SurfaceControl implements Parcelable { * {@link SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}. * * @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)} + * @hide */ @UnsupportedAppUsage public static Bitmap screenshot(Rect sourceCrop, int width, int height, @@ -1428,6 +1690,7 @@ public class SurfaceControl implements Parcelable { * this is useful for returning screenshots that are independent of * device orientation. * @return Returns a GraphicBuffer that contains the captured content. + * @hide */ public static GraphicBuffer screenshotToBuffer(IBinder display, Rect sourceCrop, int width, int height, boolean useIdentityTransform, int rotation) { @@ -1459,12 +1722,16 @@ public class SurfaceControl implements Parcelable { * screen will be scaled up/down. * * @return Returns a GraphicBuffer that contains the layer capture. + * @hide */ public static GraphicBuffer captureLayers(IBinder layerHandleToken, Rect sourceCrop, float frameScale) { return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale); } + /** + * @hide + */ public static class Transaction implements Closeable { public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( Transaction.class.getClassLoader(), @@ -1474,6 +1741,9 @@ public class SurfaceControl implements Parcelable { private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>(); Runnable mFreeNativeResources; + /** + * @hide + */ @UnsupportedAppUsage public Transaction() { mNativeObject = nativeCreateTransaction(); @@ -1484,6 +1754,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() { @@ -1493,6 +1764,7 @@ 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() { @@ -1502,6 +1774,7 @@ public class SurfaceControl implements Parcelable { /** * Jankier version of apply. Avoid use (b/28068298). + * @hide */ public void apply(boolean sync) { applyResizedSurfaces(); @@ -1520,6 +1793,9 @@ public class SurfaceControl implements Parcelable { mResizedSurfaces.clear(); } + /** + * @hide + */ @UnsupportedAppUsage public Transaction show(SurfaceControl sc) { sc.checkNotReleased(); @@ -1527,6 +1803,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ @UnsupportedAppUsage public Transaction hide(SurfaceControl sc) { sc.checkNotReleased(); @@ -1534,6 +1813,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ @UnsupportedAppUsage public Transaction setPosition(SurfaceControl sc, float x, float y) { sc.checkNotReleased(); @@ -1541,6 +1823,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ @UnsupportedAppUsage public Transaction setBufferSize(SurfaceControl sc, int w, int h) { sc.checkNotReleased(); @@ -1549,6 +1834,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ @UnsupportedAppUsage public Transaction setLayer(SurfaceControl sc, int z) { sc.checkNotReleased(); @@ -1556,6 +1844,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) { sc.checkNotReleased(); nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, @@ -1563,6 +1854,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) { sc.checkNotReleased(); nativeSetTransparentRegionHint(mNativeObject, @@ -1570,6 +1864,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ @UnsupportedAppUsage public Transaction setAlpha(SurfaceControl sc, float alpha) { sc.checkNotReleased(); @@ -1577,6 +1874,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) { sc.checkNotReleased(); nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle); @@ -1592,12 +1892,16 @@ public class SurfaceControl implements Parcelable { * @param fromToken The token of a window that currently has touch focus. * @param toToken The token of the window that should receive touch focus in * place of the first. + * @hide */ public Transaction transferTouchFocus(IBinder fromToken, IBinder toToken) { nativeTransferTouchFocus(mNativeObject, fromToken, toToken); return this; } + /** + * @hide + */ @UnsupportedAppUsage public Transaction setMatrix(SurfaceControl sc, float dsdx, float dtdx, float dtdy, float dsdy) { @@ -1607,6 +1911,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ @UnsupportedAppUsage public Transaction setMatrix(SurfaceControl sc, Matrix matrix, float[] float9) { matrix.getValues(float9); @@ -1620,6 +1927,7 @@ public class SurfaceControl implements Parcelable { * Sets the color transform for the Surface. * @param matrix A float array with 9 values represents a 3x3 transform matrix * @param translation A float array with 3 values represents a translation vector + * @hide */ public Transaction setColorTransform(SurfaceControl sc, @Size(9) float[] matrix, @Size(3) float[] translation) { @@ -1628,6 +1936,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ @UnsupportedAppUsage public Transaction setWindowCrop(SurfaceControl sc, Rect crop) { sc.checkNotReleased(); @@ -1641,6 +1952,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction setWindowCrop(SurfaceControl sc, int width, int height) { sc.checkNotReleased(); nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height); @@ -1652,6 +1966,7 @@ public class SurfaceControl implements Parcelable { * @param sc SurfaceControl * @param cornerRadius Corner radius in pixels. * @return Itself. + * @hide */ @UnsupportedAppUsage public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) { @@ -1662,6 +1977,9 @@ public class SurfaceControl implements Parcelable { } @UnsupportedAppUsage + /** + * @hide + */ public Transaction setLayerStack(SurfaceControl sc, int layerStack) { sc.checkNotReleased(); nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack); @@ -1669,6 +1987,9 @@ public class SurfaceControl implements Parcelable { } @UnsupportedAppUsage + /** + * @hide + */ public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) { if (frameNumber < 0) { @@ -1680,6 +2001,9 @@ public class SurfaceControl implements Parcelable { } @UnsupportedAppUsage + /** + * @hide + */ public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface, long frameNumber) { if (frameNumber < 0) { @@ -1691,13 +2015,18 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) { sc.checkNotReleased(); nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle); return this; } - /** Re-parents a specific child layer to a new parent */ + /** Re-parents a specific child layer to a new parent + * @hide + */ public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) { sc.checkNotReleased(); nativeReparent(mNativeObject, sc.mNativeObject, @@ -1705,12 +2034,18 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction detachChildren(SurfaceControl sc) { sc.checkNotReleased(); nativeSeverChildren(mNativeObject, sc.mNativeObject); return this; } + /** + * @hide + */ public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) { sc.checkNotReleased(); nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject, @@ -1721,6 +2056,7 @@ public class SurfaceControl implements Parcelable { /** * Sets a color for the Surface. * @param color A float array with three values to represent r, g, b in range [0..1] + * @hide */ @UnsupportedAppUsage public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) { @@ -1735,6 +2071,7 @@ public class SurfaceControl implements Parcelable { * arrives. As transform matrix and size are already frozen in this fashion, * this enables totally freezing the surface until the resize has completed * (at which point the geometry influencing aspects of this transaction will then occur) + * @hide */ public Transaction setGeometryAppliesWithResize(SurfaceControl sc) { sc.checkNotReleased(); @@ -1745,6 +2082,7 @@ public class SurfaceControl implements Parcelable { /** * Sets the security of the surface. Setting the flag is equivalent to creating the * Surface with the {@link #SECURE} flag. + * @hide */ public Transaction setSecure(SurfaceControl sc, boolean isSecure) { sc.checkNotReleased(); @@ -1759,6 +2097,7 @@ public class SurfaceControl implements Parcelable { /** * Sets the opacity of the surface. Setting the flag is equivalent to creating the * Surface with the {@link #OPAQUE} flag. + * @hide */ public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) { sc.checkNotReleased(); @@ -1770,6 +2109,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction setDisplaySurface(IBinder displayToken, Surface surface) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1785,6 +2127,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1793,6 +2138,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) { if (displayToken == null) { @@ -1810,6 +2158,9 @@ public class SurfaceControl implements Parcelable { return this; } + /** + * @hide + */ public Transaction setDisplaySize(IBinder displayToken, int width, int height) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); @@ -1822,7 +2173,9 @@ public class SurfaceControl implements Parcelable { return this; } - /** flag the transaction as an animation */ + /** flag the transaction as an animation + * @hide + */ public Transaction setAnimationTransaction() { nativeSetAnimationTransaction(mNativeObject); return this; @@ -1835,6 +2188,7 @@ public class SurfaceControl implements Parcelable { * order not to miss frame deadlines. * <p> * Corresponds to setting ISurfaceComposer::eEarlyWakeup + * @hide */ public Transaction setEarlyWakeup() { nativeSetEarlyWakeup(mNativeObject); @@ -1844,6 +2198,7 @@ public class SurfaceControl implements Parcelable { /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. + * @hide */ public Transaction merge(Transaction other) { mResizedSurfaces.putAll(other.mResizedSurfaces); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index cb2c40e6197e..cd0e5794a5c7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8199,6 +8199,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>The populated structure is then passed to the service through * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}. * + * <p><b>Note: </b>views that manage a virtual structure under this view must populate just + * the node representing this view and return right away, then asynchronously report (not + * necessarily in the UI thread) when the children nodes appear, disappear or have their text + * changed by calling + * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}, + * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and + * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence, int)} + * respectively. The structure for the a child must be created using + * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, int)}, and the + * {@code autofillId} for a child can be obtained either through + * {@code childStructure.getAutofillId()} or + * {@link ContentCaptureSession#newAutofillId(AutofillId, int)}. + * * <p><b>Note: </b>the following methods of the {@code structure} will be ignored: * <ul> * <li>{@link ViewStructure#setChildCount(int)} @@ -8235,10 +8248,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { structure.setId(id, null, null, null); } - if (viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { - //TODO(b/111276913): STOPSHIP - don't set it if not needed - structure.setDataIsSensitive(false); - } if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL || viewFor == VIEW_STRUCTURE_FOR_CONTENT_CAPTURE) { diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index 95a346f34d24..c630177e1bf2 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -794,7 +794,7 @@ public abstract class Animation implements Cloneable { * @deprecated All window animations are running with detached wallpaper. */ public boolean getDetachWallpaper() { - return false; + return true; } /** diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index e962e7c8fe41..ff45efd944cd 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -15,6 +15,8 @@ */ package android.view.contentcapture; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; @@ -24,9 +26,12 @@ import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.RemoteException; import android.util.Log; +import com.android.internal.os.IResultReceiver; import com.android.internal.util.Preconditions; +import com.android.internal.util.SyncResultReceiver; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,6 +53,11 @@ public final class ContentCaptureManager { private static final String BG_THREAD_NAME = "intel_svc_streamer_thread"; + /** + * Timeout for calls to system_server. + */ + private static final int SYNC_CALLS_TIMEOUT_MS = 5000; + // TODO(b/121044306): define a way to dynamically set them(for example, using settings?) static final boolean VERBOSE = false; static final boolean DEBUG = true; // STOPSHIP if not set to false @@ -116,8 +126,8 @@ public final class ContentCaptureManager { /** @hide */ public void onActivityStarted(@NonNull IBinder applicationToken, - @NonNull ComponentName activityComponent) { - getMainContentCaptureSession().start(applicationToken, activityComponent); + @NonNull ComponentName activityComponent, int flags) { + getMainContentCaptureSession().start(applicationToken, activityComponent, flags); } /** @hide */ @@ -142,8 +152,21 @@ public final class ContentCaptureManager { */ @Nullable public ComponentName getServiceComponentName() { - //TODO(b/121047489): implement - return null; + if (!isContentCaptureEnabled()) { + return null; + } + // Wait for system server to return the component name. + final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); + mHandler.sendMessage(obtainMessage( + ContentCaptureManager::handleReceiverServiceComponentName, + this, mContext.getUserId(), resultReceiver)); + + try { + return resultReceiver.getParcelableResult(); + } catch (RemoteException e) { + // Unable to retrieve component name in a reasonable amount of time. + throw e.rethrowFromSystemServer(); + } } /** @@ -191,4 +214,14 @@ public final class ContentCaptureManager { pw.print(prefix); pw.println("No sessions"); } } + + + /** Retrieves the component name of the target content capture service through system_server. */ + private void handleReceiverServiceComponentName(int userId, IResultReceiver resultReceiver) { + try { + mService.getReceiverServiceComponentName(userId, resultReceiver); + } catch (RemoteException e) { + Log.w(TAG, "Unable to retrieve service component name: " + e); + } + } } diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 344b9973fdb1..6890beaf4c16 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -46,8 +46,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { /** * Used on {@link #notifyViewTextChanged(AutofillId, CharSequence, int)} to indicate that the - * - * thext change was caused by user input (for example, through IME). + * text change was caused by user input (for example, through IME). */ public static final int FLAG_USER_INPUT = 0x1; @@ -86,6 +85,13 @@ public abstract class ContentCaptureSession implements AutoCloseable { */ public static final int STATE_DISABLED_DUPLICATED_ID = 4; + /** + * Session is disabled by FLAG_SECURE + * + * @hide + */ + public static final int STATE_DISABLED_BY_FLAG_SECURE = 5; + private static final int INITIAL_CHILDREN_CAPACITY = 5; private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -178,7 +184,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { mCloseGuard.close(); - //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote + // TODO(b/111276913): check state (for example, how to handle if it's waiting for remote // id) and send it to the cache of batched commands if (VERBOSE) { Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId); @@ -295,6 +301,26 @@ public abstract class ContentCaptureSession implements AutoCloseable { } /** + * Creates a new {@link AutofillId} for a virtual child, so it can be used to uniquely identify + * the children in the session. + * + * @param parentId id of the virtual view parent (it can be obtained by calling + * {@link ViewStructure#getAutofillId()} on the parent). + * @param virtualChildId id of the virtual child, relative to the parent. + * + * @return if for the virtual child + * + * @throws IllegalArgumentException if the {@code parentId} is a virtual child id. + */ + public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) { + Preconditions.checkNotNull(parentId); + Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children"); + // TODO(b/121197119): we need to add the session id to the AutofillId to make them unique + // per session + return new AutofillId(parentId, virtualChildId); + } + + /** * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy. * @@ -303,12 +329,11 @@ public abstract class ContentCaptureSession implements AutoCloseable { * @param virtualId id of the virtual child, relative to the parent. * * @return a new {@link ViewStructure} that can be used for Content Capture purposes. - * - * @hide */ @NonNull public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) { + // TODO(b/121197119): use the constructor that takes a session id / assert on unit test. return new ViewNode.ViewStructureImpl(parentId, virtualId); } @@ -356,6 +381,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { return "DISABLED_NO_SERVICE"; case STATE_DISABLED_DUPLICATED_ID: return "DISABLED_DUPLICATED_ID"; + case STATE_DISABLED_BY_FLAG_SECURE: + return "DISABLED_FLAG_SECURE"; default: return "INVALID:" + state; } diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl index 01776f846434..be9c00f04d99 100644 --- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl +++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl @@ -32,7 +32,28 @@ import java.util.List; * @hide */ oneway interface IContentCaptureManager { + /** + * Starts a new session for the provided {@code userId} running as part of the + * app's activity identified by {@code activityToken}/{@code componentName}. + * + * @param sessionId Unique session id as provided by the app. + * @param flags Meta flags that enable or disable content capture (see + * {@link IContentCaptureContext#flags}). + */ void startSession(int userId, IBinder activityToken, in ComponentName componentName, String sessionId, int flags, in IResultReceiver result); + + /** + * Marks the end of a session for the provided {@code userId} identified by + * the corresponding {@code startSession}'s {@code sessionId}. + */ void finishSession(int userId, String sessionId); + + /** + * Returns the content capture service's component name (if enabled and + * connected). + * @param Receiver of the content capture service's @{code ComponentName} + * provided {@code Bundle} with key "{@code EXTRA}". + */ + void getReceiverServiceComponentName(int userId, in IResultReceiver result); } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 8be887b6d593..a29aaf013d49 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -157,7 +157,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * * @hide */ - void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) { + void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent, + int flags) { if (!isContentCaptureEnabled()) return; if (VERBOSE) { @@ -166,7 +167,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this, - applicationToken, activityComponent)); + applicationToken, activityComponent, flags)); } @Override @@ -181,7 +182,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession { obtainMessage(MainContentCaptureSession::handleDestroySession, this)); } - private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) { + private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName, + int flags) { if (mState != STATE_UNKNOWN) { // TODO(b/111276913): revisit this scenario Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state " @@ -196,7 +198,6 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.v(TAG, "handleStartSession(): token=" + token + ", act=" + getActivityDebugName() + ", id=" + mId); } - final int flags = 0; // TODO(b/111276913): get proper flags try { if (mSystemServerInterface == null) return; @@ -245,7 +246,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.w(TAG, "Failed to link to death on " + binder + ": " + e); } } - if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID) { + + // TODO(b/111276913): change the resultCode to use flags so there's just one flag for + // disabled stuff + if (resultCode == STATE_DISABLED_NO_SERVICE || resultCode == STATE_DISABLED_DUPLICATED_ID + || resultCode == STATE_DISABLED_BY_FLAG_SECURE) { mDisabled.set(true); handleResetSession(/* resetState= */ false); } else { diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java index 86b89adb5ce3..b7a486afc118 100644 --- a/core/java/android/view/contentcapture/ViewNode.java +++ b/core/java/android/view/contentcapture/ViewNode.java @@ -24,6 +24,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewParent; @@ -32,30 +33,188 @@ import android.view.ViewStructure.HtmlInfo.Builder; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -//TODO(b/111276913): add javadocs / implement Parcelable / implement -//TODO(b/111276913): for now it's extending ViewNode directly as it needs most of its properties, +//TODO(b/122484602): add javadocs / implement Parcelable / implement +//TODO(b/122484602): for now it's extending ViewNode directly as it needs most of its properties, // but it might be better to create a common, abstract android.view.ViewNode class that both extend // instead /** @hide */ @SystemApi public final class ViewNode extends AssistStructure.ViewNode { - private static final String TAG = "ViewNode"; + private static final String TAG = ViewNode.class.getSimpleName(); + private static final boolean VERBOSE = false; + + private static final long FLAGS_HAS_TEXT = 1L << 0; + private static final long FLAGS_HAS_COMPLEX_TEXT = 1L << 1; + private static final long FLAGS_VISIBILITY_MASK = View.VISIBLE | View.INVISIBLE | View.GONE; + private static final long FLAGS_HAS_CLASSNAME = 1L << 4; + private static final long FLAGS_HAS_AUTOFILL_ID = 1L << 5; + private static final long FLAGS_HAS_AUTOFILL_PARENT_ID = 1L << 6; + private static final long FLAGS_HAS_ID = 1L << 7; + private static final long FLAGS_HAS_LARGE_COORDS = 1L << 8; + private static final long FLAGS_HAS_SCROLL = 1L << 9; + private static final long FLAGS_ASSIST_BLOCKED = 1L << 10; + private static final long FLAGS_DISABLED = 1L << 11; + private static final long FLAGS_CLICKABLE = 1L << 12; + private static final long FLAGS_LONG_CLICKABLE = 1L << 13; + private static final long FLAGS_CONTEXT_CLICKABLE = 1L << 14; + private static final long FLAGS_FOCUSABLE = 1L << 15; + private static final long FLAGS_FOCUSED = 1L << 16; + private static final long FLAGS_ACCESSIBILITY_FOCUSED = 1L << 17; + private static final long FLAGS_CHECKABLE = 1L << 18; + private static final long FLAGS_CHECKED = 1L << 19; + private static final long FLAGS_SELECTED = 1L << 20; + private static final long FLAGS_ACTIVATED = 1L << 21; + private static final long FLAGS_OPAQUE = 1L << 22; + private static final long FLAGS_HAS_MATRIX = 1L << 23; + private static final long FLAGS_HAS_ELEVATION = 1L << 24; + private static final long FLAGS_HAS_ALPHA = 1L << 25; + private static final long FLAGS_HAS_CONTENT_DESCRIPTION = 1L << 26; + private static final long FLAGS_HAS_EXTRAS = 1L << 27; + private static final long FLAGS_HAS_LOCALE_LIST = 1L << 28; + private static final long FLAGS_HAS_INPUT_TYPE = 1L << 29; + private static final long FLAGS_HAS_MIN_TEXT_EMS = 1L << 30; + private static final long FLAGS_HAS_MAX_TEXT_EMS = 1L << 31; + private static final long FLAGS_HAS_MAX_TEXT_LENGTH = 1L << 32; + private static final long FLAGS_HAS_TEXT_ID_ENTRY = 1L << 33; + private static final long FLAGS_HAS_AUTOFILL_TYPE = 1L << 34; + private static final long FLAGS_HAS_AUTOFILL_VALUE = 1L << 35; + private static final long FLAGS_HAS_AUTOFILL_HINTS = 1L << 36; + private static final long FLAGS_HAS_AUTOFILL_OPTIONS = 1L << 37; + + /** Flags used to optimize what's written to the parcel */ + private long mFlags; private AutofillId mParentAutofillId; - // TODO(b/111276913): temporarily setting some fields here while they're not accessible from the - // superclass private AutofillId mAutofillId; - private CharSequence mText; + private ViewNodeText mText; private String mClassName; + private int mId = View.NO_ID; + private String mIdPackage; + private String mIdType; + private String mIdEntry; + private int mX; + private int mY; + private int mScrollX; + private int mScrollY; + private int mWidth; + private int mHeight; + private Matrix mMatrix; + private float mElevation; + private float mAlpha = 1.0f; + private CharSequence mContentDescription; + private Bundle mExtras; + private LocaleList mLocaleList; + private int mInputType; + private int mMinEms = -1; + private int mMaxEms = -1; + private int mMaxLength = -1; + private String mTextIdEntry; + private @View.AutofillType int mAutofillType = View.AUTOFILL_TYPE_NONE; + private String[] mAutofillHints; + private AutofillValue mAutofillValue; + private CharSequence[] mAutofillOptions; /** @hide */ public ViewNode() { } + private ViewNode(long nodeFlags, @NonNull Parcel parcel) { + mFlags = nodeFlags; + + if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) { + mAutofillId = parcel.readParcelable(null); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) { + mParentAutofillId = parcel.readParcelable(null); + } + if ((nodeFlags & FLAGS_HAS_TEXT) != 0) { + mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0); + } + if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) { + mClassName = parcel.readString(); + } + if ((nodeFlags & FLAGS_HAS_ID) != 0) { + mId = parcel.readInt(); + if (mId != View.NO_ID) { + mIdEntry = parcel.readString(); + if (mIdEntry != null) { + mIdType = parcel.readString(); + mIdPackage = parcel.readString(); + } + } + } + if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) { + mX = parcel.readInt(); + mY = parcel.readInt(); + mWidth = parcel.readInt(); + mHeight = parcel.readInt(); + } else { + int val = parcel.readInt(); + mX = val & 0x7fff; + mY = (val >> 16) & 0x7fff; + val = parcel.readInt(); + mWidth = val & 0x7fff; + mHeight = (val >> 16) & 0x7fff; + } + if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) { + mScrollX = parcel.readInt(); + mScrollY = parcel.readInt(); + } + if ((nodeFlags & FLAGS_HAS_MATRIX) != 0) { + mMatrix = new Matrix(); + final float[] tmpMatrix = new float[9]; + parcel.readFloatArray(tmpMatrix); + mMatrix.setValues(tmpMatrix); + } + if ((nodeFlags & FLAGS_HAS_ELEVATION) != 0) { + mElevation = parcel.readFloat(); + } + if ((nodeFlags & FLAGS_HAS_ALPHA) != 0) { + mAlpha = parcel.readFloat(); + } + if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) { + mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + } + if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) { + mExtras = parcel.readBundle(); + } + if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) { + mLocaleList = parcel.readParcelable(null); + } + if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) { + mInputType = parcel.readInt(); + } + if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) { + mMinEms = parcel.readInt(); + } + if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) { + mMaxEms = parcel.readInt(); + } + if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) { + mMaxLength = parcel.readInt(); + } + if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) { + mTextIdEntry = parcel.readString(); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) { + mAutofillType = parcel.readInt(); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) { + mAutofillHints = parcel.readStringArray(); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) { + mAutofillValue = parcel.readParcelable(null); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) { + mAutofillOptions = parcel.readCharSequenceArray(); + } + } + /** * Returns the {@link AutofillId} of this view's parent, if the parent is also part of the * screen observation tree. @@ -65,53 +224,446 @@ public final class ViewNode extends AssistStructure.ViewNode { return mParentAutofillId; } - // TODO(b/111276913): temporarily overwriting some methods @Override public AutofillId getAutofillId() { return mAutofillId; } + @Override public CharSequence getText() { - return mText; + return mText != null ? mText.mText : null; } + @Override public String getClassName() { return mClassName; } + @Override + public int getId() { + return mId; + } + + @Override + public String getIdPackage() { + return mIdPackage; + } + + @Override + public String getIdType() { + return mIdType; + } + + @Override + public String getIdEntry() { + return mIdEntry; + } + + @Override + public int getLeft() { + return mX; + } + + @Override + public int getTop() { + return mY; + } + + @Override + public int getScrollX() { + return mScrollX; + } + + @Override + public int getScrollY() { + return mScrollY; + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + @Override + public boolean isAssistBlocked() { + return (mFlags & FLAGS_ASSIST_BLOCKED) != 0; + } + + @Override + public boolean isEnabled() { + return (mFlags & FLAGS_DISABLED) == 0; + } + + @Override + public boolean isClickable() { + return (mFlags & FLAGS_CLICKABLE) != 0; + } + + @Override + public boolean isLongClickable() { + return (mFlags & FLAGS_LONG_CLICKABLE) != 0; + } + + @Override + public boolean isContextClickable() { + return (mFlags & FLAGS_CONTEXT_CLICKABLE) != 0; + } + + @Override + public boolean isFocusable() { + return (mFlags & FLAGS_FOCUSABLE) != 0; + } + + @Override + public boolean isFocused() { + return (mFlags & FLAGS_FOCUSED) != 0; + } + + @Override + public boolean isAccessibilityFocused() { + return (mFlags & FLAGS_ACCESSIBILITY_FOCUSED) != 0; + } + + @Override + public boolean isCheckable() { + return (mFlags & FLAGS_CHECKABLE) != 0; + } + + @Override + public boolean isChecked() { + return (mFlags & FLAGS_CHECKED) != 0; + } + + @Override + public boolean isSelected() { + return (mFlags & FLAGS_SELECTED) != 0; + } + + @Override + public boolean isActivated() { + return (mFlags & FLAGS_ACTIVATED) != 0; + } + + @Override + public boolean isOpaque() { + return (mFlags & FLAGS_OPAQUE) != 0; + } + + @Override + public Matrix getTransformation() { + return mMatrix; + } + + @Override + public float getElevation() { + return mElevation; + } + + @Override + public float getAlpha() { + return mAlpha; + } + + @Override + public CharSequence getContentDescription() { + return mContentDescription; + } + + @Override + public Bundle getExtras() { + return mExtras; + } + + @Override + public String getHint() { + return mText != null ? mText.mHint : null; + } + + @Override + public int getTextSelectionStart() { + return mText != null ? mText.mTextSelectionStart : -1; + } + + @Override + public int getTextSelectionEnd() { + return mText != null ? mText.mTextSelectionEnd : -1; + } + + @Override + public int getTextColor() { + return mText != null ? mText.mTextColor : TEXT_COLOR_UNDEFINED; + } + + @Override + public int getTextBackgroundColor() { + return mText != null ? mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED; + } + + @Override + public float getTextSize() { + return mText != null ? mText.mTextSize : 0; + } + + @Override + public int getTextStyle() { + return mText != null ? mText.mTextStyle : 0; + } + + @Override + public int[] getTextLineCharOffsets() { + return mText != null ? mText.mLineCharOffsets : null; + } + + @Override + public int[] getTextLineBaselines() { + return mText != null ? mText.mLineBaselines : null; + } + + @Override + public int getVisibility() { + return (int) (mFlags & FLAGS_VISIBILITY_MASK); + } + + @Override + public int getInputType() { + return mInputType; + } + + @Override + public int getMinTextEms() { + return mMinEms; + } + + @Override + public int getMaxTextEms() { + return mMaxEms; + } + + @Override + public int getMaxTextLength() { + return mMaxLength; + } + + @Override + public String getTextIdEntry() { + return mTextIdEntry; + } + + @Override + public @View.AutofillType int getAutofillType() { + return mAutofillType; + } + + @Override + @Nullable public String[] getAutofillHints() { + return mAutofillHints; + } + + @Override + @Nullable public AutofillValue getAutofillValue() { + return mAutofillValue; + } + + @Override + @Nullable public CharSequence[] getAutofillOptions() { + return mAutofillOptions; + } + + @Override + public LocaleList getLocaleList() { + return mLocaleList; + } + + private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) { + long nodeFlags = mFlags; + + if (mAutofillId != null) { + nodeFlags |= FLAGS_HAS_AUTOFILL_ID; + } + + if (mParentAutofillId != null) { + nodeFlags |= FLAGS_HAS_AUTOFILL_PARENT_ID; + } + + if (mText != null) { + nodeFlags |= FLAGS_HAS_TEXT; + if (!mText.isSimple()) { + nodeFlags |= FLAGS_HAS_COMPLEX_TEXT; + } + } + if (mClassName != null) { + nodeFlags |= FLAGS_HAS_CLASSNAME; + } + if (mId != View.NO_ID) { + nodeFlags |= FLAGS_HAS_ID; + } + if ((mX & ~0x7fff) != 0 || (mY & ~0x7fff) != 0 + || (mWidth & ~0x7fff) != 0 | (mHeight & ~0x7fff) != 0) { + nodeFlags |= FLAGS_HAS_LARGE_COORDS; + } + if (mScrollX != 0 || mScrollY != 0) { + nodeFlags |= FLAGS_HAS_SCROLL; + } + if (mMatrix != null) { + nodeFlags |= FLAGS_HAS_MATRIX; + } + if (mElevation != 0) { + nodeFlags |= FLAGS_HAS_ELEVATION; + } + if (mAlpha != 1.0f) { + nodeFlags |= FLAGS_HAS_ALPHA; + } + if (mContentDescription != null) { + nodeFlags |= FLAGS_HAS_CONTENT_DESCRIPTION; + } + if (mExtras != null) { + nodeFlags |= FLAGS_HAS_EXTRAS; + } + if (mLocaleList != null) { + nodeFlags |= FLAGS_HAS_LOCALE_LIST; + } + if (mInputType != 0) { + nodeFlags |= FLAGS_HAS_INPUT_TYPE; + } + if (mMinEms > -1) { + nodeFlags |= FLAGS_HAS_MIN_TEXT_EMS; + } + if (mMaxEms > -1) { + nodeFlags |= FLAGS_HAS_MAX_TEXT_EMS; + } + if (mMaxLength > -1) { + nodeFlags |= FLAGS_HAS_MAX_TEXT_LENGTH; + } + if (mTextIdEntry != null) { + nodeFlags |= FLAGS_HAS_TEXT_ID_ENTRY; + } + if (mAutofillValue != null) { + nodeFlags |= FLAGS_HAS_AUTOFILL_VALUE; + } + if (mAutofillType != View.AUTOFILL_TYPE_NONE) { + nodeFlags |= FLAGS_HAS_AUTOFILL_TYPE; + } + if (mAutofillHints != null) { + nodeFlags |= FLAGS_HAS_AUTOFILL_HINTS; + } + if (mAutofillOptions != null) { + nodeFlags |= FLAGS_HAS_AUTOFILL_OPTIONS; + } + parcel.writeLong(nodeFlags); + + if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) { + parcel.writeParcelable(mAutofillId, parcelFlags); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) { + parcel.writeParcelable(mParentAutofillId, parcelFlags); + } + if ((nodeFlags & FLAGS_HAS_TEXT) != 0) { + mText.writeToParcel(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0); + } + if ((nodeFlags & FLAGS_HAS_CLASSNAME) != 0) { + parcel.writeString(mClassName); + } + if ((nodeFlags & FLAGS_HAS_ID) != 0) { + parcel.writeInt(mId); + if (mId != View.NO_ID) { + parcel.writeString(mIdEntry); + if (mIdEntry != null) { + parcel.writeString(mIdType); + parcel.writeString(mIdPackage); + } + } + } + if ((nodeFlags & FLAGS_HAS_LARGE_COORDS) != 0) { + parcel.writeInt(mX); + parcel.writeInt(mY); + parcel.writeInt(mWidth); + parcel.writeInt(mHeight); + } else { + parcel.writeInt((mY << 16) | mX); + parcel.writeInt((mHeight << 16) | mWidth); + } + if ((nodeFlags & FLAGS_HAS_SCROLL) != 0) { + parcel.writeInt(mScrollX); + parcel.writeInt(mScrollY); + } + if ((nodeFlags & FLAGS_HAS_MATRIX) != 0) { + //TODO(b/122484602): use a singleton tmpMatrix (if logic is not moved to superclass) + final float[] tmpMatrix = new float[9]; + mMatrix.getValues(tmpMatrix); + parcel.writeFloatArray(tmpMatrix); + } + if ((nodeFlags & FLAGS_HAS_ELEVATION) != 0) { + parcel.writeFloat(mElevation); + } + if ((nodeFlags & FLAGS_HAS_ALPHA) != 0) { + parcel.writeFloat(mAlpha); + } + if ((nodeFlags & FLAGS_HAS_CONTENT_DESCRIPTION) != 0) { + TextUtils.writeToParcel(mContentDescription, parcel, 0); + } + if ((nodeFlags & FLAGS_HAS_EXTRAS) != 0) { + parcel.writeBundle(mExtras); + } + if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) { + parcel.writeParcelable(mLocaleList, 0); + } + if ((nodeFlags & FLAGS_HAS_INPUT_TYPE) != 0) { + parcel.writeInt(mInputType); + } + if ((nodeFlags & FLAGS_HAS_MIN_TEXT_EMS) != 0) { + parcel.writeInt(mMinEms); + } + if ((nodeFlags & FLAGS_HAS_MAX_TEXT_EMS) != 0) { + parcel.writeInt(mMaxEms); + } + if ((nodeFlags & FLAGS_HAS_MAX_TEXT_LENGTH) != 0) { + parcel.writeInt(mMaxLength); + } + if ((nodeFlags & FLAGS_HAS_TEXT_ID_ENTRY) != 0) { + parcel.writeString(mTextIdEntry); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_TYPE) != 0) { + parcel.writeInt(mAutofillType); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_HINTS) != 0) { + parcel.writeStringArray(mAutofillHints); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) { + parcel.writeParcelable(mAutofillValue, 0); + } + if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) { + parcel.writeCharSequenceArray(mAutofillOptions); + } + } + /** @hide */ public static void writeToParcel(@NonNull Parcel parcel, @Nullable ViewNode node, int flags) { if (node == null) { - parcel.writeParcelable(null, flags); - return; + parcel.writeLong(0); + } else { + node.writeSelfToParcel(parcel, flags); } - parcel.writeParcelable(node.mAutofillId, flags); - parcel.writeParcelable(node.mParentAutofillId, flags); - parcel.writeCharSequence(node.mText); - parcel.writeString(node.mClassName); } /** @hide */ public static @Nullable ViewNode readFromParcel(@NonNull Parcel parcel) { - final AutofillId id = parcel.readParcelable(null); - if (id == null) return null; - - final ViewNode node = new ViewNode(); - - node.mAutofillId = id; - node.mParentAutofillId = parcel.readParcelable(null); - node.mText = parcel.readCharSequence(); - node.mClassName = parcel.readString(); - - return node; + final long nodeFlags = parcel.readLong(); + return nodeFlags == 0 ? new ViewNode() : new ViewNode(nodeFlags, parcel); } /** @hide */ - static final class ViewStructureImpl extends ViewStructure { + @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk. + public static final class ViewStructureImpl extends ViewStructure { final ViewNode mNode = new ViewNode(); - ViewStructureImpl(@NonNull View view) { + @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk. + public ViewStructureImpl(@NonNull View view) { mNode.mAutofillId = Preconditions.checkNotNull(view).getAutofillId(); final ViewParent parent = view.getParent(); if (parent instanceof View) { @@ -119,179 +671,212 @@ public final class ViewNode extends AssistStructure.ViewNode { } } - ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) { + @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk. + public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) { mNode.mParentAutofillId = Preconditions.checkNotNull(parentId); mNode.mAutofillId = new AutofillId(parentId, virtualId); } + @VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk. + public ViewNode getNode() { + return mNode; + } + @Override public void setId(int id, String packageName, String typeName, String entryName) { - // TODO(b/111276913): implement or move to superclass + mNode.mId = id; + mNode.mIdPackage = packageName; + mNode.mIdType = typeName; + mNode.mIdEntry = entryName; } @Override public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) { - // TODO(b/111276913): implement or move to superclass + mNode.mX = left; + mNode.mY = top; + mNode.mScrollX = scrollX; + mNode.mScrollY = scrollY; + mNode.mWidth = width; + mNode.mHeight = height; } @Override public void setTransformation(Matrix matrix) { - // TODO(b/111276913): implement or move to superclass + if (matrix == null) { + mNode.mMatrix = null; + } else { + mNode.mMatrix = new Matrix(matrix); + } } @Override public void setElevation(float elevation) { - // TODO(b/111276913): implement or move to superclass + mNode.mElevation = elevation; } @Override public void setAlpha(float alpha) { - // TODO(b/111276913): implement or move to superclass + mNode.mAlpha = alpha; } @Override public void setVisibility(int visibility) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_VISIBILITY_MASK) + | (visibility & FLAGS_VISIBILITY_MASK); } @Override public void setAssistBlocked(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_ASSIST_BLOCKED) + | (state ? FLAGS_ASSIST_BLOCKED : 0); } @Override public void setEnabled(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_DISABLED) | (state ? 0 : FLAGS_DISABLED); } @Override public void setClickable(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_CLICKABLE) | (state ? FLAGS_CLICKABLE : 0); } @Override public void setLongClickable(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_LONG_CLICKABLE) + | (state ? FLAGS_LONG_CLICKABLE : 0); } @Override public void setContextClickable(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_CONTEXT_CLICKABLE) + | (state ? FLAGS_CONTEXT_CLICKABLE : 0); } @Override public void setFocusable(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSABLE) | (state ? FLAGS_FOCUSABLE : 0); } @Override public void setFocused(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_FOCUSED) | (state ? FLAGS_FOCUSED : 0); } @Override public void setAccessibilityFocused(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_ACCESSIBILITY_FOCUSED) + | (state ? FLAGS_ACCESSIBILITY_FOCUSED : 0); } @Override public void setCheckable(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKABLE) | (state ? FLAGS_CHECKABLE : 0); } @Override public void setChecked(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_CHECKED) | (state ? FLAGS_CHECKED : 0); } @Override public void setSelected(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_SELECTED) | (state ? FLAGS_SELECTED : 0); } @Override public void setActivated(boolean state) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_ACTIVATED) | (state ? FLAGS_ACTIVATED : 0); } @Override public void setOpaque(boolean opaque) { - // TODO(b/111276913): implement or move to superclass + mNode.mFlags = (mNode.mFlags & ~FLAGS_OPAQUE) | (opaque ? FLAGS_OPAQUE : 0); } @Override public void setClassName(String className) { - // TODO(b/111276913): temporarily setting directly; should be done on superclass instead mNode.mClassName = className; } @Override public void setContentDescription(CharSequence contentDescription) { - // TODO(b/111276913): implement or move to superclass + mNode.mContentDescription = contentDescription; } @Override public void setText(CharSequence text) { - // TODO(b/111276913): temporarily setting directly; should be done on superclass instead - mNode.mText = text; + final ViewNodeText t = getNodeText(); + t.mText = TextUtils.trimNoCopySpans(text); + t.mTextSelectionStart = t.mTextSelectionEnd = -1; } @Override public void setText(CharSequence text, int selectionStart, int selectionEnd) { - // TODO(b/111276913): temporarily setting directly; should be done on superclass instead - mNode.mText = text; - // TODO(b/111276913): implement or move to superclass + final ViewNodeText t = getNodeText(); + t.mText = TextUtils.trimNoCopySpans(text); + t.mTextSelectionStart = selectionStart; + t.mTextSelectionEnd = selectionEnd; } @Override public void setTextStyle(float size, int fgColor, int bgColor, int style) { - // TODO(b/111276913): implement or move to superclass + final ViewNodeText t = getNodeText(); + t.mTextColor = fgColor; + t.mTextBackgroundColor = bgColor; + t.mTextSize = size; + t.mTextStyle = style; } @Override public void setTextLines(int[] charOffsets, int[] baselines) { - // TODO(b/111276913): implement or move to superclass + final ViewNodeText t = getNodeText(); + t.mLineCharOffsets = charOffsets; + t.mLineBaselines = baselines; + } + + @Override + public void setTextIdEntry(String entryName) { + mNode.mTextIdEntry = Preconditions.checkNotNull(entryName); } @Override public void setHint(CharSequence hint) { - // TODO(b/111276913): implement or move to superclass + getNodeText().mHint = hint != null ? hint.toString() : null; } @Override public CharSequence getText() { - // TODO(b/111276913): temporarily getting directly; should be done on superclass instead - return mNode.mText; + return mNode.getText(); } @Override public int getTextSelectionStart() { - // TODO(b/111276913): implement or move to superclass - return 0; + return mNode.getTextSelectionStart(); } @Override public int getTextSelectionEnd() { - // TODO(b/111276913): implement or move to superclass - return 0; + return mNode.getTextSelectionEnd(); } @Override public CharSequence getHint() { - // TODO(b/111276913): implement or move to superclass - return null; + return mNode.getHint(); } @Override public Bundle getExtras() { - // TODO(b/111276913): implement or move to superclass - return null; + if (mNode.mExtras != null) { + return mNode.mExtras; + } + mNode.mExtras = new Bundle(); + return mNode.mExtras; } @Override public boolean hasExtras() { - // TODO(b/111276913): implement or move to superclass - return false; + return mNode.mExtras != null; } @Override @@ -325,50 +910,64 @@ public final class ViewNode extends AssistStructure.ViewNode { @Override public AutofillId getAutofillId() { - // TODO(b/111276913): temporarily getting directly; should be done on superclass instead return mNode.mAutofillId; } @Override public void setAutofillId(AutofillId id) { - // TODO(b/111276913): temporarily setting directly; should be done on superclass instead - mNode.mAutofillId = id; + mNode.mAutofillId = Preconditions.checkNotNull(id); } + @Override public void setAutofillId(AutofillId parentId, int virtualId) { - // TODO(b/111276913): temporarily setting directly; should be done on superclass instead + mNode.mParentAutofillId = Preconditions.checkNotNull(parentId); mNode.mAutofillId = new AutofillId(parentId, virtualId); } @Override - public void setAutofillType(int type) { - // TODO(b/111276913): implement or move to superclass + public void setAutofillType(@View.AutofillType int type) { + mNode.mAutofillType = type; } @Override - public void setAutofillHints(String[] hint) { - // TODO(b/111276913): implement or move to superclass + public void setAutofillHints(String[] hints) { + mNode.mAutofillHints = hints; } @Override public void setAutofillValue(AutofillValue value) { - // TODO(b/111276913): implement or move to superclass + mNode.mAutofillValue = value; } @Override public void setAutofillOptions(CharSequence[] options) { - // TODO(b/111276913): implement or move to superclass + mNode.mAutofillOptions = options; } @Override public void setInputType(int inputType) { - // TODO(b/111276913): implement or move to superclass + mNode.mInputType = inputType; + } + + @Override + public void setMinTextEms(int minEms) { + mNode.mMinEms = minEms; + } + + @Override + public void setMaxTextEms(int maxEms) { + mNode.mMaxEms = maxEms; + } + + @Override + public void setMaxTextLength(int maxLength) { + mNode.mMaxLength = maxLength; } @Override public void setDataIsSensitive(boolean sensitive) { - // TODO(b/111276913): implement or move to superclass + Log.w(TAG, "setDataIsSensitive() is not supported"); } @Override @@ -378,7 +977,7 @@ public final class ViewNode extends AssistStructure.ViewNode { @Override public Rect getTempRect() { - // TODO(b/111276913): implement or move to superclass + Log.w(TAG, "getTempRect() is not supported"); return null; } @@ -389,7 +988,7 @@ public final class ViewNode extends AssistStructure.ViewNode { @Override public void setLocaleList(LocaleList localeList) { - // TODO(b/111276913): implement or move to superclass + mNode.mLocaleList = localeList; } @Override @@ -402,5 +1001,66 @@ public final class ViewNode extends AssistStructure.ViewNode { public void setHtmlInfo(HtmlInfo htmlInfo) { Log.w(TAG, "setHtmlInfo() is not supported"); } + + private ViewNodeText getNodeText() { + if (mNode.mText != null) { + return mNode.mText; + } + mNode.mText = new ViewNodeText(); + return mNode.mText; + } + } + + //TODO(b/122484602): copied 'as-is' from AssistStructure, except for writeSensitive + static final class ViewNodeText { + CharSequence mText; + float mTextSize; + int mTextStyle; + int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED; + int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED; + int mTextSelectionStart; + int mTextSelectionEnd; + int[] mLineCharOffsets; + int[] mLineBaselines; + String mHint; + + ViewNodeText() { + } + + boolean isSimple() { + return mTextBackgroundColor == ViewNode.TEXT_COLOR_UNDEFINED + && mTextSelectionStart == 0 && mTextSelectionEnd == 0 + && mLineCharOffsets == null && mLineBaselines == null && mHint == null; + } + + ViewNodeText(Parcel in, boolean simple) { + mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mTextSize = in.readFloat(); + mTextStyle = in.readInt(); + mTextColor = in.readInt(); + if (!simple) { + mTextBackgroundColor = in.readInt(); + mTextSelectionStart = in.readInt(); + mTextSelectionEnd = in.readInt(); + mLineCharOffsets = in.createIntArray(); + mLineBaselines = in.createIntArray(); + mHint = in.readString(); + } + } + + void writeToParcel(Parcel out, boolean simple) { + TextUtils.writeToParcel(mText, out, 0); + out.writeFloat(mTextSize); + out.writeInt(mTextStyle); + out.writeInt(mTextColor); + if (!simple) { + out.writeInt(mTextBackgroundColor); + out.writeInt(mTextSelectionStart); + out.writeInt(mTextSelectionEnd); + out.writeIntArray(mLineCharOffsets); + out.writeIntArray(mLineBaselines); + out.writeString(mHint); + } + } } } diff --git a/core/java/android/view/inputmethod/InputMethodSystemProperty.java b/core/java/android/view/inputmethod/InputMethodSystemProperty.java index b233b756686f..57ed7f923e88 100644 --- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java +++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java @@ -41,6 +41,17 @@ public class InputMethodSystemProperty { */ private static final String PROP_DEBUG_MULTI_CLIENT_IME = "persist.debug.multi_client_ime"; + /** + * System property key for debugging purpose. The value must be empty, "1", or "0". + * + * <p>Values 'y', 'yes', '1', 'true' or 'on' are considered true.</p> + * + * <p>To set, run "adb root && adb shell setprop persist.debug.per_profile_ime 1".</p> + * + * <p>This value will be ignored when {@link Build#IS_DEBUGGABLE} returns {@code false}.</p> + */ + private static final String PROP_DEBUG_PER_PROFILE_IME = "persist.debug.per_profile_ime"; + @Nullable private static ComponentName getMultiClientImeComponentName() { if (Build.IS_DEBUGGABLE) { @@ -59,6 +70,9 @@ public class InputMethodSystemProperty { /** * {@link ComponentName} of multi-client IME to be used. * + * <p>TODO: Move this back to MultiClientInputMethodManagerService once + * {@link #PER_PROFILE_IME_ENABLED} always becomes {@code true}.</p> + * * @hide */ @Nullable @@ -68,7 +82,19 @@ public class InputMethodSystemProperty { /** * {@code true} when multi-client IME is enabled. * + * <p>TODO: Move this back to MultiClientInputMethodManagerService once + * {@link #PER_PROFILE_IME_ENABLED} always becomes {@code true}.</p> + * * @hide */ public static final boolean MULTI_CLIENT_IME_ENABLED = (sMultiClientImeComponentName != null); + + /** + * {@code true} when per-profile IME is enabled. + * @hide + */ + public static final boolean PER_PROFILE_IME_ENABLED = MULTI_CLIENT_IME_ENABLED + || Build.IS_DEBUGGABLE && SystemProperties.getBoolean( + PROP_DEBUG_PER_PROFILE_IME, false); + } diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java index 4c6862c81cf2..5dc8b19444b0 100644 --- a/core/java/android/view/textservice/TextServicesManager.java +++ b/core/java/android/view/textservice/TextServicesManager.java @@ -67,12 +67,6 @@ public final class TextServicesManager { private static final String TAG = TextServicesManager.class.getSimpleName(); private static final boolean DBG = false; - /** - * A compile time switch to control per-profile spell checker, which is not yet ready. - * @hide - */ - public static final boolean DISABLE_PER_PROFILE_SPELL_CHECKER = true; - private static TextServicesManager sInstance; private final ITextServicesManager mService; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 7f636536c366..51b8734fdcc1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8057,6 +8057,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } break; + + case KeyEvent.KEYCODE_FORWARD_DEL: + if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) { + if (onTextContextMenuItem(ID_CUT)) { + return KEY_EVENT_HANDLED; + } + } + break; + + case KeyEvent.KEYCODE_INSERT: + if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) { + if (onTextContextMenuItem(ID_COPY)) { + return KEY_EVENT_HANDLED; + } + } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) { + if (onTextContextMenuItem(ID_PASTE)) { + return KEY_EVENT_HANDLED; + } + } + break; } if (mEditor != null && mEditor.mKeyListener != null) { @@ -10827,25 +10847,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return onTextContextMenuItem(ID_PASTE); } break; - case KeyEvent.KEYCODE_INSERT: - if (canCopy()) { - return onTextContextMenuItem(ID_COPY); - } - break; - } - } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { - // Handle Shift-only shortcuts. - switch (keyCode) { - case KeyEvent.KEYCODE_FORWARD_DEL: - if (canCut()) { - return onTextContextMenuItem(ID_CUT); - } - break; - case KeyEvent.KEYCODE_INSERT: - if (canPaste()) { - return onTextContextMenuItem(ID_PASTE); - } - break; } } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { // Handle Ctrl-Shift shortcuts. diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index e59bee42c21c..c4ab91f8b429 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -17,8 +17,10 @@ package com.android.internal.app; import android.app.AppOpsManager; +import android.app.AppOpsManager; import android.content.pm.ParceledListSlice; import android.os.Bundle; +import android.os.RemoteCallback; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsNotedCallback; @@ -42,10 +44,15 @@ interface IAppOpsService { int checkPackage(int uid, String packageName); List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops); List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops); - ParceledListSlice getAllHistoricalPackagesOps(in String[] ops, - long beginTimeMillis, long endTimeMillis); - AppOpsManager.HistoricalPackageOps getHistoricalPackagesOps(int uid, String packageName, - in String[] ops, long beginTimeMillis, long endTimeMillis); + void getHistoricalOps(int uid, String packageName, in String[] ops, long beginTimeMillis, + long endTimeMillis, in RemoteCallback callback); + void getHistoricalOpsFromDiskRaw(int uid, String packageName, in String[] ops, + long beginTimeMillis, long endTimeMillis, in RemoteCallback callback); + void offsetHistory(long duration); + void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep); + void addHistoricalOps(in AppOpsManager.HistoricalOps ops); + void resetHistoryParameters(); + void clearHistory(); List<AppOpsManager.PackageOps> getUidOps(int uid, in int[] ops); void setUidMode(int code, int uid, int mode); void setMode(int code, int uid, String packageName, int mode); diff --git a/core/java/com/android/internal/os/AtomicDirectory.java b/core/java/com/android/internal/os/AtomicDirectory.java new file mode 100644 index 000000000000..f24d12e0c3af --- /dev/null +++ b/core/java/com/android/internal/os/AtomicDirectory.java @@ -0,0 +1,294 @@ +/* + * 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.annotation.NonNull; +import android.annotation.Nullable; +import android.os.FileUtils; +import android.util.ArrayMap; + +import com.android.internal.util.Preconditions; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; + +/** + * Helper class for performing atomic operations on a directory, by creating a + * backup directory until a write has successfully completed. + * <p> + * Atomic directory guarantees directory integrity by ensuring that a directory has + * been completely written and sync'd to disk before removing its backup. + * As long as the backup directory exists, the original directory is considered + * to be invalid (leftover from a previous attempt to write). + * <p> + * Atomic directory does not confer any file locking semantics. Do not use this + * class when the directory may be accessed or modified concurrently + * by multiple threads or processes. The caller is responsible for ensuring + * appropriate mutual exclusion invariants whenever it accesses the directory. + * <p> + * To ensure atomicity you must always use this class to interact with the + * backing directory when checking existence, making changes, and deleting. + */ +public final class AtomicDirectory { + private final @NonNull ArrayMap<File, FileOutputStream> mOpenFiles = new ArrayMap<>(); + private final @NonNull File mBaseDirectory; + private final @NonNull File mBackupDirectory; + + private int mBaseDirectoryFd = -1; + private int mBackupDirectoryFd = -1; + + /** + * Creates a new instance. + * + * @param baseDirectory The base directory to treat atomically. + */ + public AtomicDirectory(@NonNull File baseDirectory) { + Preconditions.checkNotNull(baseDirectory, "baseDirectory cannot be null"); + mBaseDirectory = baseDirectory; + mBackupDirectory = new File(baseDirectory.getPath() + "_bak"); + } + + /** + * Gets the backup directory if present. This could be useful if you are + * writing new state to the dir but need to access the last persisted state + * at the same time. This means that this call is useful in between + * {@link #startWrite()} and {@link #finishWrite()} or {@link #failWrite()}. + * You should not modify the content returned by this method. + * + * @see #startRead() + */ + public @Nullable File getBackupDirectory() { + return mBackupDirectory; + } + + /** + * Starts reading this directory. After calling this method you should + * not make any changes to its contents. + * + * @throws IOException If an error occurs. + * + * @see #finishRead() + * @see #startWrite() + */ + public @NonNull File startRead() throws IOException { + restore(); + return getOrCreateBaseDirectory(); + } + + /** + * Finishes reading this directory. + * + * @see #startRead() + * @see #startWrite() + */ + public void finishRead() { + mBaseDirectoryFd = -1; + mBackupDirectoryFd = -1; + } + + /** + * Starts editing this directory. After calling this method you should + * add content to the directory only via the APIs on this class. To open a + * file for writing in this directory you should use {@link #openWrite(File)} + * and to close the file {@link #closeWrite(FileOutputStream)}. Once all + * content has been written and all files closed you should commit via a + * call to {@link #finishWrite()} or discard via a call to {@link #failWrite()}. + * + * @throws IOException If an error occurs. + * + * @see #startRead() + * @see #openWrite(File) + * @see #finishWrite() + * @see #failWrite() + */ + public @NonNull File startWrite() throws IOException { + backup(); + return getOrCreateBaseDirectory(); + } + + /** + * Opens a file in this directory for writing. + * + * @param file The file to open. Must be a file in the base directory. + * @return An input stream for reading. + * + * @throws IOException If an I/O error occurs. + * + * @see #closeWrite(FileOutputStream) + */ + public @NonNull FileOutputStream openWrite(@NonNull File file) throws IOException { + if (file.isDirectory() || !file.getParentFile().equals(getOrCreateBaseDirectory())) { + throw new IllegalArgumentException("Must be a file in " + getOrCreateBaseDirectory()); + } + final FileOutputStream destination = new FileOutputStream(file); + if (mOpenFiles.put(file, destination) != null) { + throw new IllegalArgumentException("Already open file" + file.getCanonicalPath()); + } + return destination; + } + + /** + * Closes a previously opened file. + * + * @param destination The stream to the file returned by {@link #openWrite(File)}. + * + * @see #openWrite(File) + */ + public void closeWrite(@NonNull FileOutputStream destination) { + final int indexOfValue = mOpenFiles.indexOfValue(destination); + if (mOpenFiles.removeAt(indexOfValue) == null) { + throw new IllegalArgumentException("Unknown file stream " + destination); + } + FileUtils.sync(destination); + try { + destination.close(); + } catch (IOException ignored) {} + } + + public void failWrite(@NonNull FileOutputStream destination) { + final int indexOfValue = mOpenFiles.indexOfValue(destination); + if (indexOfValue >= 0) { + mOpenFiles.removeAt(indexOfValue); + } + } + + /** + * Finishes the edit and commits all changes. + * + * @see #startWrite() + * + * @throws IllegalStateException is some files are not closed. + */ + public void finishWrite() { + throwIfSomeFilesOpen(); + fsyncDirectoryFd(mBaseDirectoryFd); + deleteDirectory(mBackupDirectory); + fsyncDirectoryFd(mBackupDirectoryFd); + mBaseDirectoryFd = -1; + mBackupDirectoryFd = -1; + } + + /** + * Finishes the edit and discards all changes. + * + * @see #startWrite() + */ + public void failWrite() { + throwIfSomeFilesOpen(); + try{ + restore(); + } catch (IOException ignored) {} + mBaseDirectoryFd = -1; + mBackupDirectoryFd = -1; + } + + /** + * @return Whether this directory exists. + */ + public boolean exists() { + return mBaseDirectory.exists() || mBackupDirectory.exists(); + } + + /** + * Deletes this directory. + */ + public void delete() { + if (mBaseDirectory.exists()) { + deleteDirectory(mBaseDirectory); + fsyncDirectoryFd(mBaseDirectoryFd); + } + if (mBackupDirectory.exists()) { + deleteDirectory(mBackupDirectory); + fsyncDirectoryFd(mBackupDirectoryFd); + } + } + + private @NonNull File getOrCreateBaseDirectory() throws IOException { + if (!mBaseDirectory.exists()) { + if (!mBaseDirectory.mkdirs()) { + throw new IOException("Couldn't create directory " + mBaseDirectory); + } + FileUtils.setPermissions(mBaseDirectory.getPath(), + FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, + -1, -1); + } + if (mBaseDirectoryFd < 0) { + mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath()); + } + return mBaseDirectory; + } + + private void throwIfSomeFilesOpen() { + if (!mOpenFiles.isEmpty()) { + throw new IllegalStateException("Unclosed files: " + + Arrays.toString(mOpenFiles.keySet().toArray())); + } + } + + private void backup() throws IOException { + if (!mBaseDirectory.exists()) { + return; + } + if (mBaseDirectoryFd < 0) { + mBaseDirectoryFd = getDirectoryFd(mBaseDirectory.getCanonicalPath()); + } + if (mBackupDirectory.exists()) { + deleteDirectory(mBackupDirectory); + } + if (!mBaseDirectory.renameTo(mBackupDirectory)) { + throw new IOException("Couldn't backup " + mBaseDirectory + + " to " + mBackupDirectory); + } + mBackupDirectoryFd = mBaseDirectoryFd; + mBaseDirectoryFd = -1; + fsyncDirectoryFd(mBackupDirectoryFd); + } + + private void restore() throws IOException { + if (!mBackupDirectory.exists()) { + return; + } + if (mBackupDirectoryFd == -1) { + mBackupDirectoryFd = getDirectoryFd(mBackupDirectory.getCanonicalPath()); + } + if (mBaseDirectory.exists()) { + deleteDirectory(mBaseDirectory); + } + if (!mBackupDirectory.renameTo(mBaseDirectory)) { + throw new IOException("Couldn't restore " + mBackupDirectory + + " to " + mBaseDirectory); + } + mBaseDirectoryFd = mBackupDirectoryFd; + mBackupDirectoryFd = -1; + fsyncDirectoryFd(mBaseDirectoryFd); + } + + private static void deleteDirectory(@NonNull File file) { + final File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + deleteDirectory(child); + } + } + file.delete(); + } + + private static native int getDirectoryFd(String path); + private static native void fsyncDirectoryFd(int fd); +} diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java index 100c6ee6763b..adf7692adc56 100644 --- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -16,10 +16,10 @@ package com.android.internal.policy; -import com.android.internal.R; - import android.content.res.Resources; +import com.android.internal.R; + /** * Utility functions for screen decorations used by both window manager and System UI. */ @@ -31,15 +31,19 @@ public class ScreenDecorationsUtils { * scaling, this means that we don't have to reload them on config changes. */ public static float getWindowCornerRadius(Resources resources) { + if (!supportsRoundedCornersOnWindows(resources)) { + return 0f; + } + // Radius that should be used in case top or bottom aren't defined. float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius); float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top); - if (topRadius == 0) { + if (topRadius == 0f) { topRadius = defaultRadius; } float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom); - if (bottomRadius == 0) { + if (bottomRadius == 0f) { bottomRadius = defaultRadius; } @@ -47,4 +51,11 @@ public class ScreenDecorationsUtils { // completely cover the display. return Math.min(topRadius, bottomRadius); } + + /** + * If live rounded corners are supported on windows. + */ + public static boolean supportsRoundedCornersOnWindows(Resources resources) { + return resources.getBoolean(R.bool.config_supportsRoundedCornersOnWindows); + } } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 53b56f2e937a..6a28059d3fd0 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -153,13 +153,11 @@ oneway interface IStatusBar void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated - void onBiometricAuthenticated(); + void onBiometricAuthenticated(boolean authenticated); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc void onBiometricHelp(String message); // Used to set a message - the dialog will dismiss after a certain amount of time void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); - // Used to request the "try again" button for authentications which requireConfirmation=true - void showBiometricTryAgain(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 9087dd219d97..197e873a18bc 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -97,13 +97,11 @@ interface IStatusBarService void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated - void onBiometricAuthenticated(); + void onBiometricAuthenticated(boolean authenticated); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc void onBiometricHelp(String message); // Used to set a message - the dialog will dismiss after a certain amount of time void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); - // Used to request the "try again" button for authentications which requireConfirmation=true - void showBiometricTryAgain(); } diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 470533f2d002..605df040da59 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -330,4 +330,18 @@ 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 + */ + public static @Nullable <T> T firstOrNull(@Nullable List<T> cur) { + return isEmpty(cur) ? null : cur.get(0); + } + + /** + * @return list of single given element if it's not null, empty list otherwise + */ + public static @NonNull <T> List<T> singletonOrEmpty(@Nullable T item) { + return item == null ? Collections.emptyList() : Collections.singletonList(item); + } } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index dc6a73a7d75c..088e13ff92bc 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -41,7 +41,7 @@ cc_library_shared { "com_google_android_gles_jni_EGLImpl.cpp", "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm "android_app_Activity.cpp", - "android_app_ActivityThread.cpp", + "android_app_ActivityThread.cpp", "android_app_NativeActivity.cpp", "android_app_admin_SecurityLog.cpp", "android_opengl_EGL14.cpp", @@ -202,6 +202,7 @@ cc_library_shared { "android_animation_PropertyValuesHolder.cpp", "android_security_Scrypt.cpp", "com_android_internal_net_NetworkStatsFactory.cpp", + "com_android_internal_os_AtomicDirectory.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", "com_android_internal_os_FuseAppLoop.cpp", "com_android_internal_os_Zygote.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 687b1055c3d6..109222298fe5 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -218,6 +218,7 @@ extern int register_android_animation_PropertyValuesHolder(JNIEnv *env); extern int register_android_security_Scrypt(JNIEnv *env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env); +extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); extern int register_com_android_internal_os_Zygote(JNIEnv *env); @@ -1495,6 +1496,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_security_Scrypt), REG_JNI(register_com_android_internal_content_NativeLibraryHelper), REG_JNI(register_com_android_internal_net_NetworkStatsFactory), + REG_JNI(register_com_android_internal_os_AtomicDirectory), REG_JNI(register_com_android_internal_os_FuseAppLoop), }; diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index 163b86b29030..42e3942eb350 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -329,7 +329,7 @@ static jobject JHwBinder_native_getService( return NULL; } - LOG(INFO) << "HwBinder: Starting thread pool for " << serviceName << "::" << ifaceName; + LOG(INFO) << "HwBinder: Starting thread pool for getting: " << ifaceName << "/" << serviceName; ::android::hardware::ProcessState::self()->startThreadPool(); return JHwRemoteBinder::NewObject(env, service); diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index ecf811970a99..50cff5c46da3 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -334,7 +334,7 @@ static void pointerPropertiesFromNative(JNIEnv* env, const PointerProperties* po static jlong android_view_MotionEvent_nativeInitialize(JNIEnv* env, jclass clazz, jlong nativePtr, jint deviceId, jint source, jint displayId, jint action, jint flags, jint edgeFlags, - jint metaState, jint buttonState, + jint metaState, jint buttonState, jint classification, jfloat xOffset, jfloat yOffset, jfloat xPrecision, jfloat yPrecision, jlong downTimeNanos, jlong eventTimeNanos, jint pointerCount, jobjectArray pointerPropertiesObjArray, @@ -373,7 +373,8 @@ static jlong android_view_MotionEvent_nativeInitialize(JNIEnv* env, jclass clazz } event->initialize(deviceId, source, displayId, action, 0, flags, edgeFlags, metaState, - buttonState, xOffset, yOffset, xPrecision, yPrecision, + buttonState, static_cast<MotionClassification>(classification), + xOffset, yOffset, xPrecision, yPrecision, downTimeNanos, eventTimeNanos, pointerCount, pointerProperties, rawPointerCoords); return reinterpret_cast<jlong>(event); @@ -668,6 +669,11 @@ static void android_view_MotionEvent_nativeSetButtonState(jlong nativePtr, jint event->setButtonState(buttonState); } +static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) { + MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); + return static_cast<jint>(event->getClassification()); +} + static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX, jfloat deltaY) { MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); @@ -747,7 +753,7 @@ static void android_view_MotionEvent_nativeTransform(jlong nativePtr, jlong matr static const JNINativeMethod gMotionEventMethods[] = { /* name, signature, funcPtr */ { "nativeInitialize", - "(JIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;" + "(JIIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;" "[Landroid/view/MotionEvent$PointerCoords;)J", (void*)android_view_MotionEvent_nativeInitialize }, { "nativeDispose", @@ -846,6 +852,9 @@ static const JNINativeMethod gMotionEventMethods[] = { { "nativeSetButtonState", "(JI)V", (void*)android_view_MotionEvent_nativeSetButtonState }, + { "nativeGetClassification", + "(J)I", + (void*)android_view_MotionEvent_nativeGetClassification }, { "nativeOffsetLocation", "(JFF)V", (void*)android_view_MotionEvent_nativeOffsetLocation }, diff --git a/core/jni/com_android_internal_os_AtomicDirectory.cpp b/core/jni/com_android_internal_os_AtomicDirectory.cpp new file mode 100644 index 000000000000..50b2288a614a --- /dev/null +++ b/core/jni/com_android_internal_os_AtomicDirectory.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <nativehelper/ScopedUtfChars.h> +#include "jni.h" + +#include "core_jni_helpers.h" + +namespace android { + +static jint com_android_internal_os_AtomicDirectory_getDirectoryFd(JNIEnv* env, + jobject /*clazz*/, jstring path) { + ScopedUtfChars path8(env, path); + if (path8.c_str() == NULL) { + ALOGE("Invalid path: %s", path8.c_str()); + return -1; + } + int fd; + if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY))) == -1) { + ALOGE("Cannot open directory %s, error: %s\n", path8.c_str(), strerror(errno)); + return -1; + } + return fd; +} + +static void com_android_internal_os_AtomicDirectory_fsyncDirectoryFd(JNIEnv* env, + jobject /*clazz*/, jint fd) { + if (TEMP_FAILURE_RETRY(fsync(fd)) == -1) { + ALOGE("Cannot fsync directory %d, error: %s\n", fd, strerror(errno)); + } +} + +/* + * JNI registration. + */ +static const JNINativeMethod gRegisterMethods[] = { + /* name, signature, funcPtr */ + { "fsyncDirectoryFd", + "(I)V", + (void*) com_android_internal_os_AtomicDirectory_fsyncDirectoryFd + }, + { "getDirectoryFd", + "(Ljava/lang/String;)I", + (void*) com_android_internal_os_AtomicDirectory_getDirectoryFd + }, +}; + +int register_com_android_internal_os_AtomicDirectory(JNIEnv* env) { + return RegisterMethodsOrDie(env, "com/android/internal/os/AtomicDirectory", + gRegisterMethods, NELEM(gRegisterMethods)); +} + +}; // namespace android diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index a00fde9ed0e3..cc5aa20258ce 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -1018,7 +1018,9 @@ message GlobalSettingsProto { optional SettingProto zram_enabled = 139 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto app_ops_constants = 148 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 148; + // Next tag = 149; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 449a7b3305ad..8b85a28c4a40 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4254,6 +4254,16 @@ <permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to manage the content suggestions service. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" + android:protectionLevel="signature" /> + + <!-- @SystemApi Allows an application to manage the app predictions service. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.MANAGE_APP_PREDICTIONS" + android:protectionLevel="signature" /> + <!-- Allows an app to set the theme overlay in /vendor/overlay being used. @hide <p>Not for use by third-party applications.</p> --> @@ -4334,6 +4344,11 @@ <permission android:name="android.permission.BIND_SMS_APP_SERVICE" android:protectionLevel="signature" /> + <!-- @hide Permission that allows configuring appops. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MANAGE_APPOPS" + android:protectionLevel="signature" /> + <!-- @hide Permission that allows background clipboard access. <p>Not for use by third-party applications. --> <permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1c98c66b1f48..1feb59a52ad1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1152,6 +1152,10 @@ Settings.System.NOTIFICATION_VIBRATION_INTENSITY more details on the constant values and meanings. --> <integer name="config_defaultNotificationVibrationIntensity">2</integer> + <!-- The default intensity level for ring vibrations. See + Settings.System.RING_VIBRATION_INTENSITY more details on the constant values and + meanings. --> + <integer name="config_defaultRingVibrationIntensity">2</integer> <bool name="config_use_strict_phone_number_comparation">false</bool> @@ -3419,6 +3423,23 @@ --> <string name="config_defaultAugmentedAutofillService" translatable="false"></string> + <!-- The package name for the system's app prediction service. + This service must be trusted, as it can be activated without explicit consent of the user. + Example: "com.android.intelligence/.AppPredictionService" + --> + <string name="config_defaultAppPredictionService" translatable="false"></string> + + <!-- The package name for the system's content suggestions service. + Provides suggestions for text and image selection regions in snapshots of apps and should + be able to classify the type of entities in those selections. + + This service must be trusted, as it can be activated without explicit consent of the user. + If no service with the specified name exists on the device, content suggestions wil be + disabled. + Example: "com.android.contentsuggestions/.ContentSuggestionsService" + --> + <string name="config_defaultContentSuggestionsService" translatable="false"></string> + <!-- Whether the device uses the default focus highlight when focus state isn't specified. --> <bool name="config_useDefaultFocusHighlight">true</bool> @@ -3648,4 +3669,8 @@ set in AndroidManifest. {@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} --> <string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string> + + <!-- If device supports corner radius on windows. + This should be turned off on low-end devices to improve animation performance. --> + <bool name="config_supportsRoundedCornersOnWindows">true</bool> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 01fbf80f4e63..010accf19d18 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3275,6 +3275,8 @@ <java-symbol type="string" name="config_defaultWellbeingPackage" /> <java-symbol type="string" name="config_defaultContentCaptureService" /> <java-symbol type="string" name="config_defaultAugmentedAutofillService" /> + <java-symbol type="string" name="config_defaultAppPredictionService" /> + <java-symbol type="string" name="config_defaultContentSuggestionsService" /> <java-symbol type="string" name="notification_channel_foreground_service" /> <java-symbol type="string" name="foreground_service_app_in_background" /> @@ -3489,6 +3491,7 @@ <java-symbol type="integer" name="config_defaultHapticFeedbackIntensity" /> <java-symbol type="integer" name="config_defaultNotificationVibrationIntensity" /> + <java-symbol type="integer" name="config_defaultRingVibrationIntensity" /> <java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" /> @@ -3515,6 +3518,7 @@ <java-symbol type="dimen" name="rounded_corner_radius" /> <java-symbol type="dimen" name="rounded_corner_radius_top" /> <java-symbol type="dimen" name="rounded_corner_radius_bottom" /> + <java-symbol type="bool" name="config_supportsRoundedCornersOnWindows" /> <java-symbol type="string" name="config_defaultModuleMetadataProvider" /> diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 041fb7eeb6b3..74943c7bd6dd 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -43,7 +43,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ platform-test-annotations \ compatibility-device-util \ truth-prebuilt \ - print-test-util-lib + print-test-util-lib \ + testng # TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows LOCAL_JAVA_LIBRARIES := \ android.test.runner \ diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 1750dac60e33..0f83a29d8d36 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -19,28 +19,35 @@ package android.app.activity; import static android.content.Intent.ACTION_EDIT; import static android.content.Intent.ACTION_VIEW; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.app.Activity; import android.app.ActivityThread; import android.app.IApplicationThread; +import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; import android.content.Intent; +import android.content.res.Configuration; import android.os.IBinder; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.util.MergedConfiguration; +import android.view.Display; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; + /** * Test for verifying {@link android.app.ActivityThread} class. * Build/Install/Run: @@ -50,8 +57,12 @@ import org.junit.runner.RunWith; @MediumTest public class ActivityThreadTest { - private final ActivityTestRule mActivityTestRule = - new ActivityTestRule(TestActivity.class, true /* initialTouchMode */, + // The first sequence number to try with. Use a large number to avoid conflicts with the first a + // few sequence numbers the framework used to launch the test activity. + private static final int BASE_SEQ = 10000; + + private final ActivityTestRule<TestActivity> mActivityTestRule = + new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, false /* launchActivity */); @Test @@ -129,6 +140,179 @@ public class ActivityThreadTest { }); } + @Test + public void testHandleActivityConfigurationChanged() { + final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + final int numOfConfig = activity.mNumOfConfigChanges; + applyConfigurationChange(activity, BASE_SEQ); + assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); + }); + } + + @Test + public void testHandleActivityConfigurationChanged_DropStaleConfigurations() { + final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // Set the sequence number to BASE_SEQ. + applyConfigurationChange(activity, BASE_SEQ); + + final int orientation = activity.mConfig.orientation; + final int numOfConfig = activity.mNumOfConfigChanges; + + // Try to apply an old configuration change. + applyConfigurationChange(activity, BASE_SEQ - 1); + assertEquals(numOfConfig, activity.mNumOfConfigChanges); + assertEquals(orientation, activity.mConfig.orientation); + }); + } + + @Test + public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() { + final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // Set the sequence number to BASE_SEQ and record the final sequence number it used. + final int seq = applyConfigurationChange(activity, BASE_SEQ); + + final int orientation = activity.mConfig.orientation; + final int numOfConfig = activity.mNumOfConfigChanges; + + // Try to apply an new configuration change. + applyConfigurationChange(activity, seq + 1); + assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); + assertNotEquals(orientation, activity.mConfig.orientation); + }); + } + + @Test + public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() { + final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // Set the sequence number to BASE_SEQ and record the final sequence number it used. + final int seq = applyConfigurationChange(activity, BASE_SEQ); + + final int orientation = activity.mConfig.orientation; + final int numOfConfig = activity.mNumOfConfigChanges; + + final ActivityThread activityThread = activity.getActivityThread(); + + final Configuration pendingConfig = new Configuration(); + pendingConfig.orientation = orientation == Configuration.ORIENTATION_LANDSCAPE + ? Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + pendingConfig.seq = seq + 2; + activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), + pendingConfig); + + final Configuration newConfig = new Configuration(); + newConfig.orientation = orientation; + newConfig.seq = seq + 1; + + activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), + newConfig, Display.INVALID_DISPLAY); + assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); + assertEquals(pendingConfig.orientation, activity.mConfig.orientation); + }); + } + + @Test + public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration() + throws Exception { + final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); + + final ActivityThread activityThread = activity.getActivityThread(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + final Configuration config = new Configuration(); + config.seq = BASE_SEQ; + config.orientation = Configuration.ORIENTATION_PORTRAIT; + + activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), + config, Display.INVALID_DISPLAY); + }); + + final int numOfConfig = activity.mNumOfConfigChanges; + final IApplicationThread appThread = activityThread.getApplicationThread(); + + activity.mConfigLatch = new CountDownLatch(1); + activity.mTestLatch = new CountDownLatch(1); + + Configuration config = new Configuration(); + config.seq = BASE_SEQ + 1; + config.smallestScreenWidthDp = 100; + appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); + + // Wait until the main thread is performing the configuration change for the configuration + // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where + // the activity takes very long time to process configuration changes. + activity.mTestLatch.await(); + + config = new Configuration(); + config.seq = BASE_SEQ + 2; + config.smallestScreenWidthDp = 200; + appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); + + config = new Configuration(); + config.seq = BASE_SEQ + 3; + config.smallestScreenWidthDp = 300; + appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); + + config = new Configuration(); + config.seq = BASE_SEQ + 4; + config.smallestScreenWidthDp = 400; + appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); + + activity.mConfigLatch.countDown(); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + activity.mConfigLatch = null; + activity.mTestLatch = null; + + // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq + // BASE_SEQ + 4. Configurations scheduled in between should be dropped. + assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges); + assertEquals(400, activity.mConfig.smallestScreenWidthDp); + } + + /** + * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)} + * to try to push activity configuration to the activity for the given sequence number. + * <p> + * It uses orientation to push the configuration and it tries a different orientation if the + * first attempt doesn't make through, to rule out the possibility that the previous + * configuration already has the same orientation. + * + * @param activity the test target activity + * @param seq the specified sequence number + * @return the sequence number this method tried with the last time, so that the caller can use + * the next sequence number for next configuration update. + */ + private int applyConfigurationChange(TestActivity activity, int seq) { + final ActivityThread activityThread = activity.getActivityThread(); + + final int numOfConfig = activity.mNumOfConfigChanges; + Configuration config = new Configuration(); + config.orientation = Configuration.ORIENTATION_PORTRAIT; + config.seq = seq; + activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, + Display.INVALID_DISPLAY); + + if (activity.mNumOfConfigChanges > numOfConfig) { + return config.seq; + } + + config = new Configuration(); + config.orientation = Configuration.ORIENTATION_LANDSCAPE; + config.seq = seq + 1; + activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config, + Display.INVALID_DISPLAY); + + return config.seq; + } + private static ClientTransaction newRelaunchResumeTransaction(Activity activity) { final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null, null, 0, new MergedConfiguration(), false /* preserveWindow */); @@ -162,6 +346,16 @@ public class ActivityThreadTest { return transaction; } + private static ClientTransaction newActivityConfigTransaction(Activity activity, + Configuration config) { + final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config); + + final ClientTransaction transaction = newTransaction(activity); + transaction.addCallback(item); + + return transaction; + } + private static ClientTransaction newTransaction(Activity activity) { final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); return ClientTransaction.obtain(appThread, activity.getActivityToken()); @@ -169,5 +363,37 @@ public class ActivityThreadTest { // Test activity public static class TestActivity extends Activity { + int mNumOfConfigChanges; + final Configuration mConfig = new Configuration(); + + /** + * A latch used to notify tests that we're about to wait for configuration latch. This + * is used to notify test code that preExecute phase for activity configuration change + * transaction has passed. + */ + volatile CountDownLatch mTestLatch; + /** + * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the + * latch reaches 0. + */ + volatile CountDownLatch mConfigLatch; + + @Override + public void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + mConfig.setTo(config); + ++mNumOfConfigChanges; + + if (mConfigLatch != null) { + if (mTestLatch != null) { + mTestLatch.countDown(); + } + try { + mConfigLatch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + } } } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index f8633565cc86..1ed5ce4a3929 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -27,6 +27,7 @@ import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; import android.platform.test.annotations.Presubmit; +import android.provider.Settings.Global; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -270,7 +271,6 @@ public class SettingsBackupTest { Settings.Global.GNSS_SATELLITE_BLACKLIST, Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS, Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, - Settings.Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED, Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, Settings.Global.HDMI_CONTROL_ENABLED, Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, @@ -550,7 +550,12 @@ public class SettingsBackupTest { Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, Settings.Global.BACKUP_MULTI_USER_ENABLED, Settings.Global.ISOLATED_STORAGE_LOCAL, - Settings.Global.ISOLATED_STORAGE_REMOTE); + Settings.Global.ISOLATED_STORAGE_REMOTE, + Settings.Global.APPOP_HISTORY_PARAMETERS, + Settings.Global.APPOP_HISTORY_MODE, + Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER, + Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS); + private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS = newHashSet( Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java new file mode 100644 index 000000000000..59f3a4ccc8a2 --- /dev/null +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java @@ -0,0 +1,68 @@ +/* + * 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.view.contentcapture; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.view.autofill.AutofillId; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Unit test for {@link ContentCaptureSessionTest}. + * + * <p>To run it: + * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureSessionTest} + */ +@RunWith(MockitoJUnitRunner.class) +public class ContentCaptureSessionTest { + + /** + * Uses a spy as ContentCaptureSession is abstract but (so far) we're testing its concrete + * methods. + */ + @Spy + private ContentCaptureSession mMockSession; + + @Test + public void testNewAutofillId_invalid() { + assertThrows(NullPointerException.class, () -> mMockSession.newAutofillId(null, 42)); + assertThrows(IllegalArgumentException.class, + () -> mMockSession.newAutofillId(new AutofillId(42, 42), 42)); + } + + @Test + public void testNewAutofillId_valid() { + final AutofillId parentId = new AutofillId(42); + final AutofillId childId = mMockSession.newAutofillId(parentId, 108); + assertThat(childId.getViewId()).isEqualTo(42); + assertThat(childId.getVirtualChildId()).isEqualTo(108); + // TODO(b/121197119): assert session id + } + + @Test + public void testNotifyXXX_null() { + assertThrows(NullPointerException.class, () -> mMockSession.notifyViewAppeared(null)); + assertThrows(NullPointerException.class, () -> mMockSession.notifyViewDisappeared(null)); + assertThrows(NullPointerException.class, + () -> mMockSession.notifyViewTextChanged(null, "whatever", 0)); + } +} diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java new file mode 100644 index 000000000000..995946b940f8 --- /dev/null +++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java @@ -0,0 +1,462 @@ +/* + * 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.view.contentcapture; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.content.Context; +import android.graphics.Matrix; +import android.os.Bundle; +import android.os.LocaleList; +import android.os.Parcel; +import android.support.test.InstrumentationRegistry; +import android.view.View; +import android.view.ViewStructure.HtmlInfo; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; +import android.view.contentcapture.ViewNode.ViewStructureImpl; +import android.widget.FrameLayout; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Locale; + +/** + * Unit test for {@link ViewNode}. + * + * <p>To run it: {@code atest FrameworksCoreTests:android.view.contentcapture.ViewNodeTest} + */ +@RunWith(MockitoJUnitRunner.class) +public class ViewNodeTest { + + private final Context mContext = InstrumentationRegistry.getTargetContext(); + + @Mock + private HtmlInfo mHtmlInfoMock; + + @Test + public void testAutofillIdMethods_orphanView() { + View view = new View(mContext); + AutofillId initialId = new AutofillId(42); + view.setAutofillId(initialId); + + ViewStructureImpl structure = new ViewStructureImpl(view); + ViewNode node = structure.getNode(); + + assertThat(node.getAutofillId()).isEqualTo(initialId); + assertThat(node.getParentAutofillId()).isNull(); + + AutofillId newId = new AutofillId(108); + structure.setAutofillId(newId); + assertThat(node.getAutofillId()).isEqualTo(newId); + assertThat(node.getParentAutofillId()).isNull(); + + structure.setAutofillId(new AutofillId(66), 6); + assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6)); + assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66)); + } + + @Test + public void testAutofillIdMethods_parentedView() { + FrameLayout parent = new FrameLayout(mContext); + AutofillId initialParentId = new AutofillId(48); + parent.setAutofillId(initialParentId); + + View child = new View(mContext); + AutofillId initialChildId = new AutofillId(42); + child.setAutofillId(initialChildId); + + parent.addView(child); + + ViewStructureImpl structure = new ViewStructureImpl(child); + ViewNode node = structure.getNode(); + + assertThat(node.getAutofillId()).isEqualTo(initialChildId); + assertThat(node.getParentAutofillId()).isEqualTo(initialParentId); + + AutofillId newChildId = new AutofillId(108); + structure.setAutofillId(newChildId); + assertThat(node.getAutofillId()).isEqualTo(newChildId); + assertThat(node.getParentAutofillId()).isEqualTo(initialParentId); + + AutofillId newParentId = new AutofillId(15162342); + parent.setAutofillId(newParentId); + assertThat(node.getAutofillId()).isEqualTo(newChildId); + assertThat(node.getParentAutofillId()).isEqualTo(initialParentId); + + structure.setAutofillId(new AutofillId(66), 6); + assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6)); + assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66)); + } + + @Test + public void testAutofillIdMethods_explicitIdsConstructor() { + AutofillId initialParentId = new AutofillId(42); + ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108); + ViewNode node = structure.getNode(); + + assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108)); + assertThat(node.getParentAutofillId()).isEqualTo(initialParentId); + + AutofillId newChildId = new AutofillId(108); + structure.setAutofillId(newChildId); + assertThat(node.getAutofillId()).isEqualTo(newChildId); + assertThat(node.getParentAutofillId()).isEqualTo(initialParentId); + + structure.setAutofillId(new AutofillId(66), 6); + assertThat(node.getAutofillId()).isEqualTo(new AutofillId(66, 6)); + assertThat(node.getParentAutofillId()).isEqualTo(new AutofillId(66)); + } + + @Test + public void testInvalidSetters() { + View view = new View(mContext); + AutofillId initialId = new AutofillId(42); + view.setAutofillId(initialId); + + ViewStructureImpl structure = new ViewStructureImpl(view); + ViewNode node = structure.getNode(); + assertThat(node.getAutofillId()).isEqualTo(initialId); // sanity check + + assertThrows(NullPointerException.class, () -> structure.setAutofillId(null)); + assertThat(node.getAutofillId()).isEqualTo(initialId); // invariant + + assertThrows(NullPointerException.class, () -> structure.setAutofillId(null, 666)); + assertThat(node.getAutofillId()).isEqualTo(initialId); // invariant + + assertThrows(NullPointerException.class, () -> structure.setTextIdEntry(null)); + assertThat(node.getTextIdEntry()).isNull(); + } + + @Test + public void testUnsupportedProperties() { + View view = new View(mContext); + + ViewStructureImpl structure = new ViewStructureImpl(view); + ViewNode node = structure.getNode(); + + structure.setChildCount(1); + assertThat(node.getChildCount()).isEqualTo(0); + + structure.addChildCount(1); + assertThat(node.getChildCount()).isEqualTo(0); + + assertThat(structure.newChild(0)).isNull(); + assertThat(node.getChildCount()).isEqualTo(0); + + assertThat(structure.asyncNewChild(0)).isNull(); + assertThat(node.getChildCount()).isEqualTo(0); + + structure.asyncCommit(); + assertThat(node.getChildCount()).isEqualTo(0); + + structure.setWebDomain("Y U NO SET?"); + assertThat(node.getWebDomain()).isNull(); + + assertThat(structure.newHtmlInfoBuilder("WHATEVER")).isNull(); + + structure.setHtmlInfo(mHtmlInfoMock); + assertThat(node.getHtmlInfo()).isNull(); + + structure.setDataIsSensitive(true); + + assertThat(structure.getTempRect()).isNull(); + } + + @Test + public void testValidProperties_directly() { + ViewStructureImpl structure = newSimpleStructure(); + assertSimpleStructure(structure); + assertSimpleNode(structure.getNode()); + } + + @Test + public void testValidProperties_throughParcel() { + ViewStructureImpl structure = newSimpleStructure(); + final ViewNode node = structure.getNode(); + assertSimpleNode(node); // sanity check + + final ViewNode clone = cloneThroughParcel(node); + assertSimpleNode(clone); + } + + @Test + public void testComplexText_directly() { + ViewStructureImpl structure = newStructureWithComplexText(); + assertStructureWithComplexText(structure); + assertNodeWithComplexText(structure.getNode()); + } + + @Test + public void testComplexText_throughParcel() { + ViewStructureImpl structure = newStructureWithComplexText(); + final ViewNode node = structure.getNode(); + assertNodeWithComplexText(node); // sanity check + + ViewNode clone = cloneThroughParcel(node); + assertNodeWithComplexText(clone); + } + + @Test + public void testVisibility() { + // Visibility is a special case becase it use flag masks, so we want to make sure it works + // fine + View view = new View(mContext); + ViewStructureImpl structure = new ViewStructureImpl(view); + ViewNode node = structure.getNode(); + + structure.setVisibility(View.VISIBLE); + assertThat(node.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE); + + structure.setVisibility(View.GONE); + assertThat(node.getVisibility()).isEqualTo(View.GONE); + assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.GONE); + + structure.setVisibility(View.VISIBLE); + assertThat(node.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE); + + structure.setVisibility(View.INVISIBLE); + assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE); + assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.INVISIBLE); + + structure.setVisibility(View.INVISIBLE | View.GONE); + assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE | View.GONE); + assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.INVISIBLE | View.GONE); + + + final int invalidValue = Math.max(Math.max(View.VISIBLE, View.INVISIBLE), View.GONE) * 2; + structure.setVisibility(View.VISIBLE); + structure.setVisibility(invalidValue); // should be ignored + assertThat(node.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.VISIBLE); + + structure.setVisibility(View.GONE | invalidValue); + assertThat(node.getVisibility()).isEqualTo(View.GONE); + assertThat(cloneThroughParcel(node).getVisibility()).isEqualTo(View.GONE); + } + + /** + * Creates a {@link ViewStructureImpl} that can be asserted through + * {@link #assertSimpleNode(ViewNode)}. + */ + private ViewStructureImpl newSimpleStructure() { + View view = new View(mContext); + view.setAutofillId(new AutofillId(42)); + + ViewStructureImpl structure = new ViewStructureImpl(view); + + // Basic properties + structure.setText("Text is set!"); + structure.setClassName("Classy!"); + structure.setContentDescription("Described I am!"); + structure.setVisibility(View.INVISIBLE); + + // Autofill properties + structure.setAutofillType(View.AUTOFILL_TYPE_TEXT); + structure.setAutofillHints(new String[] { "Auto", "Man" }); + structure.setAutofillOptions(new String[] { "Maybe" }); + structure.setAutofillValue(AutofillValue.forText("Malkovich")); + + // Graphic properties + structure.setElevation(6.66f); + structure.setAlpha(66.6f); + structure.setTransformation(Matrix.IDENTITY_MATRIX); + + // Extra text properties + structure.setMinTextEms(6); + structure.setMaxTextLength(66); + structure.setMaxTextEms(666); + structure.setInputType(42); + structure.setTextIdEntry("TEXT, Y U NO ENTRY?"); + structure.setLocaleList(new LocaleList(Locale.US, Locale.ENGLISH)); + + // Resource id + structure.setId(16, "package.name", "type.name", "entry.name"); + + // Dimensions + structure.setDimens(4, 8, 15, 16, 23, 42); + + // Boolean properties + structure.setAssistBlocked(true); + structure.setEnabled(true); + structure.setClickable(true); + structure.setLongClickable(true); + structure.setContextClickable(true); + structure.setFocusable(true); + structure.setFocused(true); + structure.setAccessibilityFocused(true); + structure.setChecked(true); + structure.setActivated(true); + structure.setOpaque(true); + + // Bundle + assertThat(structure.hasExtras()).isFalse(); + final Bundle bundle = structure.getExtras(); + assertThat(bundle).isNotNull(); + bundle.putString("Marlon", "Bundle"); + assertThat(structure.hasExtras()).isTrue(); + return structure; + } + + /** + * Asserts the properties of a {@link ViewNode} that was created by + * {@link #newSimpleStructure()}. + */ + private void assertSimpleNode(ViewNode node) { + + // Basic properties + assertThat(node.getAutofillId()).isEqualTo(new AutofillId(42)); + assertThat(node.getParentAutofillId()).isNull(); + assertThat(node.getText()).isEqualTo("Text is set!"); + assertThat(node.getClassName()).isEqualTo("Classy!"); + assertThat(node.getContentDescription().toString()).isEqualTo("Described I am!"); + assertThat(node.getVisibility()).isEqualTo(View.INVISIBLE); + + // Autofill properties + assertThat(node.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT); + assertThat(node.getAutofillHints()).asList().containsExactly("Auto", "Man").inOrder(); + assertThat(node.getAutofillOptions()).asList().containsExactly("Maybe").inOrder(); + assertThat(node.getAutofillValue().getTextValue()).isEqualTo("Malkovich"); + + // Graphic properties + assertThat(node.getElevation()).isWithin(1.0e-10f).of(6.66f); + assertThat(node.getAlpha()).isWithin(1.0e-10f).of(66.6f); + assertThat(node.getTransformation()).isEqualTo(Matrix.IDENTITY_MATRIX); + + // Extra text properties + assertThat(node.getMinTextEms()).isEqualTo(6); + assertThat(node.getMaxTextLength()).isEqualTo(66); + assertThat(node.getMaxTextEms()).isEqualTo(666); + assertThat(node.getInputType()).isEqualTo(42); + assertThat(node.getTextIdEntry()).isEqualTo("TEXT, Y U NO ENTRY?"); + assertThat(node.getLocaleList()).isEqualTo(new LocaleList(Locale.US, Locale.ENGLISH)); + + // Resource id + assertThat(node.getId()).isEqualTo(16); + assertThat(node.getIdPackage()).isEqualTo("package.name"); + assertThat(node.getIdType()).isEqualTo("type.name"); + assertThat(node.getIdEntry()).isEqualTo("entry.name"); + + // Dimensions + assertThat(node.getLeft()).isEqualTo(4); + assertThat(node.getTop()).isEqualTo(8); + assertThat(node.getScrollX()).isEqualTo(15); + assertThat(node.getScrollY()).isEqualTo(16); + assertThat(node.getWidth()).isEqualTo(23); + assertThat(node.getHeight()).isEqualTo(42); + + // Boolean properties + assertThat(node.isAssistBlocked()).isTrue(); + assertThat(node.isEnabled()).isTrue(); + assertThat(node.isClickable()).isTrue(); + assertThat(node.isLongClickable()).isTrue(); + assertThat(node.isContextClickable()).isTrue(); + assertThat(node.isFocusable()).isTrue(); + assertThat(node.isFocused()).isTrue(); + assertThat(node.isAccessibilityFocused()).isTrue(); + assertThat(node.isChecked()).isTrue(); + assertThat(node.isActivated()).isTrue(); + assertThat(node.isOpaque()).isTrue(); + + // Bundle + final Bundle bundle = node.getExtras(); + assertThat(bundle).isNotNull(); + assertThat(bundle.size()).isEqualTo(1); + assertThat(bundle.getString("Marlon")).isEqualTo("Bundle"); + } + + /** + * Asserts the properties of a {@link ViewStructureImpl} that was created by + * {@link #newSimpleStructure()}. + */ + private void assertSimpleStructure(ViewStructureImpl structure) { + assertThat(structure.getAutofillId()).isEqualTo(new AutofillId(42)); + assertThat(structure.getText()).isEqualTo("Text is set!"); + + // Bundle + final Bundle bundle = structure.getExtras(); + assertThat(bundle.size()).isEqualTo(1); + assertThat(bundle.getString("Marlon")).isEqualTo("Bundle"); + } + + /** + * Creates a {@link ViewStructureImpl} with "complex" text properties (such as selection); it + * can be asserted through {@link #assertNodeWithComplexText(ViewNode)}. + */ + private ViewStructureImpl newStructureWithComplexText() { + View view = new View(mContext); + ViewStructureImpl structure = new ViewStructureImpl(view); + structure.setText("IGNORE ME!"); + structure.setText("Now we're talking!", 4, 8); + structure.setHint("Soylent Green is SPOILER ALERT"); + structure.setTextStyle(15.0f, 16, 23, 42); + structure.setTextLines(new int[] {4, 8, 15} , new int[] {16, 23, 42}); + return structure; + } + + /** + * Asserts the properties of a {@link ViewNode} that was created by + * {@link #newStructureWithComplexText()}. + */ + private void assertNodeWithComplexText(ViewNode node) { + assertThat(node.getText()).isEqualTo("Now we're talking!"); + assertThat(node.getTextSelectionStart()).isEqualTo(4); + assertThat(node.getTextSelectionEnd()).isEqualTo(8); + assertThat(node.getHint()).isEqualTo("Soylent Green is SPOILER ALERT"); + assertThat(node.getTextSize()).isWithin(1.0e-10f).of(15.0f); + assertThat(node.getTextColor()).isEqualTo(16); + assertThat(node.getTextBackgroundColor()).isEqualTo(23); + assertThat(node.getTextStyle()).isEqualTo(42); + assertThat(node.getTextLineCharOffsets()).asList().containsExactly(4, 8, 15).inOrder(); + assertThat(node.getTextLineBaselines()).asList().containsExactly(16, 23, 42).inOrder(); + } + + /** + * Asserts the properties of a {@link ViewStructureImpl} that was created by + * {@link #newStructureWithComplexText()}. + */ + private void assertStructureWithComplexText(ViewStructureImpl structure) { + assertThat(structure.getText()).isEqualTo("Now we're talking!"); + assertThat(structure.getTextSelectionStart()).isEqualTo(4); + assertThat(structure.getTextSelectionEnd()).isEqualTo(8); + assertThat(structure.getHint()).isEqualTo("Soylent Green is SPOILER ALERT"); + } + + private ViewNode cloneThroughParcel(ViewNode node) { + Parcel parcel = Parcel.obtain(); + + try { + // Write to parcel + parcel.setDataPosition(0); // Sanity / paranoid check + ViewNode.writeToParcel(parcel, node, 0); + + // Read from parcel + parcel.setDataPosition(0); + ViewNode clone = ViewNode.readFromParcel(parcel); + assertThat(clone).isNotNull(); + return clone; + } finally { + parcel.recycle(); + } + } +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk index 80ab4eae689b..d161059ed024 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk @@ -18,7 +18,7 @@ LOCAL_PATH:= $(call my-dir) ## The application with a minimal main dex include $(CLEAR_VARS) -LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex android-support-multidex-instrumentation android-support-test +LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex android-support-multidex-instrumentation androidx.test.rules LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml index 98d8f27179fd..d9d9eb20e632 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml @@ -30,7 +30,7 @@ android:targetPackage="com.android.multidexlegacyandexception" android:label="Test for MultiDexLegacyAndException" /> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.multidexlegacyandexception" android:label="Test for MultiDexLegacyAndException" /> </manifest> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java index 92a3b0c0f7c3..fae345f982b7 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/MultiDexAndroidJUnitRunner.java @@ -17,8 +17,9 @@ package com.android.multidexlegacyandexception.tests; import android.os.Bundle; + import androidx.multidex.MultiDex; -import android.support.test.runner.AndroidJUnitRunner; +import androidx.test.runner.AndroidJUnitRunner; public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner { diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java index 94a5b7fa66f2..f4b02d0dc68e 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/tests/NoActivityJUnit4Test.java @@ -16,7 +16,8 @@ package com.android.multidexlegacyandexception.tests; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; + import org.junit.runner.RunWith; /** diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk index f2bd35363003..2dc30ea7c17b 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk @@ -18,7 +18,7 @@ LOCAL_PATH:= $(call my-dir) ## The tests with only one dex include $(CLEAR_VARS) -LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test +LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) @@ -41,7 +41,7 @@ include $(BUILD_PACKAGE) ## The tests with a minimal main dex include $(CLEAR_VARS) -LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation android-support-test +LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java index 4e6ec142e690..7812c581b2b8 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/InstrumentationTest.java @@ -15,10 +15,13 @@ */ package com.android.multidexlegacytestapp.test2; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; + import com.android.multidexlegacytestapp.manymethods.Big001; import com.android.multidexlegacytestapp.manymethods.Big079; + import junit.framework.Assert; + import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java index 9e41a925de89..b3044dcbeb8e 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/src/com/android/multidexlegacytestapp/test2/MultiDexAndroidJUnitRunner.java @@ -1,8 +1,9 @@ package com.android.multidexlegacytestapp.test2; import android.os.Bundle; + import androidx.multidex.MultiDex; -import android.support.test.runner.AndroidJUnitRunner; +import androidx.test.runner.AndroidJUnitRunner; public class MultiDexAndroidJUnitRunner extends AndroidJUnitRunner { diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 9b86b77a9384..25f6775f81b4 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1075,6 +1075,11 @@ public class Typeface { continue; // If alias and named family are conflict, use named family. } final Typeface base = systemFontMap.get(alias.getToName()); + if (base == null) { + // The missing target is a valid thing, some configuration don't have font files, + // e.g. wear devices. Just skip this alias. + continue; + } final int weight = alias.getWeight(); final Typeface newFace = weight == 400 ? base : new Typeface(nativeCreateWeightAlias(base.native_instance, weight)); diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index b9860ada18fc..635d0ec66673 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -34,7 +34,7 @@ namespace android::uirenderer { static std::mutex sLock{}; -static ThreadBase* sUploadThread = nullptr; +static sp<ThreadBase> sUploadThread = nullptr; static renderthread::EglManager sEglManager; static int sPendingUploads = 0; static nsecs_t sLastUpload = 0; @@ -257,4 +257,15 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou Bitmap::computePalette(bitmap)); } +void HardwareBitmapUploader::terminate() { + std::lock_guard _lock{sLock}; + LOG_ALWAYS_FATAL_IF(sPendingUploads, "terminate called while uploads in progress"); + if (sUploadThread) { + sUploadThread->requestExit(); + sUploadThread->join(); + sUploadThread = nullptr; + } + sEglManager.destroy(); +} + } // namespace android::uirenderer diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h index 6298013bd263..40f2b0c7873c 100644 --- a/libs/hwui/HardwareBitmapUploader.h +++ b/libs/hwui/HardwareBitmapUploader.h @@ -23,6 +23,7 @@ namespace android::uirenderer { class HardwareBitmapUploader { public: static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap); + static void terminate(); }; } // namespace android::uirenderer diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h index 019950fcbc02..b2351fc026de 100644 --- a/libs/hwui/private/hwui/DrawVkInfo.h +++ b/libs/hwui/private/hwui/DrawVkInfo.h @@ -53,16 +53,16 @@ struct DrawVkInfo { VkFormat format; // Input: Color space transfer params - float G; - float A; - float B; - float C; - float D; - float E; - float F; + float g; + float a; + float b; + float c; + float d; + float e; + float f; // Input: Color space transformation from linear RGB to D50-adapted XYZ - float matrix[9]; + float colorSpaceTransform[9]; // Input: current clip rect int clipLeft; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 56eedff4a6e6..8cd97ed20215 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -93,7 +93,9 @@ EglManager::EglManager() , mHasWideColorGamutSupport(false) {} EglManager::~EglManager() { - destroy(); + if (hasEglContext()) { + ALOGW("~EglManager() leaked an EGL context"); + } } void EglManager::initialize() { diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index 524dfb0fe4ef..174a14080eff 100644 --- a/libs/hwui/tests/macrobench/main.cpp +++ b/libs/hwui/tests/macrobench/main.cpp @@ -19,6 +19,8 @@ #include "Properties.h" #include "hwui/Typeface.h" +#include "HardwareBitmapUploader.h" +#include "renderthread/RenderProxy.h" #include <benchmark/benchmark.h> #include <getopt.h> @@ -353,6 +355,9 @@ int main(int argc, char* argv[]) { gBenchmarkReporter->Finalize(); } + renderthread::RenderProxy::trimMemory(100); + HardwareBitmapUploader::terminate(); + LeakChecker::checkForLeaks(); return 0; } diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h index f9de8a5037e5..8cdcc46b97fb 100644 --- a/libs/hwui/thread/ThreadBase.h +++ b/libs/hwui/thread/ThreadBase.h @@ -27,7 +27,7 @@ namespace android::uirenderer { -class ThreadBase : protected Thread { +class ThreadBase : public Thread { PREVENT_COPY_AND_ASSIGN(ThreadBase); public: diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index b9b1cdc6cbc9..b4f19c99c6fe 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -39,7 +39,7 @@ protected: virtual ~WeakLooperCallback() { } public: - WeakLooperCallback(const wp<LooperCallback>& callback) : + explicit WeakLooperCallback(const wp<LooperCallback>& callback) : mCallback(callback) { } diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h index c84de4c1c083..0b7f6e46be65 100644 --- a/libs/protoutil/include/android/util/EncodedBuffer.h +++ b/libs/protoutil/include/android/util/EncodedBuffer.h @@ -38,13 +38,13 @@ class EncodedBuffer { public: EncodedBuffer(); - EncodedBuffer(size_t chunkSize); + explicit EncodedBuffer(size_t chunkSize); ~EncodedBuffer(); class Pointer { public: Pointer(); - Pointer(size_t chunkSize); + explicit Pointer(size_t chunkSize); size_t pos() const; size_t index() const; @@ -161,7 +161,7 @@ public: friend class iterator; class iterator { public: - iterator(const EncodedBuffer& buffer); + explicit iterator(const EncodedBuffer& buffer); /** * Returns the number of bytes written in the buffer diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index bdc84da57799..e795b8119760 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -89,7 +89,6 @@ interface ILocationManager List<String> getAllProviders(); List<String> getProviders(in Criteria criteria, boolean enabledOnly); String getBestProvider(in Criteria criteria, boolean enabledOnly); - boolean providerMeetsCriteria(String provider, in Criteria criteria); ProviderProperties getProviderProperties(String provider); String getNetworkProviderPackage(); void setLocationControllerExtraPackage(String packageName); @@ -98,9 +97,7 @@ interface ILocationManager boolean isLocationControllerExtraPackageEnabled(); boolean isProviderEnabledForUser(String provider, int userId); - boolean setProviderEnabledForUser(String provider, boolean enabled, int userId); boolean isLocationEnabledForUser(int userId); - void setLocationEnabledForUser(boolean enabled, int userId); void addTestProvider(String name, in ProviderProperties properties, String opPackageName); void removeTestProvider(String provider, String opPackageName); void setTestProviderLocation(String provider, in Location loc, String opPackageName); @@ -114,10 +111,6 @@ interface ILocationManager // --- internal --- - // --- deprecated --- - void reportLocation(in Location location, boolean passive); - void reportLocationBatch(in List<Location> locations); - // for reporting callback completion void locationCallbackFinished(ILocationListener listener); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index d96597bb3b69..59c6a0a21cd1 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -43,6 +43,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import com.android.internal.location.ProviderProperties; @@ -1282,11 +1283,13 @@ public class LocationManager { @SystemApi @RequiresPermission(WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) { - try { - mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, + enabled + ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY + : Settings.Secure.LOCATION_MODE_OFF, + userHandle.getIdentifier()); } /** @@ -1372,20 +1375,22 @@ public class LocationManager { * @return true if the value was set, false on database errors * * @throws IllegalArgumentException if provider is null + * @deprecated Do not manipulate providers individually, use + * {@link #setLocationEnabledForUser(boolean, UserHandle)} instead. * @hide */ + @Deprecated @SystemApi @RequiresPermission(WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser( String provider, boolean enabled, UserHandle userHandle) { checkProvider(provider); - try { - return mService.setProviderEnabledForUser( - provider, enabled, userHandle.getIdentifier()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return Settings.Secure.setLocationProviderEnabledForUser( + mContext.getContentResolver(), + provider, + enabled, + userHandle.getIdentifier()); } /** @@ -1578,7 +1583,7 @@ public class LocationManager { */ @Deprecated public void clearTestProviderEnabled(String provider) { - setTestProviderEnabled(provider, true); + setTestProviderEnabled(provider, false); } /** diff --git a/location/lib/Android.bp b/location/lib/Android.bp index b09335c6707f..35f287737d17 100644 --- a/location/lib/Android.bp +++ b/location/lib/Android.bp @@ -18,5 +18,7 @@ java_sdk_library { name: "com.android.location.provider", srcs: ["java/**/*.java"], api_packages: ["com.android.location.provider"], - metalava_enabled: false, + srcs_lib: "framework", + srcs_lib_whitelist_dirs: ["location/java"], + srcs_lib_whitelist_pkgs: ["com.android.internal.location"], } diff --git a/media/java/android/media/Session2Command.java b/media/java/android/media/Session2Command.java index d2a5aae435a1..8b285f212a8d 100644 --- a/media/java/android/media/Session2Command.java +++ b/media/java/android/media/Session2Command.java @@ -36,8 +36,6 @@ import java.util.Objects; * 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. - * </p> - * @hide */ public final class Session2Command implements Parcelable { /** @@ -151,14 +149,17 @@ public final class Session2Command implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { + if (dest == null) { + throw new IllegalArgumentException("parcel shouldn't be null"); + } dest.writeInt(mCommandCode); dest.writeString(mCustomCommand); dest.writeBundle(mExtras); } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (!(obj instanceof Session2Command)) { return false; } @@ -185,7 +186,7 @@ public final class Session2Command implements Parcelable { * @param resultCode result code * @param resultData result data */ - public Result(int resultCode, Bundle resultData) { + public Result(int resultCode, @Nullable Bundle resultData) { mResultCode = resultCode; mResultData = resultData; } @@ -200,6 +201,7 @@ public final class Session2Command implements Parcelable { /** * Returns the result data. */ + @Nullable public Bundle getResultData() { return mResultData; } diff --git a/media/java/android/media/Session2CommandGroup.java b/media/java/android/media/Session2CommandGroup.java index 122dfb156567..4668ec4f5363 100644 --- a/media/java/android/media/Session2CommandGroup.java +++ b/media/java/android/media/Session2CommandGroup.java @@ -130,7 +130,7 @@ public final class Session2CommandGroup implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelableArray((Session2Command[]) mCommands.toArray(), 0); + dest.writeParcelableArray(mCommands.toArray(new Session2Command[0]), 0); } /** diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java index 4634c6963675..e1fff38c6a79 100644 --- a/media/java/android/media/Session2Token.java +++ b/media/java/android/media/Session2Token.java @@ -171,7 +171,7 @@ public final class Session2Token implements Parcelable { dest.writeString(mPackageName); dest.writeString(mServiceName); // TODO: Uncomment below - //dest.writeStrongBinder(mSessionLink.asBinder()); + //dest.writeStrongBinder(mSessionLink.getBinder()); dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString()); } diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/java/android/media/session/ControllerCallbackLink.aidl new file mode 100644 index 000000000000..8ee8c7d00148 --- /dev/null +++ b/media/java/android/media/session/ControllerCallbackLink.aidl @@ -0,0 +1,18 @@ +/* + * 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; + +parcelable ControllerCallbackLink; diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java new file mode 100644 index 000000000000..a143c9b98ff3 --- /dev/null +++ b/media/java/android/media/session/ControllerCallbackLink.java @@ -0,0 +1,272 @@ +/* + * 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.media.MediaMetadata; +import android.media.session.MediaController.PlaybackInfo; +import android.media.session.MediaSession.QueueItem; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +import java.util.List; + +/** + * Handles incoming commands to {@link MediaController.Callback}. + * <p> + * This API is not generally intended for third party application developers. + */ +public final class ControllerCallbackLink implements Parcelable { + final CallbackStub mCallbackStub; + final ISessionControllerCallback mIControllerCallback; + + /** + * Constructor for stub (Callee) + * @hide + */ + @SystemApi + public ControllerCallbackLink(@NonNull CallbackStub callbackStub) { + mCallbackStub = callbackStub; + mIControllerCallback = new CallbackStubProxy(); + } + + /** + * Constructor for interface (Caller) + */ + ControllerCallbackLink(Parcel in) { + mCallbackStub = null; + mIControllerCallback = ISessionControllerCallback.Stub.asInterface(in.readStrongBinder()); + } + + /** + * Notify controller that the connected session is destroyed. + */ + public void notifySessionDestroyed() { + try { + mIControllerCallback.notifySessionDestroyed(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify controller that the connected session sends an event. + * + * @param event the name of the event + * @param extras the extras included with the event + */ + public void notifyEvent(@NonNull String event, @Nullable Bundle extras) { + try { + mIControllerCallback.notifyEvent(event, extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify controller that the current playback state is changed. + * + * @param state the new playback state + */ + public void notifyPlaybackStateChanged(@Nullable PlaybackState state) { + try { + mIControllerCallback.notifyPlaybackStateChanged(state); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify controller that the current metadata is changed. + * + * @param metadata the new metadata + */ + public void notifyMetadataChanged(@Nullable MediaMetadata metadata) { + try { + mIControllerCallback.notifyMetadataChanged(metadata); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify controller that the current queue is changed. + * + * @param queue the new queue + */ + public void notifyQueueChanged(@Nullable List<QueueItem> queue) { + try { + mIControllerCallback.notifyQueueChanged(queue); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify controller that the current queue title is changed. + * + * @param title the new queue title + */ + public void notifyQueueTitleChanged(@Nullable CharSequence title) { + try { + mIControllerCallback.notifyQueueTitleChanged(title); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify controller that the extras are changed. + * + * @param extras the new extras + */ + public void notifyExtrasChanged(@Nullable Bundle extras) { + try { + mIControllerCallback.notifyExtrasChanged(extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify controller that the playback info is changed. + * + * @param info the new playback info + */ + public void notifyVolumeInfoChanged(@NonNull PlaybackInfo info) { + try { + mIControllerCallback.notifyVolumeInfoChanged(info); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** Gets the binder */ + @NonNull + public IBinder getBinder() { + return mIControllerCallback.asBinder(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mIControllerCallback.asBinder()); + } + + public static final Parcelable.Creator<ControllerCallbackLink> CREATOR = + new Parcelable.Creator<ControllerCallbackLink>() { + @Override + public ControllerCallbackLink createFromParcel(Parcel in) { + return new ControllerCallbackLink(in); + } + + @Override + public ControllerCallbackLink[] newArray(int size) { + return new ControllerCallbackLink[size]; + } + }; + + /** + * Class for Stub implementation + * @hide + */ + @SystemApi + public abstract static class CallbackStub { + /** Stub method for ISessionControllerCallback.notifySessionDestroyed */ + public void onSessionDestroyed() { + } + + /** Stub method for ISessionControllerCallback.notifyEvent */ + public void onEvent(@NonNull String event, @Nullable Bundle extras) { + } + + /** Stub method for ISessionControllerCallback.notifyPlaybackStateChanged */ + public void onPlaybackStateChanged(@Nullable PlaybackState state) { + } + + /** Stub method for ISessionControllerCallback.notifyMetadataChanged */ + public void onMetadataChanged(@Nullable MediaMetadata metadata) { + } + + /** Stub method for ISessionControllerCallback.notifyQueueChanged */ + public void onQueueChanged(@Nullable List<QueueItem> queue) { + } + + /** Stub method for ISessionControllerCallback.notifyQueueTitleChanged */ + public void onQueueTitleChanged(@Nullable CharSequence title) { + } + + /** Stub method for ISessionControllerCallback.notifyExtrasChanged */ + public void onExtrasChanged(@Nullable Bundle extras) { + } + + /** Stub method for ISessionControllerCallback.notifyVolumeInfoChanged */ + public void onVolumeInfoChanged(@NonNull PlaybackInfo info) { + } + } + + private class CallbackStubProxy extends ISessionControllerCallback.Stub { + @Override + public void notifyEvent(String event, Bundle extras) { + mCallbackStub.onEvent(event, extras); + } + + @Override + public void notifySessionDestroyed() { + mCallbackStub.onSessionDestroyed(); + } + + @Override + public void notifyPlaybackStateChanged(PlaybackState state) { + mCallbackStub.onPlaybackStateChanged(state); + } + + @Override + public void notifyMetadataChanged(MediaMetadata metadata) { + mCallbackStub.onMetadataChanged(metadata); + } + + @Override + public void notifyQueueChanged(List<QueueItem> queue) { + mCallbackStub.onQueueChanged(queue); + } + + @Override + public void notifyQueueTitleChanged(CharSequence title) { + mCallbackStub.onQueueTitleChanged(title); + } + + @Override + public void notifyExtrasChanged(Bundle extras) { + mCallbackStub.onExtrasChanged(extras); + } + + @Override + public void notifyVolumeInfoChanged(PlaybackInfo info) { + mCallbackStub.onVolumeInfoChanged(info); + } + } +} diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index bfc05fa4e3a4..1524ad9c5de1 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -16,7 +16,6 @@ package android.media.session; import android.app.PendingIntent; -import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; import android.media.MediaMetadata; import android.media.session.ISessionController; @@ -41,7 +40,8 @@ interface ISession { // These commands are for the TransportPerformer void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription); void setPlaybackState(in PlaybackState state); - void setQueue(in ParceledListSlice queue); + // TODO(b/122432476): Replace List with MediaParceledListSlice + void setQueue(in List<MediaSession.QueueItem> queue); void setQueueTitle(CharSequence title); void setExtras(in Bundle extras); void setRatingType(int type); diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 626338d97ccb..9b86bfced340 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -17,7 +17,7 @@ package android.media.session; import android.content.Intent; import android.media.Rating; -import android.media.session.ISessionControllerCallback; +import android.media.session.ControllerCallbackLink; import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -26,46 +26,46 @@ import android.os.ResultReceiver; * @hide */ oneway interface ISessionCallback { - void onCommand(String packageName, int pid, int uid, ISessionControllerCallback caller, + void notifyCommand(String packageName, int pid, int uid, in ControllerCallbackLink caller, String command, in Bundle args, in ResultReceiver cb); - void onMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent, + void notifyMediaButton(String packageName, int pid, int uid, in Intent mediaButtonIntent, int sequenceNumber, in ResultReceiver cb); - void onMediaButtonFromController(String packageName, int pid, int uid, - ISessionControllerCallback caller, in Intent mediaButtonIntent); + void notifyMediaButtonFromController(String packageName, int pid, int uid, + in ControllerCallbackLink caller, in Intent mediaButtonIntent); // These callbacks are for the TransportPerformer - void onPrepare(String packageName, int pid, int uid, ISessionControllerCallback caller); - void onPrepareFromMediaId(String packageName, int pid, int uid, - ISessionControllerCallback caller, String mediaId, in Bundle extras); - void onPrepareFromSearch(String packageName, int pid, int uid, - ISessionControllerCallback caller, String query, in Bundle extras); - void onPrepareFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller, + void notifyPrepare(String packageName, int pid, int uid, in ControllerCallbackLink caller); + void notifyPrepareFromMediaId(String packageName, int pid, int uid, + in ControllerCallbackLink caller, String mediaId, in Bundle extras); + void notifyPrepareFromSearch(String packageName, int pid, int uid, + in ControllerCallbackLink caller, String query, in Bundle extras); + void notifyPrepareFromUri(String packageName, int pid, int uid, + in ControllerCallbackLink caller, in Uri uri, in Bundle extras); + void notifyPlay(String packageName, int pid, int uid, in ControllerCallbackLink caller); + void notifyPlayFromMediaId(String packageName, int pid, int uid, + in ControllerCallbackLink caller, String mediaId, in Bundle extras); + void notifyPlayFromSearch(String packageName, int pid, int uid, + in ControllerCallbackLink caller, String query, in Bundle extras); + void notifyPlayFromUri(String packageName, int pid, int uid, in ControllerCallbackLink caller, in Uri uri, in Bundle extras); - void onPlay(String packageName, int pid, int uid, ISessionControllerCallback caller); - void onPlayFromMediaId(String packageName, int pid, int uid, ISessionControllerCallback caller, - String mediaId, in Bundle extras); - void onPlayFromSearch(String packageName, int pid, int uid, ISessionControllerCallback caller, - String query, in Bundle extras); - void onPlayFromUri(String packageName, int pid, int uid, ISessionControllerCallback caller, - in Uri uri, in Bundle extras); - void onSkipToTrack(String packageName, int pid, int uid, ISessionControllerCallback caller, + void notifySkipToTrack(String packageName, int pid, int uid, in ControllerCallbackLink caller, long id); - void onPause(String packageName, int pid, int uid, ISessionControllerCallback caller); - void onStop(String packageName, int pid, int uid, ISessionControllerCallback caller); - void onNext(String packageName, int pid, int uid, ISessionControllerCallback caller); - void onPrevious(String packageName, int pid, int uid, ISessionControllerCallback caller); - void onFastForward(String packageName, int pid, int uid, ISessionControllerCallback caller); - void onRewind(String packageName, int pid, int uid, ISessionControllerCallback caller); - void onSeekTo(String packageName, int pid, int uid, ISessionControllerCallback caller, + void notifyPause(String packageName, int pid, int uid, in ControllerCallbackLink caller); + void notifyStop(String packageName, int pid, int uid, in ControllerCallbackLink caller); + void notifyNext(String packageName, int pid, int uid, in ControllerCallbackLink caller); + void notifyPrevious(String packageName, int pid, int uid, in ControllerCallbackLink caller); + void notifyFastForward(String packageName, int pid, int uid, in ControllerCallbackLink caller); + void notifyRewind(String packageName, int pid, int uid, in ControllerCallbackLink caller); + void notifySeekTo(String packageName, int pid, int uid, in ControllerCallbackLink caller, long pos); - void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller, + void notifyRate(String packageName, int pid, int uid, in ControllerCallbackLink caller, in Rating rating); - void onCustomAction(String packageName, int pid, int uid, ISessionControllerCallback caller, + void notifyCustomAction(String packageName, int pid, int uid, in ControllerCallbackLink caller, String action, in Bundle args); // These callbacks are for volume handling - void onAdjustVolume(String packageName, int pid, int uid, ISessionControllerCallback caller, + void notifyAdjustVolume(String packageName, int pid, int uid, in ControllerCallbackLink caller, int direction); - void onSetVolumeTo(String packageName, int pid, int uid, - ISessionControllerCallback caller, int value); + void notifySetVolumeTo(String packageName, int pid, int uid, + in ControllerCallbackLink caller, int value); } diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index a843881e95ae..2ba09fd92af5 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -17,10 +17,9 @@ package android.media.session; import android.app.PendingIntent; import android.content.Intent; -import android.content.pm.ParceledListSlice; import android.media.MediaMetadata; import android.media.Rating; -import android.media.session.ISessionControllerCallback; +import android.media.session.ControllerCallbackLink; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; @@ -36,12 +35,12 @@ import java.util.List; * @hide */ interface ISessionController { - void sendCommand(String packageName, ISessionControllerCallback caller, + void sendCommand(String packageName, in ControllerCallbackLink caller, String command, in Bundle args, in ResultReceiver cb); - boolean sendMediaButton(String packageName, ISessionControllerCallback caller, + boolean sendMediaButton(String packageName, in ControllerCallbackLink caller, boolean asSystemService, in KeyEvent mediaButton); - void registerCallbackListener(String packageName, ISessionControllerCallback cb); - void unregisterCallbackListener(ISessionControllerCallback cb); + void registerCallbackListener(String packageName, in ControllerCallbackLink cb); + void unregisterCallbackListener(in ControllerCallbackLink cb); boolean isTransportControlEnabled(); String getPackageName(); String getTag(); @@ -49,40 +48,40 @@ interface ISessionController { long getFlags(); MediaController.PlaybackInfo getVolumeAttributes(); void adjustVolume(String packageName, String opPackageName, - in ISessionControllerCallback caller, boolean asSystemService, int direction, + in ControllerCallbackLink caller, boolean asSystemService, int direction, int flags); - void setVolumeTo(String packageName, String opPackageName, in ISessionControllerCallback caller, + void setVolumeTo(String packageName, String opPackageName, in ControllerCallbackLink caller, int value, int flags); // These commands are for the TransportControls - void prepare(String packageName, ISessionControllerCallback caller); - void prepareFromMediaId(String packageName, ISessionControllerCallback caller, + void prepare(String packageName, in ControllerCallbackLink caller); + void prepareFromMediaId(String packageName, in ControllerCallbackLink caller, String mediaId, in Bundle extras); - void prepareFromSearch(String packageName, ISessionControllerCallback caller, + void prepareFromSearch(String packageName, in ControllerCallbackLink caller, String string, in Bundle extras); - void prepareFromUri(String packageName, ISessionControllerCallback caller, + void prepareFromUri(String packageName, in ControllerCallbackLink caller, in Uri uri, in Bundle extras); - void play(String packageName, ISessionControllerCallback caller); - void playFromMediaId(String packageName, ISessionControllerCallback caller, + void play(String packageName, in ControllerCallbackLink caller); + void playFromMediaId(String packageName, in ControllerCallbackLink caller, String mediaId, in Bundle extras); - void playFromSearch(String packageName, ISessionControllerCallback caller, + void playFromSearch(String packageName, in ControllerCallbackLink caller, String string, in Bundle extras); - void playFromUri(String packageName, ISessionControllerCallback caller, + void playFromUri(String packageName, in ControllerCallbackLink caller, in Uri uri, in Bundle extras); - void skipToQueueItem(String packageName, ISessionControllerCallback caller, long id); - void pause(String packageName, ISessionControllerCallback caller); - void stop(String packageName, ISessionControllerCallback caller); - void next(String packageName, ISessionControllerCallback caller); - void previous(String packageName, ISessionControllerCallback caller); - void fastForward(String packageName, ISessionControllerCallback caller); - void rewind(String packageName, ISessionControllerCallback caller); - void seekTo(String packageName, ISessionControllerCallback caller, long pos); - void rate(String packageName, ISessionControllerCallback caller, in Rating rating); - void sendCustomAction(String packageName, ISessionControllerCallback caller, + void skipToQueueItem(String packageName, in ControllerCallbackLink caller, long id); + void pause(String packageName, in ControllerCallbackLink caller); + void stop(String packageName, in ControllerCallbackLink caller); + void next(String packageName, in ControllerCallbackLink caller); + void previous(String packageName, in ControllerCallbackLink caller); + void fastForward(String packageName, in ControllerCallbackLink caller); + void rewind(String packageName, in ControllerCallbackLink caller); + void seekTo(String packageName, in ControllerCallbackLink caller, long pos); + void rate(String packageName, in ControllerCallbackLink caller, in Rating rating); + void sendCustomAction(String packageName, in ControllerCallbackLink caller, String action, in Bundle args); MediaMetadata getMetadata(); PlaybackState getPlaybackState(); - ParceledListSlice getQueue(); + List<MediaSession.QueueItem> getQueue(); CharSequence getQueueTitle(); Bundle getExtras(); int getRatingType(); diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl index fac8897d1b89..5c02e7c2f6e3 100644 --- a/media/java/android/media/session/ISessionControllerCallback.aidl +++ b/media/java/android/media/session/ISessionControllerCallback.aidl @@ -15,25 +15,24 @@ package android.media.session; -import android.content.pm.ParceledListSlice; import android.media.MediaMetadata; -import android.media.session.PlaybackState; import android.media.session.MediaController; import android.media.session.MediaSession; +import android.media.session.PlaybackState; import android.os.Bundle; /** * @hide */ oneway interface ISessionControllerCallback { - void onEvent(String event, in Bundle extras); - void onSessionDestroyed(); + void notifyEvent(String event, in Bundle extras); + void notifySessionDestroyed(); // These callbacks are for the TransportController - void onPlaybackStateChanged(in PlaybackState state); - void onMetadataChanged(in MediaMetadata metadata); - void onQueueChanged(in ParceledListSlice queue); - void onQueueTitleChanged(CharSequence title); - void onExtrasChanged(in Bundle extras); - void onVolumeInfoChanged(in MediaController.PlaybackInfo info); + void notifyPlaybackStateChanged(in PlaybackState state); + void notifyMetadataChanged(in MediaMetadata metadata); + void notifyQueueChanged(in List<MediaSession.QueueItem> queue); + void notifyQueueTitleChanged(CharSequence title); + void notifyExtrasChanged(in Bundle extras); + void notifyVolumeInfoChanged(in MediaController.PlaybackInfo info); } diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 51148e2f1af4..7ac3ef25d691 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -23,7 +23,7 @@ import android.media.session.ICallback; import android.media.session.IOnMediaKeyListener; import android.media.session.IOnVolumeKeyLongPressListener; import android.media.session.ISession; -import android.media.session.ISessionCallback; +import android.media.session.SessionCallbackLink; import android.os.Bundle; import android.view.KeyEvent; @@ -32,9 +32,10 @@ import android.view.KeyEvent; * @hide */ interface ISessionManager { - ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId); + ISession createSession(String packageName, in SessionCallbackLink cb, String tag, int userId); void notifySession2Created(in Session2Token sessionToken); List<IBinder> getSessions(in ComponentName compName, int userId); + List<Session2Token> getSession2Tokens(int userId); void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent, boolean needWakeLock); void dispatchVolumeKeyEvent(String packageName, String opPackageName, boolean asSystemService, diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index ef2df15da8f4..a1b8170cf4c3 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -21,12 +21,12 @@ import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.app.PendingIntent; import android.content.Context; -import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; +import android.media.session.MediaSession.QueueItem; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -72,7 +72,8 @@ public final class MediaController { private final MediaSession.Token mToken; private final Context mContext; - private final CallbackStub mCbStub = new CallbackStub(this); + private final ControllerCallbackLink mCbStub = + new ControllerCallbackLink(new CallbackStub(this)); private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>(); private final Object mLock = new Object(); @@ -251,10 +252,7 @@ public final class MediaController { */ public @Nullable List<MediaSession.QueueItem> getQueue() { try { - ParceledListSlice queue = mSessionBinder.getQueue(); - if (queue != null) { - return queue.getList(); - } + return mSessionBinder.getQueue(); } catch (RemoteException e) { Log.wtf(TAG, "Error calling getQueue.", e); } @@ -1113,10 +1111,10 @@ public final class MediaController { }; } - private final static class CallbackStub extends ISessionControllerCallback.Stub { + private static final class CallbackStub extends ControllerCallbackLink.CallbackStub { private final WeakReference<MediaController> mController; - public CallbackStub(MediaController controller) { + CallbackStub(MediaController controller) { mController = new WeakReference<MediaController>(controller); } @@ -1153,9 +1151,7 @@ public final class MediaController { } @Override - public void onQueueChanged(ParceledListSlice parceledQueue) { - List<MediaSession.QueueItem> queue = parceledQueue == null ? null : parceledQueue - .getList(); + public void onQueueChanged(List<QueueItem> queue) { MediaController controller = mController.get(); if (controller != null) { controller.postMessage(MSG_UPDATE_QUEUE, queue, null); @@ -1185,7 +1181,6 @@ public final class MediaController { controller.postMessage(MSG_UPDATE_VOLUME, info, null); } } - } private final static class MessageHandler extends Handler { diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index df8cc35945d3..e07cf15d11c9 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -24,7 +24,6 @@ import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; import android.media.MediaDescription; import android.media.MediaMetadata; @@ -130,7 +129,7 @@ public final class MediaSession { private final MediaSession.Token mSessionToken; private final MediaController mController; private final ISession mBinder; - private final CallbackStub mCbStub; + private final SessionCallbackLink mCbStub; // Do not change the name of mCallback. Support lib accesses this by using reflection. @UnsupportedAppUsage @@ -173,7 +172,7 @@ public final class MediaSession { } mMaxBitmapSize = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize); - mCbStub = new CallbackStub(this); + mCbStub = new SessionCallbackLink(new CallbackStub(this)); MediaSessionManager manager = (MediaSessionManager) context .getSystemService(Context.MEDIA_SESSION_SERVICE); try { @@ -467,7 +466,7 @@ public final class MediaSession { */ public void setQueue(@Nullable List<QueueItem> queue) { try { - mBinder.setQueue(queue == null ? null : new ParceledListSlice<QueueItem>(queue)); + mBinder.setQueue(queue); } catch (RemoteException e) { Log.wtf("Dead object in setQueue.", e); } @@ -1063,7 +1062,7 @@ public final class MediaSession { /** * @hide */ - public static class CallbackStub extends ISessionCallback.Stub { + public static final class CallbackStub extends SessionCallbackLink.CallbackStub { private WeakReference<MediaSession> mMediaSession; public CallbackStub(MediaSession session) { @@ -1071,14 +1070,14 @@ public final class MediaSession { } private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { return new RemoteUserInfo(packageName, pid, uid, - caller != null ? caller.asBinder() : null); + caller != null ? caller.getBinder() : null); } @Override public void onCommand(String packageName, int pid, int uid, - ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) { + ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller), @@ -1104,7 +1103,7 @@ public final class MediaSession { @Override public void onMediaButtonFromController(String packageName, int pid, int uid, - ISessionControllerCallback caller, Intent mediaButtonIntent) { + ControllerCallbackLink caller, Intent mediaButtonIntent) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller), @@ -1114,7 +1113,7 @@ public final class MediaSession { @Override public void onPrepare(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller)); @@ -1123,7 +1122,7 @@ public final class MediaSession { @Override public void onPrepareFromMediaId(String packageName, int pid, int uid, - ISessionControllerCallback caller, String mediaId, + ControllerCallbackLink caller, String mediaId, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { @@ -1134,7 +1133,7 @@ public final class MediaSession { @Override public void onPrepareFromSearch(String packageName, int pid, int uid, - ISessionControllerCallback caller, String query, + ControllerCallbackLink caller, String query, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { @@ -1145,7 +1144,7 @@ public final class MediaSession { @Override public void onPrepareFromUri(String packageName, int pid, int uid, - ISessionControllerCallback caller, Uri uri, Bundle extras) { + ControllerCallbackLink caller, Uri uri, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller), @@ -1155,7 +1154,7 @@ public final class MediaSession { @Override public void onPlay(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller)); @@ -1164,7 +1163,7 @@ public final class MediaSession { @Override public void onPlayFromMediaId(String packageName, int pid, int uid, - ISessionControllerCallback caller, String mediaId, + ControllerCallbackLink caller, String mediaId, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { @@ -1175,7 +1174,7 @@ public final class MediaSession { @Override public void onPlayFromSearch(String packageName, int pid, int uid, - ISessionControllerCallback caller, String query, + ControllerCallbackLink caller, String query, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { @@ -1186,7 +1185,7 @@ public final class MediaSession { @Override public void onPlayFromUri(String packageName, int pid, int uid, - ISessionControllerCallback caller, Uri uri, Bundle extras) { + ControllerCallbackLink caller, Uri uri, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller), @@ -1196,7 +1195,7 @@ public final class MediaSession { @Override public void onSkipToTrack(String packageName, int pid, int uid, - ISessionControllerCallback caller, long id) { + ControllerCallbackLink caller, long id) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id); @@ -1205,7 +1204,7 @@ public final class MediaSession { @Override public void onPause(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller)); @@ -1214,7 +1213,7 @@ public final class MediaSession { @Override public void onStop(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller)); @@ -1223,7 +1222,7 @@ public final class MediaSession { @Override public void onNext(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller)); @@ -1232,7 +1231,7 @@ public final class MediaSession { @Override public void onPrevious(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller)); @@ -1241,7 +1240,7 @@ public final class MediaSession { @Override public void onFastForward(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller)); @@ -1250,7 +1249,7 @@ public final class MediaSession { @Override public void onRewind(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller)); @@ -1259,7 +1258,7 @@ public final class MediaSession { @Override public void onSeekTo(String packageName, int pid, int uid, - ISessionControllerCallback caller, long pos) { + ControllerCallbackLink caller, long pos) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos); @@ -1267,7 +1266,7 @@ public final class MediaSession { } @Override - public void onRate(String packageName, int pid, int uid, ISessionControllerCallback caller, + public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller, Rating rating) { MediaSession session = mMediaSession.get(); if (session != null) { @@ -1277,7 +1276,7 @@ public final class MediaSession { @Override public void onCustomAction(String packageName, int pid, int uid, - ISessionControllerCallback caller, String action, Bundle args) { + ControllerCallbackLink caller, String action, Bundle args) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller), @@ -1287,7 +1286,7 @@ public final class MediaSession { @Override public void onAdjustVolume(String packageName, int pid, int uid, - ISessionControllerCallback caller, int direction) { + ControllerCallbackLink caller, int direction) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller), @@ -1297,7 +1296,7 @@ public final class MediaSession { @Override public void onSetVolumeTo(String packageName, int pid, int uid, - ISessionControllerCallback caller, int value) { + ControllerCallbackLink caller, int value) { MediaSession session = mMediaSession.get(); if (session != null) { session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller), diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index ef5cf008de88..56ea484968cb 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -98,7 +98,7 @@ public final class MediaSessionManager { * @return The binder object from the system * @hide */ - public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub, + public @NonNull ISession createSession(@NonNull SessionCallbackLink cbStub, @NonNull String tag, int userId) throws RemoteException { return mService.createSession(mContext.getPackageName(), cbStub, tag, userId); } @@ -179,6 +179,44 @@ public final class MediaSessionManager { } /** + * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the + * current user. + * <p> + * Although this API can be used without any restriction, each session owners can accept or + * reject your uses of {@link MediaSession2}. + * + * @return A list of {@link Session2Token}. + * @hide + */ + // TODO: unhide + @NonNull + public List<Session2Token> getSession2Tokens() { + return getSession2Tokens(UserHandle.myUserId()); + } + + /** + * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the + * 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. + * + * @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 { + return mService.getSession2Tokens(userId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get session tokens", e); + } + return new ArrayList<>(); + } + + /** * Add a listener to be notified when the list of active sessions * changes.This requires the * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/java/android/media/session/SessionCallbackLink.aidl new file mode 100644 index 000000000000..c489e5bee6e2 --- /dev/null +++ b/media/java/android/media/session/SessionCallbackLink.aidl @@ -0,0 +1,18 @@ +/* + * 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; + +parcelable SessionCallbackLink; diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java new file mode 100644 index 000000000000..7547bffa3b18 --- /dev/null +++ b/media/java/android/media/session/SessionCallbackLink.java @@ -0,0 +1,776 @@ +/* + * 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.content.Intent; +import android.media.Rating; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ResultReceiver; + +/** + * Handles incoming commands to {@link MediaSession.Callback}. + * <p> + * This API is not generally intended for third party application developers. + */ +public final class SessionCallbackLink implements Parcelable { + final CallbackStub mCallbackStub; + final ISessionCallback mISessionCallback; + + /** + * Constructor for stub (Callee) + */ + SessionCallbackLink(@NonNull CallbackStub callbackStub) { + mCallbackStub = callbackStub; + mISessionCallback = new CallbackStubProxy(); + } + + /** + * Constructor for interface (Caller) + */ + SessionCallbackLink(Parcel in) { + mCallbackStub = null; + mISessionCallback = ISessionCallback.Stub.asInterface(in.readStrongBinder()); + } + + /** + * Notify session that a controller sends a command. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param command the name of the command + * @param args the arguments included with the command + * @param cb the result receiver for getting the result of the command + */ + public void notifyCommand(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String command, + @Nullable Bundle args, @Nullable ResultReceiver cb) { + try { + mISessionCallback.notifyCommand(packageName, pid, uid, caller, command, args, cb); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that the android system sends a media button event. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param mediaButtonIntent the media button intent + * @param sequenceNumber the sequence number of this call + * @param cb the result receiver for getting the result of the command + */ + public void notifyMediaButton(@NonNull String packageName, int pid, int uid, + @NonNull Intent mediaButtonIntent, int sequenceNumber, + @Nullable ResultReceiver cb) { + try { + mISessionCallback.notifyMediaButton(packageName, pid, uid, mediaButtonIntent, + sequenceNumber, cb); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller sends a media button event. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param mediaButtonIntent the media button intent + */ + public void notifyMediaButtonFromController(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) { + try { + mISessionCallback.notifyMediaButtonFromController(packageName, pid, uid, caller, + mediaButtonIntent); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests preparing media. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + */ + public void notifyPrepare(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + try { + mISessionCallback.notifyPrepare(packageName, pid, uid, caller); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests preparing media from given media ID. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param mediaId the ID of the media + * @param extras the extras included with this request. + */ + public void notifyPrepareFromMediaId(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String mediaId, + @Nullable Bundle extras) { + try { + mISessionCallback.notifyPrepareFromMediaId(packageName, pid, uid, caller, mediaId, + extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests preparing media from given search query. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param query the search query + * @param extras the extras included with this request. + */ + public void notifyPrepareFromSearch(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String query, + @Nullable Bundle extras) { + try { + mISessionCallback.notifyPrepareFromSearch(packageName, pid, uid, caller, query, extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests preparing media from given uri. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param uri the uri of the media + * @param extras the extras included with this request. + */ + public void notifyPrepareFromUri(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) { + try { + mISessionCallback.notifyPrepareFromUri(packageName, pid, uid, caller, uri, extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests playing media. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + */ + public void notifyPlay(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + try { + mISessionCallback.notifyPlay(packageName, pid, uid, caller); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests playing media from given media ID. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param mediaId the ID of the media + * @param extras the extras included with this request. + */ + public void notifyPlayFromMediaId(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String mediaId, + @Nullable Bundle extras) { + try { + mISessionCallback.notifyPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests playing media from given search query. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param query the search query + * @param extras the extras included with this request. + */ + public void notifyPlayFromSearch(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String query, + @Nullable Bundle extras) { + try { + mISessionCallback.notifyPlayFromSearch(packageName, pid, uid, caller, query, extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests playing media from given uri. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param uri the uri of the media + * @param extras the extras included with this request. + */ + public void notifyPlayFromUri(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) { + try { + mISessionCallback.notifyPlayFromUri(packageName, pid, uid, caller, uri, extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests skipping to the queue item with given ID. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param id the queue id of the item + */ + public void notifySkipToTrack(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, long id) { + try { + mISessionCallback.notifySkipToTrack(packageName, pid, uid, caller, id); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests pausing media. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + */ + public void notifyPause(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + try { + mISessionCallback.notifyPause(packageName, pid, uid, caller); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests stopping media. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + */ + public void notifyStop(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + try { + mISessionCallback.notifyStop(packageName, pid, uid, caller); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests skipping to the next queue item. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + */ + public void notifyNext(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + try { + mISessionCallback.notifyNext(packageName, pid, uid, caller); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests skipping to the previous queue item. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + */ + public void notifyPrevious(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + try { + mISessionCallback.notifyPrevious(packageName, pid, uid, caller); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests fast-forwarding. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + */ + public void notifyFastForward(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + try { + mISessionCallback.notifyFastForward(packageName, pid, uid, caller); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests rewinding. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + */ + public void notifyRewind(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + try { + mISessionCallback.notifyRewind(packageName, pid, uid, caller); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests seeking to the specific position. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param pos the position to move to, in milliseconds + */ + public void notifySeekTo(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, long pos) { + try { + mISessionCallback.notifySeekTo(packageName, pid, uid, caller, pos); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests rating of the current media. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param rating the rating of the current media + */ + public void notifyRate(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull Rating rating) { + try { + mISessionCallback.notifyRate(packageName, pid, uid, caller, rating); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller sends a custom action. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param action the name of the action + * @param args the arguments included with this action + */ + public void notifyCustomAction(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String action, @Nullable Bundle args) { + try { + mISessionCallback.notifyCustomAction(packageName, pid, uid, caller, action, args); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests adjusting volume. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param direction the direction of the volume change. + */ + public void notifyAdjustVolume(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, int direction) { + try { + mISessionCallback.notifyAdjustVolume(packageName, pid, uid, caller, direction); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Notify session that a controller requests setting volume. + * + * @param packageName the package name of the controller + * @param pid the pid of the controller + * @param uid the uid of the controller + * @param caller the {@link ControllerCallbackLink} of the controller + * @param value the volume value to set + */ + public void notifySetVolumeTo(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, int value) { + try { + mISessionCallback.notifySetVolumeTo(packageName, pid, uid, caller, value); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** Gets the binder */ + @NonNull + public IBinder getBinder() { + return mISessionCallback.asBinder(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mISessionCallback.asBinder()); + } + + public static final Parcelable.Creator<SessionCallbackLink> CREATOR = + new Parcelable.Creator<SessionCallbackLink>() { + @Override + public SessionCallbackLink createFromParcel(Parcel in) { + return new SessionCallbackLink(in); + } + + @Override + public SessionCallbackLink[] newArray(int size) { + return new SessionCallbackLink[size]; + } + }; + + /** + * Class for Stub implementation + */ + abstract static class CallbackStub { + /** Stub method for ISessionCallback.notifyCommand */ + public void onCommand(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String command, + @Nullable Bundle args, @Nullable ResultReceiver cb) { + } + + /** Stub method for ISessionCallback.notifyMediaButton */ + public void onMediaButton(@NonNull String packageName, int pid, int uid, + @NonNull Intent mediaButtonIntent, int sequenceNumber, + @Nullable ResultReceiver cb) { + } + + /** Stub method for ISessionCallback.notifyMediaButtonFromController */ + public void onMediaButtonFromController(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull Intent mediaButtonIntent) { + } + + /** Stub method for ISessionCallback.notifyPrepare */ + public void onPrepare(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + } + + /** Stub method for ISessionCallback.notifyPrepareFromMediaId */ + public void onPrepareFromMediaId(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String mediaId, + @Nullable Bundle extras) { + } + + /** Stub method for ISessionCallback.notifyPrepareFromSearch */ + public void onPrepareFromSearch(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, String query, @Nullable Bundle extras) { + } + + /** Stub method for ISessionCallback.notifyPrepareFromUri */ + public void onPrepareFromUri(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) { + } + + /** Stub method for ISessionCallback.notifyPlay */ + public void onPlay(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + } + + /** Stub method for ISessionCallback.notifyPlayFromMediaId */ + public void onPlayFromMediaId(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String mediaId, + @Nullable Bundle extras) { + } + + /** Stub method for ISessionCallback.notifyPlayFromSearch */ + public void onPlayFromSearch(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, String query, @Nullable Bundle extras) { + } + + /** Stub method for ISessionCallback.notifyPlayFromUri */ + public void onPlayFromUri(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull Uri uri, @Nullable Bundle extras) { + } + + /** Stub method for ISessionCallback.notifySkipToTrack */ + public void onSkipToTrack(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, long id) { + } + + /** Stub method for ISessionCallback.notifyPause */ + public void onPause(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + } + + /** Stub method for ISessionCallback.notifyStop */ + public void onStop(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + } + + /** Stub method for ISessionCallback.notifyNext */ + public void onNext(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + } + + /** Stub method for ISessionCallback.notifyPrevious */ + public void onPrevious(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + } + + /** Stub method for ISessionCallback.notifyFastForward */ + public void onFastForward(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + } + + /** Stub method for ISessionCallback.notifyRewind */ + public void onRewind(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller) { + } + + /** Stub method for ISessionCallback.notifySeekTo */ + public void onSeekTo(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, long pos) { + } + + /** Stub method for ISessionCallback.notifyRate */ + public void onRate(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull Rating rating) { + } + + /** Stub method for ISessionCallback.notifyCustomAction */ + public void onCustomAction(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, @NonNull String action, + @Nullable Bundle args) { + } + + /** Stub method for ISessionCallback.notifyAdjustVolume */ + public void onAdjustVolume(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, int direction) { + } + + /** Stub method for ISessionCallback.notifySetVolumeTo */ + public void onSetVolumeTo(@NonNull String packageName, int pid, int uid, + @NonNull ControllerCallbackLink caller, int value) { + } + } + + private class CallbackStubProxy extends ISessionCallback.Stub { + @Override + public void notifyCommand(String packageName, int pid, int uid, + ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) { + mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb); + } + + @Override + public void notifyMediaButton(String packageName, int pid, int uid, + Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) { + mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent, sequenceNumber, + cb); + } + + @Override + public void notifyMediaButtonFromController(String packageName, int pid, int uid, + ControllerCallbackLink caller, Intent mediaButtonIntent) { + mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller, + mediaButtonIntent); + } + + @Override + public void notifyPrepare(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + mCallbackStub.onPrepare(packageName, pid, uid, caller); + } + + @Override + public void notifyPrepareFromMediaId(String packageName, int pid, int uid, + ControllerCallbackLink caller, String mediaId, Bundle extras) { + mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras); + } + + @Override + public void notifyPrepareFromSearch(String packageName, int pid, int uid, + ControllerCallbackLink caller, String query, Bundle extras) { + mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras); + } + + @Override + public void notifyPrepareFromUri(String packageName, int pid, int uid, + ControllerCallbackLink caller, Uri uri, Bundle extras) { + mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras); + } + + @Override + public void notifyPlay(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + mCallbackStub.onPlay(packageName, pid, uid, caller); + } + + @Override + public void notifyPlayFromMediaId(String packageName, int pid, int uid, + ControllerCallbackLink caller, String mediaId, Bundle extras) { + mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras); + } + + @Override + public void notifyPlayFromSearch(String packageName, int pid, int uid, + ControllerCallbackLink caller, String query, Bundle extras) { + mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras); + } + + @Override + public void notifyPlayFromUri(String packageName, int pid, int uid, + ControllerCallbackLink caller, Uri uri, Bundle extras) { + mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras); + } + + @Override + public void notifySkipToTrack(String packageName, int pid, int uid, + ControllerCallbackLink caller, long id) { + mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id); + } + + @Override + public void notifyPause(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + mCallbackStub.onPause(packageName, pid, uid, caller); + } + + @Override + public void notifyStop(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + mCallbackStub.onStop(packageName, pid, uid, caller); + } + + @Override + public void notifyNext(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + mCallbackStub.onNext(packageName, pid, uid, caller); + } + + @Override + public void notifyPrevious(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + mCallbackStub.onPrevious(packageName, pid, uid, caller); + } + + @Override + public void notifyFastForward(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + mCallbackStub.onFastForward(packageName, pid, uid, caller); + } + + @Override + public void notifyRewind(String packageName, int pid, int uid, + ControllerCallbackLink caller) { + mCallbackStub.onRewind(packageName, pid, uid, caller); + } + + @Override + public void notifySeekTo(String packageName, int pid, int uid, + ControllerCallbackLink caller, long pos) { + mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos); + } + + @Override + public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller, + Rating rating) { + mCallbackStub.onRate(packageName, pid, uid, caller, rating); + } + + @Override + public void notifyCustomAction(String packageName, int pid, int uid, + ControllerCallbackLink caller, String action, Bundle args) { + mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args); + } + + @Override + public void notifyAdjustVolume(String packageName, int pid, int uid, + ControllerCallbackLink caller, int direction) { + mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction); + } + + @Override + public void notifySetVolumeTo(String packageName, int pid, int uid, + ControllerCallbackLink caller, int value) { + mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value); + } + } +} diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp index 8c43683c2eec..44f8725ec6f5 100644 --- a/media/lib/signer/Android.bp +++ b/media/lib/signer/Android.bp @@ -18,5 +18,7 @@ java_sdk_library { name: "com.android.mediadrm.signer", srcs: ["java/**/*.java"], api_packages: ["com.android.mediadrm.signer"], - metalava_enabled: false, + srcs_lib: "framework", + srcs_lib_whitelist_dirs: ["media/java"], + srcs_lib_whitelist_pkgs: ["android.media"], } diff --git a/native/android/Android.bp b/native/android/Android.bp index 5cfb09b9a68c..fdcfc446a448 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -83,6 +83,10 @@ cc_library_shared { include_dirs: ["bionic/libc/dns/include"], version_script: "libandroid.map.txt", + stubs: { + symbol_file: "libandroid.map.txt", + versions: ["29"], + }, } // Network library. diff --git a/native/webview/plat_support/Android.bp b/native/webview/plat_support/Android.bp index 09362566d915..88decc86c387 100644 --- a/native/webview/plat_support/Android.bp +++ b/native/webview/plat_support/Android.bp @@ -24,7 +24,6 @@ cc_library_shared { srcs: [ "draw_functor.cpp", "draw_gl_functor.cpp", - "draw_vk_functor.cpp", "functor_utils.cpp", "jni_entry_point.cpp", "graphics_utils.cpp", diff --git a/native/webview/plat_support/draw_vk.h b/native/webview/plat_support/draw_vk.h deleted file mode 100644 index 6b7d8d0b9118..000000000000 --- a/native/webview/plat_support/draw_vk.h +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2018 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// -//****************************************************************************** -// This is a copy of the coresponding android_webview/public/browser header. -// Any changes to the interface should be made there. -// -// The purpose of having the copy is twofold: -// - it removes the need to have Chromium sources present in the tree in order -// to build the plat_support library, -// - it captures API that the corresponding Android release supports. -//****************************************************************************** - -#ifndef ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_ -#define ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_ - -#include <vulkan/vulkan.h> - -#ifdef __cplusplus -extern "C" { -#endif - -static const int kAwDrawVKInfoVersion = 1; - -// Holds the information required to trigger initialization of the Vulkan -// functor. -struct InitParams { - // All params are input - VkInstance instance; - VkPhysicalDevice physical_device; - VkDevice device; - VkQueue queue; - uint32_t graphics_queue_index; - uint32_t instance_version; - const char* const* enabled_extension_names; - // Only one of device_features and device_features_2 should be non-null. - // If both are null then no features are enabled. - VkPhysicalDeviceFeatures* device_features; - VkPhysicalDeviceFeatures2* device_features_2; -}; - -// Holds the information required to trigger an Vulkan composite operation. -struct CompositeParams { - // Input: current width/height of destination surface. - int width; - int height; - - // Input: is the render target a FBO - bool is_layer; - - // Input: current transform matrix - float transform[16]; - - // Input WebView should do its main compositing draws into this. It cannot do - // anything that would require stopping the render pass. - VkCommandBuffer secondary_command_buffer; - - // Input: The main color attachment index where secondary_command_buffer will - // eventually be submitted. - uint32_t color_attachment_index; - - // Input: A render pass which will be compatible to the one which the - // secondary_command_buffer will be submitted into. - VkRenderPass compatible_render_pass; - - // Input: Format of the destination surface. - VkFormat format; - - // Input: Color space transfer params - float G; - float A; - float B; - float C; - float D; - float E; - float F; - - // Input: Color space transformation from linear RGB to D50-adapted XYZ - float matrix[9]; - - // Input: current clip rect - int clip_left; - int clip_top; - int clip_right; - int clip_bottom; -}; - -// Holds the information for the post-submission callback of main composite -// draw. -struct PostCompositeParams { - // Input: Fence for the composite command buffer to signal it has finished its - // work on the GPU. - int fd; -}; - -// Holds the information required to trigger an Vulkan operation. -struct AwDrawVKInfo { - int version; // The AwDrawVKInfo this struct was built with. - - // Input: tells the draw function what action to perform. - enum Mode { - kModeInit = 0, - kModeReInit = 1, - kModePreComposite = 2, - kModeComposite = 3, - kModePostComposite = 4, - kModeSync = 5, - } mode; - - // Input: The parameters for the functor being called - union ParamUnion { - struct InitParams init_params; - struct CompositeParams composite_params; - struct PostCompositeParams post_composite_params; - } info; -}; - -typedef void(AwDrawVKFunction)(long view_context, AwDrawVKInfo* draw_info); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // ANDROID_WEBVIEW_PUBLIC_BROWSER_DRAW_VK_H_ diff --git a/native/webview/plat_support/draw_vk_functor.cpp b/native/webview/plat_support/draw_vk_functor.cpp deleted file mode 100644 index eab134020f71..000000000000 --- a/native/webview/plat_support/draw_vk_functor.cpp +++ /dev/null @@ -1,142 +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. - */ - -// Provides a webviewchromium glue layer adapter from the internal Android -// Vulkan Functor data types into the types the chromium stack expects, and -// back. - -#define LOG_TAG "webviewchromium_plat_support" - -#include "draw_fn.h" -#include "draw_vk.h" - -#include <jni.h> -#include <private/hwui/DrawVkInfo.h> -#include <utils/Functor.h> -#include <utils/Log.h> - -#include "functor_utils.h" - -#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) - -namespace android { -namespace { - -AwDrawVKFunction* g_aw_drawvk_function = NULL; - -class DrawVKFunctor : public Functor { - public: - explicit DrawVKFunctor(jlong view_context) : view_context_(view_context) {} - ~DrawVKFunctor() override {} - - // Functor - status_t operator ()(int what, void* data) override { - using uirenderer::DrawVkInfo; - if (!g_aw_drawvk_function) { - ALOGE("Cannot draw: no DrawVK Function installed"); - return DrawVkInfo::kStatusDone; - } - - AwDrawVKInfo aw_info; - aw_info.version = kAwDrawVKInfoVersion; - switch (what) { - case DrawVkInfo::kModeComposite: { - aw_info.mode = AwDrawVKInfo::kModeComposite; - DrawVkInfo* vk_info = reinterpret_cast<DrawVkInfo*>(data); - - // Map across the input values. - CompositeParams& params = aw_info.info.composite_params; - params.width = vk_info->width; - params.height = vk_info->height; - params.is_layer = vk_info->isLayer; - for (size_t i = 0; i < 16; i++) { - params.transform[i] = vk_info->transform[i]; - } - params.secondary_command_buffer = vk_info->secondaryCommandBuffer; - params.color_attachment_index = vk_info->colorAttachmentIndex; - params.compatible_render_pass = vk_info->compatibleRenderPass; - params.format = vk_info->format; - params.G = vk_info->G; - params.A = vk_info->A; - params.B = vk_info->B; - params.C = vk_info->C; - params.D = vk_info->D; - params.E = vk_info->E; - params.F = vk_info->F; - for (size_t i = 0; i < 9; i++) { - params.matrix[i] = vk_info->matrix[i]; - } - params.clip_left = vk_info->clipLeft; - params.clip_top = vk_info->clipTop; - params.clip_right = vk_info->clipRight; - params.clip_bottom = vk_info->clipBottom; - - break; - } - case DrawVkInfo::kModePostComposite: - break; - case DrawVkInfo::kModeSync: - aw_info.mode = AwDrawVKInfo::kModeSync; - break; - default: - ALOGE("Unexpected DrawVKInfo type %d", what); - return DrawVkInfo::kStatusDone; - } - - // Invoke the DrawVK method. - g_aw_drawvk_function(view_context_, &aw_info); - - return DrawVkInfo::kStatusDone; - } - - private: - intptr_t view_context_; -}; - -jlong CreateVKFunctor(JNIEnv*, jclass, jlong view_context) { - RaiseFileNumberLimit(); - return reinterpret_cast<jlong>(new DrawVKFunctor(view_context)); -} - -void DestroyVKFunctor(JNIEnv*, jclass, jlong functor) { - delete reinterpret_cast<DrawVKFunctor*>(functor); -} - -void SetChromiumAwDrawVKFunction(JNIEnv*, jclass, jlong draw_function) { - g_aw_drawvk_function = reinterpret_cast<AwDrawVKFunction*>(draw_function); -} - -const char kClassName[] = "com/android/webview/chromium/DrawVKFunctor"; -const JNINativeMethod kJniMethods[] = { - { "nativeCreateVKFunctor", "(J)J", - reinterpret_cast<void*>(CreateVKFunctor) }, - { "nativeDestroyVKFunctor", "(J)V", - reinterpret_cast<void*>(DestroyVKFunctor) }, - { "nativeSetChromiumAwDrawVKFunction", "(J)V", - reinterpret_cast<void*>(SetChromiumAwDrawVKFunction) }, -}; - -} // namespace - -void RegisterDrawVKFunctor(JNIEnv* env) { - jclass clazz = env->FindClass(kClassName); - LOG_ALWAYS_FATAL_IF(!clazz, "Unable to find class '%s'", kClassName); - - int res = env->RegisterNatives(clazz, kJniMethods, NELEM(kJniMethods)); - LOG_ALWAYS_FATAL_IF(res < 0, "register native methods failed: res=%d", res); -} - -} // namespace android diff --git a/packages/AppPredictionLib/Android.bp b/packages/AppPredictionLib/Android.bp new file mode 100644 index 000000000000..e0f4dedc9368 --- /dev/null +++ b/packages/AppPredictionLib/Android.bp @@ -0,0 +1,24 @@ +// 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. + +android_library { + name: "app_prediction", + + sdk_version: "system_current", + min_sdk_version: "system_current", + + srcs: [ + "src/**/*.java", + ], +} diff --git a/packages/AppPredictionLib/AndroidManifest.xml b/packages/AppPredictionLib/AndroidManifest.xml new file mode 100644 index 000000000000..b99278881281 --- /dev/null +++ b/packages/AppPredictionLib/AndroidManifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.app.prediction"> +</manifest> diff --git a/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java b/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java new file mode 100644 index 000000000000..0993c9a13f99 --- /dev/null +++ b/packages/AppPredictionLib/src/com/android/app/prediction/Constants.java @@ -0,0 +1,71 @@ +/* + * 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.app.prediction; + +/** + * Constants to be used with {@link android.app.prediction.AppPredictor}. + */ +public class Constants { + + /** + * UI surface for predictions displayed on the user's home screen + */ + public static final String UI_SURFACE_HOME = "home"; + + /** + * UI surface for predictions displayed on the recents/task switcher view + */ + public static final String UI_SURFACE_RECENTS = "recents"; + + /** + * UI surface for predictions displayed on the share sheet. + */ + public static final String UI_SURFACE_SHARE = "share"; + + /** + * Location constant when an app target or shortcut is started from the apps list + */ + public static final String LAUNCH_LOCATION_APPS_LIST = "apps_list"; + + /** + * Location constant when an app target or shortcut is started from the user's home screen + */ + public static final String LAUNCH_LOCATION_APPS_HOME = "home"; + + /** + * Location constant when an app target or shortcut is started from task switcher + */ + public static final String LAUNCH_LOCATION_APPS_RECENTS = "recents"; + + /** + * Location constant when an app target or shortcut is started in the share sheet while it is + * in collapsed state (showing a limited set of result). + */ + public static final String LAUNCH_LOCATION_APPS_SHARE_COLLAPSED = "share_collapsed"; + + /** + * Location constant when an app target or shortcut is started in the share sheet while it is + * in expended state and showing all the results. + */ + public static final String LAUNCH_LOCATION_APPS_SHARE_EXPANDED = "shared_expanded"; + + /** + * Location constant when an app target or shortcut is started in the share sheet when the + * target is displayed as a placeholder for an deprecated object. + */ + public static final String LAUNCH_LOCATION_APPS_SHARE_LEGACY = "share_legacy"; +} diff --git a/packages/CarrierDefaultApp/tests/unit/Android.mk b/packages/CarrierDefaultApp/tests/unit/Android.mk index 8e3785e7bda8..4c6388110ebf 100644 --- a/packages/CarrierDefaultApp/tests/unit/Android.mk +++ b/packages/CarrierDefaultApp/tests/unit/Android.mk @@ -21,7 +21,7 @@ LOCAL_CERTIFICATE := platform LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.test.base -LOCAL_STATIC_JAVA_LIBRARIES := android-support-test mockito-target-minus-junit4 +LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4 # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml index 3a06a09fbc7c..7a26d95551df 100644 --- a/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/tests/unit/AndroidManifest.xml @@ -21,7 +21,7 @@ <uses-library android:name="android.test.runner" /> </application> - <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.carrierdefaultapp" android:label="CarrierDefaultApp Unit Test Cases"> </instrumentation> diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 4688848dee0f..2f7d599c68bf 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -21,10 +21,10 @@ java_library { installable: true, srcs: [ "src/**/*.java", + ":services-networkstack-shared-srcs", ], static_libs: [ "dhcp-packet-lib", - "frameworks-net-shared-utils", ] } diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index 8516d9485f57..0b0f1eca7aa5 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -22,8 +22,11 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> + <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> <!-- Launch captive portal app as specific user --> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.NETWORK_STACK" /> <application android:label="NetworkStack" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/NetworkStack/src/android/net/util/SharedLog.java b/packages/NetworkStack/src/android/net/util/SharedLog.java index 74bc1470293f..4fabf10bbd37 100644 --- a/packages/NetworkStack/src/android/net/util/SharedLog.java +++ b/packages/NetworkStack/src/android/net/util/SharedLog.java @@ -69,6 +69,10 @@ public class SharedLog { mComponent = component; } + public String getTag() { + return mTag; + } + /** * Create a SharedLog based on this log with an additional component prefix on each logged line. */ diff --git a/services/net/java/android/net/util/Stopwatch.java b/packages/NetworkStack/src/android/net/util/Stopwatch.java index cb15ee580a73..c3166999cdca 100644 --- a/services/net/java/android/net/util/Stopwatch.java +++ b/packages/NetworkStack/src/android/net/util/Stopwatch.java @@ -38,9 +38,9 @@ public class Stopwatch { return (isStarted() && !isStopped()); } - // Returning |this| makes possible the following usage pattern: - // - // Stopwatch s = new Stopwatch().start(); + /** + * Start the Stopwatch. + */ public Stopwatch start() { if (!isStarted()) { mStartTimeMs = SystemClock.elapsedRealtime(); @@ -48,7 +48,10 @@ public class Stopwatch { return this; } - // Returns the total time recorded, in milliseconds, or 0 if not started. + /** + * Stop the Stopwatch. + * @return the total time recorded, in milliseconds, or 0 if not started. + */ public long stop() { if (isRunning()) { mStopTimeMs = SystemClock.elapsedRealtime(); @@ -57,9 +60,11 @@ public class Stopwatch { return (mStopTimeMs - mStartTimeMs); } - // Returns the total time recorded to date, in milliseconds. - // If the Stopwatch is not running, returns the same value as stop(), - // i.e. either the total time recorded before stopping or 0. + /** + * Return the total time recorded to date, in milliseconds. + * If the Stopwatch is not running, returns the same value as stop(), + * i.e. either the total time recorded before stopping or 0. + */ public long lap() { if (isRunning()) { return (SystemClock.elapsedRealtime() - mStartTimeMs); @@ -68,6 +73,9 @@ public class Stopwatch { } } + /** + * Reset the Stopwatch. It will be stopped when this method returns. + */ public void reset() { mStartTimeMs = 0; mStopTimeMs = 0; diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 7fea1e038cee..057012de433a 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -25,18 +25,31 @@ import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPer import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Service; +import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.INetworkMonitor; +import android.net.INetworkMonitorCallbacks; import android.net.INetworkStackConnector; +import android.net.Network; +import android.net.NetworkRequest; +import android.net.PrivateDnsConfigParcel; import android.net.dhcp.DhcpServer; import android.net.dhcp.DhcpServingParams; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.shared.PrivateDnsConfig; import android.net.util.SharedLog; import android.os.IBinder; import android.os.RemoteException; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.connectivity.NetworkMonitor; + import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayDeque; /** * Android service used to start the network stack when bound to via an intent. @@ -52,17 +65,41 @@ public class NetworkStackService extends Service { * <p>On platforms where the network stack runs in the system server process, this method may * be called directly instead of obtaining the connector by binding to the service. */ - public static IBinder makeConnector() { - return new NetworkStackConnector(); + public static IBinder makeConnector(Context context) { + return new NetworkStackConnector(context); } @NonNull @Override public IBinder onBind(Intent intent) { - return makeConnector(); + return makeConnector(this); } private static class NetworkStackConnector extends INetworkStackConnector.Stub { + private static final int NUM_VALIDATION_LOG_LINES = 20; + private final Context mContext; + private final ConnectivityManager mCm; + + private static final int MAX_VALIDATION_LOGS = 10; + @GuardedBy("mValidationLogs") + private final ArrayDeque<SharedLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS); + + private SharedLog addValidationLogs(Network network, String name) { + final SharedLog log = new SharedLog(NUM_VALIDATION_LOG_LINES, network + " - " + name); + synchronized (mValidationLogs) { + while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) { + mValidationLogs.removeLast(); + } + mValidationLogs.addFirst(log); + } + return log; + } + + NetworkStackConnector(Context context) { + mContext = context; + mCm = context.getSystemService(ConnectivityManager.class); + } + @NonNull private final SharedLog mLog = new SharedLog(TAG); @@ -89,11 +126,102 @@ public class NetworkStackService extends Service { } @Override + public void makeNetworkMonitor(int netId, String name, INetworkMonitorCallbacks cb) + throws RemoteException { + final Network network = new Network(netId, false /* privateDnsBypass */); + final NetworkRequest defaultRequest = mCm.getDefaultRequest(); + final SharedLog log = addValidationLogs(network, name); + final NetworkMonitor nm = new NetworkMonitor( + mContext, cb, network, defaultRequest, log); + cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm)); + } + + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { checkNetworkStackCallingPermission(); - fout.println("NetworkStack logs:"); - mLog.dump(fd, fout, args); + final IndentingPrintWriter pw = new IndentingPrintWriter(fout, " "); + pw.println("NetworkStack logs:"); + mLog.dump(fd, pw, args); + + pw.println(); + pw.println("Validation logs (most recent first):"); + synchronized (mValidationLogs) { + for (SharedLog p : mValidationLogs) { + pw.println(p.getTag()); + pw.increaseIndent(); + p.dump(fd, pw, args); + pw.decreaseIndent(); + } + } + } + } + + private static class NetworkMonitorImpl extends INetworkMonitor.Stub { + private final NetworkMonitor mNm; + + NetworkMonitorImpl(NetworkMonitor nm) { + mNm = nm; + } + + @Override + public void start() { + checkNetworkStackCallingPermission(); + mNm.start(); + } + + @Override + public void launchCaptivePortalApp() { + checkNetworkStackCallingPermission(); + mNm.launchCaptivePortalApp(); + } + + @Override + public void forceReevaluation(int uid) { + checkNetworkStackCallingPermission(); + mNm.forceReevaluation(uid); + } + + @Override + public void notifyPrivateDnsChanged(PrivateDnsConfigParcel config) { + checkNetworkStackCallingPermission(); + mNm.notifyPrivateDnsSettingsChanged(PrivateDnsConfig.fromParcel(config)); + } + + @Override + public void notifyDnsResponse(int returnCode) { + checkNetworkStackCallingPermission(); + mNm.notifyDnsResponse(returnCode); + } + + @Override + public void notifySystemReady() { + checkNetworkStackCallingPermission(); + mNm.notifySystemReady(); + } + + @Override + public void notifyNetworkConnected() { + checkNetworkStackCallingPermission(); + mNm.notifyNetworkConnected(); + } + + @Override + public void notifyNetworkDisconnected() { + checkNetworkStackCallingPermission(); + mNm.notifyNetworkDisconnected(); + } + + @Override + public void notifyLinkPropertiesChanged() { + checkNetworkStackCallingPermission(); + mNm.notifyLinkPropertiesChanged(); + } + + @Override + public void notifyNetworkCapabilitiesChanged() { + checkNetworkStackCallingPermission(); + mNm.notifyNetworkCapabilitiesChanged(); } } } diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java index 2a000252d6f4..94ea1b931348 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java @@ -21,6 +21,11 @@ import static android.net.CaptivePortal.APP_RETURN_UNWANTED; import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; 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.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE; import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS; import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK; @@ -35,6 +40,9 @@ import android.content.IntentFilter; import android.net.CaptivePortal; import android.net.ConnectivityManager; import android.net.ICaptivePortal; +import android.net.INetworkMonitor; +import android.net.INetworkMonitorCallbacks; +import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; @@ -46,11 +54,14 @@ import android.net.captiveportal.CaptivePortalProbeSpec; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.metrics.ValidationProbeEvent; +import android.net.shared.NetworkMonitorUtils; +import android.net.shared.PrivateDnsConfig; +import android.net.util.SharedLog; import android.net.util.Stopwatch; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.os.Handler; import android.os.Message; +import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -65,8 +76,6 @@ import android.telephony.CellInfoLte; import android.telephony.CellInfoWcdma; import android.telephony.TelephonyManager; import android.text.TextUtils; -import android.util.LocalLog; -import android.util.LocalLog.ReadOnlyLocalLog; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -75,7 +84,6 @@ import com.android.internal.util.Protocol; import com.android.internal.util.RingBufferIndices; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import com.android.server.connectivity.DnsManager.PrivateDnsConfig; import java.io.IOException; import java.net.HttpURLConnection; @@ -104,9 +112,7 @@ public class NetworkMonitor extends StateMachine { // Default configuration values for captive portal detection probes. // TODO: append a random length parameter to the default HTTPS url. // TODO: randomize browser version ids in the default User-Agent String. - private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204"; - private static final String DEFAULT_HTTP_URL = - "http://connectivitycheck.gstatic.com/generate_204"; + private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204"; private static final String DEFAULT_FALLBACK_URL = "http://www.google.com/gen_204"; private static final String DEFAULT_OTHER_FALLBACK_URLS = "http://play.googleapis.com/generate_204"; @@ -144,33 +150,12 @@ public class NetworkMonitor extends StateMachine { } } - // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. - // The network should be used as a default internet connection. It was found to be: - // 1. a functioning network providing internet access, or - // 2. a captive portal and the user decided to use it as is. - public static final int NETWORK_TEST_RESULT_VALID = 0; - // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. - // The network should not be used as a default internet connection. It was found to be: - // 1. a captive portal and the user is prompted to sign-in, or - // 2. a captive portal and the user did not want to use it, or - // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed). - public static final int NETWORK_TEST_RESULT_INVALID = 1; - private static final int BASE = Protocol.BASE_NETWORK_MONITOR; - /** - * Inform NetworkMonitor that their network is connected. + * ConnectivityService has sent a notification to indicate that network has connected. * Initiates Network Validation. */ - public static final int CMD_NETWORK_CONNECTED = BASE + 1; - - /** - * Inform ConnectivityService that the network has been tested. - * obj = String representing URL that Internet probe was redirect to, if it was redirected. - * arg1 = One of the NETWORK_TESTED_RESULT_* constants. - * arg2 = NetID. - */ - public static final int EVENT_NETWORK_TESTED = BASE + 2; + private static final int CMD_NETWORK_CONNECTED = BASE + 1; /** * Message to self indicating it's time to evaluate a network's connectivity. @@ -179,9 +164,9 @@ public class NetworkMonitor extends StateMachine { private static final int CMD_REEVALUATE = BASE + 6; /** - * Inform NetworkMonitor that the network has disconnected. + * ConnectivityService has sent a notification to indicate that network has disconnected. */ - public static final int CMD_NETWORK_DISCONNECTED = BASE + 7; + private static final int CMD_NETWORK_DISCONNECTED = BASE + 7; /** * Force evaluation even if it has succeeded in the past. @@ -199,21 +184,13 @@ public class NetworkMonitor extends StateMachine { private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9; /** - * Request ConnectivityService display provisioning notification. - * arg1 = Whether to make the notification visible. - * arg2 = NetID. - * obj = Intent to be launched when notification selected by user, null if !arg1. - */ - public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10; - - /** * Message indicating sign-in app should be launched. * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the * user touches the sign in notification, or sent by * ConnectivityService when the user touches the "sign into * network" button in the wifi access point detail page. */ - public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; + private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; /** * Retest network to see if captive portal is still in place. @@ -234,7 +211,6 @@ public class NetworkMonitor extends StateMachine { * validation phase is completed. */ private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13; - public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = BASE + 14; private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15; /** @@ -263,23 +239,16 @@ public class NetworkMonitor extends StateMachine { // Delay between reevaluations once a captive portal has been found. private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000; - private static final int NUM_VALIDATION_LOG_LINES = 20; - private String mPrivateDnsProviderHostname = ""; - public static boolean isValidationRequired( - NetworkCapabilities dfltNetCap, NetworkCapabilities nc) { - // TODO: Consider requiring validation for DUN networks. - return dfltNetCap.satisfiedByNetworkCapabilities(nc); - } - private final Context mContext; - private final Handler mConnectivityServiceHandler; - private final NetworkAgentInfo mNetworkAgentInfo; + 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; private final NetworkRequest mDefaultRequest; private final IpConnectivityLog mMetricsLog; private final Dependencies mDependencies; @@ -292,6 +261,9 @@ public class NetworkMonitor extends StateMachine { @Nullable private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs; + private NetworkCapabilities mNetworkCapabilities; + private LinkProperties mLinkProperties; + @VisibleForTesting protected boolean mIsCaptivePortalCheckEnabled; @@ -304,7 +276,7 @@ public class NetworkMonitor extends StateMachine { // Avoids surfacing "Sign in to network" notification. private boolean mDontDisplaySigninNotification = false; - public boolean systemReady = false; + private volatile boolean mSystemReady = false; private final State mDefaultState = new DefaultState(); private final State mValidatedState = new ValidatedState(); @@ -317,7 +289,7 @@ public class NetworkMonitor extends StateMachine { private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; - private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES); + private final SharedLog mValidationLogs; private final Stopwatch mEvaluationTimer = new Stopwatch(); @@ -328,6 +300,7 @@ public class NetworkMonitor extends StateMachine { private final Random mRandom; private int mNextFallbackUrlIndex = 0; + private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS; private int mEvaluateAttempts = 0; private volatile int mProbeToken = 0; @@ -338,17 +311,18 @@ public class NetworkMonitor extends StateMachine { private final DnsStallDetector mDnsStallDetector; private long mLastProbeTime; - public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, - NetworkRequest defaultRequest) { - this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(), + public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, + NetworkRequest defaultRequest, SharedLog validationLog) { + this(context, cb, network, defaultRequest, new IpConnectivityLog(), validationLog, Dependencies.DEFAULT); } @VisibleForTesting - protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, - NetworkRequest defaultRequest, IpConnectivityLog logger, Dependencies deps) { + protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network, + NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs, + Dependencies deps) { // Add suffix indicating which NetworkMonitor we're talking about. - super(TAG + networkAgentInfo.name()); + super(TAG + "/" + network.netId); // Logs with a tag of the form given just above, e.g. // <timestamp> 862 2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ... @@ -356,15 +330,18 @@ public class NetworkMonitor extends StateMachine { mContext = context; mMetricsLog = logger; - mConnectivityServiceHandler = handler; + mValidationLogs = validationLogs; + mCallback = cb; mDependencies = deps; - mNetworkAgentInfo = networkAgentInfo; - mNetwork = deps.getNetwork(networkAgentInfo).getPrivateDnsBypassingCopy(); + 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); mDefaultRequest = defaultRequest; + // CHECKSTYLE:OFF IndentationCheck addState(mDefaultState); addState(mMaybeNotifyState, mDefaultState); addState(mEvaluatingState, mMaybeNotifyState); @@ -374,12 +351,13 @@ public class NetworkMonitor extends StateMachine { addState(mEvaluatingPrivateDnsState, mDefaultState); addState(mValidatedState, mDefaultState); setInitialState(mDefaultState); + // CHECKSTYLE:ON IndentationCheck mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled(); mUseHttps = getUseHttpsValidation(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl()); - mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(deps, context)); + mCaptivePortalHttpUrl = makeURL(deps.getCaptivePortalServerHttpUrl(context)); mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls(); mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs(); mRandom = deps.getRandom(); @@ -390,7 +368,13 @@ public class NetworkMonitor extends StateMachine { mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold(); mDataStallEvaluationType = getDataStallEvalutionType(); - start(); + // mLinkProperties and mNetworkCapbilities must never be null or we will NPE. + // Provide empty objects in case we are started and the network disconnects before + // we can ever fetch them. + // TODO: Delete ASAP. + mLinkProperties = new LinkProperties(); + mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities.clearAll(); } /** @@ -401,6 +385,14 @@ public class NetworkMonitor extends StateMachine { } /** + * Send a notification to NetworkMonitor indicating that there was a DNS query response event. + * @param returnCode the DNS return code of the response. + */ + public void notifyDnsResponse(int returnCode) { + sendMessage(EVENT_DNS_NOTIFICATION, returnCode); + } + + /** * Send a notification to NetworkMonitor indicating that private DNS settings have changed. * @param newCfg The new private DNS configuration. */ @@ -411,9 +403,75 @@ public class NetworkMonitor extends StateMachine { sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg); } + /** + * Send a notification to NetworkMonitor indicating that the system is ready. + */ + public void notifySystemReady() { + // No need to run on the handler thread: mSystemReady is volatile and read only once on the + // isCaptivePortal() thread. + mSystemReady = true; + } + + /** + * Send a notification to NetworkMonitor indicating that the network is now connected. + */ + public void notifyNetworkConnected() { + sendMessage(CMD_NETWORK_CONNECTED); + } + + /** + * Send a notification to NetworkMonitor indicating that the network is now disconnected. + */ + public void notifyNetworkDisconnected() { + sendMessage(CMD_NETWORK_DISCONNECTED); + } + + /** + * Send a notification to NetworkMonitor indicating that link properties have changed. + */ + public void notifyLinkPropertiesChanged() { + getHandler().post(() -> { + updateLinkProperties(); + }); + } + + private void updateLinkProperties() { + final LinkProperties lp = mCm.getLinkProperties(mNetwork); + // If null, we should soon get a message that the network was disconnected, and will stop. + if (lp != null) { + // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start(). + mLinkProperties = lp; + } + } + + /** + * Send a notification to NetworkMonitor indicating that network capabilities have changed. + */ + public void notifyNetworkCapabilitiesChanged() { + getHandler().post(() -> { + updateNetworkCapabilities(); + }); + } + + private void updateNetworkCapabilities() { + final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork); + // If null, we should soon get a message that the network was disconnected, and will stop. + if (nc != null) { + // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start(). + mNetworkCapabilities = nc; + } + } + + /** + * Request the captive portal application to be launched. + */ + public void launchCaptivePortalApp() { + sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP); + } + @Override protected void log(String s) { - if (DBG) Log.d(TAG + "/" + mNetworkAgentInfo.name(), s); + if (DBG) Log.d(TAG + "/" + mNetwork.netId, s); } private void validationLog(int probeType, Object url, String msg) { @@ -423,11 +481,7 @@ public class NetworkMonitor extends StateMachine { private void validationLog(String s) { if (DBG) log(s); - validationLogs.log(s); - } - - public ReadOnlyLocalLog getValidationLogs() { - return validationLogs.readOnlyLocalLog(); + mValidationLogs.log(s); } private ValidationStage validationStage() { @@ -435,20 +489,46 @@ public class NetworkMonitor extends StateMachine { } private boolean isValidationRequired() { - return isValidationRequired( - mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities); + return NetworkMonitorUtils.isValidationRequired( + mDefaultRequest.networkCapabilities, mNetworkCapabilities); + } + + + private void notifyNetworkTested(int result, @Nullable String redirectUrl) { + try { + mCallback.notifyNetworkTested(result, redirectUrl); + } catch (RemoteException e) { + Log.e(TAG, "Error sending network test result", e); + } } + private void showProvisioningNotification(String action) { + try { + mCallback.showProvisioningNotification(action); + } catch (RemoteException e) { + Log.e(TAG, "Error showing provisioning notification", e); + } + } - private void notifyNetworkTestResultInvalid(Object obj) { - mConnectivityServiceHandler.sendMessage(obtainMessage( - EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj)); + private void hideProvisioningNotification() { + try { + mCallback.hideProvisioningNotification(); + } catch (RemoteException e) { + Log.e(TAG, "Error hiding provisioning notification", e); + } } // DefaultState is the parent of all States. It exists only to handle CMD_* messages but // does not entail any real state (hence no enter() or exit() routines). private class DefaultState extends State { @Override + public void enter() { + // TODO: have those passed parceled in start() and remove this + updateLinkProperties(); + updateNetworkCapabilities(); + } + + @Override public boolean processMessage(Message message) { switch (message.what) { case CMD_NETWORK_CONNECTED: @@ -499,7 +579,7 @@ public class NetworkMonitor extends StateMachine { case APP_RETURN_UNWANTED: mDontDisplaySigninNotification = true; mUserDoesNotWant = true; - notifyNetworkTestResultInvalid(null); + notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null); // TODO: Should teardown network. mUidResponsibleForReeval = 0; transitionTo(mEvaluatingState); @@ -563,8 +643,7 @@ public class NetworkMonitor extends StateMachine { public void enter() { maybeLogEvaluationResult( networkEventType(validationStage(), EvaluationResult.VALIDATED)); - mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, - NETWORK_TEST_RESULT_VALID, mNetId, null)); + notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null); mValidations++; } @@ -633,8 +712,7 @@ public class NetworkMonitor extends StateMachine { @Override public void exit() { - Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null); - mConnectivityServiceHandler.sendMessage(message); + hideProvisioningNotification(); } } @@ -751,9 +829,7 @@ public class NetworkMonitor extends StateMachine { CMD_LAUNCH_CAPTIVE_PORTAL_APP); } // Display the sign in notification. - Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetId, - mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent()); - mConnectivityServiceHandler.sendMessage(message); + showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction); // Retest for captive portal occasionally. sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); @@ -839,12 +915,15 @@ public class NetworkMonitor extends StateMachine { } private void notifyPrivateDnsConfigResolved() { - mConnectivityServiceHandler.sendMessage(obtainMessage( - EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, mNetId, mPrivateDnsConfig)); + try { + mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel()); + } catch (RemoteException e) { + Log.e(TAG, "Error sending private DNS config resolved notification", e); + } } private void handlePrivateDnsEvaluationFailure() { - notifyNetworkTestResultInvalid(null); + notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null); // Queue up a re-evaluation with backoff. // @@ -865,7 +944,7 @@ public class NetworkMonitor extends StateMachine { + oneTimeHostnameSuffix; final Stopwatch watch = new Stopwatch().start(); try { - final InetAddress[] ips = mNetworkAgentInfo.network().getAllByName(host); + final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host); final long time = watch.stop(); final String strIps = Arrays.toString(ips); final boolean success = (ips != null && ips.length > 0); @@ -915,12 +994,12 @@ public class NetworkMonitor extends StateMachine { // state (even if no Private DNS validation required). transitionTo(mEvaluatingPrivateDnsState); } else if (probeResult.isPortal()) { - notifyNetworkTestResultInvalid(probeResult.redirectUrl); + notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl); mLastPortalProbeResult = probeResult; transitionTo(mCaptivePortalState); } else { logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED); - notifyNetworkTestResultInvalid(probeResult.redirectUrl); + notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl); transitionTo(mWaitingForNextProbeState); } return HANDLED; @@ -996,18 +1075,18 @@ public class NetworkMonitor extends StateMachine { } } - public boolean getIsCaptivePortalCheckEnabled() { + private boolean getIsCaptivePortalCheckEnabled() { String symbol = Settings.Global.CAPTIVE_PORTAL_MODE; int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT; int mode = mDependencies.getSetting(mContext, symbol, defaultValue); return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE; } - public boolean getUseHttpsValidation() { + private boolean getUseHttpsValidation() { return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; } - public boolean getWifiScansAlwaysAvailableDisabled() { + private boolean getWifiScansAlwaysAvailableDisabled() { return mDependencies.getSetting( mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0; } @@ -1040,15 +1119,6 @@ public class NetworkMonitor extends StateMachine { DEFAULT_DATA_STALL_EVALUATION_TYPES); } - // Static for direct access by ConnectivityService - public static String getCaptivePortalServerHttpUrl(Context context) { - return getCaptivePortalServerHttpUrl(Dependencies.DEFAULT, context); - } - - public static String getCaptivePortalServerHttpUrl(Dependencies deps, Context context) { - return deps.getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL); - } - private URL[] makeCaptivePortalFallbackUrls() { try { String separator = ","; @@ -1144,7 +1214,7 @@ public class NetworkMonitor extends StateMachine { // 3. PAC scripts are sometimes used to block or restrict Internet access and may in // fact block fetching of the generate_204 URL which would lead to false negative // results for network validation. - final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy(); + final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy(); if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { pacUrl = makeURL(proxyInfo.getPacFileUrl().toString()); if (pacUrl == null) { @@ -1416,89 +1486,86 @@ public class NetworkMonitor extends StateMachine { return; } - if (!systemReady) { + if (!mSystemReady) { return; } Intent latencyBroadcast = - new Intent(ConnectivityConstants.ACTION_NETWORK_CONDITIONS_MEASURED); - switch (mNetworkAgentInfo.networkInfo.getType()) { - case ConnectivityManager.TYPE_WIFI: - WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); - if (currentWifiInfo != null) { - // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not - // surrounded by double quotation marks (thus violating the Javadoc), but this - // was changed to match the Javadoc in API 17. Since clients may have started - // sanitizing the output of this method since API 17 was released, we should - // not change it here as it would become impossible to tell whether the SSID is - // simply being surrounded by quotes due to the API, or whether those quotes - // are actually part of the SSID. - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_SSID, - currentWifiInfo.getSSID()); - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_BSSID, - currentWifiInfo.getBSSID()); - } else { - if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); - return; - } - break; - case ConnectivityManager.TYPE_MOBILE: - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_NETWORK_TYPE, - mTelephonyManager.getNetworkType()); - List<CellInfo> info = mTelephonyManager.getAllCellInfo(); - if (info == null) return; - int numRegisteredCellInfo = 0; - for (CellInfo cellInfo : info) { - if (cellInfo.isRegistered()) { - numRegisteredCellInfo++; - if (numRegisteredCellInfo > 1) { - if (VDBG) { - logw("more than one registered CellInfo." - + " Can't tell which is active. Bailing."); - } - return; - } - if (cellInfo instanceof CellInfoCdma) { - CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoGsm) { - CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoLte) { - CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId); - } else if (cellInfo instanceof CellInfoWcdma) { - CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId); - } else { - if (VDBG) logw("Registered cellinfo is unrecognized"); - return; + new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED); + if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) { + WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo(); + if (currentWifiInfo != null) { + // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not + // surrounded by double quotation marks (thus violating the Javadoc), but this + // was changed to match the Javadoc in API 17. Since clients may have started + // sanitizing the output of this method since API 17 was released, we should + // not change it here as it would become impossible to tell whether the SSID is + // simply being surrounded by quotes due to the API, or whether those quotes + // are actually part of the SSID. + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID, + currentWifiInfo.getSSID()); + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID, + currentWifiInfo.getBSSID()); + } else { + if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); + return; + } + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI); + } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE, + mTelephonyManager.getNetworkType()); + List<CellInfo> info = mTelephonyManager.getAllCellInfo(); + if (info == null) return; + int numRegisteredCellInfo = 0; + for (CellInfo cellInfo : info) { + if (cellInfo.isRegistered()) { + numRegisteredCellInfo++; + if (numRegisteredCellInfo > 1) { + if (VDBG) { + logw("more than one registered CellInfo." + + " Can't tell which is active. Bailing."); } + return; + } + if (cellInfo instanceof CellInfoCdma) { + CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity(); + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); + } else if (cellInfo instanceof CellInfoGsm) { + CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity(); + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); + } else if (cellInfo instanceof CellInfoLte) { + CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity(); + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); + } else if (cellInfo instanceof CellInfoWcdma) { + CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId); + } else { + if (VDBG) logw("Registered cellinfo is unrecognized"); + return; } } - break; - default: - return; + } + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE); + } else { + return; } - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CONNECTIVITY_TYPE, - mNetworkAgentInfo.networkInfo.getType()); - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_RECEIVED, + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED, responseReceived); - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_REQUEST_TIMESTAMP_MS, + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS, requestTimestampMs); if (responseReceived) { - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_IS_CAPTIVE_PORTAL, + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL, isCaptivePortal); - latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_TIMESTAMP_MS, + latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS, responseTimestampMs); } mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT, - ConnectivityConstants.PERMISSION_ACCESS_NETWORK_CONDITIONS); + NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS); } private void logNetworkEvent(int evtype) { - int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes(); + int[] transports = mNetworkCapabilities.getTransportTypes(); mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype)); } @@ -1520,14 +1587,14 @@ public class NetworkMonitor extends StateMachine { private void maybeLogEvaluationResult(int evtype) { if (mEvaluationTimer.isRunning()) { - int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes(); + int[] transports = mNetworkCapabilities.getTransportTypes(); mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop())); mEvaluationTimer.reset(); } } private void logValidationProbe(long durationMs, int probeType, int probeResult) { - int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes(); + int[] transports = mNetworkCapabilities.getTransportTypes(); boolean isFirstValidation = validationStage().mIsFirstValidation; ValidationProbeEvent ev = new ValidationProbeEvent(); ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation); @@ -1537,9 +1604,9 @@ public class NetworkMonitor extends StateMachine { } @VisibleForTesting - public static class Dependencies { - public Network getNetwork(NetworkAgentInfo networkAgentInfo) { - return new OneAddressPerFamilyNetwork(networkAgentInfo.network()); + static class Dependencies { + public Network getPrivateDnsBypassNetwork(Network network) { + return new OneAddressPerFamilyNetwork(network); } public Random getRandom() { @@ -1547,6 +1614,13 @@ public class NetworkMonitor extends StateMachine { } /** + * Get the captive portal server HTTP URL that is configured on the device. + */ + public String getCaptivePortalServerHttpUrl(Context context) { + return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(context); + } + + /** * Get the value of a global integer setting. * @param symbol Name of the setting * @param defaultValue Value to return if the setting is not defined. @@ -1666,7 +1740,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 (mNetworkAgentInfo.networkCapabilities.isMetered() + if (mNetworkCapabilities.isMetered() && (SystemClock.elapsedRealtime() - getLastProbeTime() < mDataStallMinEvaluateTime)) { return false; diff --git a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java index 6e07b26e883a..d31fa7732e66 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java +++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -16,6 +16,14 @@ package com.android.server.connectivity; +import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; +import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; + +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -24,13 +32,21 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.Intent; +import android.net.CaptivePortal; import android.net.ConnectivityManager; +import android.net.INetworkMonitorCallbacks; +import android.net.InetAddresses; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; @@ -38,9 +54,12 @@ import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.captiveportal.CaptivePortalProbeResult; import android.net.metrics.IpConnectivityLog; +import android.net.util.SharedLog; import android.net.wifi.WifiManager; +import android.os.ConditionVariable; import android.os.Handler; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -50,8 +69,10 @@ import android.util.ArrayMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.IOException; import java.net.HttpURLConnection; @@ -68,21 +89,23 @@ public class NetworkMonitorTest { private static final String LOCATION_HEADER = "location"; private @Mock Context mContext; - private @Mock Handler mHandler; private @Mock IpConnectivityLog mLogger; - private @Mock NetworkAgentInfo mAgent; - private @Mock NetworkAgentInfo mNotMeteredAgent; + private @Mock SharedLog mValidationLogger; private @Mock NetworkInfo mNetworkInfo; - private @Mock NetworkRequest mRequest; + private @Mock ConnectivityManager mCm; private @Mock TelephonyManager mTelephony; private @Mock WifiManager mWifi; - private @Mock Network mNetwork; private @Mock HttpURLConnection mHttpConnection; private @Mock HttpURLConnection mHttpsConnection; private @Mock HttpURLConnection mFallbackConnection; private @Mock HttpURLConnection mOtherFallbackConnection; private @Mock Random mRandom; private @Mock NetworkMonitor.Dependencies mDependencies; + private @Mock INetworkMonitorCallbacks mCallbacks; + private @Spy Network mNetwork = new Network(TEST_NETID); + private NetworkRequest mRequest; + + private static final int TEST_NETID = 4242; private static final String TEST_HTTP_URL = "http://www.google.com/gen_204"; private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204"; @@ -93,33 +116,37 @@ public class NetworkMonitorTest { private static final int RETURN_CODE_DNS_SUCCESS = 0; private static final int RETURN_CODE_DNS_TIMEOUT = 255; - @Before - public void setUp() throws IOException { - MockitoAnnotations.initMocks(this); - mAgent.linkProperties = new LinkProperties(); - mAgent.networkCapabilities = new NetworkCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - mAgent.networkInfo = mNetworkInfo; + private static final int HANDLER_TIMEOUT_MS = 1000; + + private static final LinkProperties TEST_LINKPROPERTIES = new LinkProperties(); + + private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET); - mNotMeteredAgent.linkProperties = new LinkProperties(); - mNotMeteredAgent.networkCapabilities = new NetworkCapabilities() + private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); - mNotMeteredAgent.networkInfo = mNetworkInfo; - when(mAgent.network()).thenReturn(mNetwork); - when(mDependencies.getNetwork(any())).thenReturn(mNetwork); + private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork); when(mDependencies.getRandom()).thenReturn(mRandom); when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())) .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT); when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_USE_HTTPS), anyInt())).thenReturn(1); - when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL), - anyString())).thenReturn(TEST_HTTP_URL); + when(mDependencies.getCaptivePortalServerHttpUrl(any())).thenReturn(TEST_HTTP_URL); when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), anyString())).thenReturn(TEST_HTTPS_URL); - when(mNetwork.getPrivateDnsBypassingCopy()).thenReturn(mNetwork); + doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy(); + when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm); when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony); when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi); @@ -129,7 +156,7 @@ public class NetworkMonitorTest { setFallbackSpecs(null); // Test with no fallback spec by default when(mRandom.nextInt()).thenReturn(0); - when(mNetwork.openConnection(any())).then((invocation) -> { + doAnswer((invocation) -> { URL url = invocation.getArgument(0); switch(url.toString()) { case TEST_HTTP_URL: @@ -144,12 +171,20 @@ public class NetworkMonitorTest { fail("URL not mocked: " + url.toString()); return null; } - }); + }).when(mNetwork).openConnection(any()); when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>()); when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>()); - when(mNetwork.getAllByName(any())).thenReturn(new InetAddress[] { - InetAddress.parseNumericAddress("192.168.0.0") - }); + doReturn(new InetAddress[] { + InetAddresses.parseNumericAddress("192.168.0.0") + }).when(mNetwork).getAllByName(any()); + + mRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_RESTRICTED) + .build(); + // Default values. Individual tests can override these. + when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES); + when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); setMinDataStallEvaluateInterval(500); setDataStallEvaluationType(1 << DATA_STALL_EVALUATION_TYPE_DNS); @@ -160,10 +195,10 @@ public class NetworkMonitorTest { private class WrappedNetworkMonitor extends NetworkMonitor { private long mProbeTime = 0; - WrappedNetworkMonitor(Context context, Handler handler, - NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest, + WrappedNetworkMonitor(Context context, Network network, NetworkRequest defaultRequest, IpConnectivityLog logger, Dependencies deps) { - super(context, handler, networkAgentInfo, defaultRequest, logger, deps); + super(context, mCallbacks, network, defaultRequest, logger, + new SharedLog("test_nm"), deps); } @Override @@ -176,19 +211,39 @@ public class NetworkMonitorTest { } } - WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() { - return new WrappedNetworkMonitor( - mContext, mHandler, mAgent, mRequest, mLogger, mDependencies); + private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() { + final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( + mContext, mNetwork, mRequest, mLogger, mDependencies); + when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); + nm.start(); + waitForIdle(nm.getHandler()); + return nm; } - WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() { - return new WrappedNetworkMonitor( - mContext, mHandler, mNotMeteredAgent, mRequest, mLogger, mDependencies); + private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() { + final WrappedNetworkMonitor nm = new WrappedNetworkMonitor( + mContext, mNetwork, mRequest, mLogger, mDependencies); + when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES); + nm.start(); + waitForIdle(nm.getHandler()); + return nm; } - NetworkMonitor makeMonitor() { - return new NetworkMonitor( - mContext, mHandler, mAgent, mRequest, mLogger, mDependencies); + private NetworkMonitor makeMonitor() { + final NetworkMonitor nm = new NetworkMonitor( + mContext, mCallbacks, mNetwork, mRequest, mLogger, mValidationLogger, + mDependencies); + nm.start(); + waitForIdle(nm.getHandler()); + return nm; + } + + private void waitForIdle(Handler handler) { + final ConditionVariable cv = new ConditionVariable(false); + handler.post(cv::open); + if (!cv.block(HANDLER_TIMEOUT_MS)) { + fail("Timed out waiting for handler"); + } } @Test @@ -319,6 +374,15 @@ public class NetworkMonitorTest { } @Test + public void testIsCaptivePortal_IgnorePortals() throws IOException { + setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE); + setSslException(mHttpsConnection); + setPortal302(mHttpConnection); + + assertNotPortal(makeMonitor().isCaptivePortal()); + } + + @Test public void testIsDataStall_EvaluationDisabled() { setDataStallEvaluationType(0); WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor(); @@ -390,6 +454,63 @@ public class NetworkMonitorTest { assertFalse(wrappedMonitor.isDataStall()); } + @Test + public void testBrokenNetworkNotValidated() throws Exception { + setSslException(mHttpsConnection); + setStatus(mHttpConnection, 500); + setStatus(mFallbackConnection, 404); + when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES); + + final NetworkMonitor nm = makeMonitor(); + nm.notifyNetworkConnected(); + + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null); + } + + @Test + public void testNoInternetCapabilityValidated() throws Exception { + when(mCm.getNetworkCapabilities(any())).thenReturn(NO_INTERNET_CAPABILITIES); + + final NetworkMonitor nm = makeMonitor(); + nm.notifyNetworkConnected(); + + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); + verify(mNetwork, never()).openConnection(any()); + } + + @Test + public void testLaunchCaptivePortalApp() throws Exception { + setSslException(mHttpsConnection); + setPortal302(mHttpConnection); + + final NetworkMonitor nm = makeMonitor(); + nm.notifyNetworkConnected(); + + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .showProvisioningNotification(any()); + + // Check that startCaptivePortalApp sends the expected intent. + nm.launchCaptivePortalApp(); + + final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, timeout(HANDLER_TIMEOUT_MS).times(1)) + .startActivityAsUser(intentCaptor.capture(), eq(UserHandle.CURRENT)); + final Intent intent = intentCaptor.getValue(); + assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction()); + final Network network = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK); + assertEquals(TEST_NETID, network.netId); + + // Have the app report that the captive portal is dismissed, and check that we revalidate. + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + final CaptivePortal captivePortal = intent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL); + captivePortal.reportCaptivePortalDismissed(); + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null); + } + private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) { for (int i = 0; i < count; i++) { wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount( @@ -440,6 +561,11 @@ public class NetworkMonitorTest { eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs); } + private void setCaptivePortalMode(int mode) { + when(mDependencies.getSetting(any(), + eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode); + } + private void assertPortal(CaptivePortalProbeResult result) { assertTrue(result.isPortal()); assertFalse(result.isFailed()); @@ -459,12 +585,12 @@ public class NetworkMonitorTest { } private void setSslException(HttpURLConnection connection) throws IOException { - when(connection.getResponseCode()).thenThrow(new SSLHandshakeException("Invalid cert")); + doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode(); } private void set302(HttpURLConnection connection, String location) throws IOException { setStatus(connection, 302); - when(connection.getHeaderField(LOCATION_HEADER)).thenReturn(location); + doReturn(location).when(connection).getHeaderField(LOCATION_HEADER); } private void setPortal302(HttpURLConnection connection) throws IOException { @@ -472,7 +598,7 @@ public class NetworkMonitorTest { } private void setStatus(HttpURLConnection connection, int status) throws IOException { - when(connection.getResponseCode()).thenReturn(status); + doReturn(status).when(connection).getResponseCode(); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 419273ee28bb..bcf37ffef262 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1557,6 +1557,10 @@ class SettingsProtoDumpUtil { Settings.Global.ZRAM_ENABLED, GlobalSettingsProto.ZRAM_ENABLED); + dumpSetting(s, p, + Global.APP_OPS_CONSTANTS, + GlobalSettingsProto.APP_OPS_CONSTANTS); + p.end(token); // Please insert new settings using the same order as in GlobalSettingsProto. diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index bff2c844bb48..fa95bf2ee302 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -134,6 +134,8 @@ <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> <uses-permission android:name="android.permission.MANAGE_AUTO_FILL" /> <uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" /> + <uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" /> + <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" /> <uses-permission android:name="android.permission.NETWORK_SETTINGS" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.SET_TIME" /> @@ -162,6 +164,8 @@ <uses-permission android:name="android.permission.SUSPEND_APPS" /> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> + <uses-permission android:name="android.permission.MANAGE_APPOPS" /> + <application android:label="@string/app_label" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true"> diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml index d04155618781..0c6d57dd6183 100644 --- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml +++ b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml @@ -18,7 +18,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="?android:attr/colorBackgroundFloating" /> - <corners android:radius="1dp" + <corners android:topLeftRadius="@dimen/biometric_dialog_corner_size" android:topRightRadius="@dimen/biometric_dialog_corner_size" android:bottomLeftRadius="@dimen/biometric_dialog_corner_size" diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml index 2000104ad0cd..74002ac38c02 100644 --- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml +++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml @@ -51,11 +51,4 @@ android:layout_width="wrap_content" android:paddingEnd="2dp" /> - <TextView - android:id="@+id/batteryRemainingText" - android:textAppearance="@style/TextAppearance.QS.TileLabel" - android:layout_height="match_parent" - android:layout_width="wrap_content" - android:gravity="center_vertical" /> - </LinearLayout> diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml index 307b5389521a..5bcc1b3b49bf 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml @@ -39,7 +39,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/monitoring_title_device_owned" - style="@android:style/TextAppearance.Material.Title" + style="@style/TextAppearance.DeviceManagementDialog.Title" android:textColor="?android:attr/textColorPrimary" android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding" /> @@ -64,7 +64,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/monitoring_subtitle_ca_certificate" - style="@android:style/TextAppearance.Material.Title" + style="@style/TextAppearance.DeviceManagementDialog.Title" android:textColor="?android:attr/textColorPrimary" android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding" /> @@ -89,7 +89,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/monitoring_subtitle_network_logging" - style="@android:style/TextAppearance.Material.Title" + style="@style/TextAppearance.DeviceManagementDialog.Title" android:textColor="?android:attr/textColorPrimary" android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding" /> @@ -114,7 +114,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/monitoring_subtitle_vpn" - style="@android:style/TextAppearance.Material.Title" + style="@style/TextAppearance.DeviceManagementDialog.Title" android:textColor="?android:attr/textColorPrimary" android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding" /> diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml index 22b8d2ff4db0..cd9f780ca249 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml @@ -24,6 +24,7 @@ android:clipToPadding="false" android:gravity="center" android:orientation="horizontal" + android:clickable="true" android:paddingStart="@dimen/status_bar_padding_start" android:paddingEnd="@dimen/status_bar_padding_end" > @@ -63,11 +64,5 @@ <include layout="@layout/ongoing_privacy_chip" /> - <com.android.systemui.BatteryMeterView - android:id="@+id/battery" - android:layout_height="match_parent" - android:layout_width="wrap_content" - android:gravity="center_vertical|end" - android:layout_gravity="center_vertical|end" /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml deleted file mode 100644 index cfa372b8fc2e..000000000000 --- a/packages/SystemUI/res/layout/signal_cluster_view.xml +++ /dev/null @@ -1,129 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** Copyright 2011, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> -<!-- extends LinearLayout --> -<com.android.systemui.statusbar.SignalClusterView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/signal_cluster" - android:layout_height="match_parent" - android:layout_width="wrap_content" - android:gravity="center_vertical" - android:orientation="horizontal" - android:paddingEnd="@dimen/signal_cluster_battery_padding" - > - <ImageView - android:id="@+id/vpn" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:paddingEnd="6dp" - android:src="@drawable/stat_sys_vpn_ic" - android:tint="@color/background_protect_secondary" - android:contentDescription="@string/accessibility_vpn_on" - /> - <FrameLayout - android:id="@+id/ethernet_combo" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - > - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:theme="?attr/lightIconTheme" - android:id="@+id/ethernet" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - /> - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:theme="?attr/darkIconTheme" - android:id="@+id/ethernet_dark" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:alpha="0.0" - /> - </FrameLayout> - <FrameLayout - android:layout_height="17dp" - android:layout_width="wrap_content"> - <ImageView - android:id="@+id/wifi_in" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_activity_down" - android:visibility="gone" - android:paddingEnd="2dp" - /> - <ImageView - android:id="@+id/wifi_out" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_activity_up" - android:paddingEnd="2dp" - android:visibility="gone" - /> - </FrameLayout> - <FrameLayout - android:id="@+id/wifi_combo" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - > - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:theme="?attr/lightIconTheme" - android:id="@+id/wifi_signal" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - /> - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:theme="?attr/darkIconTheme" - android:id="@+id/wifi_signal_dark" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:alpha="0.0" - /> - <ImageView - android:id="@+id/wifi_inout" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - /> - </FrameLayout> - <View - android:id="@+id/wifi_signal_spacer" - android:layout_width="@dimen/status_bar_wifi_signal_spacer_width" - android:layout_height="4dp" - android:visibility="gone" - /> - <ViewStub - android:id="@+id/connected_device_signals_stub" - android:layout="@layout/connected_device_signal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - <LinearLayout - android:id="@+id/mobile_signal_group" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - > - </LinearLayout> - <View - android:id="@+id/wifi_airplane_spacer" - android:layout_width="@dimen/status_bar_airplane_spacer_width" - android:layout_height="4dp" - android:visibility="gone" - /> - <ImageView - android:id="@+id/airplane" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - /> -</com.android.systemui.statusbar.SignalClusterView> diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index 34c208ab81aa..02062bb7b2c4 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -43,6 +43,14 @@ android:visibility="invisible" /> </com.android.systemui.statusbar.BackDropView> + <com.android.systemui.wallpaper.AodMaskView + android:id="@+id/aod_mask" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:importantForAccessibility="no" + android:visibility="invisible" + sysui:ignoreRightInset="true" /> + <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_behind" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index dac20b588dd0..633f8686b804 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -102,12 +102,17 @@ <item type="id" name="action_snooze_assistant_suggestion_1"/> <item type="id" name="action_snooze"/> - <!-- For StatusBarIconContainer to tag its icon views --> + <!-- For StatusIconContainer to tag its icon views --> <item type="id" name="status_bar_view_state_tag" /> <item type="id" name="display_cutout" /> <!-- Optional cancel button on Keyguard --> <item type="id" name="cancel_button"/> + + <!-- AodMaskView transition tag --> + <item type="id" name="aod_mask_transition_progress_tag" /> + <item type="id" name="aod_mask_transition_progress_end_tag" /> + <item type="id" name="aod_mask_transition_progress_start_tag" /> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 87155c4d41a2..22a0b3642a35 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -265,6 +265,10 @@ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> + <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"> + <item name="android:gravity">center</item> + </style> + <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index f3bdbae97e42..078947ca0468 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -76,4 +76,10 @@ interface ISystemUiProxy { */ float getWindowCornerRadius() = 10; + /** + * If device supports live rounded corners on windows. + * This might be turned off for performance reasons + */ + boolean supportsRoundedCornersOnWindows() = 11; + } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java index c0a1d891cd5b..e6acfbe8df53 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java @@ -16,14 +16,10 @@ package com.android.systemui.shared.system; +import android.graphics.HardwareRenderer; import android.graphics.Matrix; import android.graphics.Rect; import android.view.Surface; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; -import android.view.SyncRtSurfaceTransactionApplier; -import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; -import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewRootImpl; @@ -31,20 +27,21 @@ import java.util.function.Consumer; /** * Helper class to apply surface transactions in sync with RenderThread. + * + * NOTE: This is a modification of {@link android.view.SyncRtSurfaceTransactionApplier}, we can't + * currently reference that class from the shared lib as it is hidden. */ public class SyncRtSurfaceTransactionApplierCompat { - private final SyncRtSurfaceTransactionApplier mApplier; + private final Surface mTargetSurface; + private final ViewRootImpl mTargetViewRootImpl; /** * @param targetView The view in the surface that acts as synchronization anchor. */ public SyncRtSurfaceTransactionApplierCompat(View targetView) { - mApplier = new SyncRtSurfaceTransactionApplier(targetView); - } - - private SyncRtSurfaceTransactionApplierCompat(SyncRtSurfaceTransactionApplier applier) { - mApplier = applier; + mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; + mTargetSurface = mTargetViewRootImpl != null ? mTargetViewRootImpl.mSurface : null; } /** @@ -53,38 +50,74 @@ public class SyncRtSurfaceTransactionApplierCompat { * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into * this method to avoid synchronization issues. */ - public void scheduleApply(final SurfaceParams... params) { - mApplier.scheduleApply(convert(params)); - } - - private SyncRtSurfaceTransactionApplier.SurfaceParams[] convert(SurfaceParams[] params) { - SyncRtSurfaceTransactionApplier.SurfaceParams[] result = - new SyncRtSurfaceTransactionApplier.SurfaceParams[params.length]; - for (int i = 0; i < params.length; i++) { - result[i] = params[i].mParams; + public void scheduleApply(final SyncRtSurfaceTransactionApplierCompat.SurfaceParams... params) { + if (mTargetViewRootImpl == null) { + return; } - return result; + mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() { + @Override + public void onFrameDraw(long frame) { + if (mTargetSurface == null || !mTargetSurface.isValid()) { + return; + } + TransactionCompat t = new TransactionCompat(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = + params[i]; + SurfaceControlCompat surface = surfaceParams.surface; + t.deferTransactionUntil(surface, mTargetSurface, frame); + applyParams(t, surfaceParams); + } + t.setEarlyWakeup(); + t.apply(); + } + }); + + // Make sure a frame gets scheduled. + mTargetViewRootImpl.getView().invalidate(); } - public static void applyParams(TransactionCompat t, SurfaceParams params) { - SyncRtSurfaceTransactionApplier.applyParams(t.mTransaction, params.mParams, t.mTmpValues); + public static void applyParams(TransactionCompat t, + SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) { + t.setMatrix(params.surface, params.matrix); + t.setWindowCrop(params.surface, params.windowCrop); + t.setAlpha(params.surface, params.alpha); + t.setLayer(params.surface, params.layer); + t.setCornerRadius(params.surface, params.cornerRadius); + t.show(params.surface); } + /** + * Creates an instance of SyncRtSurfaceTransactionApplier, deferring until the target view is + * attached if necessary. + */ public static void create(final View targetView, final Consumer<SyncRtSurfaceTransactionApplierCompat> callback) { - SyncRtSurfaceTransactionApplier.create(targetView, - new Consumer<SyncRtSurfaceTransactionApplier>() { - @Override - public void accept(SyncRtSurfaceTransactionApplier applier) { - callback.accept(new SyncRtSurfaceTransactionApplierCompat(applier)); - } - }); + if (targetView == null) { + // No target view, no applier + callback.accept(null); + } else if (targetView.getViewRootImpl() != null) { + // Already attached, we're good to go + callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); + } else { + // Haven't been attached before we can get the view root + targetView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + targetView.removeOnAttachStateChangeListener(this); + callback.accept(new SyncRtSurfaceTransactionApplierCompat(targetView)); + } + + @Override + public void onViewDetachedFromWindow(View v) { + // Do nothing + } + }); + } } public static class SurfaceParams { - private final SyncRtSurfaceTransactionApplier.SurfaceParams mParams; - /** * Constructs surface parameters to be applied when the current view state gets pushed to * RenderThread. @@ -96,8 +129,19 @@ public class SyncRtSurfaceTransactionApplierCompat { */ public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix, Rect windowCrop, int layer, float cornerRadius) { - mParams = new SyncRtSurfaceTransactionApplier.SurfaceParams(surface.mSurfaceControl, - alpha, matrix, windowCrop, layer, cornerRadius); + this.surface = surface; + this.alpha = alpha; + this.matrix = new Matrix(matrix); + this.windowCrop = new Rect(windowCrop); + this.layer = layer; + this.cornerRadius = cornerRadius; } + + public final SurfaceControlCompat surface; + public final float alpha; + final float cornerRadius; + public final Matrix matrix; + public final Rect windowCrop; + public final int layer; } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java index 2aba3fa607b7..af32f48936f2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java @@ -82,6 +82,11 @@ public class TransactionCompat { return this; } + public TransactionCompat setCornerRadius(SurfaceControlCompat surfaceControl, float radius) { + mTransaction.setCornerRadius(surfaceControl.mSurfaceControl, radius); + return this; + } + public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl, IBinder handle, long frameNumber) { mTransaction.deferTransactionUntil(surfaceControl.mSurfaceControl, handle, frameNumber); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 27d624ab0b58..17cc1d57582c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -14,13 +14,13 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; 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; /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. @@ -47,43 +47,19 @@ public class KeyguardClockSwitch extends RelativeLayout { * or not to show it below the alternate clock. */ 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; - private final PluginListener<ClockPlugin> mClockPluginListener = - new PluginListener<ClockPlugin>() { - @Override - public void onPluginConnected(ClockPlugin plugin, Context pluginContext) { - disconnectPlugin(); - View smallClockView = plugin.getView(); - if (smallClockView != null) { - // For now, assume that the most recently connected plugin is the - // selected clock face. In the future, the user should be able to - // pick a clock face from the available plugins. - mSmallClockFrame.addView(smallClockView, -1, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - initPluginParams(); - mClockView.setVisibility(View.GONE); - } - View bigClockView = plugin.getBigClockView(); - if (bigClockView != null && mBigClockContainer != null) { - mBigClockContainer.addView(bigClockView); - mBigClockContainer.setVisibility(View.VISIBLE); - } - if (!plugin.shouldShowStatusArea()) { - mKeyguardStatusArea.setVisibility(View.GONE); - } - mClockPlugin = plugin; - } - - @Override - public void onPluginDisconnected(ClockPlugin plugin) { - if (Objects.equals(plugin, mClockPlugin)) { - disconnectPlugin(); - mClockView.setVisibility(View.VISIBLE); - mKeyguardStatusArea.setVisibility(View.VISIBLE); - } - } - }; private final StatusBarStateController.StateListener mStateListener = new StatusBarStateController.StateListener() { @Override @@ -122,18 +98,62 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - Dependency.get(PluginManager.class).addPluginListener(mClockPluginListener, - ClockPlugin.class); + mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class) + .withPlugin(ClockPlugin.class) + .withCallback(mClockPluginConsumer) + .build(); Dependency.get(StatusBarStateController.class).addCallback(mStateListener); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(mClockPluginListener); + mClockExtension.destroy(); Dependency.get(StatusBarStateController.class).removeCallback(mStateListener); } + private void setClockPlugin(ClockPlugin plugin) { + // Disconnect from existing plugin. + if (mClockPlugin != null) { + View smallClockView = mClockPlugin.getView(); + if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) { + mSmallClockFrame.removeView(smallClockView); + } + if (mBigClockContainer != null) { + mBigClockContainer.removeAllViews(); + mBigClockContainer.setVisibility(View.GONE); + } + mClockPlugin = null; + } + if (plugin == null) { + mClockView.setVisibility(View.VISIBLE); + mKeyguardStatusArea.setVisibility(View.VISIBLE); + return; + } + // Attach small and big clock views to hierarchy. + View smallClockView = plugin.getView(); + if (smallClockView != null) { + mSmallClockFrame.addView(smallClockView, -1, + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + mClockView.setVisibility(View.GONE); + } + View bigClockView = plugin.getBigClockView(); + if (bigClockView != null && mBigClockContainer != null) { + mBigClockContainer.addView(bigClockView); + mBigClockContainer.setVisibility(View.VISIBLE); + } + // Hide default clock. + if (!plugin.shouldShowStatusArea()) { + mKeyguardStatusArea.setVisibility(View.GONE); + } + // Initialize plugin parameters. + mClockPlugin = plugin; + mClockPlugin.setStyle(getPaint().getStyle()); + mClockPlugin.setTextColor(getCurrentTextColor()); + mClockPlugin.setDarkAmount(mDarkAmount); + } + /** * Set container for big clock face appearing behind NSSL and KeyguardStatusView. */ @@ -193,6 +213,7 @@ public class KeyguardClockSwitch extends RelativeLayout { * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. */ public void setDarkAmount(float darkAmount) { + mDarkAmount = darkAmount; if (mClockPlugin != null) { mClockPlugin.setDarkAmount(darkAmount); } @@ -232,33 +253,9 @@ public class KeyguardClockSwitch extends RelativeLayout { } } - /** - * When plugin changes, set all kept parameters into newer plugin. - */ - private void initPluginParams() { - if (mClockPlugin != null) { - mClockPlugin.setStyle(getPaint().getStyle()); - mClockPlugin.setTextColor(getCurrentTextColor()); - } - } - - private void disconnectPlugin() { - if (mClockPlugin != null) { - View smallClockView = mClockPlugin.getView(); - if (smallClockView != null) { - mSmallClockFrame.removeView(smallClockView); - } - if (mBigClockContainer != null) { - mBigClockContainer.removeAllViews(); - mBigClockContainer.setVisibility(View.GONE); - } - mClockPlugin = null; - } - } - @VisibleForTesting (otherwise = VisibleForTesting.NONE) - PluginListener getClockPluginListener() { - return mClockPluginListener; + Consumer<ClockPlugin> getClockPluginConsumer() { + return mClockPluginConsumer; } @VisibleForTesting (otherwise = VisibleForTesting.NONE) diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 6864ea185834..200679432200 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -40,7 +40,6 @@ import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; @@ -56,7 +55,6 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.IconLogger; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.Utils.DisableStateTracker; @@ -71,11 +69,12 @@ public class BatteryMeterView extends LinearLayout implements @Retention(SOURCE) - @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF}) + @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE}) public @interface BatteryPercentMode {} public static final int MODE_DEFAULT = 0; public static final int MODE_ON = 1; public static final int MODE_OFF = 2; + public static final int MODE_ESTIMATE = 3; private final BatteryMeterDrawableBase mDrawable; private final String mSlotBattery; @@ -93,6 +92,7 @@ public class BatteryMeterView extends LinearLayout implements // Some places may need to show the battery conditionally, and not obey the tuner private boolean mIgnoreTunerUpdates; private boolean mIsSubscribedForTunerUpdates; + private boolean mCharging; private int mDarkModeBackgroundColor; private int mDarkModeFillColor; @@ -276,9 +276,6 @@ public class BatteryMeterView extends LinearLayout implements public void onTuningChanged(String key, String newValue) { if (StatusBarIconController.ICON_BLACKLIST.equals(key)) { ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue); - boolean hidden = icons.contains(mSlotBattery); - Dependency.get(IconLogger.class).onIconVisibility(mSlotBattery, !hidden); - setVisibility(hidden ? View.GONE : View.VISIBLE); } } @@ -308,6 +305,7 @@ public class BatteryMeterView extends LinearLayout implements public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { mDrawable.setBatteryLevel(level); mDrawable.setCharging(pluggedIn); + mCharging = pluggedIn; mLevel = level; updatePercentText(); setContentDescription( @@ -337,9 +335,19 @@ public class BatteryMeterView extends LinearLayout implements } private void updatePercentText() { + if (mBatteryController == null) { + return; + } + if (mBatteryPercentView != null) { - mBatteryPercentView.setText( - NumberFormat.getPercentInstance().format(mLevel / 100f)); + if (mShowPercentMode == MODE_ESTIMATE && !mCharging) { + mBatteryController.getEstimatedTimeRemainingString((String estimate) -> { + mBatteryPercentView.setText(estimate); + }); + } else { + mBatteryPercentView.setText( + NumberFormat.getPercentInstance().format(mLevel / 100f)); + } } } @@ -350,7 +358,7 @@ public class BatteryMeterView extends LinearLayout implements SHOW_BATTERY_PERCENT, 0, mUser); if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF) - || mShowPercentMode == MODE_ON) { + || mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) { if (!showing) { mBatteryPercentView = loadPercentView(); if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 9d810645f6a2..ec6ecc64d07e 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -86,7 +86,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.IconLogger; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; @@ -217,7 +216,6 @@ public class Dependency extends SystemUI { @Inject Lazy<LeakDetector> mLeakDetector; @Inject Lazy<LeakReporter> mLeakReporter; @Inject Lazy<GarbageMonitor> mGarbageMonitor; - @Inject Lazy<IconLogger> mIconLogger; @Inject Lazy<TunerService> mTunerService; @Inject Lazy<StatusBarWindowController> mStatusBarWindowController; @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher; @@ -392,8 +390,6 @@ public class Dependency extends SystemUI { mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get); - mProviders.put(IconLogger.class, mIconLogger::get); - mProviders.put(LightBarController.class, mLightBarController::get); mProviders.put(IWindowManager.class, mIWindowManager::get); diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java index f324a05bafff..ce9c637cebf6 100644 --- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java +++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java @@ -46,8 +46,6 @@ import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FlashlightControllerImpl; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotControllerImpl; -import com.android.systemui.statusbar.policy.IconLogger; -import com.android.systemui.statusbar.policy.IconLoggerImpl; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.LocationController; @@ -133,11 +131,6 @@ public abstract class DependencyBinder { /** */ @Binds - public abstract IconLogger provideIconLogger(IconLoggerImpl loggerImpl); - - /** - */ - @Binds public abstract CastController provideCastController(CastControllerImpl controllerImpl); /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index 3167b9e0a458..016b8fe93093 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -33,9 +33,6 @@ import com.android.internal.os.SomeArgs; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.CommandQueue; -import java.util.HashMap; -import java.util.Map; - /** * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g. * BiometricDialogView). @@ -52,10 +49,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private static final int MSG_BUTTON_NEGATIVE = 6; private static final int MSG_USER_CANCELED = 7; private static final int MSG_BUTTON_POSITIVE = 8; - private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9; - private static final int MSG_TRY_AGAIN_PRESSED = 10; + private static final int MSG_TRY_AGAIN_PRESSED = 9; - private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view private SomeArgs mCurrentDialogArgs; private BiometricDialogView mCurrentDialog; private WindowManager mWindowManager; @@ -63,21 +58,22 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private boolean mDialogShowing; private Callback mCallback = new Callback(); - private boolean mTryAgainShowing; // No good place to save state before config change :/ - private boolean mConfirmShowing; // No good place to save state before config change :/ - private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what) { case MSG_SHOW_DIALOG: - handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */); + handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */, + null /* savedState */); break; case MSG_BIOMETRIC_AUTHENTICATED: - handleBiometricAuthenticated(); + handleBiometricAuthenticated((boolean) msg.obj); break; case MSG_BIOMETRIC_HELP: - handleBiometricHelp((String) msg.obj); + SomeArgs args = (SomeArgs) msg.obj; + handleBiometricHelp((String) args.arg1 /* message */, + (boolean) args.arg2 /* requireTryAgain */); + args.recycle(); break; case MSG_BIOMETRIC_ERROR: handleBiometricError((String) msg.obj); @@ -94,9 +90,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba case MSG_BUTTON_POSITIVE: handleButtonPositive(); break; - case MSG_BIOMETRIC_SHOW_TRY_AGAIN: - handleShowTryAgain(); - break; case MSG_TRY_AGAIN_PRESSED: handleTryAgainPressed(); break; @@ -137,30 +130,22 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba @Override public void start() { - createDialogs(); - - if (!mDialogs.isEmpty()) { + final PackageManager pm = mContext.getPackageManager(); + if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) + || pm.hasSystemFeature(PackageManager.FEATURE_FACE) + || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) { getComponent(CommandQueue.class).addCallback(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } } - private void createDialogs() { - final PackageManager pm = mContext.getPackageManager(); - mDialogs = new HashMap<>(); - if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) { - mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback)); - } - if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT, - new FingerprintDialogView(mContext, mCallback)); - } - } - @Override public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { - if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type); + if (DEBUG) { + Log.d(TAG, "showBiometricDialog, type: " + type + + ", requireConfirmation: " + requireConfirmation); + } // Remove these messages as they are part of the previous client mHandler.removeMessages(MSG_BIOMETRIC_ERROR); mHandler.removeMessages(MSG_BIOMETRIC_HELP); @@ -176,15 +161,18 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } @Override - public void onBiometricAuthenticated() { - if (DEBUG) Log.d(TAG, "onBiometricAuthenticated"); - mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); + public void onBiometricAuthenticated(boolean authenticated) { + if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated); + mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget(); } @Override public void onBiometricHelp(String message) { if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message); - mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget(); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = message; + args.arg2 = false; // requireTryAgain + mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget(); } @Override @@ -199,16 +187,21 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget(); } - @Override - public void showBiometricTryAgain() { - if (DEBUG) Log.d(TAG, "showBiometricTryAgain"); - mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget(); - } - - private void handleShowDialog(SomeArgs args, boolean skipAnimation) { + private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { mCurrentDialogArgs = args; final int type = args.argi1; - mCurrentDialog = mDialogs.get(type); + + if (type == BiometricAuthenticator.TYPE_FINGERPRINT) { + mCurrentDialog = new FingerprintDialogView(mContext, mCallback); + } else if (type == BiometricAuthenticator.TYPE_FACE) { + mCurrentDialog = new FaceDialogView(mContext, mCallback); + } else { + Log.e(TAG, "Unsupported type: " + type); + } + + if (savedState != null) { + mCurrentDialog.restoreState(savedState); + } if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: " + mCurrentDialog.isAnimatingAway() + " type: " + type); @@ -224,32 +217,36 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mCurrentDialog.setRequireConfirmation((boolean) args.arg3); mCurrentDialog.setUserId(args.argi2); mCurrentDialog.setSkipIntro(skipAnimation); - mCurrentDialog.setPendingTryAgain(mTryAgainShowing); - mCurrentDialog.setPendingConfirm(mConfirmShowing); mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); mDialogShowing = true; } - private void handleBiometricAuthenticated() { - if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated"); - - mCurrentDialog.announceForAccessibility( - mContext.getResources() - .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); - if (mCurrentDialog.requiresConfirmation()) { - mConfirmShowing = true; - mCurrentDialog.showConfirmationButton(true /* show */); + private void handleBiometricAuthenticated(boolean authenticated) { + if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated); + + if (authenticated) { + mCurrentDialog.announceForAccessibility( + mContext.getResources() + .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); + if (mCurrentDialog.requiresConfirmation()) { + mCurrentDialog.showConfirmationButton(true /* show */); + } else { + mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED); + mHandler.postDelayed(() -> { + handleHideDialog(false /* userCanceled */); + }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs()); + } } else { - mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED); - mHandler.postDelayed(() -> { - handleHideDialog(false /* userCanceled */); - }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs()); + handleBiometricHelp(mContext.getResources() + .getString(com.android.internal.R.string.biometric_not_recognized), + true /* requireTryAgain */); + mCurrentDialog.showTryAgainButton(true /* show */); } } - private void handleBiometricHelp(String message) { + private void handleBiometricHelp(String message, boolean requireTryAgain) { if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message); - mCurrentDialog.showHelpMessage(message); + mCurrentDialog.showHelpMessage(message, requireTryAgain); } private void handleBiometricError(String error) { @@ -258,7 +255,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba if (DEBUG) Log.d(TAG, "Dialog already dismissed"); return; } - mTryAgainShowing = false; mCurrentDialog.showErrorMessage(error); } @@ -279,8 +275,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } mReceiver = null; mDialogShowing = false; - mConfirmShowing = false; - mTryAgainShowing = false; mCurrentDialog.startDismiss(); } @@ -294,7 +288,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling negative button", e); } - mTryAgainShowing = false; handleHideDialog(false /* userCanceled */); } @@ -308,25 +301,16 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling positive button", e); } - mConfirmShowing = false; handleHideDialog(false /* userCanceled */); } private void handleUserCanceled() { - mTryAgainShowing = false; - mConfirmShowing = false; handleHideDialog(true /* userCanceled */); } - private void handleShowTryAgain() { - mCurrentDialog.showTryAgainButton(true /* show */); - mTryAgainShowing = true; - } - private void handleTryAgainPressed() { try { mCurrentDialog.clearTemporaryMessage(); - mTryAgainShowing = false; mReceiver.onTryAgainPressed(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when handling try again", e); @@ -337,13 +321,20 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); final boolean wasShowing = mDialogShowing; + + // Save the state of the current dialog (buttons showing, etc) + final Bundle savedState = new Bundle(); + if (mCurrentDialog != null) { + mCurrentDialog.onSaveState(savedState); + } + if (mDialogShowing) { mCurrentDialog.forceRemove(); mDialogShowing = false; } - createDialogs(); + if (wasShowing) { - handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */); + handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 9934bfd11f12..c92767763cb4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -56,12 +56,15 @@ public abstract class BiometricDialogView extends LinearLayout { private static final String TAG = "BiometricDialogView"; + private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility"; + private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility"; + private static final int ANIMATION_DURATION_SHOW = 250; // ms private static final int ANIMATION_DURATION_AWAY = 350; // ms private static final int MSG_CLEAR_MESSAGE = 1; - protected static final int STATE_NONE = 0; + protected static final int STATE_IDLE = 0; protected static final int STATE_AUTHENTICATING = 1; protected static final int STATE_ERROR = 2; protected static final int STATE_PENDING_CONFIRMATION = 3; @@ -74,16 +77,24 @@ public abstract class BiometricDialogView extends LinearLayout { private final DevicePolicyManager mDevicePolicyManager; private final float mAnimationTranslationOffset; private final int mErrorColor; - private final int mTextColor; private final float mDialogWidth; private final DialogViewCallback mCallback; - private ViewGroup mLayout; - private final Button mPositiveButton; - private final Button mNegativeButton; - private final TextView mErrorText; + protected final ViewGroup mLayout; + protected final LinearLayout mDialog; + protected final TextView mTitleText; + protected final TextView mSubtitleText; + protected final TextView mDescriptionText; + protected final ImageView mBiometricIcon; + protected final TextView mErrorText; + protected final Button mPositiveButton; + protected final Button mNegativeButton; + protected final Button mTryAgainButton; + + protected final int mTextColor; + private Bundle mBundle; - private final LinearLayout mDialog; + private int mLastState; private boolean mAnimatingAway; private boolean mWasForceRemoved; @@ -91,15 +102,14 @@ public abstract class BiometricDialogView extends LinearLayout { protected boolean mRequireConfirmation; private int mUserId; // used to determine if we should show work background - private boolean mPendingShowTryAgain; - private boolean mPendingShowConfirm; - protected abstract int getHintStringResourceId(); protected abstract int getAuthenticatedAccessibilityResourceId(); protected abstract int getIconDescriptionResourceId(); protected abstract Drawable getAnimationForTransition(int oldState, int newState); protected abstract boolean shouldAnimateForTransition(int oldState, int newState); protected abstract int getDelayAfterAuthenticatedDurationMs(); + protected abstract boolean shouldGrayAreaDismissDialog(); + protected abstract void handleClearMessage(boolean requireTryAgain); private final Runnable mShowAnimationRunnable = new Runnable() { @Override @@ -124,7 +134,7 @@ public abstract class BiometricDialogView extends LinearLayout { public void handleMessage(Message msg) { switch(msg.what) { case MSG_CLEAR_MESSAGE: - handleClearMessage(); + handleClearMessage((boolean) msg.obj /* requireTryAgain */); break; default: Log.e(TAG, "Unhandled message: " + msg.what); @@ -158,10 +168,6 @@ public abstract class BiometricDialogView extends LinearLayout { mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false); addView(mLayout); - mDialog = mLayout.findViewById(R.id.dialog); - - mErrorText = mLayout.findViewById(R.id.error); - mLayout.setOnKeyListener(new View.OnKeyListener() { boolean downPressed = false; @Override @@ -184,12 +190,19 @@ public abstract class BiometricDialogView extends LinearLayout { final View space = mLayout.findViewById(R.id.space); final View leftSpace = mLayout.findViewById(R.id.left_space); final View rightSpace = mLayout.findViewById(R.id.right_space); - final ImageView icon = mLayout.findViewById(R.id.biometric_icon); - final Button tryAgain = mLayout.findViewById(R.id.button_try_again); + + mDialog = mLayout.findViewById(R.id.dialog); + mTitleText = mLayout.findViewById(R.id.title); + mSubtitleText = mLayout.findViewById(R.id.subtitle); + mDescriptionText = mLayout.findViewById(R.id.description); + mBiometricIcon = mLayout.findViewById(R.id.biometric_icon); + mErrorText = mLayout.findViewById(R.id.error); mNegativeButton = mLayout.findViewById(R.id.button2); mPositiveButton = mLayout.findViewById(R.id.button1); + mTryAgainButton = mLayout.findViewById(R.id.button_try_again); - icon.setContentDescription(getResources().getString(getIconDescriptionResourceId())); + mBiometricIcon.setContentDescription( + getResources().getString(getIconDescriptionResourceId())); setDismissesDialog(space); setDismissesDialog(leftSpace); @@ -206,8 +219,9 @@ public abstract class BiometricDialogView extends LinearLayout { }, getDelayAfterAuthenticatedDurationMs()); }); - tryAgain.setOnClickListener((View v) -> { + mTryAgainButton.setOnClickListener((View v) -> { showTryAgainButton(false /* show */); + handleClearMessage(false /* requireTryAgain */); mCallback.onTryAgainPressed(); }); @@ -215,15 +229,17 @@ public abstract class BiometricDialogView extends LinearLayout { mLayout.requestFocus(); } + public void onSaveState(Bundle bundle) { + bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility()); + bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility()); + } + @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mErrorText.setText(getHintStringResourceId()); - final TextView title = mLayout.findViewById(R.id.title); - final TextView subtitle = mLayout.findViewById(R.id.subtitle); - final TextView description = mLayout.findViewById(R.id.description); final ImageView backgroundView = mLayout.findViewById(R.id.background); if (mUserManager.isManagedProfile(mUserId)) { @@ -244,36 +260,34 @@ public abstract class BiometricDialogView extends LinearLayout { mDialog.getLayoutParams().width = (int) mDialogWidth; } - mLastState = STATE_NONE; + mLastState = STATE_IDLE; updateState(STATE_AUTHENTICATING); CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE); - title.setText(titleText); - title.setSelected(true); + mTitleText.setVisibility(View.VISIBLE); + mTitleText.setText(titleText); + mTitleText.setSelected(true); final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); if (TextUtils.isEmpty(subtitleText)) { - subtitle.setVisibility(View.GONE); + mSubtitleText.setVisibility(View.GONE); } else { - subtitle.setVisibility(View.VISIBLE); - subtitle.setText(subtitleText); + mSubtitleText.setVisibility(View.VISIBLE); + mSubtitleText.setText(subtitleText); } final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); if (TextUtils.isEmpty(descriptionText)) { - description.setVisibility(View.GONE); + mDescriptionText.setVisibility(View.GONE); } else { - description.setVisibility(View.VISIBLE); - description.setText(descriptionText); + mDescriptionText.setVisibility(View.VISIBLE); + mDescriptionText.setText(descriptionText); } mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); - showTryAgainButton(mPendingShowTryAgain); - showConfirmationButton(mPendingShowConfirm); - if (mWasForceRemoved || mSkipIntro) { // Show the dialog immediately mLayout.animate().cancel(); @@ -302,8 +316,7 @@ public abstract class BiometricDialogView extends LinearLayout { ? (AnimatedVectorDrawable) icon : null; - final ImageView imageView = getLayout().findViewById(R.id.biometric_icon); - imageView.setImageDrawable(icon); + mBiometricIcon.setImageDrawable(icon); if (animation != null && shouldAnimateForTransition(lastState, newState)) { animation.forceAnimationOnUI(); @@ -314,7 +327,7 @@ public abstract class BiometricDialogView extends LinearLayout { private void setDismissesDialog(View v) { v.setClickable(true); v.setOnTouchListener((View view, MotionEvent event) -> { - if (mLastState != STATE_AUTHENTICATED) { + if (mLastState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) { mCallback.onUserCanceled(); } return true; @@ -331,11 +344,9 @@ public abstract class BiometricDialogView extends LinearLayout { mWindowManager.removeView(BiometricDialogView.this); mAnimatingAway = false; // Set the icons / text back to normal state - handleClearMessage(); + handleClearMessage(false /* requireTryAgain */); showTryAgainButton(false /* show */); - mPendingShowTryAgain = false; - mPendingShowConfirm = false; - updateState(STATE_NONE); + updateState(STATE_IDLE); } }; @@ -412,35 +423,28 @@ public abstract class BiometricDialogView extends LinearLayout { return mLayout; } - // Clears the temporary message and shows the help message. - private void handleClearMessage() { - updateState(STATE_AUTHENTICATING); - mErrorText.setText(getHintStringResourceId()); - mErrorText.setTextColor(mTextColor); - } - // Shows an error/help message - private void showTemporaryMessage(String message) { + private void showTemporaryMessage(String message, boolean requireTryAgain) { mHandler.removeMessages(MSG_CLEAR_MESSAGE); updateState(STATE_ERROR); mErrorText.setText(message); mErrorText.setTextColor(mErrorColor); mErrorText.setContentDescription(message); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE), + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE, requireTryAgain), BiometricPrompt.HIDE_DIALOG_DELAY); } public void clearTemporaryMessage() { mHandler.removeMessages(MSG_CLEAR_MESSAGE); - mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget(); + mHandler.obtainMessage(MSG_CLEAR_MESSAGE, false /* requireTryAgain */).sendToTarget(); } - public void showHelpMessage(String message) { - showTemporaryMessage(message); + public void showHelpMessage(String message, boolean requireTryAgain) { + showTemporaryMessage(message, requireTryAgain); } public void showErrorMessage(String error) { - showTemporaryMessage(error); + showTemporaryMessage(error, false /* requireTryAgain */); showTryAgainButton(false /* show */); mCallback.onErrorShown(); } @@ -459,22 +463,11 @@ public abstract class BiometricDialogView extends LinearLayout { } public void showTryAgainButton(boolean show) { - final Button tryAgain = mLayout.findViewById(R.id.button_try_again); - if (show) { - tryAgain.setVisibility(View.VISIBLE); - } else { - tryAgain.setVisibility(View.GONE); - } - } - - // Set the state before the window is attached, so we know if the dialog should be started - // with or without the button. This is because there's no good onPause signal - public void setPendingTryAgain(boolean show) { - mPendingShowTryAgain = show; } - public void setPendingConfirm(boolean show) { - mPendingShowConfirm = show; + public void restoreState(Bundle bundle) { + mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY)); + mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY)); } public WindowManager.LayoutParams getLayoutParams() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java index de3f9471a6ba..9fba44b76863 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java @@ -16,8 +16,18 @@ package com.android.systemui.biometrics; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Outline; import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewOutlineProvider; import com.android.systemui.R; @@ -28,13 +38,263 @@ import com.android.systemui.R; */ public class FaceDialogView extends BiometricDialogView { + private static final String TAG = "FaceDialogView"; + private static final String KEY_DIALOG_SIZE = "key_dialog_size"; + private static final int HIDE_DIALOG_DELAY = 500; // ms + private static final int IMPLICIT_Y_PADDING = 16; // dp + private static final int GROW_DURATION = 150; // ms + private static final int TEXT_ANIMATE_DISTANCE = 32; // dp + + private static final int SIZE_UNKNOWN = 0; + private static final int SIZE_SMALL = 1; + private static final int SIZE_GROWING = 2; + private static final int SIZE_BIG = 3; + + private int mSize; + private float mIconOriginalY; + private DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider(); + + private final class DialogOutlineProvider extends ViewOutlineProvider { + + float mY; + + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect( + 0 /* left */, + (int) mY, /* top */ + mDialog.getWidth() /* right */, + mDialog.getBottom(), /* bottom */ + getResources().getDimension(R.dimen.biometric_dialog_corner_size)); + } + + int calculateSmall() { + final float padding = dpToPixels(IMPLICIT_Y_PADDING); + return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding; + } + + void setOutlineY(float y) { + mY = y; + } + } public FaceDialogView(Context context, DialogViewCallback callback) { super(context, callback); } + private void updateSize(int newSize) { + final float padding = dpToPixels(IMPLICIT_Y_PADDING); + final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding; + + if (newSize == SIZE_SMALL) { + // These fields are required and/or always hold a spot on the UI, so should be set to + // INVISIBLE so they keep their position + mTitleText.setVisibility(View.INVISIBLE); + mErrorText.setVisibility(View.INVISIBLE); + mNegativeButton.setVisibility(View.INVISIBLE); + + // These fields are optional, so set them to gone or invisible depending on their + // usage. If they're empty, they're already set to GONE in BiometricDialogView. + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setVisibility(View.INVISIBLE); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setVisibility(View.INVISIBLE); + } + + // Move the biometric icon to the small spot + mBiometricIcon.setY(iconSmallPositionY); + + // Clip the dialog to the small size + mDialog.setOutlineProvider(mOutlineProvider); + mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall()); + + mDialog.setClipToOutline(true); + mDialog.invalidateOutline(); + + mSize = newSize; + } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) { + mSize = SIZE_GROWING; + + // Animate the outline + final ValueAnimator outlineAnimator = + ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0); + outlineAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mOutlineProvider.setOutlineY(y); + mDialog.invalidateOutline(); + }); + + // Animate the icon back to original big position + final ValueAnimator iconAnimator = + ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY); + iconAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mBiometricIcon.setY(y); + }); + + // Animate the error text so it slides up with the icon + final ValueAnimator textSlideAnimator = + ValueAnimator.ofFloat(dpToPixels(TEXT_ANIMATE_DISTANCE), 0); + textSlideAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mErrorText.setTranslationY(y); + }); + + // Opacity animator for things that should fade in (title, subtitle, details, negative + // button) + final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1); + opacityAnimator.addUpdateListener((animation) -> { + final float opacity = (float) animation.getAnimatedValue(); + + // These fields are required and/or always hold a spot on the UI + mTitleText.setAlpha(opacity); + mErrorText.setAlpha(opacity); + mNegativeButton.setAlpha(opacity); + mTryAgainButton.setAlpha(opacity); + + // These fields are optional, so only animate them if they're supposed to be showing + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setAlpha(opacity); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setAlpha(opacity); + } + }); + + // Choreograph together + final AnimatorSet as = new AnimatorSet(); + as.setDuration(GROW_DURATION); + as.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + // Set the visibility of opacity-animating views back to VISIBLE + mTitleText.setVisibility(View.VISIBLE); + mErrorText.setVisibility(View.VISIBLE); + mNegativeButton.setVisibility(View.VISIBLE); + mTryAgainButton.setVisibility(View.VISIBLE); + + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setVisibility(View.VISIBLE); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setVisibility(View.VISIBLE); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mSize = SIZE_BIG; + } + }); + as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator) + .with(textSlideAnimator); + as.start(); + } else if (mSize == SIZE_BIG) { + mDialog.setClipToOutline(false); + mDialog.invalidateOutline(); + + mBiometricIcon.setY(mIconOriginalY); + + mSize = newSize; + } + } + + @Override + public void onSaveState(Bundle bundle) { + super.onSaveState(bundle); + bundle.putInt(KEY_DIALOG_SIZE, mSize); + } + + + @Override + protected void handleClearMessage(boolean requireTryAgain) { + // Clears the temporary message and shows the help message. If requireTryAgain is true, + // we will start the authenticating state again. + if (!requireTryAgain) { + updateState(STATE_AUTHENTICATING); + mErrorText.setText(getHintStringResourceId()); + mErrorText.setTextColor(mTextColor); + mErrorText.setVisibility(View.VISIBLE); + } else { + updateState(STATE_IDLE); + mErrorText.setVisibility(View.INVISIBLE); + } + } + + @Override + public void restoreState(Bundle bundle) { + super.restoreState(bundle); + // Keep in mind that this happens before onAttachedToWindow() + mSize = bundle.getInt(KEY_DIALOG_SIZE); + } + + /** + * Do small/big layout here instead of onAttachedToWindow, since: + * 1) We need the big layout to be measured, etc for small -> big animation + * 2) We need the dialog measurements to know where to move the biometric icon to + * + * BiometricDialogView already sets the views to their default big state, so here we only + * need to hide the ones that are unnecessary. + */ + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mIconOriginalY == 0) { + mIconOriginalY = mBiometricIcon.getY(); + } + + // UNKNOWN means size hasn't been set yet. First time we create the dialog. + // onLayout can happen when visibility of views change (during animation, etc). + if (mSize != SIZE_UNKNOWN) { + // Probably not the cleanest way to do this, but since dialog is big by default, + // and small dialogs can persist across orientation changes, we need to set it to + // small size here again. + if (mSize == SIZE_SMALL) { + updateSize(SIZE_SMALL); + } + return; + } + + // If we don't require confirmation, show the small dialog first (until errors occur). + if (!requiresConfirmation()) { + updateSize(SIZE_SMALL); + } else { + updateSize(SIZE_BIG); + } + } + + @Override + public void showErrorMessage(String error) { + super.showErrorMessage(error); + + // All error messages will cause the dialog to go from small -> big. Error messages + // are messages such as lockout, auth failed, etc. + if (mSize == SIZE_SMALL) { + updateSize(SIZE_BIG); + } + } + + @Override + public void showTryAgainButton(boolean show) { + if (show && mSize == SIZE_SMALL) { + // Do not call super, we will nicely animate the alpha together with the rest + // of the elements in here. + updateSize(SIZE_BIG); + } else { + if (show) { + mTryAgainButton.setVisibility(View.VISIBLE); + } else { + mTryAgainButton.setVisibility(View.GONE); + } + } + } + @Override protected int getHintStringResourceId() { return R.string.face_dialog_looking_for_face; @@ -56,7 +316,9 @@ public class FaceDialogView extends BiometricDialogView { @Override protected boolean shouldAnimateForTransition(int oldState, int newState) { - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_ERROR && newState == STATE_IDLE) { + return true; + } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { return false; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { return true; @@ -78,9 +340,19 @@ public class FaceDialogView extends BiometricDialogView { } @Override + protected boolean shouldGrayAreaDismissDialog() { + if (mSize == SIZE_SMALL) { + return false; + } + return true; + } + + @Override protected Drawable getAnimationForTransition(int oldState, int newState) { int iconRes; - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_ERROR && newState == STATE_IDLE) { + iconRes = R.drawable.face_dialog_error_to_face; + } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.face_dialog_face_to_error; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { iconRes = R.drawable.face_dialog_face_to_error; @@ -97,4 +369,14 @@ public class FaceDialogView extends BiometricDialogView { } return mContext.getDrawable(iconRes); } + + private float dpToPixels(float dp) { + return dp * ((float) mContext.getResources().getDisplayMetrics().densityDpi + / DisplayMetrics.DENSITY_DEFAULT); + } + + private float pixelsToDp(float pixels) { + return pixels / ((float) mContext.getResources().getDisplayMetrics().densityDpi + / DisplayMetrics.DENSITY_DEFAULT); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java index 1a6cee281c84..c9b30ba3ce8d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java @@ -32,6 +32,14 @@ public class FingerprintDialogView extends BiometricDialogView { DialogViewCallback callback) { super(context, callback); } + + @Override + protected void handleClearMessage(boolean requireTryAgain) { + updateState(STATE_AUTHENTICATING); + mErrorText.setText(getHintStringResourceId()); + mErrorText.setTextColor(mTextColor); + } + @Override protected int getHintStringResourceId() { return R.string.fingerprint_dialog_touch_sensor; @@ -49,7 +57,7 @@ public class FingerprintDialogView extends BiometricDialogView { @Override protected boolean shouldAnimateForTransition(int oldState, int newState) { - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { return false; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { return true; @@ -68,9 +76,15 @@ public class FingerprintDialogView extends BiometricDialogView { } @Override + protected boolean shouldGrayAreaDismissDialog() { + // Fingerprint dialog always dismisses when region outside the dialog is tapped + return true; + } + + @Override protected Drawable getAnimationForTransition(int oldState, int newState) { int iconRes; - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 323cf1fe7022..f14495bb959d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1843,6 +1843,13 @@ public class KeyguardViewMediator extends SystemUI { */ private void handleHide() { Trace.beginSection("KeyguardViewMediator#handleHide"); + + // It's possible that the device was unlocked in a dream state. It's time to wake up. + if (mAodShowing) { + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:BOUNCER_DOZING"); + } + synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleHide"); diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt index 01ee5cabe5a0..6ed1ebad8e51 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt @@ -52,7 +52,8 @@ class OngoingPrivacyDialog constructor( @Suppress("DEPRECATION") override fun onClick(dialog: DialogInterface?, which: Int) { - Dependency.get(ActivityStarter::class.java).startActivity(intent, false) + Dependency.get(ActivityStarter::class.java) + .postStartActivityDismissingKeyguard(intent, 0) } }) } @@ -118,12 +119,13 @@ class OngoingPrivacyDialog constructor( appName.text = app.applicationName if (showIcons) { - dialogBuilder.generateIconsForApp(types).forEach { + dialogBuilder.generateIconsForApp(types).forEachIndexed { index, it -> it.setBounds(0, 0, iconSize, iconSize) val image = ImageView(context).apply { imageTintList = ColorStateList.valueOf(iconColor) setImageDrawable(it) } + image.contentDescription = types[index].getName(context) icons.addView(image, lp) } icons.visibility = View.VISIBLE diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index e030e404af6a..e63f88a898cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -284,10 +284,17 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, public void updateEverything() { post(() -> { updateVisibilities(); + updateClickabilities(); setClickable(false); }); } + private void updateClickabilities() { + mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE); + mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE); + mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE); + } + private void updateVisibilities() { mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 40c60396e40d..75ab5dfd2977 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -141,14 +141,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements private View mStatusSeparator; private ImageView mRingerModeIcon; private TextView mRingerModeTextView; - private BatteryMeterView mBatteryMeterView; private Clock mClockView; private DateView mDateView; private OngoingPrivacyChip mPrivacyChip; private Space mSpace; private BatteryMeterView mBatteryRemainingIcon; - private TextView mBatteryRemainingText; - private boolean mShowBatteryPercentAndEstimate; private PrivacyItemController mPrivacyItemController; /** Counts how many times the long press tooltip has been shown to the user. */ @@ -229,13 +226,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements // Set the correct tint for the status icons so they contrast mIconManager.setTint(fillColor); - mShowBatteryPercentAndEstimate = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_battery_percentage_setting_available); - - mBatteryMeterView = findViewById(R.id.battery); - mBatteryMeterView.setPercentShowMode(mShowBatteryPercentAndEstimate - ? BatteryMeterView.MODE_ON : BatteryMeterView.MODE_OFF); - mBatteryMeterView.setOnClickListener(this); mClockView = findViewById(R.id.clock); mClockView.setOnClickListener(this); mDateView = findViewById(R.id.date); @@ -245,13 +235,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements // Tint for the battery icons are handled in setupHost() mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon); - mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_OFF); // Don't need to worry about tuner settings for this icon mBatteryRemainingIcon.setIgnoreTunerUpdates(true); - - mBatteryRemainingText = findViewById(R.id.batteryRemainingText); - mBatteryRemainingText.setTextColor(fillColor); - updateShowPercent(); } @@ -268,10 +253,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements } private void setChipVisibility(boolean chipVisible) { - mBatteryMeterView.setVisibility(View.VISIBLE); if (chipVisible) { mPrivacyChip.setVisibility(View.VISIBLE); - if (mHasTopCutout) mBatteryMeterView.setVisibility(View.GONE); } else { mPrivacyChip.setVisibility(View.GONE); } @@ -339,7 +322,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements // Update color schemes in landscape to use wallpaperTextColor boolean shouldUseWallpaperTextColor = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; - mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor); mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor); } @@ -415,13 +397,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements .build(); } - private void updateBatteryRemainingText() { - if (!mShowBatteryPercentAndEstimate) { - return; - } - mBatteryRemainingText.setText(mBatteryController.getEstimatedTimeRemainingString()); - } - public void setExpanded(boolean expanded) { if (mExpanded == expanded) return; mExpanded = expanded; @@ -519,7 +494,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements } } mSpace.setLayoutParams(lp); - // Decide whether to show BatteryMeterView setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE); return super.onApplyWindowInsets(insets); } @@ -546,7 +520,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements mAlarmController.addCallback(this); mContext.registerReceiver(mRingerReceiver, new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); - updateBatteryRemainingText(); } else { mZenController.removeCallback(this); mAlarmController.removeCallback(this); @@ -559,9 +532,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements if (v == mClockView) { mActivityStarter.postStartActivityDismissingKeyguard(new Intent( AlarmClock.ACTION_SHOW_ALARMS),0); - } else if (v == mBatteryMeterView) { - mActivityStarter.postStartActivityDismissingKeyguard(new Intent( - Intent.ACTION_POWER_USAGE_SUMMARY),0); } else if (v == mPrivacyChip) { Handler mUiHandler = new Handler(Looper.getMainLooper()); mUiHandler.post(() -> { @@ -699,7 +669,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements } public void updateEverything() { - post(() -> setClickable(false)); + post(() -> setClickable(!mExpanded)); } public void setQSPanel(final QSPanel qsPanel) { @@ -713,9 +683,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this); mHeaderQsPanel.setHost(host, null /* No customization in header */); - // Use SystemUI context to get battery meter colors, and let it use the default tint (white) - mBatteryMeterView.setColorsFromContext(mHost.getContext()); - mBatteryMeterView.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); Rect tintArea = new Rect(0, 0, 0, 0); int colorForeground = Utils.getColorAttrDefaultColor(getContext(), @@ -763,22 +730,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements .getIntForUser(getContext().getContentResolver(), SHOW_BATTERY_PERCENT, 0, ActivityManager.getCurrentUser()); - mShowBatteryPercentAndEstimate = systemSetting; - - updateBatteryViews(); - } - - private void updateBatteryViews() { - if (mShowBatteryPercentAndEstimate) { - mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON); - mBatteryRemainingIcon.setVisibility(View.VISIBLE); - mBatteryRemainingText.setVisibility(View.VISIBLE); - updateBatteryRemainingText(); - } else { - mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_OFF); - mBatteryRemainingIcon.setVisibility(View.GONE); - mBatteryRemainingText.setVisibility(View.GONE); - } + mBatteryRemainingIcon.setPercentShowMode(systemSetting + ? BatteryMeterView.MODE_ESTIMATE : BatteryMeterView.MODE_ON); } private final class PercentSettingObserver extends ContentObserver { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 81757d0aadd4..c474faf6b1e0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -101,6 +101,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private float mBackButtonAlpha; private MotionEvent mStatusBarGestureDownEvent; private float mWindowCornerRadius; + private boolean mSupportsRoundedCornersOnWindows; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -244,6 +245,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public boolean supportsRoundedCornersOnWindows() { + if (!verifyCaller("supportsRoundedCornersOnWindows")) { + return false; + } + long token = Binder.clearCallingIdentity(); + try { + return mSupportsRoundedCornersOnWindows; + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -353,6 +366,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS, getDefaultInteractionFlags()); mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()); + mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils + .supportsRoundedCornersOnWindows(mContext.getResources()); // Listen for the package update changes. if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 6a015630a076..904478efb568 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -111,7 +111,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT; - private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -271,11 +270,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { } - default void onBiometricAuthenticated() { } + default void onBiometricAuthenticated(boolean authenticated) { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } default void hideBiometricDialog() { } - default void showBiometricTryAgain() { } } @VisibleForTesting @@ -736,9 +734,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override - public void onBiometricAuthenticated() { + public void onBiometricAuthenticated(boolean authenticated) { synchronized (mLock) { - mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); + mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget(); } } @@ -763,13 +761,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } - @Override - public void showBiometricTryAgain() { - synchronized (mLock) { - mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget(); - } - } - private final class H extends Handler { private H(Looper l) { super(l); @@ -991,7 +982,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< break; case MSG_BIOMETRIC_AUTHENTICATED: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onBiometricAuthenticated(); + mCallbacks.get(i).onBiometricAuthenticated((boolean) msg.obj); } break; case MSG_BIOMETRIC_HELP: @@ -1024,11 +1015,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< mCallbacks.get(i).showPinningEscapeToast(); } break; - case MSG_BIOMETRIC_TRY_AGAIN: - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).showBiometricTryAgain(); - } - break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java deleted file mode 100644 index 6d2c001dc153..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ /dev/null @@ -1,692 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar; - -import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; -import static android.app.StatusBarManager.DISABLE_NONE; - -import android.annotation.DrawableRes; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.telephony.SubscriptionInfo; -import android.util.ArraySet; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.android.settingslib.graph.SignalDrawable; -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.phone.StatusBarIconController; -import com.android.systemui.statusbar.policy.IconLogger; -import com.android.systemui.statusbar.policy.NetworkController; -import com.android.systemui.statusbar.policy.NetworkController.IconState; -import com.android.systemui.statusbar.policy.NetworkControllerImpl; -import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.Utils.DisableStateTracker; - -import java.util.ArrayList; -import java.util.List; - -// Intimately tied to the design of res/layout/signal_cluster_view.xml -public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback, - SecurityController.SecurityControllerCallback, Tunable, DarkReceiver { - - static final String TAG = "SignalClusterView"; - static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private static final String SLOT_AIRPLANE = "airplane"; - private static final String SLOT_MOBILE = "mobile"; - private static final String SLOT_WIFI = "wifi"; - private static final String SLOT_ETHERNET = "ethernet"; - private static final String SLOT_VPN = "vpn"; - - private final NetworkController mNetworkController; - private final SecurityController mSecurityController; - - private boolean mVpnVisible = false; - private int mVpnIconId = 0; - private int mLastVpnIconId = -1; - private boolean mEthernetVisible = false; - private int mEthernetIconId = 0; - private int mLastEthernetIconId = -1; - private boolean mWifiVisible = false; - private int mWifiStrengthId = 0; - private int mLastWifiStrengthId = -1; - private boolean mWifiIn; - private boolean mWifiOut; - private boolean mIsAirplaneMode = false; - private int mAirplaneIconId = 0; - private int mLastAirplaneIconId = -1; - private String mAirplaneContentDescription; - private String mWifiDescription; - private String mEthernetDescription; - private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>(); - private int mIconTint = Color.WHITE; - private float mDarkIntensity; - private final Rect mTintArea = new Rect(); - - ViewGroup mEthernetGroup, mWifiGroup; - ImageView mVpn, mEthernet, mWifi, mAirplane, mEthernetDark, mWifiDark; - ImageView mWifiActivityIn; - ImageView mWifiActivityOut; - View mWifiAirplaneSpacer; - View mWifiSignalSpacer; - LinearLayout mMobileSignalGroup; - - private final int mMobileSignalGroupEndPadding; - private final int mMobileDataIconStartPadding; - private final int mSecondaryTelephonyPadding; - private final int mEndPadding; - private final int mEndPaddingNothingVisible; - private final float mIconScaleFactor; - - private boolean mBlockAirplane; - private boolean mBlockMobile; - private boolean mBlockWifi; - private boolean mBlockEthernet; - private boolean mActivityEnabled; - private boolean mForceBlockWifi; - - private final IconLogger mIconLogger = Dependency.get(IconLogger.class); - - public SignalClusterView(Context context) { - this(context, null); - } - - public SignalClusterView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SignalClusterView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - Resources res = getResources(); - mMobileSignalGroupEndPadding = - res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding); - mMobileDataIconStartPadding = - res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding); - mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding); - mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding); - mEndPaddingNothingVisible = res.getDimensionPixelSize( - R.dimen.no_signal_cluster_battery_padding); - - TypedValue typedValue = new TypedValue(); - res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); - mIconScaleFactor = typedValue.getFloat(); - mNetworkController = Dependency.get(NetworkController.class); - mSecurityController = Dependency.get(SecurityController.class); - addOnAttachStateChangeListener( - new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS)); - updateActivityEnabled(); - } - - public void setForceBlockWifi() { - mForceBlockWifi = true; - mBlockWifi = true; - if (isAttachedToWindow()) { - // Re-register to get new callbacks. - mNetworkController.removeCallback(this); - mNetworkController.addCallback(this); - } - } - - @Override - public void onTuningChanged(String key, String newValue) { - if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) { - return; - } - ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue); - boolean blockAirplane = blockList.contains(SLOT_AIRPLANE); - boolean blockMobile = blockList.contains(SLOT_MOBILE); - boolean blockWifi = blockList.contains(SLOT_WIFI); - boolean blockEthernet = blockList.contains(SLOT_ETHERNET); - - if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile - || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) { - mBlockAirplane = blockAirplane; - mBlockMobile = blockMobile; - mBlockEthernet = blockEthernet; - mBlockWifi = blockWifi || mForceBlockWifi; - // Re-register to get new callbacks. - mNetworkController.removeCallback(this); - mNetworkController.addCallback(this); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mVpn = findViewById(R.id.vpn); - mEthernetGroup = findViewById(R.id.ethernet_combo); - mEthernet = findViewById(R.id.ethernet); - mEthernetDark = findViewById(R.id.ethernet_dark); - mWifiGroup = findViewById(R.id.wifi_combo); - mWifi = findViewById(R.id.wifi_signal); - mWifiDark = findViewById(R.id.wifi_signal_dark); - mWifiActivityIn = findViewById(R.id.wifi_in); - mWifiActivityOut= findViewById(R.id.wifi_out); - mAirplane = findViewById(R.id.airplane); - mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer); - mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer); - mMobileSignalGroup = findViewById(R.id.mobile_signal_group); - - maybeScaleVpnAndNoSimsIcons(); - } - - /** - * Extracts the icon off of the VPN and no sims views and maybe scale them by - * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are - * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}. - */ - private void maybeScaleVpnAndNoSimsIcons() { - if (mIconScaleFactor == 1.f) { - return; - } - - mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor)); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mVpnVisible = mSecurityController.isVpnEnabled(); - mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); - - for (PhoneState state : mPhoneStates) { - if (state.mMobileGroup.getParent() == null) { - mMobileSignalGroup.addView(state.mMobileGroup); - } - } - - int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0; - mMobileSignalGroup.setPaddingRelative(0, 0, endPadding, 0); - - Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST); - - apply(); - applyIconTint(); - mNetworkController.addCallback(this); - mSecurityController.addCallback(this); - } - - @Override - protected void onDetachedFromWindow() { - mMobileSignalGroup.removeAllViews(); - Dependency.get(TunerService.class).removeTunable(this); - mSecurityController.removeCallback(this); - mNetworkController.removeCallback(this); - - super.onDetachedFromWindow(); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - // Re-run all checks against the tint area for all icons - applyIconTint(); - } - - // From SecurityController. - @Override - public void onStateChanged() { - post(new Runnable() { - @Override - public void run() { - mVpnVisible = mSecurityController.isVpnEnabled(); - mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); - apply(); - } - }); - } - - private void updateActivityEnabled() { - mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity); - } - - @Override - public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, - boolean activityIn, boolean activityOut, String description, boolean isTransient, - String secondaryLabel) { - mWifiVisible = statusIcon.visible && !mBlockWifi; - mWifiStrengthId = statusIcon.icon; - mWifiDescription = statusIcon.contentDescription; - mWifiIn = activityIn && mActivityEnabled && mWifiVisible; - mWifiOut = activityOut && mActivityEnabled && mWifiVisible; - - apply(); - } - - @Override - public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType, - int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, - String description, boolean isWide, int subId, boolean roaming) { - PhoneState state = getState(subId); - if (state == null) { - return; - } - state.mMobileVisible = statusIcon.visible && !mBlockMobile; - state.mMobileStrengthId = statusIcon.icon; - state.mMobileTypeId = statusType; - state.mMobileDescription = statusIcon.contentDescription; - state.mMobileTypeDescription = typeContentDescription; - state.mRoaming = roaming; - state.mActivityIn = activityIn && mActivityEnabled; - state.mActivityOut = activityOut && mActivityEnabled; - - apply(); - } - - @Override - public void setEthernetIndicators(IconState state) { - mEthernetVisible = state.visible && !mBlockEthernet; - mEthernetIconId = state.icon; - mEthernetDescription = state.contentDescription; - - apply(); - } - - @Override - public void setNoSims(boolean show, boolean simDetected) { - // Noop. Status bar no longer shows no sim icon. - } - - @Override - public void setSubs(List<SubscriptionInfo> subs) { - if (hasCorrectSubs(subs)) { - return; - } - mPhoneStates.clear(); - if (mMobileSignalGroup != null) { - mMobileSignalGroup.removeAllViews(); - } - final int n = subs.size(); - for (int i = 0; i < n; i++) { - inflatePhoneState(subs.get(i).getSubscriptionId()); - } - if (isAttachedToWindow()) { - applyIconTint(); - } - } - - private boolean hasCorrectSubs(List<SubscriptionInfo> subs) { - final int N = subs.size(); - if (N != mPhoneStates.size()) { - return false; - } - for (int i = 0; i < N; i++) { - if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) { - return false; - } - } - return true; - } - - private PhoneState getState(int subId) { - for (PhoneState state : mPhoneStates) { - if (state.mSubId == subId) { - return state; - } - } - Log.e(TAG, "Unexpected subscription " + subId); - return null; - } - - private PhoneState inflatePhoneState(int subId) { - PhoneState state = new PhoneState(subId, mContext); - if (mMobileSignalGroup != null) { - mMobileSignalGroup.addView(state.mMobileGroup); - } - mPhoneStates.add(state); - return state; - } - - @Override - public void setIsAirplaneMode(IconState icon) { - mIsAirplaneMode = icon.visible && !mBlockAirplane; - mAirplaneIconId = icon.icon; - mAirplaneContentDescription = icon.contentDescription; - - apply(); - } - - @Override - public void setMobileDataEnabled(boolean enabled) { - // Don't care. - } - - @Override - public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { - // Standard group layout onPopulateAccessibilityEvent() implementations - // ignore content description, so populate manually - if (mEthernetVisible && mEthernetGroup != null && - mEthernetGroup.getContentDescription() != null) - event.getText().add(mEthernetGroup.getContentDescription()); - if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null) - event.getText().add(mWifiGroup.getContentDescription()); - for (PhoneState state : mPhoneStates) { - state.populateAccessibilityEvent(event); - } - return super.dispatchPopulateAccessibilityEventInternal(event); - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - - if (mEthernet != null) { - mEthernet.setImageDrawable(null); - mEthernetDark.setImageDrawable(null); - mLastEthernetIconId = -1; - } - - if (mWifi != null) { - mWifi.setImageDrawable(null); - mWifiDark.setImageDrawable(null); - mLastWifiStrengthId = -1; - } - - for (PhoneState state : mPhoneStates) { - if (state.mMobileType != null) { - state.mMobileType.setImageDrawable(null); - state.mLastMobileTypeId = -1; - } - } - - if (mAirplane != null) { - mAirplane.setImageDrawable(null); - mLastAirplaneIconId = -1; - } - - apply(); - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - // Run after each indicator change. - private void apply() { - if (mWifiGroup == null) return; - - if (mVpnVisible) { - if (mLastVpnIconId != mVpnIconId) { - setIconForView(mVpn, mVpnIconId); - mLastVpnIconId = mVpnIconId; - } - mIconLogger.onIconShown(SLOT_VPN); - mVpn.setVisibility(View.VISIBLE); - } else { - mIconLogger.onIconHidden(SLOT_VPN); - mVpn.setVisibility(View.GONE); - } - if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE")); - - if (mEthernetVisible) { - if (mLastEthernetIconId != mEthernetIconId) { - setIconForView(mEthernet, mEthernetIconId); - setIconForView(mEthernetDark, mEthernetIconId); - mLastEthernetIconId = mEthernetIconId; - } - mEthernetGroup.setContentDescription(mEthernetDescription); - mIconLogger.onIconShown(SLOT_ETHERNET); - mEthernetGroup.setVisibility(View.VISIBLE); - } else { - mIconLogger.onIconHidden(SLOT_ETHERNET); - mEthernetGroup.setVisibility(View.GONE); - } - - if (DEBUG) Log.d(TAG, - String.format("ethernet: %s", - (mEthernetVisible ? "VISIBLE" : "GONE"))); - - if (mWifiVisible) { - if (mWifiStrengthId != mLastWifiStrengthId) { - setIconForView(mWifi, mWifiStrengthId); - setIconForView(mWifiDark, mWifiStrengthId); - mLastWifiStrengthId = mWifiStrengthId; - } - mIconLogger.onIconShown(SLOT_WIFI); - mWifiGroup.setContentDescription(mWifiDescription); - mWifiGroup.setVisibility(View.VISIBLE); - } else { - mIconLogger.onIconHidden(SLOT_WIFI); - mWifiGroup.setVisibility(View.GONE); - } - - if (DEBUG) Log.d(TAG, - String.format("wifi: %s sig=%d", - (mWifiVisible ? "VISIBLE" : "GONE"), - mWifiStrengthId)); - - mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE); - mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE); - - boolean anyMobileVisible = false; - int firstMobileTypeId = 0; - for (PhoneState state : mPhoneStates) { - if (state.apply(anyMobileVisible)) { - if (!anyMobileVisible) { - firstMobileTypeId = state.mMobileTypeId; - anyMobileVisible = true; - } - } - } - if (anyMobileVisible) { - mIconLogger.onIconShown(SLOT_MOBILE); - } else { - mIconLogger.onIconHidden(SLOT_MOBILE); - } - - if (mIsAirplaneMode) { - if (mLastAirplaneIconId != mAirplaneIconId) { - setIconForView(mAirplane, mAirplaneIconId); - mLastAirplaneIconId = mAirplaneIconId; - } - mAirplane.setContentDescription(mAirplaneContentDescription); - mIconLogger.onIconShown(SLOT_AIRPLANE); - mAirplane.setVisibility(View.VISIBLE); - } else { - mIconLogger.onIconHidden(SLOT_AIRPLANE); - mAirplane.setVisibility(View.GONE); - } - - if (mIsAirplaneMode && mWifiVisible) { - mWifiAirplaneSpacer.setVisibility(View.VISIBLE); - } else { - mWifiAirplaneSpacer.setVisibility(View.GONE); - } - - if ((anyMobileVisible && firstMobileTypeId != 0) && mWifiVisible) { - mWifiSignalSpacer.setVisibility(View.VISIBLE); - } else { - mWifiSignalSpacer.setVisibility(View.GONE); - } - - boolean anythingVisible = mWifiVisible || mIsAirplaneMode - || anyMobileVisible || mVpnVisible || mEthernetVisible; - setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0); - } - - /** - * Sets the given drawable id on the view. This method will also scale the icon by - * {@link #mIconScaleFactor} if appropriate. - */ - private void setIconForView(ImageView imageView, @DrawableRes int iconId) { - // Using the imageView's context to retrieve the Drawable so that theme is preserved. - Drawable icon = imageView.getContext().getDrawable(iconId); - - if (mIconScaleFactor == 1.f) { - imageView.setImageDrawable(icon); - } else { - imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor)); - } - } - - - @Override - public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) { - boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity - || !mTintArea.equals(tintArea); - mIconTint = tint; - mDarkIntensity = darkIntensity; - mTintArea.set(tintArea); - if (changed && isAttachedToWindow()) { - applyIconTint(); - } - } - - private void applyIconTint() { - setTint(mVpn, DarkIconDispatcher.getTint(mTintArea, mVpn, mIconTint)); - setTint(mAirplane, DarkIconDispatcher.getTint(mTintArea, mAirplane, mIconTint)); - applyDarkIntensity( - DarkIconDispatcher.getDarkIntensity(mTintArea, mWifi, mDarkIntensity), - mWifi, mWifiDark); - setTint(mWifiActivityIn, - DarkIconDispatcher.getTint(mTintArea, mWifiActivityIn, mIconTint)); - setTint(mWifiActivityOut, - DarkIconDispatcher.getTint(mTintArea, mWifiActivityOut, mIconTint)); - applyDarkIntensity( - DarkIconDispatcher.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity), - mEthernet, mEthernetDark); - for (int i = 0; i < mPhoneStates.size(); i++) { - mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea); - } - } - - private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) { - lightIcon.setAlpha(1 - darkIntensity); - darkIcon.setAlpha(darkIntensity); - } - - private void setTint(ImageView v, int tint) { - v.setImageTintList(ColorStateList.valueOf(tint)); - } - - private int currentVpnIconId(boolean isBranded) { - return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic; - } - - private class PhoneState { - private final int mSubId; - private boolean mMobileVisible = false; - private int mMobileStrengthId = 0, mMobileTypeId = 0; - private int mLastMobileStrengthId = -1; - private int mLastMobileTypeId = -1; - private String mMobileDescription, mMobileTypeDescription; - - private ViewGroup mMobileGroup; - private ImageView mMobile, mMobileType, mMobileRoaming; - private View mMobileRoamingSpace; - public boolean mRoaming; - private ImageView mMobileActivityIn; - private ImageView mMobileActivityOut; - public boolean mActivityIn; - public boolean mActivityOut; - private SignalDrawable mMobileSignalDrawable; - - public PhoneState(int subId, Context context) { - ViewGroup root = (ViewGroup) LayoutInflater.from(context) - .inflate(R.layout.mobile_signal_group, null); - setViews(root); - mSubId = subId; - } - - public void setViews(ViewGroup root) { - mMobileGroup = root; - mMobile = root.findViewById(R.id.mobile_signal); - mMobileType = root.findViewById(R.id.mobile_type); - mMobileRoaming = root.findViewById(R.id.mobile_roaming); - mMobileRoamingSpace = root.findViewById(R.id.mobile_roaming_space); - mMobileActivityIn = root.findViewById(R.id.mobile_in); - mMobileActivityOut = root.findViewById(R.id.mobile_out); - mMobileSignalDrawable = new SignalDrawable(mMobile.getContext()); - mMobile.setImageDrawable(mMobileSignalDrawable); - } - - public boolean apply(boolean isSecondaryIcon) { - if (mMobileVisible && !mIsAirplaneMode) { - if (mLastMobileStrengthId != mMobileStrengthId) { - mMobile.getDrawable().setLevel(mMobileStrengthId); - mLastMobileStrengthId = mMobileStrengthId; - } - - if (mLastMobileTypeId != mMobileTypeId) { - mMobileType.setImageResource(mMobileTypeId); - mLastMobileTypeId = mMobileTypeId; - } - - mMobileGroup.setContentDescription(mMobileTypeDescription - + " " + mMobileDescription); - mMobileGroup.setVisibility(View.VISIBLE); - } else { - mMobileGroup.setVisibility(View.GONE); - } - - // When this isn't next to wifi, give it some extra padding between the signals. - mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0, - 0, 0, 0); - mMobile.setPaddingRelative(mMobileDataIconStartPadding, 0, 0, 0); - - if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d", - (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId)); - - mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE); - mMobileRoaming.setVisibility(mRoaming ? View.VISIBLE : View.GONE); - mMobileRoamingSpace.setVisibility(mRoaming ? View.VISIBLE : View.GONE); - mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE); - mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE); - - return mMobileVisible; - } - - public void populateAccessibilityEvent(AccessibilityEvent event) { - if (mMobileVisible && mMobileGroup != null - && mMobileGroup.getContentDescription() != null) { - event.getText().add(mMobileGroup.getContentDescription()); - } - } - - public void setIconTint(int tint, float darkIntensity, Rect tintArea) { - mMobileSignalDrawable.setDarkIntensity(darkIntensity); - setTint(mMobileType, DarkIconDispatcher.getTint(tintArea, mMobileType, tint)); - setTint(mMobileRoaming, DarkIconDispatcher.getTint(tintArea, mMobileRoaming, - tint)); - setTint(mMobileActivityIn, - DarkIconDispatcher.getTint(tintArea, mMobileActivityIn, tint)); - setTint(mMobileActivityOut, - DarkIconDispatcher.getTint(tintArea, mMobileActivityOut, tint)); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index e698e643534b..45db00210a2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification; import android.annotation.Nullable; import android.app.Notification; import android.content.Context; -import android.os.Handler; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -51,7 +50,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.concurrent.TimeUnit; /** * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. @@ -64,11 +62,10 @@ public class NotificationEntryManager implements NotificationUpdateHandler, VisualStabilityManager.Callback { private static final String TAG = "NotificationEntryMgr"; - protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); - - protected final Context mContext; + private final Context mContext; + @VisibleForTesting protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>(); private final DeviceProvisionedController mDeviceProvisionedController = @@ -80,13 +77,11 @@ public class NotificationEntryManager implements private NotificationRemoteInputManager mRemoteInputManager; private NotificationRowBinder mNotificationRowBinder; - private final Handler mDeferredNotificationViewUpdateHandler; - private Runnable mUpdateNotificationViewsCallback; - private NotificationPresenter mPresenter; private NotificationListenerService.RankingMap mLatestRankingMap; + @VisibleForTesting protected NotificationData mNotificationData; - protected NotificationListContainer mListContainer; + private NotificationListContainer mListContainer; @VisibleForTesting final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders = new ArrayList<>(); @@ -121,7 +116,6 @@ public class NotificationEntryManager implements public NotificationEntryManager(Context context) { mContext = context; mNotificationData = new NotificationData(); - mDeferredNotificationViewUpdateHandler = new Handler(); } /** Adds a {@link NotificationEntryListener}. */ @@ -156,7 +150,6 @@ public class NotificationEntryManager implements NotificationListContainer listContainer, HeadsUpManager headsUpManager) { mPresenter = presenter; - mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews; mNotificationData.setHeadsUpManager(headsUpManager); mListContainer = listContainer; @@ -180,14 +173,6 @@ public class NotificationEntryManager implements return mNotificationData; } - protected Context getContext() { - return mContext; - } - - protected NotificationPresenter getPresenter() { - return mPresenter; - } - @Override public void onReorderingAllowed() { updateNotifications(); @@ -229,15 +214,6 @@ public class NotificationEntryManager implements } } - private void maybeScheduleUpdateNotificationViews(NotificationEntry entry) { - long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS - - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs); - if (audibleAlertTimeout > 0) { - mDeferredNotificationViewUpdateHandler.postDelayed( - mUpdateNotificationViewsCallback, audibleAlertTimeout); - } - } - @Override public void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags) { @@ -256,7 +232,6 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onNotificationAdded(entry); } - maybeScheduleUpdateNotificationViews(entry); } else { for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onEntryReinflated(entry); @@ -463,8 +438,6 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPostEntryUpdated(entry); } - - maybeScheduleUpdateNotificationViews(entry); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9e0dd8461d21..95bd1ce1f9a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -86,7 +86,6 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -106,6 +105,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -122,6 +122,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private static final int MENU_VIEW_INDEX = 0; private static final String TAG = "ExpandableNotifRow"; public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; + private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); private boolean mUpdateBackgroundOnUpdate; /** @@ -1693,17 +1694,31 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** Sets the last time the notification being displayed audibly alerted the user. */ public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) { if (NotificationUtils.useNewInterruptionModel(mContext)) { - boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs - < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS; - if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { - mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted( - recentlyAudiblyAlerted); + long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs; + boolean alertedRecently = + timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS; + + applyAudiblyAlertedRecently(alertedRecently); + + removeCallbacks(mExpireRecentlyAlertedFlag); + if (alertedRecently) { + long timeUntilNoLongerRecent = + RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly; + postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent); } - mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted); - mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted); } } + private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false); + + private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) { + if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { + mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently); + } + mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + } + public View.OnClickListener getAppOpsOnClickListener() { return mOnAppOpsClickListener; } 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 80db6c11e324..99b0cd2a7dfd 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 @@ -430,7 +430,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private int mHeadsUpInset; private HeadsUpAppearanceController mHeadsUpAppearanceController; private NotificationIconAreaController mIconAreaController; - private float mVerticalPanelTranslation; + private float mHorizontalPanelTranslation; private final NotificationLockscreenUserManager mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class); protected final NotificationGutsManager mGutsManager = @@ -4377,12 +4377,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updatePanelTranslation() { - setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount); + setTranslationX(mHorizontalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount); } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setVerticalPanelTranslation(float verticalPanelTranslation) { - mVerticalPanelTranslation = verticalPanelTranslation; + public void setHorizontalPanelTranslation(float verticalPanelTranslation) { + mHorizontalPanelTranslation = verticalPanelTranslation; updatePanelTranslation(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java index 280dda0cd1dc..577e8d614acb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java @@ -84,6 +84,14 @@ public class DozeScrimController implements StateListener { public void onCancelled() { pulseFinished(); } + + /** + * Whether to fade out wallpaper. + */ + @Override + public boolean isFadeOutWallpaper() { + return mPulseReason == DozeLog.PULSE_REASON_DOCKING; + } }; public DozeScrimController(DozeParameters dozeParameters) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java new file mode 100644 index 000000000000..ebcd39b99778 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java @@ -0,0 +1,49 @@ +/* + * 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.systemui.statusbar.phone; + +import android.annotation.NonNull; +import android.os.Bundle; +import android.view.MotionEvent; + +import com.android.systemui.assist.AssistManager; +import com.android.systemui.recents.OverviewProxyService; + +/** + * Assistant is triggered with this action + */ +public class NavigationAssistantAction extends NavigationGestureAction { + private static final String TAG = "NavigationAssistantActions"; + + private final AssistManager mAssistManager; + + public NavigationAssistantAction(@NonNull NavigationBarView navigationBarView, + @NonNull OverviewProxyService service, AssistManager assistManager) { + super(navigationBarView, service); + mAssistManager = assistManager; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void onGestureStart(MotionEvent event) { + mAssistManager.startAssist(new Bundle()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java index 9c8b1b1e5227..93605adf4589 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java @@ -67,7 +67,7 @@ public class NavigationBackAction extends NavigationGestureAction { @Override public boolean isEnabled() { - return !getGlobalBoolean(NavigationPrototypeController.NAVBAR_EXPERIMENTS_DISABLED); + return true; } @Override @@ -102,8 +102,7 @@ public class NavigationBackAction extends NavigationGestureAction { } private boolean shouldExecuteBackOnUp() { - return !getGlobalBoolean(NavigationPrototypeController.NAVBAR_EXPERIMENTS_DISABLED) - && getGlobalBoolean(BACK_AFTER_END_PROP); + return getGlobalBoolean(BACK_AFTER_END_PROP); } private void sendEvent(int action, int code) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 6d97d6724d40..d3c6a1d8ee74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -269,7 +269,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY; } - mNavigationBarView.setComponents(mStatusBar.getPanel()); + mNavigationBarView.setComponents(mStatusBar.getPanel(), mAssistManager); mNavigationBarView.setDisabledFlags(mDisabledFlags1); mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged); mNavigationBarView.setOnTouchListener(this::onNavigationTouch); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 2fc7b78c0ca3..8bf1c58df699 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -69,6 +69,7 @@ import com.android.systemui.DockedStackExistsListener; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.assist.AssistManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.phone.NavGesture; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; @@ -156,6 +157,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private QuickStepAction mQuickStepAction; private NavigationBackAction mBackAction; private QuickSwitchAction mQuickSwitchAction; + private NavigationAssistantAction mAssistantAction; /** * Helper that is responsible for showing the right toast when a disallowed activity operation @@ -366,8 +368,12 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mBarTransitions.getLightTransitionsController(); } - public void setComponents(NotificationPanelView panel) { + public void setComponents(NotificationPanelView panel, AssistManager assistManager) { mPanelView = panel; + if (mAssistantAction == null) { + mAssistantAction = new NavigationAssistantAction(this, mOverviewProxyService, + assistManager); + } if (mGestureHelper instanceof QuickStepController) { ((QuickStepController) mGestureHelper).setComponents(this); updateNavigationGestures(); @@ -398,6 +404,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return mBackAction; case NavigationPrototypeController.ACTION_QUICKSWITCH: return mQuickSwitchAction; + case NavigationPrototypeController.ACTION_ASSISTANT: + return mAssistantAction; + case NavigationPrototypeController.ACTION_NOTHING: + return null; default: return defaultAction; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java index 8c57fc31c3b4..a5d938216f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java @@ -24,6 +24,7 @@ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ import android.annotation.NonNull; import android.content.Context; import android.graphics.Canvas; +import android.provider.Settings; import android.view.MotionEvent; import com.android.systemui.recents.OverviewProxyService; @@ -32,6 +33,9 @@ import com.android.systemui.recents.OverviewProxyService; * A gesture action that would be triggered and reassigned by {@link QuickStepController} */ public abstract class NavigationGestureAction { + private static final String ENABLE_TASK_STABILIZER_FLAG = "ENABLE_TASK_STABILIZER"; + + static private boolean sLastTaskStabilizationFlag; protected final NavigationBarView mNavigationBarView; protected final OverviewProxyService mProxySender; @@ -45,6 +49,9 @@ public abstract class NavigationGestureAction { @NonNull OverviewProxyService service) { mNavigationBarView = navigationBarView; mProxySender = service; + sLastTaskStabilizationFlag = Settings.Global.getInt( + mNavigationBarView.getContext().getContentResolver(), + ENABLE_TASK_STABILIZER_FLAG, 0) != 0; } /** @@ -74,6 +81,15 @@ public abstract class NavigationGestureAction { */ public void startGesture(MotionEvent event) { mIsActive = true; + + // Tell launcher that this action requires a stable task list or not + boolean flag = requiresStableTaskList(); + if (flag != sLastTaskStabilizationFlag) { + Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(), + ENABLE_TASK_STABILIZER_FLAG, flag ? 1 : 0); + sLastTaskStabilizationFlag = flag; + } + onGestureStart(event); } @@ -146,6 +162,13 @@ public abstract class NavigationGestureAction { */ public abstract boolean isEnabled(); + /** + * @return action requires a stable task list from launcher + */ + protected boolean requiresStableTaskList() { + return false; + } + protected void onDarkIntensityChange(float intensity) { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index fb6254b0e4be..a09e5858d576 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -22,7 +22,6 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,18 +35,20 @@ public class NavigationPrototypeController extends ContentObserver { private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback"; private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome"; - static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled"; private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map"; public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable"; @Retention(RetentionPolicy.SOURCE) - @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK}) + @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK, + ACTION_QUICKSWITCH, ACTION_NOTHING, ACTION_ASSISTANT}) @interface GestureAction {} static final int ACTION_DEFAULT = 0; static final int ACTION_QUICKSTEP = 1; static final int ACTION_QUICKSCRUB = 2; static final int ACTION_BACK = 3; static final int ACTION_QUICKSWITCH = 4; + static final int ACTION_NOTHING = 5; + static final int ACTION_ASSISTANT = 6; private OnPrototypeChangedListener mListener; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index d873b0cd1f26..512f56dafc11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -956,7 +956,7 @@ public class NotificationPanelView extends PanelView implements handled = true; } handled |= super.onTouchEvent(event); - return mDozing ? handled : true; + return !mDozing || mPulsing || handled; } private boolean handleQsTouch(MotionEvent event) { @@ -1233,7 +1233,7 @@ public class NotificationPanelView extends PanelView implements updateDozingVisibilities(false /* animate */); } - resetVerticalPanelPosition(); + resetHorizontalPanelPosition(); updateQsState(); } @@ -2043,7 +2043,7 @@ public class NotificationPanelView extends PanelView implements super.onConfigurationChanged(newConfig); mAffordanceHelper.onConfigurationChanged(); if (newConfig.orientation != mLastOrientation) { - resetVerticalPanelPosition(); + resetHorizontalPanelPosition(); } mLastOrientation = newConfig.orientation; } @@ -2529,7 +2529,7 @@ public class NotificationPanelView extends PanelView implements @Override protected void onClosingFinished() { super.onClosingFinished(); - resetVerticalPanelPosition(); + resetHorizontalPanelPosition(); setClosingWithAlphaFadeout(false); } @@ -2546,7 +2546,7 @@ public class NotificationPanelView extends PanelView implements */ protected void updateVerticalPanelPosition(float x) { if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) { - resetVerticalPanelPosition(); + resetHorizontalPanelPosition(); return; } float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2; @@ -2556,16 +2556,17 @@ public class NotificationPanelView extends PanelView implements x = getWidth() / 2; } x = Math.min(rightMost, Math.max(leftMost, x)); - setVerticalPanelTranslation(x - - (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2)); + float center = + mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2; + setHorizontalPanelTranslation(x - center); } - private void resetVerticalPanelPosition() { - setVerticalPanelTranslation(0f); + private void resetHorizontalPanelPosition() { + setHorizontalPanelTranslation(0f); } - protected void setVerticalPanelTranslation(float translation) { - mNotificationStackScroller.setVerticalPanelTranslation(translation); + protected void setHorizontalPanelTranslation(float translation) { + mNotificationStackScroller.setHorizontalPanelTranslation(translation); mQsFrame.setTranslationX(translation); int size = mVerticalTranslationListener.size(); for (int i = 0; i < size; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java index b18b79e0e6d6..1999f9a8e04d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java @@ -47,6 +47,10 @@ public class QuickStepAction extends NavigationGestureAction { return mNavigationBarView.isQuickStepSwipeUpEnabled(); } + protected boolean requiresStableTaskList() { + return true; + } + @Override public void onGestureStart(MotionEvent event) { try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index e25c8292b637..853d7ab9a76d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -275,9 +275,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo holdWakeLock(); } - // AOD wallpapers should fade away after a while - if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn() - && mState == ScrimState.AOD) { + // AOD wallpapers should fade away after a while. + // Docking pulses may take a long time, wallpapers should also fade away after a while. + if (mWallpaperSupportsAmbientMode && ( + mDozeParameters.getAlwaysOn() && mState == ScrimState.AOD + || mState == ScrimState.PULSING && mCallback != null + && mCallback.isFadeOutWallpaper())) { if (!mWallpaperVisibilityTimedOut) { mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); @@ -329,7 +332,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo @VisibleForTesting protected void onHideWallpaperTimeout() { - if (mState != ScrimState.AOD) { + if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { return; } @@ -364,7 +367,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo mExpansionFraction = fraction; final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED - || mState == ScrimState.KEYGUARD; + || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING; if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) { return; } @@ -409,7 +412,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo behindFraction = (float) Math.pow(behindFraction, 0.8f); mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY; mCurrentInFrontAlpha = 0; - } else if (mState == ScrimState.KEYGUARD) { + } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) { // Either darken of make the scrim transparent when you // pull down the shade float interpolatedFract = getInterpolatedFraction(); @@ -504,7 +507,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo // We want to override the back scrim opacity for the AOD state // when it's time to fade the wallpaper away. - boolean aodWallpaperTimeout = mState == ScrimState.AOD && mWallpaperVisibilityTimedOut; + boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING) + && mWallpaperVisibilityTimedOut; // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD) && mKeyguardOccluded; @@ -562,8 +566,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo if (alpha == 0f) { scrim.setClickable(false); } else { - // Eat touch events (unless dozing or pulsing). - scrim.setClickable(mState != ScrimState.AOD && mState != ScrimState.PULSING); + // Eat touch events (unless dozing). + scrim.setClickable(mState != ScrimState.AOD); } updateScrim(scrim, alpha); } @@ -917,6 +921,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } default void onCancelled() { } + default boolean isFadeOutWallpaper() { + return false; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index fb3c4aa16ef5..72519ba3503c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -219,6 +219,14 @@ public enum ScrimState { public void prepare(ScrimState previousState) { } + /** + * Check if lockscreen wallpaper or music album art exists. + * @return true if lockscreen wallpaper or music album art exists. + */ + public boolean hasBackdrop() { + return mHasBackdrop; + } + public int getIndex() { return mIndex; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 6d78f7204163..9abd86d7088b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -70,6 +70,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -98,6 +99,7 @@ import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; import android.util.DisplayMetrics; import android.util.EventLog; +import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Slog; import android.view.Display; @@ -478,8 +480,13 @@ public class StatusBar extends SystemUI implements DemoMode, WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT); final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean( com.android.internal.R.bool.config_dozeSupportsAodWallpaper); + final boolean aodImageWallpaperEnabled = FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED); + updateAodMaskVisibility(deviceSupportsAodWallpaper && aodImageWallpaperEnabled); + // If WallpaperInfo is null, it must be ImageWallpaper. final boolean supportsAmbientMode = deviceSupportsAodWallpaper - && info != null && info.supportsAmbientMode(); + && (info == null && aodImageWallpaperEnabled + || info != null && info.supportsAmbientMode()); mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); @@ -581,6 +588,7 @@ public class StatusBar extends SystemUI implements DemoMode, protected NotificationPresenter mPresenter; private NotificationActivityStarter mNotificationActivityStarter; private boolean mPulsing; + private ContentObserver mFeatureFlagObserver; @Override public void onActiveStateChanged(int code, int uid, String packageName, boolean active) { @@ -697,6 +705,9 @@ public class StatusBar extends SystemUI implements DemoMode, mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL, wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */); mWallpaperChangedReceiver.onReceive(mContext, null); + mFeatureFlagObserver = new FeatureFlagObserver( + FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED /* feature */, + () -> mWallpaperChangedReceiver.onReceive(mContext, null) /* callback */); // Set up the initial notification state. This needs to happen before CommandQueue.disable() setUpPresenter(); @@ -3573,10 +3584,7 @@ public class StatusBar extends SystemUI implements DemoMode, mVisualStabilityManager.setScreenOn(false); updateVisibleToUser(); - // We need to disable touch events because these might - // collapse the panel after we expanded it, and thus we would end up with a blank - // Keyguard. - mNotificationPanel.setTouchAndAnimationDisabled(true); + updateNotificationPanelTouchState(); mStatusBarWindow.cancelCurrentTouch(); if (mLaunchCameraOnFinishedGoingToSleep) { mLaunchCameraOnFinishedGoingToSleep = false; @@ -3599,13 +3607,22 @@ public class StatusBar extends SystemUI implements DemoMode, mDeviceInteractive = true; mAmbientPulseManager.releaseAllImmediately(); mVisualStabilityManager.setScreenOn(true); - mNotificationPanel.setTouchAndAnimationDisabled(false); + updateNotificationPanelTouchState(); updateVisibleToUser(); updateIsKeyguard(); mDozeServiceHost.stopDozing(); } }; + /** + * We need to disable touch events because these might + * collapse the panel after we expanded it, and thus we would end up with a blank + * Keyguard. + */ + private void updateNotificationPanelTouchState() { + mNotificationPanel.setTouchAndAnimationDisabled(!mDeviceInteractive && !mPulsing); + } + final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurningOn() { @@ -3871,17 +3888,15 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); - if (mAmbientPulseManager.hasNotifications()) { - // Only pulse the stack scroller if there's actually something to show. - // Otherwise just show the always-on screen. - setPulsing(true); - } + updateNotificationPanelTouchState(); + setPulsing(true); } @Override public void onPulseFinished() { mPulsing = false; callback.onPulseFinished(); + updateNotificationPanelTouchState(); setPulsing(false); } @@ -4411,4 +4426,33 @@ public class StatusBar extends SystemUI implements DemoMode, public @TransitionMode int getStatusBarMode() { return mStatusBarMode; } + + private void updateAodMaskVisibility(boolean supportsAodWallpaper) { + View mask = mStatusBarWindow.findViewById(R.id.aod_mask); + if (mask != null) { + mask.setVisibility(supportsAodWallpaper ? View.VISIBLE : View.INVISIBLE); + } + } + + private final class FeatureFlagObserver extends ContentObserver { + private final Runnable mCallback; + + FeatureFlagObserver(String feature, Runnable callback) { + this(null, feature, callback); + } + + private FeatureFlagObserver(Handler handler, String feature, Runnable callback) { + super(handler); + mCallback = callback; + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(feature), false, this); + } + + @Override + public void onChange(boolean selfChange) { + if (mCallback != null) { + mStatusBarWindow.post(mCallback); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index db7589d0f333..f846036248d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -36,7 +36,6 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.IconLogger; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -61,7 +60,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu private final ArrayList<IconManager> mIconGroups = new ArrayList<>(); private final ArraySet<String> mIconBlacklist = new ArraySet<>(); - private final IconLogger mIconLogger = Dependency.get(IconLogger.class); // Points to light or dark context depending on the... context? private Context mContext; @@ -147,7 +145,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu int viewIndex = getViewIndex(index, holder.getTag()); boolean blocked = mIconBlacklist.contains(slot); - mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible()); mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder)); } @@ -281,8 +278,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu return; } - mIconLogger.onIconHidden(slotName); - int slotIndex = getSlotIndex(slotName); List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder(); for (StatusBarIconHolder holder : iconsToRemove) { @@ -297,7 +292,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu if (getIcon(index, tag) == null) { return; } - mIconLogger.onIconHidden(getSlotName(index)); super.removeIcon(index, tag); int viewIndex = getViewIndex(index, 0); mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); @@ -305,7 +299,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu private void handleSet(int index, StatusBarIconHolder holder) { int viewIndex = getViewIndex(index, holder.getTag()); - mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible()); mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0f8970f1069f..bb23608799f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -187,7 +187,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else if (bouncerNeedsScrimming()) { mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); - } else if (mShowing && !mDozing) { + } else if (mShowing) { if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) { mBouncer.setExpansion(expansion); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 53e461db3dd1..8b25c3469abe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -339,7 +339,7 @@ public class StatusBarWindowView extends FrameLayout { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); - if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) { + if (mService.isDozing() && !mService.isPulsing()) { // Capture all touch events in always-on. return true; } @@ -347,8 +347,7 @@ public class StatusBarWindowView extends FrameLayout { if (mNotificationPanel.isFullyExpanded() && stackScrollLayout.getVisibility() == View.VISIBLE && mStatusBarStateController.getState() == StatusBarState.KEYGUARD - && !mService.isBouncerShowing() - && !mService.isDozing()) { + && !mService.isBouncerShowing()) { intercept = mDragDownHelper.onInterceptTouchEvent(ev); } if (!intercept) { @@ -369,7 +368,7 @@ public class StatusBarWindowView extends FrameLayout { boolean handled = false; if (mService.isDozing()) { mDoubleTapHelper.onTouchEvent(ev); - handled = true; + handled = !mService.isPulsing(); } if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled) || mDragDownHelper.isDraggingDown()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index f65f8261dcfb..5e94152e7eab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -48,18 +48,36 @@ public interface BatteryController extends DemoMode, Dumpable, } /** - * A listener that will be notified whenever a change in battery level or power save mode - * has occurred. + * A listener that will be notified whenever a change in battery level or power save mode has + * occurred. */ interface BatteryStateChangeCallback { - default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {} - default void onPowerSaveChanged(boolean isPowerSave) {} + + default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { + } + + default void onPowerSaveChanged(boolean isPowerSave) { + } } /** - * If available, get the estimated battery time remaining as a string + * If available, get the estimated battery time remaining as a string. + * + * @param completion A lambda that will be called with the result of fetching the estimate. The + * first time this method is called may need to be dispatched to a background thread. The + * completion is called on the main thread + */ + default void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {} + + /** + * Callback called when the estimated time remaining text is fetched. */ - default String getEstimatedTimeRemainingString() { - return null; + public interface EstimateFetchCompletion { + + /** + * The callback + * @param estimate the estimate + */ + void onBatteryRemainingEstimateRetrieved(String estimate); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 6190c8fff8cc..af3c96f73642 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -27,9 +27,12 @@ import android.os.PowerManager; import android.os.PowerSaveState; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.utils.PowerUtil; +import com.android.systemui.Dependency; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.Estimate; @@ -56,6 +59,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private final EnhancedEstimates mEstimates; private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>(); + private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>(); private final PowerManager mPowerManager; private final Handler mHandler; private final Context mContext; @@ -70,6 +74,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private boolean mHasReceivedBattery = false; private Estimate mEstimate; private long mLastEstimateTimestamp = -1; + private boolean mFetchingEstimate = false; @Inject public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates) { @@ -197,20 +202,61 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } @Override - public String getEstimatedTimeRemainingString() { - if (mEstimate == null - || System.currentTimeMillis() > mLastEstimateTimestamp + UPDATE_GRANULARITY_MSEC) { - updateEstimate(); + public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) { + if (mEstimate != null + && mLastEstimateTimestamp > System.currentTimeMillis() - UPDATE_GRANULARITY_MSEC) { + String percentage = generateTimeRemainingString(); + completion.onBatteryRemainingEstimateRetrieved(percentage); + return; + } + + // Need to fetch or refresh the estimate, but it may involve binder calls so offload the + // work + synchronized (mFetchCallbacks) { + mFetchCallbacks.add(completion); } - // Estimates may not exist yet even if we've checked + updateEstimateInBackground(); + } + + @Nullable + private String generateTimeRemainingString() { if (mEstimate == null) { return null; } - final String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0); + + String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0); return PowerUtil.getBatteryRemainingShortStringFormatted( mContext, mEstimate.estimateMillis); } + private void updateEstimateInBackground() { + if (mFetchingEstimate) { + // Already dispatched a fetch. It will notify all listeners when finished + return; + } + + mFetchingEstimate = true; + Dependency.get(Dependency.BG_HANDLER).post(() -> { + mEstimate = mEstimates.getEstimate(); + mLastEstimateTimestamp = System.currentTimeMillis(); + mFetchingEstimate = false; + + Dependency.get(Dependency.MAIN_HANDLER).post(this::notifyEstimateFetchCallbacks); + }); + } + + private void notifyEstimateFetchCallbacks() { + String estimate = generateTimeRemainingString(); + + synchronized (mFetchCallbacks) { + for (EstimateFetchCompletion completion : mFetchCallbacks) { + completion.onBatteryRemainingEstimateRetrieved(estimate); + } + + mFetchCallbacks.clear(); + } + } + private void updateEstimate() { mEstimate = mEstimates.getEstimate(); mLastEstimateTimestamp = System.currentTimeMillis(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index de7ef3ba645d..4299af7daf66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -274,7 +274,6 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C private void updateClockVisibility() { boolean visible = shouldBeVisible(); - Dependency.get(IconLogger.class).onIconVisibility("clock", visible); int visibility = visible ? View.VISIBLE : View.GONE; super.setVisibility(visibility); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java deleted file mode 100644 index 710e1df61404..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java +++ /dev/null @@ -1,29 +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.systemui.statusbar.policy; - -public interface IconLogger { - - void onIconShown(String tag); - void onIconHidden(String tag); - - default void onIconVisibility(String tag, boolean visible) { - if (visible) { - onIconShown(tag); - } else { - onIconHidden(tag); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java deleted file mode 100644 index ba6369e2c90e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java +++ /dev/null @@ -1,115 +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.systemui.statusbar.policy; - -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.STATUS_BAR_ICONS_CHANGED; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION; -import static com.android.systemui.Dependency.BG_LOOPER_NAME; - -import android.content.Context; -import android.metrics.LogMaker; -import android.os.Handler; -import android.os.Looper; -import android.util.ArraySet; - -import androidx.annotation.VisibleForTesting; - -import com.android.internal.logging.MetricsLogger; - -import java.util.Arrays; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; - -public class IconLoggerImpl implements IconLogger { - - // Minimum ms between log statements. - // NonFinalForTesting - @VisibleForTesting - protected static long MIN_LOG_INTERVAL = 1000; - - private final Context mContext; - private final Handler mHandler; - private final MetricsLogger mLogger; - private final ArraySet<String> mIcons = new ArraySet<>(); - private final List<String> mIconIndex; - private long mLastLog = System.currentTimeMillis(); - - @Inject - public IconLoggerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper, - MetricsLogger logger) { - mContext = context; - mHandler = new Handler(bgLooper); - mLogger = logger; - String[] icons = mContext.getResources().getStringArray( - com.android.internal.R.array.config_statusBarIcons); - mIconIndex = Arrays.asList(icons); - doLog(); - } - - @Override - public void onIconShown(String tag) { - synchronized (mIcons) { - if (mIcons.contains(tag)) return; - mIcons.add(tag); - } - if (!mHandler.hasCallbacks(mLog)) { - mHandler.postDelayed(mLog, MIN_LOG_INTERVAL); - } - } - - @Override - public void onIconHidden(String tag) { - synchronized (mIcons) { - if (!mIcons.contains(tag)) return; - mIcons.remove(tag); - } - if (!mHandler.hasCallbacks(mLog)) { - mHandler.postDelayed(mLog, MIN_LOG_INTERVAL); - } - } - - private void doLog() { - long time = System.currentTimeMillis(); - long timeSinceLastLog = time - mLastLog; - mLastLog = time; - - ArraySet<String> icons; - synchronized (mIcons) { - icons = new ArraySet<>(mIcons); - } - mLogger.write(new LogMaker(STATUS_BAR_ICONS_CHANGED) - .setType(TYPE_ACTION) - .setLatency(timeSinceLastLog) - .addTaggedData(FIELD_NUM_STATUS_ICONS, icons.size()) - .addTaggedData(FIELD_STATUS_ICONS, getBitField(icons))); - } - - private int getBitField(ArraySet<String> icons) { - int iconsVisible = 0; - for (String icon : icons) { - int index = mIconIndex.indexOf(icon); - if (index >= 0) { - iconsVisible |= (1 << index); - } - } - return iconsVisible; - } - - private final Runnable mLog = this::doLog; -} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java new file mode 100644 index 000000000000..52cabe278e2d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpaper; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.WallpaperManager; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.RectF; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.util.AttributeSet; +import android.util.FeatureFlagUtils; +import android.util.Log; +import android.view.Display; +import android.view.DisplayInfo; +import android.widget.ImageView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.PropertyAnimator; +import com.android.systemui.statusbar.notification.stack.AnimationProperties; +import com.android.systemui.statusbar.phone.ScrimState; + +/** + * A view that draws mask upon either image wallpaper or music album art in AOD. + */ +public class AodMaskView extends ImageView implements StatusBarStateController.StateListener, + ImageWallpaperTransformer.TransformationListener { + private static final String TAG = AodMaskView.class.getSimpleName(); + private static final int TRANSITION_DURATION = 1000; + + private static final AnimatableProperty TRANSITION_PROGRESS = AnimatableProperty.from( + "transition_progress", + AodMaskView::setTransitionAmount, + AodMaskView::getTransitionAmount, + R.id.aod_mask_transition_progress_tag, + R.id.aod_mask_transition_progress_start_tag, + R.id.aod_mask_transition_progress_end_tag + ); + + private final AnimationProperties mTransitionProperties = new AnimationProperties(); + private final ImageWallpaperTransformer mTransformer; + private final RectF mBounds = new RectF(); + private boolean mChangingStates; + private boolean mNeedMask; + private float mTransitionAmount; + private final WallpaperManager mWallpaperManager; + private final DisplayManager mDisplayManager; + private DisplayListener mDisplayListener = new DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + // We just support DEFAULT_DISPLAY currently. + if (displayId == Display.DEFAULT_DISPLAY) { + mTransformer.updateDisplayInfo(getDisplayInfo(displayId)); + } + } + }; + + public AodMaskView(Context context) { + this(context, null); + } + + public AodMaskView(Context context, AttributeSet attrs) { + this(context, attrs, null); + } + + @VisibleForTesting + public AodMaskView(Context context, AttributeSet attrs, ImageWallpaperTransformer transformer) { + super(context, attrs); + setClickable(false); + + StatusBarStateController controller = Dependency.get(StatusBarStateController.class); + if (controller != null) { + controller.addCallback(this); + } else { + Log.d(TAG, "Can not get StatusBarStateController!"); + } + + mDisplayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE); + mDisplayManager.registerDisplayListener(mDisplayListener, null); + mWallpaperManager = + (WallpaperManager) getContext().getSystemService(Context.WALLPAPER_SERVICE); + + if (transformer == null) { + mTransformer = new ImageWallpaperTransformer(this); + mTransformer.addFilter(new ScrimFilter()); + mTransformer.addFilter(new VignetteFilter()); + mTransformer.updateOffsets(); + mTransformer.updateDisplayInfo(getDisplayInfo(Display.DEFAULT_DISPLAY)); + + mTransitionProperties.setDuration(TRANSITION_DURATION); + mTransitionProperties.setAnimationFinishListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mTransformer.setIsTransiting(false); + } + + @Override + public void onAnimationStart(Animator animation) { + mTransformer.setIsTransiting(true); + } + }); + } else { + // This part should only be hit by test cases. + mTransformer = transformer; + } + } + + private DisplayInfo getDisplayInfo(int displayId) { + DisplayInfo displayInfo = new DisplayInfo(); + mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo); + return displayInfo; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mBounds.set(0, 0, w, h); + mTransformer.updateOffsets(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mNeedMask) { + mTransformer.drawTransformedImage(canvas, null /* target */, null /* src */, mBounds); + } + } + + private boolean checkIfNeedMask() { + // We need mask for ImageWallpaper / LockScreen Wallpaper (Music album art). + return mWallpaperManager.getWallpaperInfo() == null || ScrimState.AOD.hasBackdrop(); + } + + @Override + public void onStatePreChange(int oldState, int newState) { + mChangingStates = oldState != newState; + mNeedMask = checkIfNeedMask(); + } + + @Override + public void onStatePostChange() { + mChangingStates = false; + } + + @Override + public void onStateChanged(int newState) { + } + + @Override + public void onDozingChanged(boolean isDozing) { + if (!mNeedMask) { + return; + } + + boolean enabled = checkFeatureIsEnabled(); + mTransformer.updateAmbientModeState(enabled && isDozing); + + if (enabled && !mChangingStates) { + setAnimatorProperty(isDozing); + } else { + invalidate(); + } + } + + private boolean checkFeatureIsEnabled() { + return FeatureFlagUtils.isEnabled( + getContext(), FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED); + } + + @VisibleForTesting + void setAnimatorProperty(boolean isDozing) { + PropertyAnimator.setProperty( + this, + TRANSITION_PROGRESS, + isDozing ? 1f : 0f /* newEndValue */, + mTransitionProperties, + true /* animated */); + } + + @Override + public void onTransformationUpdated() { + invalidate(); + } + + private void setTransitionAmount(float amount) { + mTransitionAmount = amount; + mTransformer.updateTransitionAmount(amount); + } + + private float getTransitionAmount() { + return mTransitionAmount; + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java new file mode 100644 index 000000000000..d457dac3e14f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpaper; + +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * Abstract filter used by static image wallpaper. + */ +abstract class ImageWallpaperFilter { + protected static final boolean DEBUG = false; + + private ImageWallpaperTransformer mTransformer; + + /** + * Apply this filter to the bitmap before drawing on canvas. + * @param c The canvas that will draw to. + * @param bitmap The bitmap to apply this filter. + * @param src The subset of the bitmap to be drawn. + * @param dest The rectangle that the bitmap will be scaled/translated to fit into. + */ + public abstract void apply(@NonNull Canvas c, @Nullable Bitmap bitmap, + @Nullable Rect src, @NonNull RectF dest); + + /** + * Notifies the occurrence of built-in transition of the animation. + * @param animator The animator which was animated. + */ + public abstract void onAnimatorUpdate(ValueAnimator animator); + + /** + * Notifies the occurrence of another transition of the animation. + * @param amount The transition amount. + */ + public abstract void onTransitionAmountUpdate(float amount); + + /** + * To set the associated transformer. + * @param transformer The transformer that is associated with this filter. + */ + public void setTransformer(ImageWallpaperTransformer transformer) { + if (transformer != null) { + mTransformer = transformer; + } + } + + protected ImageWallpaperTransformer getTransformer() { + return mTransformer; + } + + /** + * Notifies the changing of the offset value of the ImageWallpaper. + * @param force True to force re-evaluate offsets. + * @param xOffset X offset of the ImageWallpaper in percentage. + * @param yOffset Y offset of the ImageWallpaper in percentage. + */ + public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) { + // No-op + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java new file mode 100644 index 000000000000..25b0b0aaf60b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpaper; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.DisplayInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is used to manage the filters that will be applied. + */ +public class ImageWallpaperTransformer { + private static final String TAG = ImageWallpaperTransformer.class.getSimpleName(); + + private DisplayInfo mDisplayInfo; + private final List<ImageWallpaperFilter> mFilters; + private final TransformationListener mListener; + private boolean mIsInAmbientMode; + private boolean mIsTransiting; + + /** + * Constructor. + * @param listener A listener to inform you the transformation has updated. + */ + public ImageWallpaperTransformer(TransformationListener listener) { + mFilters = new ArrayList<>(); + mListener = listener; + } + + /** + * Claim that we want to use the specified filter. + * @param filter The filter will be used. + */ + public void addFilter(ImageWallpaperFilter filter) { + if (filter != null) { + filter.setTransformer(this); + mFilters.add(filter); + } + } + + /** + * Check if any transition is running. + * @return True if the transition is running, false otherwise. + */ + boolean isTransiting() { + return mIsTransiting; + } + + /** + * Indicate if any transition is running. <br/> + * @param isTransiting True if the transition is running. + */ + void setIsTransiting(boolean isTransiting) { + mIsTransiting = isTransiting; + } + + /** + * Check if the device is in ambient mode. + * @return True if the device is in ambient mode, false otherwise. + */ + public boolean isInAmbientMode() { + return mIsInAmbientMode; + } + + /** + * Update current state of ambient mode. + * @param isInAmbientMode Current ambient mode state. + */ + public void updateAmbientModeState(boolean isInAmbientMode) { + mIsInAmbientMode = isInAmbientMode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int idx = 0; + for (ImageWallpaperFilter filter : mFilters) { + sb.append(idx++).append(": ").append(filter.getClass().getSimpleName()).append("\n"); + } + if (sb.length() == 0) { + sb.append("No filters applied"); + } + return sb.toString(); + } + + /** + * Set a new display info. + * @param displayInfo New display info. + */ + public void updateDisplayInfo(DisplayInfo displayInfo) { + mDisplayInfo = displayInfo; + } + + /** + * To get current display info. + * @return Current display info. + */ + public DisplayInfo getDisplayInfo() { + return mDisplayInfo; + } + + /** + * Update the offsets with default value. + */ + public void updateOffsets() { + this.updateOffsets(true, 0f, .5f); + } + + /** + * To notify the filters that the offset of the ImageWallpaper changes. + * @param force True to force re-evaluate offsets. + * @param offsetX X offset of the ImageWallpaper in percentage. + * @param offsetY Y offset of the ImageWallpaper in percentage. + */ + public void updateOffsets(boolean force, float offsetX, float offsetY) { + mFilters.forEach(filter -> filter.onOffsetsUpdate(force, offsetX, offsetY)); + } + + /** + * Apply all specified filters to the bitmap then draw to the canvas. + * @param c The canvas that will draw to. + * @param target The bitmap to apply filters. + * @param src The subset of the bitmap to be drawn + * @param dest The rectangle that the bitmap will be scaled/translated to fit into. + */ + void drawTransformedImage(@NonNull Canvas c, @Nullable Bitmap target, + @Nullable Rect src, @NonNull RectF dest) { + mFilters.forEach(filter -> filter.apply(c, target, src, dest)); + } + + /** + * Update the transition amount. <br/> + * Must invoke this to update transition amount if not running built-in transition. + * @param amount The transition amount. + */ + void updateTransitionAmount(float amount) { + mFilters.forEach(filter -> filter.onTransitionAmountUpdate(amount)); + if (mListener != null) { + mListener.onTransformationUpdated(); + } + } + + /** + * An interface that informs the transformation status. + */ + public interface TransformationListener { + /** + * Notifies the update of the transformation. + */ + void onTransformationUpdated(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java new file mode 100644 index 000000000000..637e48e67de6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpaper; + +import android.animation.ValueAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * A filter that implements 70% black scrim effect. + */ +public class ScrimFilter extends ImageWallpaperFilter { + private static final int MAX_ALPHA = (int) (255 * .7f); + private static final int MIN_ALPHA = 0; + + private final Paint mPaint; + + public ScrimFilter() { + mPaint = new Paint(); + mPaint.setColor(Color.BLACK); + mPaint.setAlpha(MAX_ALPHA); + } + + @Override + public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) { + ImageWallpaperTransformer transformer = getTransformer(); + + // If it is not in the transition, we need to set the property according to aod state. + if (!transformer.isTransiting()) { + mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA); + } + + c.drawRect(dest, mPaint); + } + + @Override + public void onAnimatorUpdate(ValueAnimator animator) { + ImageWallpaperTransformer transformer = getTransformer(); + float fraction = animator.getAnimatedFraction(); + float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction; + mPaint.setAlpha((int) (factor * MAX_ALPHA)); + } + + @Override + public void onTransitionAmountUpdate(float amount) { + mPaint.setAlpha((int) (amount * MAX_ALPHA)); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java new file mode 100644 index 000000000000..ad0b98b67e68 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpaper; + +import android.animation.ValueAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.RadialGradient; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.util.Log; +import android.view.DisplayInfo; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * A filter that implements vignette effect. + */ +public class VignetteFilter extends ImageWallpaperFilter { + private static final String TAG = VignetteFilter.class.getSimpleName(); + private static final int MAX_ALPHA = 255; + private static final int MIN_ALPHA = 0; + + private final Paint mPaint; + private final Matrix mMatrix; + private final Shader mShader; + + private float mXOffset; + private float mYOffset; + private float mCenterX; + private float mCenterY; + private float mStretchX; + private float mStretchY; + private boolean mCalculateOffsetNeeded; + + public VignetteFilter() { + mPaint = new Paint(); + mMatrix = new Matrix(); + mShader = new RadialGradient(0, 0, 1, + Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP); + } + + @Override + public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) { + DisplayInfo info = getTransformer().getDisplayInfo(); + + if (mCalculateOffsetNeeded) { + int lw = info.logicalWidth; + int lh = info.logicalHeight; + mCenterX = lw / 2 + (dest.width() - lw) * mXOffset; + mCenterY = lh / 2 + (dest.height() - lh) * mYOffset; + mStretchX = info.logicalWidth / 2; + mStretchY = info.logicalHeight / 2; + mCalculateOffsetNeeded = false; + } + + if (DEBUG) { + Log.d(TAG, "apply: lw=" + info.logicalWidth + ", lh=" + info.logicalHeight + + ", center=(" + mCenterX + "," + mCenterY + ")" + + ", stretch=(" + mStretchX + "," + mStretchY + ")"); + } + + mMatrix.reset(); + mMatrix.postTranslate(mCenterX, mCenterY); + mMatrix.postScale(mStretchX, mStretchY, mCenterX, mCenterY); + mShader.setLocalMatrix(mMatrix); + mPaint.setShader(mShader); + + ImageWallpaperTransformer transformer = getTransformer(); + + // If it is not in the transition, we need to set the property according to aod state. + if (!transformer.isTransiting()) { + mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA); + } + + c.drawRect(dest, mPaint); + } + + @Override + public void onAnimatorUpdate(ValueAnimator animator) { + ImageWallpaperTransformer transformer = getTransformer(); + float fraction = animator.getAnimatedFraction(); + float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction; + mPaint.setAlpha((int) (factor * MAX_ALPHA)); + } + + @Override + public void onTransitionAmountUpdate(float amount) { + mPaint.setAlpha((int) (amount * MAX_ALPHA)); + } + + @Override + public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) { + if (force || mXOffset != xOffset || mYOffset != yOffset) { + mXOffset = xOffset; + mYOffset = yOffset; + mCalculateOffsetNeeded = true; + } + } + + @VisibleForTesting + public PointF getCenterPoint() { + return new PointF(mCenterX, mCenterY); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 1844df5c070a..fbc1c20755a1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -21,7 +21,6 @@ import static android.view.View.VISIBLE; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -42,19 +41,18 @@ import android.widget.TextClock; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; 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 @@ -62,7 +60,6 @@ import org.mockito.MockitoAnnotations; // the keyguard_clcok_switch layout is inflated. @RunWithLooper(setAsMainLooper = true) public class KeyguardClockSwitchTest extends SysuiTestCase { - private PluginManager mPluginManager; private FrameLayout mClockContainer; private StatusBarStateController.StateListener mStateListener; @@ -73,7 +70,6 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Before public void setUp() { - mPluginManager = mDependency.injectMockDependency(PluginManager.class); LayoutInflater layoutInflater = LayoutInflater.from(getContext()); mKeyguardClockSwitch = (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null); @@ -84,29 +80,12 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { } @Test - public void onAttachToWindow_addPluginListener() { - mKeyguardClockSwitch.onAttachedToWindow(); - - ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class); - verify(mPluginManager).addPluginListener(listener.capture(), eq(ClockPlugin.class)); - } - - @Test - public void onDetachToWindow_removePluginListener() { - mKeyguardClockSwitch.onDetachedFromWindow(); - - ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class); - verify(mPluginManager).removePluginListener(listener.capture()); - } - - @Test public void onPluginConnected_showPluginClock() { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); verify(mClockView).setVisibility(GONE); assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer); @@ -122,9 +101,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getBigClockView()).thenReturn(pluginView); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); // WHEN the plugin is connected - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); // THEN the big clock container is visible and it is the parent of the // big clock view. assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE); @@ -134,8 +112,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Test public void onPluginConnected_nullView() { ClockPlugin plugin = mock(ClockPlugin.class); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); verify(mClockView, never()).setVisibility(GONE); } @@ -144,27 +121,36 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // GIVEN a plugin has already connected ClockPlugin plugin1 = mock(ClockPlugin.class); when(plugin1.getView()).thenReturn(new TextClock(getContext())); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin1, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1); // WHEN a second plugin is connected ClockPlugin plugin2 = mock(ClockPlugin.class); when(plugin2.getView()).thenReturn(new TextClock(getContext())); - listener.onPluginConnected(plugin2, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(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(); } @Test + public void onPluginConnected_darkAmountInitialized() { + // GIVEN that the dark amount has already been set + mKeyguardClockSwitch.setDarkAmount(0.5f); + // WHEN a plugin is connected + ClockPlugin plugin = mock(ClockPlugin.class); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + // THEN dark amount should be initalized on the plugin. + verify(plugin).setDarkAmount(0.5f); + } + + @Test public void onPluginDisconnected_showDefaultClock() { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); mClockView.setVisibility(GONE); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); - listener.onPluginDisconnected(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(null); verify(mClockView).setVisibility(VISIBLE); assertThat(plugin.getView().getParent()).isNull(); @@ -180,10 +166,9 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getBigClockView()).thenReturn(pluginView); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); - // WHEN the plugin is disconnected - listener.onPluginDisconnected(plugin); + // WHEN the plugin is connected and then disconnected + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(null); // THEN the big lock container is GONE and the big clock view doesn't have // a parent. assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE); @@ -193,41 +178,23 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Test public void onPluginDisconnected_nullView() { ClockPlugin plugin = mock(ClockPlugin.class); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); - listener.onPluginDisconnected(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(null); verify(mClockView, never()).setVisibility(GONE); } @Test - public void onPluginDisconnected_firstOfTwoDisconnected() { - // GIVEN two plugins are connected - ClockPlugin plugin1 = mock(ClockPlugin.class); - when(plugin1.getView()).thenReturn(new TextClock(getContext())); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin1, null); - ClockPlugin plugin2 = mock(ClockPlugin.class); - when(plugin2.getView()).thenReturn(new TextClock(getContext())); - listener.onPluginConnected(plugin2, null); - // WHEN the first plugin is disconnected - listener.onPluginDisconnected(plugin1); - // THEN the view from the second plugin is still a child of KeyguardClockSwitch. - assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer); - assertThat(plugin1.getView().getParent()).isNull(); - } - - @Test public void onPluginDisconnected_secondOfTwoDisconnected() { // GIVEN two plugins are connected ClockPlugin plugin1 = mock(ClockPlugin.class); when(plugin1.getView()).thenReturn(new TextClock(getContext())); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin1, null); + Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer(); + consumer.accept(plugin1); ClockPlugin plugin2 = mock(ClockPlugin.class); when(plugin2.getView()).thenReturn(new TextClock(getContext())); - listener.onPluginConnected(plugin2, null); + consumer.accept(plugin2); // WHEN the second plugin is disconnected - listener.onPluginDisconnected(plugin2); + consumer.accept(null); // THEN the default clock should be shown. verify(mClockView).setVisibility(VISIBLE); assertThat(plugin1.getView().getParent()).isNull(); @@ -246,8 +213,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); mKeyguardClockSwitch.setTextColor(Color.WHITE); @@ -271,8 +237,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); Style style = mock(Style.class); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(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 521d5d10a621..53ad0b5132c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java @@ -16,17 +16,12 @@ package com.android.systemui; - -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +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.graphics.Bitmap; @@ -58,13 +53,15 @@ public class ImageWallpaperTest extends SysuiTestCase { @Mock private SurfaceHolder mSurfaceHolder; @Mock private DisplayInfo mDisplayInfo; - CountDownLatch mEventCountdown; + private CountDownLatch mEventCountdown; + private CountDownLatch mAmbientEventCountdown; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mEventCountdown = new CountDownLatch(1); + mAmbientEventCountdown = new CountDownLatch(2); mImageWallpaper = new ImageWallpaper() { @Override @@ -86,6 +83,11 @@ public class ImageWallpaperTest extends SysuiTestCase { assertTrue("mFixedSizeAllowed should be true", allowed); mEventCountdown.countDown(); } + + @Override + public void onAmbientModeChanged(boolean inAmbientMode, long duration) { + mAmbientEventCountdown.countDown(); + } }; } }; @@ -132,4 +134,23 @@ public class ImageWallpaperTest extends SysuiTestCase { verify(mSurfaceHolder, times(1)).setFixedSize(ImageWallpaper.DrawableEngine.MIN_BACKGROUND_WIDTH, ImageWallpaper.DrawableEngine.MIN_BACKGROUND_HEIGHT); } + @Test + public void testDeliversAmbientModeChanged() { + ImageWallpaper.DrawableEngine wallpaperEngine = + (ImageWallpaper.DrawableEngine) mImageWallpaper.onCreateEngine(); + + assertEquals("setFixedSizeAllowed should have been called.", + 0, mEventCountdown.getCount()); + + wallpaperEngine.setCreated(true); + wallpaperEngine.doAmbientModeChanged(false, 1000); + assertFalse("ambient mode should be false", wallpaperEngine.isInAmbientMode()); + assertEquals("onAmbientModeChanged should have been called.", + 1, mAmbientEventCountdown.getCount()); + + wallpaperEngine.doAmbientModeChanged(true, 1000); + assertTrue("ambient mode should be true", wallpaperEngine.isInAmbientMode()); + assertEquals("onAmbientModeChanged should have been called.", + 0, mAmbientEventCountdown.getCount()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java index 2805908d114a..382dde9ce043 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java @@ -97,6 +97,7 @@ public class QuickStepControllerTest extends SysuiTestCase { doReturn(HIT_TARGET_NONE).when(mNavigationBarView).getDownHitTarget(); doReturn(backButton).when(mNavigationBarView).getBackButton(); doReturn(mResources).when(mNavigationBarView).getResources(); + doReturn(mContext).when(mNavigationBarView).getContext(); mController = new QuickStepController(mContext); mController.setComponents(mNavigationBarView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 146c5d647198..8eb42c4b088c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -54,7 +54,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.function.Consumer; @@ -94,6 +94,7 @@ public class ScrimControllerTest extends SysuiTestCase { }, visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager); mScrimController.setHasBackdrop(false); + mScrimController.setWallpaperSupportsAmbientMode(false); } @Test @@ -474,6 +475,26 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void testHoldsPulsingWallpaperAnimationLock() { + // Pre-conditions + mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() { + @Override + public boolean isFadeOutWallpaper() { + return true; + } + }); + mScrimController.finishAnimationsImmediately(); + reset(mWakeLock); + + mScrimController.onHideWallpaperTimeout(); + verify(mWakeLock).acquire(); + verify(mWakeLock, never()).release(); + mScrimController.finishAnimationsImmediately(); + verify(mWakeLock).release(); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); + } + + @Test public void testWillHideAodWallpaper() { mScrimController.setWallpaperSupportsAmbientMode(true); mScrimController.transitionTo(ScrimState.AOD); @@ -483,6 +504,34 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void testWillHidePulsingWallpaper_withRequestFadeOut() { + mScrimController.setWallpaperSupportsAmbientMode(true); + mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() { + @Override + public boolean isFadeOutWallpaper() { + return true; + } + }); + verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any()); + mScrimController.transitionTo(ScrimState.KEYGUARD); + verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class)); + } + + @Test + public void testDoesNotHidePulsingWallpaper_withoutRequestFadeOut() { + mScrimController.setWallpaperSupportsAmbientMode(true); + mScrimController.transitionTo(ScrimState.PULSING, new ScrimController.Callback() {}); + verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any()); + } + + @Test + public void testDoesNotHidePulsingWallpaper_withoutCallback() { + mScrimController.setWallpaperSupportsAmbientMode(true); + mScrimController.transitionTo(ScrimState.PULSING); + verify(mAlarmManager, never()).setExact(anyInt(), anyLong(), any(), any(), any()); + } + + @Test public void testConservesExpansionOpacityAfterTransition() { mScrimController.transitionTo(ScrimState.UNLOCKED); mScrimController.setPanelExpansion(0.5f); @@ -578,7 +627,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testEatsTouchEvent() { HashSet<ScrimState> eatsTouches = - new HashSet<>(Arrays.asList(ScrimState.AOD, ScrimState.PULSING)); + new HashSet<>(Collections.singletonList(ScrimState.AOD)); for (ScrimState state : ScrimState.values()) { if (state == ScrimState.UNINITIALIZED) { continue; @@ -738,5 +787,4 @@ public class ScrimControllerTest extends SysuiTestCase { callback.run(); } } - } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java deleted file mode 100644 index 5c347301bd8c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java +++ /dev/null @@ -1,177 +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.systemui.statusbar.policy; - -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent - .RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS; - -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import static java.lang.Thread.sleep; - -import android.metrics.LogMaker; -import android.support.test.filters.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.MessageHandler; -import android.testing.TestableLooper.RunWithLooper; -import android.util.Log; - -import com.android.internal.logging.MetricsLogger; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatcher; - -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -@SmallTest -public class IconLoggerImplTest extends SysuiTestCase { - - private MetricsLogger mMetricsLogger; - private IconLoggerImpl mIconLogger; - private TestableLooper mTestableLooper; - private MessageHandler mMessageHandler; - - @Before - public void setup() { - IconLoggerImpl.MIN_LOG_INTERVAL = 5; // Low interval for testing - mMetricsLogger = mock(MetricsLogger.class); - mTestableLooper = TestableLooper.get(this); - mMessageHandler = mock(MessageHandler.class); - mTestableLooper.setMessageHandler(mMessageHandler); - String[] iconArray = new String[] { - "test_icon_1", - "test_icon_2", - }; - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.array.config_statusBarIcons, iconArray); - mIconLogger = new IconLoggerImpl(mContext, mTestableLooper.getLooper(), mMetricsLogger); - when(mMessageHandler.onMessageHandled(any())).thenReturn(true); - clearInvocations(mMetricsLogger); - } - - @Test - public void testIconShown() throws InterruptedException { - // Should only get one message, for the same icon shown twice. - mIconLogger.onIconShown("test_icon_2"); - mIconLogger.onIconShown("test_icon_2"); - - // There should be some delay before execute. - mTestableLooper.processAllMessages(); - verify(mMessageHandler, never()).onMessageHandled(any()); - - sleep(10); - mTestableLooper.processAllMessages(); - verify(mMessageHandler, times(1)).onMessageHandled(any()); - } - - @Test - public void testIconHidden() throws InterruptedException { - // Add the icon so that it can be removed. - mIconLogger.onIconShown("test_icon_2"); - sleep(10); - mTestableLooper.processAllMessages(); - clearInvocations(mMessageHandler); - - // Should only get one message, for the same icon shown twice. - mIconLogger.onIconHidden("test_icon_2"); - mIconLogger.onIconHidden("test_icon_2"); - - // There should be some delay before execute. - mTestableLooper.processAllMessages(); - verify(mMessageHandler, never()).onMessageHandled(any()); - - sleep(10); - mTestableLooper.processAllMessages(); - verify(mMessageHandler, times(1)).onMessageHandled(any()); - } - - @Test - public void testLog() throws InterruptedException { - mIconLogger.onIconShown("test_icon_2"); - sleep(10); - mTestableLooper.processAllMessages(); - - verify(mMetricsLogger).write(argThat(maker -> { - if (IconLoggerImpl.MIN_LOG_INTERVAL > - (long) maker.getTaggedData(RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS)) { - Log.e("IconLoggerImplTest", "Invalid latency " - + maker.getTaggedData(RESERVED_FOR_LOGBUILDER_LATENCY_MILLIS)); - return false; - } - if (1 != (int) maker.getTaggedData(FIELD_NUM_STATUS_ICONS)) { - Log.e("IconLoggerImplTest", "Invalid icon count " - + maker.getTaggedData(FIELD_NUM_STATUS_ICONS)); - return false; - } - return true; - })); - } - - @Test - public void testBitField() throws InterruptedException { - mIconLogger.onIconShown("test_icon_2"); - sleep(10); - mTestableLooper.processAllMessages(); - - verify(mMetricsLogger).write(argThat(maker -> { - if ((1 << 1) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) { - Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString( - (Integer) maker.getTaggedData(FIELD_NUM_STATUS_ICONS))); - return false; - } - return true; - })); - - mIconLogger.onIconShown("test_icon_1"); - sleep(10); - mTestableLooper.processAllMessages(); - - verify(mMetricsLogger).write(argThat(maker -> { - if ((1 << 1 | 1 << 0) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) { - Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString( - (Integer) maker.getTaggedData(FIELD_NUM_STATUS_ICONS))); - return false; - } - return true; - })); - - mIconLogger.onIconHidden("test_icon_2"); - sleep(10); - mTestableLooper.processAllMessages(); - - verify(mMetricsLogger).write(argThat(maker -> { - if ((1 << 0) != (int) maker.getTaggedData(FIELD_STATUS_ICONS)) { - Log.e("IconLoggerImplTest", "Invalid bitfield " + Integer.toHexString( - (Integer) maker.getTaggedData(FIELD_STATUS_ICONS))); - return false; - } - return true; - })); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java new file mode 100644 index 000000000000..c44a366e683c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/AodMaskViewTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpaper; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.WallpaperManager; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.RectF; +import android.hardware.display.DisplayManager; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.FeatureFlagUtils; +import android.view.DisplayInfo; +import android.view.WindowManager; + +import com.android.systemui.SysuiTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AodMaskViewTest extends SysuiTestCase { + private AodMaskView mMaskView; + private DisplayInfo mDisplayInfo; + private ImageWallpaperTransformer mTransformer; + + @Before + public void setUp() throws Exception { + DisplayManager displayManager = + spy((DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE)); + doNothing().when(displayManager).registerDisplayListener(any(), any()); + mContext.addMockSystemService(DisplayManager.class, displayManager); + + WallpaperManager wallpaperManager = + spy((WallpaperManager) mContext.getSystemService(Context.WALLPAPER_SERVICE)); + doReturn(null).when(wallpaperManager).getWallpaperInfo(); + mContext.addMockSystemService(WallpaperManager.class, wallpaperManager); + + mTransformer = spy(new ImageWallpaperTransformer(null /* listener */)); + mMaskView = spy(new AodMaskView(getContext(), null /* attrs */, mTransformer)); + mDisplayInfo = new DisplayInfo(); + + ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay().getDisplayInfo(mDisplayInfo); + + FeatureFlagUtils.setEnabled( + mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, true); + } + + @After + public void tearDown() { + FeatureFlagUtils.setEnabled( + mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED, false); + } + + @Test + public void testCreateMaskView_TransformerIsNotNull() { + assertNotNull("mTransformer should not be null", mTransformer); + } + + @Test + public void testAodMaskView_ShouldNotClickable() { + assertFalse("MaskView should not be clickable", mMaskView.isClickable()); + } + + @Test + public void testAodMaskView_OnSizeChange_ShouldUpdateTransformerOffsets() { + mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0); + verify(mTransformer, times(1)).updateOffsets(); + } + + @Test + public void testAodMaskView_OnDraw_ShouldDrawTransformedImage() { + Canvas c = new Canvas(); + RectF bounds = new RectF(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); + mMaskView.onSizeChanged(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, 0, 0); + mMaskView.onStatePreChange(0, 1); + mMaskView.onDraw(c); + verify(mTransformer, times(1)).drawTransformedImage(c, null, null, bounds); + } + + @Test + public void testAodMaskView_IsDozing_ShouldUpdateAmbientModeState() { + doNothing().when(mMaskView).setAnimatorProperty(anyBoolean()); + mMaskView.onStatePreChange(0, 1); + mMaskView.onDozingChanged(true); + verify(mTransformer, times(1)).updateAmbientModeState(true); + } + + @Test + public void testAodMaskView_IsDozing_ShouldDoTransitionOrDrawFinalFrame() { + doNothing().when(mMaskView).setAnimatorProperty(anyBoolean()); + mMaskView.onStatePreChange(0, 1); + mMaskView.onDozingChanged(true); + mMaskView.onStatePostChange(); + mMaskView.onDozingChanged(false); + verify(mMaskView, times(1)).invalidate(); + verify(mMaskView, times(1)).setAnimatorProperty(false); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java new file mode 100644 index 000000000000..55b0aaefff51 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpaper/ImageWallpaperTransformerTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wallpaper; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PointF; +import android.graphics.RectF; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.DisplayInfo; +import android.view.WindowManager; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ImageWallpaperTransformerTest extends SysuiTestCase { + private DisplayInfo mDisplayInfo; + private Bitmap mBitmap; + private Canvas mCanvas; + private RectF mDestination; + + @Before + public void setUp() throws Exception { + mDisplayInfo = new DisplayInfo(); + ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay().getDisplayInfo(mDisplayInfo); + int dimension = Math.max(mDisplayInfo.logicalHeight, mDisplayInfo.logicalWidth); + mBitmap = Bitmap.createBitmap(dimension, dimension, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBitmap); + mCanvas.drawColor(Color.RED); + mDestination = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); + } + + @Test + public void testVignetteFilter() { + VignetteFilter vignette = new VignetteFilter(); + + ImageWallpaperTransformer transformer = getTransformer(vignette); + transformer.drawTransformedImage(mCanvas, mBitmap, null, mDestination); + + PointF center = vignette.getCenterPoint(); + int p1 = mBitmap.getPixel((int) center.x, (int) center.y); + int p2 = mBitmap.getPixel(0, 0); + int p3 = mBitmap.getPixel(mBitmap.getWidth() - 1, mBitmap.getHeight() - 1); + + assertThat(p1).isEqualTo(Color.RED); + assertThat(p2 | p3).isEqualTo(Color.BLACK); + } + + @Test + public void testScrimFilter() { + getTransformer(new ScrimFilter()) + .drawTransformedImage(mCanvas, mBitmap, null, mDestination); + + int pixel = mBitmap.getPixel(0, 0); + + // 0xff4d0000 is the result of 70% alpha pre-multiplied which is 0.7*(0,0,0)+0.3*(255,0,0). + assertThat(pixel).isEqualTo(0xff4d0000); + } + + private ImageWallpaperTransformer getTransformer(ImageWallpaperFilter filter) { + ImageWallpaperTransformer transformer = new ImageWallpaperTransformer(null); + transformer.addFilter(filter); + transformer.updateDisplayInfo(mDisplayInfo); + transformer.updateOffsets(); + transformer.updateAmbientModeState(true); + return transformer; + } +} diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 665773c28235..aff5e1302e98 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -5325,7 +5325,7 @@ message MetricsEvent { // OS: P ACCESSIBILITY_VIBRATION = 1292; - // OPEN: Settings > Accessibility > Vibration > Ring & notification vibration + // OPEN: Settings > Accessibility > Vibration > Notification vibration // CATEGORY: SETTINGS // OS: P ACCESSIBILITY_VIBRATION_NOTIFICATION = 1293; @@ -6763,6 +6763,11 @@ message MetricsEvent { // OS: Q ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED = 1619; + // OPEN: Settings > Accessibility > Vibration > Ring vibration + // CATEGORY: SETTINGS + // OS: Q + ACCESSIBILITY_VIBRATION_RING = 1620; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/Android.bp b/services/Android.bp index 01734f4051d3..31385edd015f 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -17,13 +17,16 @@ java_library { static_libs: [ "services.core", "services.accessibility", + "services.appprediction", "services.appwidget", "services.autofill", "services.backup", "services.companion", "services.contentcapture", + "services.contentsuggestions", "services.coverage", "services.devicepolicy", + "services.ipmemorystore", "services.midi", "services.net", "services.print", diff --git a/services/appprediction/Android.bp b/services/appprediction/Android.bp new file mode 100644 index 000000000000..a7be58783aab --- /dev/null +++ b/services/appprediction/Android.bp @@ -0,0 +1,5 @@ +java_library_static { + name: "services.appprediction", + srcs: ["java/**/*.java"], + libs: ["services.core"], +} diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java new file mode 100644 index 000000000000..833eaa06d759 --- /dev/null +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java @@ -0,0 +1,156 @@ +/* + * 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.appprediction; + +import static android.Manifest.permission.MANAGE_APP_PREDICTIONS; +import static android.content.Context.APP_PREDICTION_SERVICE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.IPredictionCallback; +import android.app.prediction.IPredictionManager; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.UserHandle; + +import com.android.server.infra.AbstractMasterSystemService; +import com.android.server.infra.FrameworkResourcesServiceNameResolver; + +import java.io.FileDescriptor; +import java.util.function.Consumer; + +/** + * A service used to predict app and shortcut usage. + * + * <p>The data collected by this service can be analyzed and combined with other sources to provide + * predictions in different areas of the system such as Launcher and Share sheet. + */ +public class AppPredictionManagerService extends + AbstractMasterSystemService<AppPredictionManagerService, AppPredictionPerUserService> { + + private static final String TAG = AppPredictionManagerService.class.getSimpleName(); + + private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes + + public AppPredictionManagerService(Context context) { + super(context, new FrameworkResourcesServiceNameResolver(context, + com.android.internal.R.string.config_defaultAppPredictionService), null); + } + + @Override + protected AppPredictionPerUserService newServiceLocked(int resolvedUserId, boolean disabled) { + return new AppPredictionPerUserService(this, mLock, resolvedUserId); + } + + @Override + public void onStart() { + publishBinderService(APP_PREDICTION_SERVICE, new PredictionManagerServiceStub()); + } + + @Override + protected void enforceCallingPermissionForManagement() { + getContext().enforceCallingPermission(MANAGE_APP_PREDICTIONS, TAG); + } + + @Override + protected int getMaximumTemporaryServiceDurationMs() { + return MAX_TEMP_SERVICE_DURATION_MS; + } + + private class PredictionManagerServiceStub extends IPredictionManager.Stub { + + @Override + public void createPredictionSession(@NonNull AppPredictionContext context, + @NonNull AppPredictionSessionId sessionId) { + runForUserLocked((service) -> + service.onCreatePredictionSessionLocked(context, sessionId)); + } + + @Override + public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId, + @NonNull AppTargetEvent event) { + runForUserLocked((service) -> service.notifyAppTargetEventLocked(sessionId, event)); + } + + @Override + public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId, + @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { + runForUserLocked((service) -> + service.notifyLocationShownLocked(sessionId, launchLocation, targetIds)); + } + + @Override + public void sortAppTargets(@NonNull AppPredictionSessionId sessionId, + @NonNull ParceledListSlice targets, + IPredictionCallback callback) { + runForUserLocked((service) -> + service.sortAppTargetsLocked(sessionId, targets, callback)); + } + + @Override + public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + runForUserLocked((service) -> + service.registerPredictionUpdatesLocked(sessionId, callback)); + } + + public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + runForUserLocked((service) -> + service.unregisterPredictionUpdatesLocked(sessionId, callback)); + } + + @Override + public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { + runForUserLocked((service) -> service.requestPredictionUpdateLocked(sessionId)); + } + + @Override + public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { + runForUserLocked((service) -> service.onDestroyPredictionSessionLocked(sessionId)); + } + + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new AppPredictionManagerServiceShellCommand(AppPredictionManagerService.this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + private void runForUserLocked(@NonNull Consumer<AppPredictionPerUserService> c) { + final int userId = UserHandle.getCallingUserId(); + // TODO(b/111701043): Determine what permission model we want for this + long origId = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + final AppPredictionPerUserService service = getServiceForUserLocked(userId); + c.accept(service); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } +} diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java new file mode 100644 index 000000000000..ed7cc67aa46f --- /dev/null +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerServiceShellCommand.java @@ -0,0 +1,83 @@ +/* + * 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.appprediction; + +import android.annotation.NonNull; +import android.os.ShellCommand; + +import java.io.PrintWriter; + +/** + * The shell command implementation for the AppPredictionManagerService. + */ +public class AppPredictionManagerServiceShellCommand extends ShellCommand { + + private static final String TAG = + AppPredictionManagerServiceShellCommand.class.getSimpleName(); + + private final AppPredictionManagerService mService; + + public AppPredictionManagerServiceShellCommand(@NonNull AppPredictionManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "set": { + final String what = getNextArgRequired(); + switch (what) { + case "temporary-service": { + final int userId = Integer.parseInt(getNextArgRequired()); + String serviceName = getNextArg(); + if (serviceName == null) { + mService.resetTemporaryService(userId); + return 0; + } + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryService(userId, serviceName, duration); + pw.println("AppPredictionService temporarily set to " + serviceName + + " for " + duration + "ms"); + break; + } + } + } + break; + default: + return handleDefaultCommands(cmd); + } + return 0; + } + + @Override + public void onHelp() { + try (PrintWriter pw = getOutPrintWriter()) { + pw.println("AppPredictionManagerService commands:"); + pw.println(" help"); + pw.println(" Prints this help text."); + pw.println(""); + pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the service implemtation."); + pw.println(" To reset, call with just the USER_ID argument."); + pw.println(""); + } + } +} diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java new file mode 100644 index 000000000000..24d592c38afb --- /dev/null +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -0,0 +1,218 @@ +/* + * 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.appprediction; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppGlobals; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.IPredictionCallback; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ParceledListSlice; +import android.content.pm.ServiceInfo; +import android.os.RemoteException; +import android.service.appprediction.AppPredictionService; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.infra.AbstractPerUserSystemService; + +/** + * Per-user instance of {@link AppPredictionManagerService}. + */ +public class AppPredictionPerUserService extends + AbstractPerUserSystemService<AppPredictionPerUserService, AppPredictionManagerService> + implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks { + + private static final String TAG = AppPredictionPerUserService.class.getSimpleName(); + + @Nullable + @GuardedBy("mLock") + private RemoteAppPredictionService mRemoteService; + + protected AppPredictionPerUserService(AppPredictionManagerService master, + Object lock, int userId) { + super(master, lock, userId); + } + + @Override // from PerUserSystemService + protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) + throws NameNotFoundException { + + ServiceInfo si; + try { + si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, + PackageManager.GET_META_DATA, mUserId); + } catch (RemoteException e) { + throw new NameNotFoundException("Could not get service for " + serviceComponent); + } + // TODO(b/111701043): must check that either the service is from a system component, + // or it matches a service set by shell cmd (so it can be used on CTS tests and when + // OEMs are implementing the real service and also verify the proper permissions + return si; + } + + @GuardedBy("mLock") + @Override // from PerUserSystemService + protected boolean updateLocked(boolean disabled) { + final boolean enabledChanged = super.updateLocked(disabled); + if (enabledChanged) { + if (!isEnabledLocked()) { + // Clear the remote service for the next call + mRemoteService = null; + } + } + return enabledChanged; + } + + /** + * Notifies the service of a new prediction session. + */ + @GuardedBy("mLock") + public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context, + @NonNull AppPredictionSessionId sessionId) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.onCreatePredictionSession(context, sessionId); + } + } + + /** + * Records an app target event to the service. + */ + @GuardedBy("mLock") + public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull AppTargetEvent event) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.notifyAppTargetEvent(sessionId, event); + } + } + + /** + * Records when a launch location is shown. + */ + @GuardedBy("mLock") + public void notifyLocationShownLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.notifyLocationShown(sessionId, launchLocation, targetIds); + } + } + + /** + * Requests the service to sort a list of apps or shortcuts. + */ + @GuardedBy("mLock") + public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.sortAppTargets(sessionId, targets, callback); + } + } + + /** + * Registers a callback for continuous updates of predicted apps or shortcuts. + */ + @GuardedBy("mLock") + public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.registerPredictionUpdates(sessionId, callback); + } + } + + /** + * Unregisters a callback for continuous updates of predicted apps or shortcuts. + */ + @GuardedBy("mLock") + public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.unregisterPredictionUpdates(sessionId, callback); + } + } + + /** + * Requests a new set of predicted apps or shortcuts. + */ + @GuardedBy("mLock") + public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.requestPredictionUpdate(sessionId); + } + } + + /** + * Notifies the service of the end of an existing prediction session. + */ + @GuardedBy("mLock") + public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.onDestroyPredictionSession(sessionId); + } + } + + @Override + public void onFailureOrTimeout(boolean timedOut) { + if (isDebug()) { + Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut); + } + + // Do nothing, we are just proxying to the prediction service + } + + @Override + public void onServiceDied(RemoteAppPredictionService service) { + if (isDebug()) { + Slog.d(TAG, "onServiceDied():"); + } + + // Do nothing, we are just proxying to the prediction service + } + + @GuardedBy("mLock") + @Nullable + private RemoteAppPredictionService getRemoteServiceLocked() { + if (mRemoteService == null) { + final String serviceName = getComponentNameLocked(); + if (serviceName == null) { + if (mMaster.verbose) { + Slog.v(TAG, "getRemoteServiceLocked(): not set"); + } + return null; + } + ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); + + mRemoteService = new RemoteAppPredictionService(getContext(), + AppPredictionService.SERVICE_INTERFACE, serviceComponent, mUserId, this, + mMaster.isBindInstantServiceAllowed(), mMaster.verbose); + } + + return mRemoteService; + } +} diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java new file mode 100644 index 000000000000..45ea86f9e74e --- /dev/null +++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java @@ -0,0 +1,142 @@ +/* + * 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.appprediction; + +import android.annotation.NonNull; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.IPredictionCallback; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.IBinder; +import android.service.appprediction.IPredictionService; +import android.text.format.DateUtils; + +import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; + + +/** + * Proxy to the {@link android.service.appprediction.AppPredictionService} implemention in another + * process. + */ +public class RemoteAppPredictionService extends + AbstractMultiplePendingRequestsRemoteService<RemoteAppPredictionService, + IPredictionService> { + + private static final String TAG = "RemoteAppPredictionService"; + + private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; + + public RemoteAppPredictionService(Context context, String serviceInterface, + ComponentName componentName, int userId, + RemoteAppPredictionServiceCallbacks callback, boolean bindInstantServiceAllowed, + boolean verbose) { + super(context, serviceInterface, componentName, userId, callback, bindInstantServiceAllowed, + verbose, /* initialCapacity= */ 1); + } + + @Override + protected IPredictionService getServiceInterface(IBinder service) { + return IPredictionService.Stub.asInterface(service); + } + + @Override + protected long getTimeoutIdleBindMillis() { + return PERMANENT_BOUND_TIMEOUT_MS; + } + + @Override + protected long getRemoteRequestMillis() { + return TIMEOUT_REMOTE_REQUEST_MILLIS; + } + + /** + * Notifies the service of a new prediction session. + */ + public void onCreatePredictionSession(@NonNull AppPredictionContext context, + @NonNull AppPredictionSessionId sessionId) { + scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId)); + } + + /** + * Records an app target event to the service. + */ + public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId, + @NonNull AppTargetEvent event) { + scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event)); + } + + /** + * Records when a launch location is shown. + */ + public void notifyLocationShown(@NonNull AppPredictionSessionId sessionId, + @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { + scheduleAsyncRequest((s) -> s.notifyLocationShown(sessionId, launchLocation, targetIds)); + } + + /** + * Requests the service to sort a list of apps or shortcuts. + */ + public void sortAppTargets(@NonNull AppPredictionSessionId sessionId, + @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { + scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback)); + } + + + /** + * Registers a callback for continuous updates of predicted apps or shortcuts. + */ + public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback)); + } + + /** + * Unregisters a callback for continuous updates of predicted apps or shortcuts. + */ + public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, + @NonNull IPredictionCallback callback) { + scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback)); + } + + /** + * Requests a new set of predicted apps or shortcuts. + */ + public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { + scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId)); + } + + /** + * Notifies the service of the end of an existing prediction session. + */ + public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { + scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId)); + } + + /** + * Failure callback + */ + public interface RemoteAppPredictionServiceCallbacks + extends VultureCallback<RemoteAppPredictionService> { + + /** + * Notifies a the failure or timeout of a remote call. + */ + void onFailureOrTimeout(boolean timedOut); + } +} diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 4b24ef96cc25..dc0f6028b0f8 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -39,6 +39,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; +import com.android.internal.util.SyncResultReceiver; import com.android.server.LocalServices; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; @@ -50,8 +51,9 @@ import java.util.ArrayList; /** * A service used to observe the contents of the screen. * - * <p>The data collected by this service can be analyzed and combined with other sources to provide - * contextual data in other areas of the system such as Autofill. + * <p>The data collected by this service can be analyzed on-device and combined + * with other sources to provide contextual data in other areas of the system + * such as Autofill. */ public final class ContentCaptureManagerService extends AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> { @@ -196,6 +198,22 @@ public final class ContentCaptureManagerService extends } @Override + public void getReceiverServiceComponentName(@UserIdInt int userId, + IResultReceiver receiver) { + ComponentName connectedServiceComponentName; + synchronized (mLock) { + final ContentCapturePerUserService service = getServiceForUserLocked(userId); + connectedServiceComponentName = service.getServiceComponentName(); + } + try { + receiver.send(0, SyncResultReceiver.bundleFor(connectedServiceComponentName)); + } catch (RemoteException e) { + // Ignore exception as we need to be resilient against app behavior. + Slog.w(TAG, "Unable to send service component name: " + e); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; diff --git a/services/contentsuggestions/Android.bp b/services/contentsuggestions/Android.bp new file mode 100644 index 000000000000..fc09d2e5196a --- /dev/null +++ b/services/contentsuggestions/Android.bp @@ -0,0 +1,5 @@ +java_library_static { + name: "services.contentsuggestions", + srcs: ["java/**/*.java"], + libs: ["services.core"], +}
\ No newline at end of file diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java new file mode 100644 index 000000000000..58dbea469b9c --- /dev/null +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java @@ -0,0 +1,205 @@ +/* + * 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.contentsuggestions; + +import static android.Manifest.permission.MANAGE_CONTENT_SUGGESTIONS; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.contentsuggestions.ClassificationsRequest; +import android.app.contentsuggestions.IClassificationsCallback; +import android.app.contentsuggestions.IContentSuggestionsManager; +import android.app.contentsuggestions.ISelectionsCallback; +import android.app.contentsuggestions.SelectionsRequest; +import android.content.Context; +import android.os.Binder; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.UserHandle; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.infra.AbstractMasterSystemService; +import com.android.server.infra.FrameworkResourcesServiceNameResolver; +import com.android.server.wm.ActivityTaskManagerInternal; + +import java.io.FileDescriptor; + +/** + * The system service for providing recents / overview with content suggestion selections and + * classifications. + * + * <p>Calls are received here from + * {@link android.app.contentsuggestions.ContentSuggestionsManager} then delegated to + * a per user version of the service. From there they are routed to the remote actual implementation + * that provides the suggestion selections and classifications. + */ +public class ContentSuggestionsManagerService extends + AbstractMasterSystemService< + ContentSuggestionsManagerService, ContentSuggestionsPerUserService> { + + private static final String TAG = ContentSuggestionsManagerService.class.getSimpleName(); + private static final boolean VERBOSE = false; // TODO: make dynamic + + private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes + + private ActivityTaskManagerInternal mActivityTaskManagerInternal; + + public ContentSuggestionsManagerService(Context context) { + super(context, new FrameworkResourcesServiceNameResolver(context, + com.android.internal.R.string.config_defaultContentSuggestionsService), null); + mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); + } + + @Override + protected ContentSuggestionsPerUserService newServiceLocked(int resolvedUserId, + boolean disabled) { + return new ContentSuggestionsPerUserService(this, mLock, resolvedUserId); + } + + @Override + public void onStart() { + publishBinderService( + Context.CONTENT_SUGGESTIONS_SERVICE, new ContentSuggestionsManagerStub()); + } + + @Override + protected void enforceCallingPermissionForManagement() { + getContext().enforceCallingPermission(MANAGE_CONTENT_SUGGESTIONS, TAG); + } + + @Override + protected int getMaximumTemporaryServiceDurationMs() { + return MAX_TEMP_SERVICE_DURATION_MS; + } + + private boolean isCallerRecents(int userId) { + if (mServiceNameResolver.isTemporary(userId)) { + // If a temporary service is set then skip the recents check + return true; + } + return mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()); + } + + private void enforceCallerIsRecents(int userId, String func) { + if (isCallerRecents(userId)) { + return; + } + + String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " expected caller is recents"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + private class ContentSuggestionsManagerStub extends IContentSuggestionsManager.Stub { + @Override + public void provideContextImage(int taskId, @NonNull Bundle imageContextRequestExtras) { + if (imageContextRequestExtras == null) { + throw new IllegalArgumentException("Expected non-null imageContextRequestExtras"); + } + + final int userId = UserHandle.getCallingUserId(); + enforceCallerIsRecents(userId, "provideContextImage"); + + synchronized (mLock) { + final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.provideContextImageLocked(taskId, imageContextRequestExtras); + } else { + if (VERBOSE) { + Slog.v(TAG, "provideContextImageLocked: no service for " + userId); + } + } + } + } + + @Override + public void suggestContentSelections( + @NonNull SelectionsRequest selectionsRequest, + @NonNull ISelectionsCallback selectionsCallback) { + final int userId = UserHandle.getCallingUserId(); + enforceCallerIsRecents(userId, "suggestContentSelections"); + + synchronized (mLock) { + final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.suggestContentSelectionsLocked(selectionsRequest, selectionsCallback); + } else { + if (VERBOSE) { + Slog.v(TAG, "suggestContentSelectionsLocked: no service for " + userId); + } + } + } + } + + @Override + public void classifyContentSelections( + @NonNull ClassificationsRequest classificationsRequest, + @NonNull IClassificationsCallback callback) { + final int userId = UserHandle.getCallingUserId(); + enforceCallerIsRecents(userId, "classifyContentSelections"); + + synchronized (mLock) { + final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.classifyContentSelectionsLocked(classificationsRequest, callback); + } else { + if (VERBOSE) { + Slog.v(TAG, "classifyContentSelectionsLocked: no service for " + userId); + } + } + } + } + + @Override + public void notifyInteraction(@NonNull String requestId, @NonNull Bundle bundle) { + final int userId = UserHandle.getCallingUserId(); + enforceCallerIsRecents(userId, "notifyInteraction"); + + synchronized (mLock) { + final ContentSuggestionsPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.notifyInteractionLocked(requestId, bundle); + } else { + if (VERBOSE) { + Slog.v(TAG, "reportInteractionLocked: no service for " + userId); + } + } + } + } + + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + // Ensure that the caller is the shell process + final int callingUid = Binder.getCallingUid(); + if (callingUid != android.os.Process.SHELL_UID + && callingUid != android.os.Process.ROOT_UID) { + Slog.e(TAG, "Expected shell caller"); + return; + } + new ContentSuggestionsManagerServiceShellCommand(ContentSuggestionsManagerService.this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + } +} diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java new file mode 100644 index 000000000000..e34f1eadcd02 --- /dev/null +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerServiceShellCommand.java @@ -0,0 +1,84 @@ +/* + * 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.contentsuggestions; + +import android.annotation.NonNull; +import android.os.ShellCommand; + +import java.io.PrintWriter; + +/** + * The shell command implementation for the ContentSuggestionsManagerService. + */ +public class ContentSuggestionsManagerServiceShellCommand extends ShellCommand { + + private static final String TAG = + ContentSuggestionsManagerServiceShellCommand.class.getSimpleName(); + + private final ContentSuggestionsManagerService mService; + + public ContentSuggestionsManagerServiceShellCommand( + @NonNull ContentSuggestionsManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "set": { + final String what = getNextArgRequired(); + switch (what) { + case "temporary-service": { + final int userId = Integer.parseInt(getNextArgRequired()); + String serviceName = getNextArg(); + if (serviceName == null) { + mService.resetTemporaryService(userId); + return 0; + } + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryService(userId, serviceName, duration); + pw.println("ContentSuggestionsService temporarily set to " + serviceName + + " for " + duration + "ms"); + break; + } + } + } + break; + default: + return handleDefaultCommands(cmd); + } + return 0; + } + + @Override + public void onHelp() { + try (PrintWriter pw = getOutPrintWriter()) { + pw.println("ContentSuggestionsManagerService commands:"); + pw.println(" help"); + pw.println(" Prints this help text."); + pw.println(""); + pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the service implemtation."); + pw.println(" To reset, call with just the USER_ID argument."); + pw.println(""); + } + } +} diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java new file mode 100644 index 000000000000..385bc6cf3932 --- /dev/null +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.contentsuggestions; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.AppGlobals; +import android.app.contentsuggestions.ClassificationsRequest; +import android.app.contentsuggestions.IClassificationsCallback; +import android.app.contentsuggestions.ISelectionsCallback; +import android.app.contentsuggestions.SelectionsRequest; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.graphics.GraphicBuffer; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; +import com.android.server.infra.AbstractPerUserSystemService; +import com.android.server.wm.ActivityTaskManagerInternal; + +/** + * Per user delegate of {@link ContentSuggestionsManagerService}. + * + * <p>Main job is to forward calls to the remote implementation that can provide suggestion + * selections and classifications. + */ +public final class ContentSuggestionsPerUserService extends + AbstractPerUserSystemService< + ContentSuggestionsPerUserService, ContentSuggestionsManagerService> { + private static final String TAG = ContentSuggestionsPerUserService.class.getSimpleName(); + + @Nullable + @GuardedBy("mLock") + private RemoteContentSuggestionsService mRemoteService; + + @NonNull + private final ActivityTaskManagerInternal mActivityTaskManagerInternal; + + ContentSuggestionsPerUserService( + ContentSuggestionsManagerService master, Object lock, int userId) { + super(master, lock, userId); + mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); + } + + @GuardedBy("mLock") + @Override // from PerUserSystemService + protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) + throws PackageManager.NameNotFoundException { + ServiceInfo si; + try { + si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, + PackageManager.GET_META_DATA, mUserId); + } catch (RemoteException e) { + throw new PackageManager.NameNotFoundException( + "Could not get service for " + serviceComponent); + } + return si; + } + + @GuardedBy("mLock") + @Override // from PerUserSystemService + protected boolean updateLocked(boolean disabled) { + final boolean enabledChanged = super.updateLocked(disabled); + if (enabledChanged) { + if (!isEnabledLocked()) { + // Clear the remote service for the next call + mRemoteService = null; + } + } + return enabledChanged; + } + + @GuardedBy("mLock") + void provideContextImageLocked(int taskId, @NonNull Bundle imageContextRequestExtras) { + RemoteContentSuggestionsService service = getRemoteServiceLocked(); + if (service != null) { + ActivityManager.TaskSnapshot snapshot = + mActivityTaskManagerInternal.getTaskSnapshot(taskId, false); + GraphicBuffer snapshotBuffer = null; + if (snapshot != null) { + snapshotBuffer = snapshot.getSnapshot(); + } + + service.provideContextImage(taskId, snapshotBuffer, imageContextRequestExtras); + } + } + + @GuardedBy("mLock") + void suggestContentSelectionsLocked( + @NonNull SelectionsRequest selectionsRequest, + @NonNull ISelectionsCallback selectionsCallback) { + RemoteContentSuggestionsService service = getRemoteServiceLocked(); + if (service != null) { + service.suggestContentSelections(selectionsRequest, selectionsCallback); + } + } + + @GuardedBy("mLock") + void classifyContentSelectionsLocked( + @NonNull ClassificationsRequest classificationsRequest, + @NonNull IClassificationsCallback callback) { + RemoteContentSuggestionsService service = getRemoteServiceLocked(); + if (service != null) { + service.classifyContentSelections(classificationsRequest, callback); + } + } + + @GuardedBy("mLock") + void notifyInteractionLocked(@NonNull String requestId, @NonNull Bundle bundle) { + RemoteContentSuggestionsService service = getRemoteServiceLocked(); + if (service != null) { + service.notifyInteraction(requestId, bundle); + } + } + + @GuardedBy("mLock") + @Nullable + private RemoteContentSuggestionsService getRemoteServiceLocked() { + if (mRemoteService == null) { + final String serviceName = getComponentNameLocked(); + if (serviceName == null) { + if (mMaster.verbose) { + Slog.v(TAG, "getRemoteServiceLocked(): not set"); + } + return null; + } + ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); + + mRemoteService = new RemoteContentSuggestionsService(getContext(), + serviceComponent, mUserId, + new RemoteContentSuggestionsService.Callbacks() { + @Override + public void onServiceDied( + @NonNull RemoteContentSuggestionsService service) { + // TODO(b/120865921): properly implement + Slog.w(TAG, "remote content suggestions service died"); + } + }, mMaster.isBindInstantServiceAllowed(), mMaster.verbose); + } + + return mRemoteService; + } +} diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java new file mode 100644 index 000000000000..bf48d7623255 --- /dev/null +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/RemoteContentSuggestionsService.java @@ -0,0 +1,97 @@ +/* + * 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.contentsuggestions; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.contentsuggestions.ClassificationsRequest; +import android.app.contentsuggestions.IClassificationsCallback; +import android.app.contentsuggestions.ISelectionsCallback; +import android.app.contentsuggestions.SelectionsRequest; +import android.content.ComponentName; +import android.content.Context; +import android.graphics.GraphicBuffer; +import android.os.Bundle; +import android.os.IBinder; +import android.service.contentsuggestions.ContentSuggestionsService; +import android.service.contentsuggestions.IContentSuggestionsService; +import android.text.format.DateUtils; + +import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService; + +/** + * Delegates calls from {@link ContentSuggestionsPerUserService} to the remote actual implementation + * of the suggestion selection and classification service. + */ +public class RemoteContentSuggestionsService extends + AbstractMultiplePendingRequestsRemoteService<RemoteContentSuggestionsService, + IContentSuggestionsService> { + + private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; + + RemoteContentSuggestionsService(Context context, ComponentName serviceName, + int userId, Callbacks callbacks, + boolean bindInstantServiceAllowed, boolean verbose) { + super(context, ContentSuggestionsService.SERVICE_INTERFACE, serviceName, userId, callbacks, + bindInstantServiceAllowed, verbose, /* initialCapacity= */ 1); + } + + @Override + protected IContentSuggestionsService getServiceInterface(IBinder service) { + return IContentSuggestionsService.Stub.asInterface(service); + } + + @Override + protected long getTimeoutIdleBindMillis() { + return PERMANENT_BOUND_TIMEOUT_MS; + } + + @Override + protected long getRemoteRequestMillis() { + return TIMEOUT_REMOTE_REQUEST_MILLIS; + } + + void provideContextImage(int taskId, @Nullable GraphicBuffer contextImage, + @NonNull Bundle imageContextRequestExtras) { + scheduleAsyncRequest((s) -> s.provideContextImage(taskId, contextImage, + imageContextRequestExtras)); + } + + void suggestContentSelections( + @NonNull SelectionsRequest selectionsRequest, + @NonNull ISelectionsCallback selectionsCallback) { + scheduleAsyncRequest( + (s) -> s.suggestContentSelections(selectionsRequest, selectionsCallback)); + } + + void classifyContentSelections( + @NonNull ClassificationsRequest classificationsRequest, + @NonNull IClassificationsCallback callback) { + scheduleAsyncRequest((s) -> s.classifyContentSelections(classificationsRequest, callback)); + } + + void notifyInteraction(@NonNull String requestId, @NonNull Bundle bundle) { + scheduleAsyncRequest((s) -> s.notifyInteraction(requestId, bundle)); + } + + interface Callbacks + extends VultureCallback<RemoteContentSuggestionsService> { + // NOTE: so far we don't need to notify the callback implementation + // (ContentSuggestionsManager) of the request results (success, timeouts, etc..), so this + // callback interface is empty. + } +} diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 7bbc543a6aab..a33338164caf 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -279,7 +279,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { Slog.d(TAG, "Airplane Mode change - current state: " + BluetoothAdapter.nameForState( - st)); + st) + ", isAirplaneModeOn()=" + isAirplaneModeOn()); if (isAirplaneModeOn()) { // Clear registered LE apps to force shut-off diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 66ceae432c6e..d0666b98c0e0 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -25,6 +25,7 @@ import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; @@ -37,6 +38,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.uidRulesToString; +import static android.net.NetworkStack.NETWORKSTACK_PACKAGE_NAME; +import static android.net.shared.NetworkMonitorUtils.isValidationRequired; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; @@ -62,6 +65,8 @@ import android.net.IIpConnectivityMetrics; import android.net.INetd; import android.net.INetdEventCallback; import android.net.INetworkManagementEventObserver; +import android.net.INetworkMonitor; +import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; @@ -79,9 +84,11 @@ import android.net.NetworkPolicyManager; import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkSpecifier; +import android.net.NetworkStack; import android.net.NetworkState; import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; +import android.net.PrivateDnsConfigParcel; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.UidRange; @@ -90,12 +97,13 @@ import android.net.VpnService; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.netlink.InetDiagMessage; +import android.net.shared.NetworkMonitorUtils; +import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; import android.net.util.NetdService; import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -123,8 +131,8 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.LocalLog; -import android.util.LocalLog.ReadOnlyLocalLog; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -149,7 +157,6 @@ import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.DnsManager; -import com.android.server.connectivity.DnsManager.PrivateDnsConfig; import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.KeepaliveTracker; @@ -158,7 +165,6 @@ import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.MultipathPolicyTracker; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkDiagnostics; -import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.PermissionMonitor; @@ -186,7 +192,6 @@ import java.io.PrintWriter; import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -442,6 +447,43 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_DATA_SAVER_CHANGED = 40; + /** + * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has + * been tested. + * obj = String representing URL that Internet probe was redirect to, if it was redirected. + * arg1 = One of the NETWORK_TESTED_RESULT_* constants. + * arg2 = NetID. + */ + public static final int EVENT_NETWORK_TESTED = 41; + + /** + * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the private DNS + * config was resolved. + * obj = PrivateDnsConfig + * arg2 = netid + */ + public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = 42; + + /** + * Request ConnectivityService display provisioning notification. + * arg1 = Whether to make the notification visible. + * arg2 = NetID. + * obj = Intent to be launched when notification selected by user, null if !arg1. + */ + public static final int EVENT_PROVISIONING_NOTIFICATION = 43; + + /** + * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification + * should be shown. + */ + public static final int PROVISIONING_NOTIFICATION_SHOW = 1; + + /** + * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification + * should be hidden. + */ + public static final int PROVISIONING_NOTIFICATION_HIDE = 0; + private static String eventName(int what) { return sMagicDecoderRing.get(what, Integer.toString(what)); } @@ -506,30 +548,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private long mMaxWakelockDurationMs = 0; private long mLastWakeLockAcquireTimestamp = 0; - // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results - private static final int MAX_VALIDATION_LOGS = 10; - private static class ValidationLog { - final Network mNetwork; - final String mName; - final ReadOnlyLocalLog mLog; - - ValidationLog(Network network, String name, ReadOnlyLocalLog log) { - mNetwork = network; - mName = name; - mLog = log; - } - } - private final ArrayDeque<ValidationLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS); - - private void addValidationLogs(ReadOnlyLocalLog log, Network network, String name) { - synchronized (mValidationLogs) { - while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) { - mValidationLogs.removeLast(); - } - mValidationLogs.addFirst(new ValidationLog(network, name, log)); - } - } - private final IpConnectivityLog mMetricsLog; @GuardedBy("mBandwidthRequests") @@ -1684,7 +1702,11 @@ public class ConnectivityService extends IConnectivityManager.Stub // caller type. Need to re-factor NetdEventListenerService to allow multiple // NetworkMonitor registrants. if (nai != null && nai.satisfies(mDefaultRequest)) { - nai.networkMonitor.sendMessage(NetworkMonitor.EVENT_DNS_NOTIFICATION, returnCode); + try { + nai.networkMonitor().notifyDnsResponse(returnCode); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } } }; @@ -2266,17 +2288,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (ArrayUtils.contains(args, SHORT_ARG) == false) { pw.println(); - synchronized (mValidationLogs) { - pw.println("mValidationLogs (most recent first):"); - for (ValidationLog p : mValidationLogs) { - pw.println(p.mNetwork + " - " + p.mName); - pw.increaseIndent(); - p.mLog.dump(fd, pw, args); - pw.decreaseIndent(); - } - } - - pw.println(); pw.println("mNetworkRequestInfoLogs (most recent first):"); pw.increaseIndent(); mNetworkRequestInfoLogs.reverseDump(fd, pw, args); @@ -2455,11 +2466,11 @@ public class ConnectivityService extends IConnectivityManager.Stub switch (msg.what) { default: return false; - case NetworkMonitor.EVENT_NETWORK_TESTED: { + case EVENT_NETWORK_TESTED: { final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); if (nai == null) break; - final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID); + final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID); final boolean wasValidated = nai.lastValidated; final boolean wasDefault = isDefaultNetwork(nai); @@ -2497,7 +2508,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } break; } - case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: { + case EVENT_PROVISIONING_NOTIFICATION: { final int netId = msg.arg2; final boolean visible = toBool(msg.arg1); final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId); @@ -2530,7 +2541,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } break; } - case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: { + case EVENT_PRIVATE_DNS_CONFIG_RESOLVED: { final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); if (nai == null) break; @@ -2572,8 +2583,61 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub { + private final NetworkAgentInfo mNai; + + private NetworkMonitorCallbacks(NetworkAgentInfo nai) { + mNai = nai; + } + + @Override + public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, + new Pair<>(mNai, networkMonitor))); + } + + @Override + public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) { + mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED, + testResult, mNai.network.netId, redirectUrl)); + } + + @Override + public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) { + mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage( + EVENT_PRIVATE_DNS_CONFIG_RESOLVED, + 0, mNai.network.netId, PrivateDnsConfig.fromParcel(config))); + } + + @Override + public void showProvisioningNotification(String action) { + final Intent intent = new Intent(action); + intent.setPackage(NETWORKSTACK_PACKAGE_NAME); + + final PendingIntent pendingIntent; + // Only the system server can register notifications with package "android" + final long token = Binder.clearCallingIdentity(); + try { + pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + } finally { + Binder.restoreCallingIdentity(token); + } + mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage( + EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW, + mNai.network.netId, + pendingIntent)); + } + + @Override + public void hideProvisioningNotification() { + mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage( + EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE, + mNai.network.netId)); + } + } + private boolean networkRequiresValidation(NetworkAgentInfo nai) { - return NetworkMonitor.isValidationRequired( + return isValidationRequired( mDefaultRequest.networkCapabilities, nai.networkCapabilities); } @@ -2603,10 +2667,14 @@ public class ConnectivityService extends IConnectivityManager.Stub // Internet access and therefore also require validation. if (!networkRequiresValidation(nai)) return; - // Notify the NetworkMonitor thread in case it needs to cancel or + // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or // schedule DNS resolutions. If a DNS resolution is required the // result will be sent back to us. - nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg); + try { + nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } // With Private DNS bypass support, we can proceed to update the // Private DNS config immediately, even if we're in strict mode @@ -2736,7 +2804,11 @@ public class ConnectivityService extends IConnectivityManager.Stub // Disable wakeup packet monitoring for each interface. wakeupModifyInterface(iface, nai.networkCapabilities, false); } - nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED); + try { + nai.networkMonitor().notifyNetworkDisconnected(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } mNetworkAgentInfos.remove(nai.messenger); nai.maybeStopClat(); synchronized (mNetworkForNetId) { @@ -3096,7 +3168,11 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return; if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return; - nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP); + try { + nai.networkMonitor().launchCaptivePortalApp(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } }); } @@ -3217,6 +3293,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return mMultinetworkPolicyTracker.getMeteredMultipathPreference(); } + @Override + public NetworkRequest getDefaultRequest() { + return mDefaultRequest; + } + private class InternalHandler extends Handler { public InternalHandler(Looper looper) { super(looper); @@ -3247,7 +3328,9 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case EVENT_REGISTER_NETWORK_AGENT: { - handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj); + final Pair<NetworkAgentInfo, INetworkMonitor> arg = + (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj; + handleRegisterNetworkAgent(arg.first, arg.second); break; } case EVENT_REGISTER_NETWORK_REQUEST: @@ -3305,7 +3388,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } case EVENT_SYSTEM_READY: { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { - nai.networkMonitor.systemReady = true; + // Might have been called already in handleRegisterNetworkAgent since + // mSystemReady is set before sending EVENT_SYSTEM_READY, but calling + // this several times is fine. + try { + nai.networkMonitor().notifySystemReady(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } mMultipathPolicyTracker.start(); break; @@ -3577,7 +3667,11 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) { return; } - nai.networkMonitor.forceReevaluation(uid); + try { + nai.networkMonitor().forceReevaluation(uid); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } @Override @@ -4785,27 +4879,49 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, - mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this); + mContext, mTrackerHandler, new NetworkMisc(networkMisc), this); // Make sure the network capabilities reflect what the agent info says. nai.networkCapabilities = mixInCapabilities(nai, nc); - synchronized (this) { - nai.networkMonitor.systemReady = mSystemReady; - } final String extraInfo = networkInfo.getExtraInfo(); final String name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSSID() : extraInfo; - addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network, name); if (DBG) log("registerNetworkAgent " + nai); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai)); + final long token = Binder.clearCallingIdentity(); + try { + mContext.getSystemService(NetworkStack.class) + .makeNetworkMonitor(nai.network, name, new NetworkMonitorCallbacks(nai)); + } finally { + Binder.restoreCallingIdentity(token); + } + // NetworkAgentInfo registration will finish when the NetworkMonitor is created. + // If the network disconnects or sends any other event before that, messages are deferred by + // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the + // registration. return nai.network.netId; } - private void handleRegisterNetworkAgent(NetworkAgentInfo nai) { + private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) { + nai.onNetworkMonitorCreated(networkMonitor); if (VDBG) log("Got NetworkAgent Messenger"); mNetworkAgentInfos.put(nai.messenger, nai); synchronized (mNetworkForNetId) { mNetworkForNetId.put(nai.network.netId, nai); } + synchronized (this) { + if (mSystemReady) { + try { + networkMonitor.notifySystemReady(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + try { + networkMonitor.start(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger); NetworkInfo networkInfo = nai.networkInfo; nai.networkInfo = null; @@ -4855,6 +4971,11 @@ public class ConnectivityService extends IConnectivityManager.Stub networkAgent.updateClat(mNMS); notifyIfacesChangedForNetworkStats(); if (networkAgent.everConnected) { + try { + networkAgent.networkMonitor().notifyLinkPropertiesChanged(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED); } } @@ -5092,6 +5213,11 @@ public class ConnectivityService extends IConnectivityManager.Stub // If the requestable capabilities have changed or the score changed, we can't have been // called by rematchNetworkAndRequests, so it's safe to start a rematch. rematchAllNetworksAndRequests(nai, oldScore); + try { + nai.networkMonitor().notifyNetworkCapabilitiesChanged(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } @@ -5339,6 +5465,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (capabilitiesChanged) { + try { + nai.networkMonitor().notifyNetworkCapabilitiesChanged(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } @@ -5739,7 +5870,15 @@ public class ConnectivityService extends IConnectivityManager.Stub updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties), null); - networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); + // Until parceled LinkProperties are sent directly to NetworkMonitor, the connect + // command must be sent after updating LinkProperties to maximize chances of + // NetworkMonitor seeing the correct LinkProperties when starting. + // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call. + try { + networkAgent.networkMonitor().notifyNetworkConnected(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } scheduleUnvalidatedPrompt(networkAgent); if (networkAgent.isVPN()) { @@ -6020,7 +6159,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String getCaptivePortalServerUrl() { enforceConnectivityInternalPermission(); - return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext); + return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext); } @Override @@ -6113,12 +6252,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } @VisibleForTesting - public NetworkMonitor createNetworkMonitor(Context context, Handler handler, - NetworkAgentInfo nai, NetworkRequest defaultRequest) { - return new NetworkMonitor(context, handler, nai, defaultRequest); - } - - @VisibleForTesting MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) { return new MultinetworkPolicyTracker(c, h, r); } diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index ea80ac1aecef..80fda19aa10c 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -16,32 +16,31 @@ package com.android.server; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - +import android.content.Intent; +import android.content.IntentFilter; import android.net.Uri; -import android.util.FastImmutableArraySet; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.FastImmutableArraySet; import android.util.Log; +import android.util.LogPrinter; import android.util.MutableInt; import android.util.PrintWriterPrinter; -import android.util.Slog; -import android.util.LogPrinter; import android.util.Printer; - -import android.content.Intent; -import android.content.IntentFilter; +import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.util.FastPrintWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + /** * {@hide} */ @@ -788,6 +787,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { + filter.hasCategory(Intent.CATEGORY_DEFAULT)); if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) { final R oneResult = newResult(filter, match, userId); + if (debug) Slog.v(TAG, " Created result: " + oneResult); if (oneResult != null) { dest.add(oneResult); if (debug) { diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 2346cfc07a83..869d564b4329 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -17,9 +17,14 @@ package com.android.server; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.location.LocationManager.FUSED_PROVIDER; +import static android.location.LocationManager.GPS_PROVIDER; +import static android.location.LocationManager.NETWORK_PROVIDER; +import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.location.LocationProvider.AVAILABLE; import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS; +import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; import android.Manifest; @@ -29,7 +34,6 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -64,7 +68,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -81,12 +84,14 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; +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; @@ -116,7 +121,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; -import java.util.Set; /** * The service class that manages LocationProviders and issues location @@ -137,16 +141,12 @@ public class LocationManagerService extends ILocationManager.Stub { private static final String ACCESS_LOCATION_EXTRA_COMMANDS = android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; - private static final String INSTALL_LOCATION_PROVIDER = - android.Manifest.permission.INSTALL_LOCATION_PROVIDER; private static final String NETWORK_LOCATION_SERVICE_ACTION = "com.android.location.service.v3.NetworkLocationProvider"; private static final String FUSED_LOCATION_SERVICE_ACTION = "com.android.location.service.FusedLocationProvider"; - private static final int MSG_LOCATION_CHANGED = 1; - private static final long NANOS_PER_MILLI = 1000000L; // The maximum interval a location request can have and still be considered "high power". @@ -170,75 +170,62 @@ public class LocationManagerService extends ILocationManager.Stub { private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest(); - private final Context mContext; - private final AppOpsManager mAppOps; - - // used internally for synchronization private final Object mLock = new Object(); + private final Context mContext; + private final Handler mHandler; - // --- fields below are final after systemRunning() --- - private LocationFudger mLocationFudger; - private GeofenceManager mGeofenceManager; + private AppOpsManager mAppOps; private PackageManager mPackageManager; private PowerManager mPowerManager; private ActivityManager mActivityManager; private UserManager mUserManager; + + private GeofenceManager mGeofenceManager; + private LocationFudger mLocationFudger; private GeocoderProxy mGeocodeProvider; private GnssStatusListenerHelper mGnssStatusProvider; private INetInitiatedListener mNetInitiatedListener; - private LocationWorkerHandler mLocationHandler; private PassiveProvider mPassiveProvider; // track passive provider for special cases private LocationBlacklist mBlacklist; private GnssMeasurementsProvider mGnssMeasurementsProvider; private GnssNavigationMessageProvider mGnssNavigationMessageProvider; + @GuardedBy("mLock") private String mLocationControllerExtraPackage; private boolean mLocationControllerExtraPackageEnabled; private IGpsGeofenceHardware mGpsGeofenceProxy; - // --- fields below are protected by mLock --- + // list of currently active providers + @GuardedBy("mLock") + private final ArrayList<LocationProvider> mProviders = new ArrayList<>(); - // Mock (test) providers - private final HashMap<String, MockProvider> mMockProviders = - new HashMap<>(); + // list of non-mock providers, so that when mock providers replace real providers, they can be + // later re-replaced + @GuardedBy("mLock") + private final ArrayList<LocationProvider> mRealProviders = new ArrayList<>(); - // all receivers + @GuardedBy("mLock") private final HashMap<Object, Receiver> mReceivers = new HashMap<>(); - - // currently installed providers (with mocks replacing real providers) - private final ArrayList<LocationProvider> mProviders = - new ArrayList<>(); - - // real providers, saved here when mocked out - private final HashMap<String, LocationProvider> mRealProviders = - new HashMap<>(); - - // mapping from provider name to provider - private final HashMap<String, LocationProvider> mProvidersByName = - new HashMap<>(); - - // mapping from provider name to all its UpdateRecords private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider = new HashMap<>(); private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics(); // mapping from provider name to last known location + @GuardedBy("mLock") private final HashMap<String, Location> mLastLocation = new HashMap<>(); // same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS. // locations stored here are not fudged for coarse permissions. + @GuardedBy("mLock") private final HashMap<String, Location> mLastLocationCoarseInterval = new HashMap<>(); - // all providers that operate over proxy, for authorizing incoming location and whitelisting - // throttling - private final ArrayList<LocationProviderProxy> mProxyProviders = - new ArrayList<>(); - private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>(); + @GuardedBy("mLock") private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>(); + @GuardedBy("mLock") private final ArrayMap<IBinder, Identity> mGnssNavigationMessageListeners = new ArrayMap<>(); @@ -246,22 +233,22 @@ public class LocationManagerService extends ILocationManager.Stub { private int mCurrentUserId = UserHandle.USER_SYSTEM; private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM}; - // Maximum age of last location returned to clients with foreground-only location permissions. - private long mLastLocationMaxAgeMs; - private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider; private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider; private GnssBatchingProvider mGnssBatchingProvider; + @GuardedBy("mLock") private IBatchedLocationCallback mGnssBatchingCallback; + @GuardedBy("mLock") private LinkedCallback mGnssBatchingDeathCallback; + @GuardedBy("mLock") private boolean mGnssBatchingInProgress = false; public LocationManagerService(Context context) { super(); mContext = context; - mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mHandler = BackgroundThread.getHandler(); // Let the package manager query which are the default location // providers as they get certain permissions granted by default. @@ -271,134 +258,110 @@ public class LocationManagerService extends ILocationManager.Stub { userId -> mContext.getResources().getStringArray( com.android.internal.R.array.config_locationProviderPackageNames)); - if (D) Log.d(TAG, "Constructed"); - // most startup is deferred until systemRunning() } public void systemRunning() { synchronized (mLock) { - if (D) Log.d(TAG, "systemRunning()"); - - // fetch package manager - mPackageManager = mContext.getPackageManager(); - - // fetch power manager - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - - // fetch activity manager - mActivityManager - = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); - - // prepare worker thread - mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper()); - - // prepare mLocationHandler's dependents - mLocationFudger = new LocationFudger(mContext, mLocationHandler); - mBlacklist = new LocationBlacklist(mContext, mLocationHandler); - mBlacklist.init(); - mGeofenceManager = new GeofenceManager(mContext, mBlacklist); - - // Monitor for app ops mode changes. - AppOpsManager.OnOpChangedListener callback - = new AppOpsManager.OnOpChangedInternalListener() { - public void onOpChanged(int op, String packageName) { - mLocationHandler.post(() -> { - synchronized (mLock) { - for (Receiver receiver : mReceivers.values()) { - receiver.updateMonitoring(true); - } - applyAllProviderRequirementsLocked(); - } - }); - } - }; - mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, - AppOpsManager.WATCH_FOREGROUND_CHANGES, callback); - - PackageManager.OnPermissionsChangedListener permissionListener = uid -> { - synchronized (mLock) { - applyAllProviderRequirementsLocked(); - } - }; - mPackageManager.addOnPermissionsChangeListener(permissionListener); + initializeLocked(); + } + } - // listen for background/foreground changes - ActivityManager.OnUidImportanceListener uidImportanceListener = - (uid, importance) -> mLocationHandler.post( - () -> onUidImportanceChanged(uid, importance)); - mActivityManager.addOnUidImportanceListener(uidImportanceListener, - FOREGROUND_IMPORTANCE_CUTOFF); + @GuardedBy("mLock") + private void initializeLocked() { + mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mPackageManager = mContext.getPackageManager(); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - updateUserProfiles(mCurrentUserId); + mLocationFudger = new LocationFudger(mContext, mHandler); + mBlacklist = new LocationBlacklist(mContext, mHandler); + mBlacklist.init(); + mGeofenceManager = new GeofenceManager(mContext, mBlacklist); - updateBackgroundThrottlingWhitelistLocked(); - updateLastLocationMaxAgeLocked(); + // prepare providers + initializeProvidersLocked(); - // prepare providers - loadProvidersLocked(); - updateProvidersSettingsLocked(); - for (LocationProvider provider : mProviders) { - applyRequirementsLocked(provider.getName()); - } - } + // add listeners + mAppOps.startWatchingMode( + AppOpsManager.OP_COARSE_LOCATION, + null, + AppOpsManager.WATCH_FOREGROUND_CHANGES, + new AppOpsManager.OnOpChangedInternalListener() { + public void onOpChanged(int op, String packageName) { + synchronized (mLock) { + onAppOpChangedLocked(); + } + } + }); + mPackageManager.addOnPermissionsChangeListener( + uid -> { + synchronized (mLock) { + onPermissionsChangedLocked(); + } + }); - // listen for settings changes + mActivityManager.addOnUidImportanceListener( + (uid, importance) -> { + synchronized (mLock) { + onUidImportanceChangedLocked(uid, importance); + } + }, + FOREGROUND_IMPORTANCE_CUTOFF); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true, - new ContentObserver(mLocationHandler) { + Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE), true, + new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - updateProvidersSettingsLocked(); + onLocationModeChangedLocked(true); } } }, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS), - true, - new ContentObserver(mLocationHandler) { + Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true, + new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - for (LocationProvider provider : mProviders) { - applyRequirementsLocked(provider.getName()); - } + onProviderAllowedChangedLocked(true); } } }, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS), + Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS), true, - new ContentObserver(mLocationHandler) { + new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - updateLastLocationMaxAgeLocked(); + onBackgroundThrottleIntervalChangedLocked(); } } - } - ); + }, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor( Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST), true, - new ContentObserver(mLocationHandler) { + new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - updateBackgroundThrottlingWhitelistLocked(); - for (LocationProvider provider : mProviders) { - applyRequirementsLocked(provider.getName()); - } + onBackgroundThrottleWhitelistChangedLocked(); } } }, UserHandle.USER_ALL); - mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true); + new PackageMonitor() { + @Override + public void onPackageDisappeared(String packageName, int reason) { + synchronized (mLock) { + LocationManagerService.this.onPackageDisappearedLocked(packageName); + } + } + }.register(mContext, mHandler.getLooper(), true); - // listen for user change IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); @@ -407,81 +370,152 @@ public class LocationManagerService extends ILocationManager.Stub { mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) - || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { - updateUserProfiles(mCurrentUserId); + synchronized (mLock) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + onUserChangedLocked(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) + || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { + onUserProfilesChangedLocked(); + } } } - }, UserHandle.ALL, intentFilter, null, mLocationHandler); + }, UserHandle.ALL, intentFilter, null, mHandler); + + // switching the user from null to system here performs the bulk of the initialization work. + // the user being changed will cause a reload of all user specific settings, which causes + // provider initialization, and propagates changes until a steady state is reached + mCurrentUserId = UserHandle.USER_NULL; + onUserChangedLocked(UserHandle.USER_SYSTEM); + + // initialize in-memory settings values + onBackgroundThrottleWhitelistChangedLocked(); } - private void onUidImportanceChanged(int uid, int importance) { - boolean foreground = isImportanceForeground(importance); - HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size()); - synchronized (mLock) { - for (Entry<String, ArrayList<UpdateRecord>> entry - : mRecordsByProvider.entrySet()) { - String provider = entry.getKey(); - for (UpdateRecord record : entry.getValue()) { - if (record.mReceiver.mIdentity.mUid == uid - && record.mIsForegroundUid != foreground) { - if (D) { - Log.d(TAG, "request from uid " + uid + " is now " - + (foreground ? "foreground" : "background)")); - } - record.updateForeground(foreground); + @GuardedBy("mLock") + private void onAppOpChangedLocked() { + for (Receiver receiver : mReceivers.values()) { + receiver.updateMonitoring(true); + } + for (LocationProvider p : mProviders) { + applyRequirementsLocked(p); + } + } - if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) { - affectedProviders.add(provider); - } - } + @GuardedBy("mLock") + private void onPermissionsChangedLocked() { + for (LocationProvider p : mProviders) { + applyRequirementsLocked(p); + } + } + + @GuardedBy("mLock") + private void onLocationModeChangedLocked(boolean broadcast) { + for (LocationProvider p : mProviders) { + p.onLocationModeChangedLocked(); + } + + if (broadcast) { + mContext.sendBroadcastAsUser( + new Intent(LocationManager.MODE_CHANGED_ACTION), + UserHandle.ALL); + } + } + + @GuardedBy("mLock") + private void onProviderAllowedChangedLocked(boolean broadcast) { + for (LocationProvider p : mProviders) { + p.onAllowedChangedLocked(); + } + + if (broadcast) { + mContext.sendBroadcastAsUser( + new Intent(LocationManager.PROVIDERS_CHANGED_ACTION), + UserHandle.ALL); + } + } + + @GuardedBy("mLock") + private void onPackageDisappearedLocked(String packageName) { + ArrayList<Receiver> deadReceivers = null; + + for (Receiver receiver : mReceivers.values()) { + if (receiver.mIdentity.mPackageName.equals(packageName)) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<>(); } + deadReceivers.add(receiver); } - for (String provider : affectedProviders) { - applyRequirementsLocked(provider); + } + + // perform removal outside of mReceivers loop + if (deadReceivers != null) { + for (Receiver receiver : deadReceivers) { + removeUpdatesLocked(receiver); } + } + } - for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) { - Identity callerIdentity = entry.getValue(); - if (callerIdentity.mUid == uid) { + @GuardedBy("mLock") + private void onUidImportanceChangedLocked(int uid, int importance) { + boolean foreground = isImportanceForeground(importance); + HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size()); + for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) { + String provider = entry.getKey(); + for (UpdateRecord record : entry.getValue()) { + if (record.mReceiver.mIdentity.mUid == uid + && record.mIsForegroundUid != foreground) { if (D) { - Log.d(TAG, "gnss measurements listener from uid " + uid - + " is now " + (foreground ? "foreground" : "background)")); + Log.d(TAG, "request from uid " + uid + " is now " + + (foreground ? "foreground" : "background)")); } - if (foreground || isThrottlingExemptLocked(entry.getValue())) { - mGnssMeasurementsProvider.addListener( - IGnssMeasurementsListener.Stub.asInterface(entry.getKey()), - callerIdentity.mUid, callerIdentity.mPackageName); - } else { - mGnssMeasurementsProvider.removeListener( - IGnssMeasurementsListener.Stub.asInterface(entry.getKey())); + record.updateForeground(foreground); + + if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) { + affectedProviders.add(provider); } } } + } + for (String provider : affectedProviders) { + applyRequirementsLocked(provider); + } - for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) { - Identity callerIdentity = entry.getValue(); - if (callerIdentity.mUid == uid) { - if (D) { - Log.d(TAG, "gnss navigation message listener from uid " - + uid + " is now " - + (foreground ? "foreground" : "background)")); - } - if (foreground || isThrottlingExemptLocked(entry.getValue())) { - mGnssNavigationMessageProvider.addListener( - IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()), - callerIdentity.mUid, callerIdentity.mPackageName); - } else { - mGnssNavigationMessageProvider.removeListener( - IGnssNavigationMessageListener.Stub.asInterface(entry.getKey())); - } + for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) { + Identity callerIdentity = entry.getValue(); + if (callerIdentity.mUid == uid) { + if (D) { + Log.d(TAG, "gnss measurements listener from uid " + uid + + " is now " + (foreground ? "foreground" : "background)")); + } + if (foreground || isThrottlingExemptLocked(entry.getValue())) { + mGnssMeasurementsProvider.addListener( + IGnssMeasurementsListener.Stub.asInterface(entry.getKey()), + callerIdentity.mUid, callerIdentity.mPackageName); + } else { + mGnssMeasurementsProvider.removeListener( + IGnssMeasurementsListener.Stub.asInterface(entry.getKey())); } } + } - // TODO(b/120449926): The GNSS status listeners should be handled similar to the above. + for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) { + Identity callerIdentity = entry.getValue(); + if (callerIdentity.mUid == uid) { + if (D) { + Log.d(TAG, "gnss navigation message listener from uid " + + uid + " is now " + + (foreground ? "foreground" : "background)")); + } + if (foreground || isThrottlingExemptLocked(entry.getValue())) { + mGnssNavigationMessageProvider.addListener( + IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()), + callerIdentity.mUid, callerIdentity.mPackageName); + } else { + mGnssNavigationMessageProvider.removeListener( + IGnssNavigationMessageListener.Stub.asInterface(entry.getKey())); + } + } } } @@ -489,30 +523,43 @@ public class LocationManagerService extends ILocationManager.Stub { return importance <= FOREGROUND_IMPORTANCE_CUTOFF; } - /** - * Makes a list of userids that are related to the current user. This is - * relevant when using managed profiles. Otherwise the list only contains - * the current user. - * - * @param currentUserId the current user, who might have an alter-ego. - */ - private void updateUserProfiles(int currentUserId) { - int[] profileIds = mUserManager.getProfileIdsWithDisabled(currentUserId); - synchronized (mLock) { - mCurrentUserProfiles = profileIds; + @GuardedBy("mLock") + private void onBackgroundThrottleIntervalChangedLocked() { + for (LocationProvider provider : mProviders) { + applyRequirementsLocked(provider); } } - /** - * Checks if the specified userId matches any of the current foreground - * users stored in mCurrentUserProfiles. - */ - private boolean isCurrentProfile(int userId) { - synchronized (mLock) { - return ArrayUtils.contains(mCurrentUserProfiles, userId); + @GuardedBy("mLock") + private void onBackgroundThrottleWhitelistChangedLocked() { + String setting = Settings.Global.getString( + mContext.getContentResolver(), + Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST); + if (setting == null) { + setting = ""; + } + + mBackgroundThrottlePackageWhitelist.clear(); + mBackgroundThrottlePackageWhitelist.addAll( + SystemConfig.getInstance().getAllowUnthrottledLocation()); + mBackgroundThrottlePackageWhitelist.addAll(Arrays.asList(setting.split(","))); + + for (LocationProvider p : mProviders) { + applyRequirementsLocked(p); } } + @GuardedBy("mLock") + private void onUserProfilesChangedLocked() { + mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId); + } + + @GuardedBy("mLock") + private boolean isCurrentProfileLocked(int userId) { + return ArrayUtils.contains(mCurrentUserProfiles, userId); + } + + @GuardedBy("mLock") private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) { PackageManager pm = mContext.getPackageManager(); String systemPackageName = mContext.getPackageName(); @@ -583,30 +630,30 @@ public class LocationManagerService extends ILocationManager.Stub { + "partition. The fallback must also be marked coreApp=\"true\" in the manifest"); } - private void loadProvidersLocked() { + @GuardedBy("mLock") + private void initializeProvidersLocked() { // create a passive location provider, which is always enabled - LocationProvider passiveProviderManager = new LocationProvider( - LocationManager.PASSIVE_PROVIDER); - PassiveProvider passiveProvider = new PassiveProvider(passiveProviderManager); - + LocationProvider passiveProviderManager = new LocationProvider(PASSIVE_PROVIDER); addProviderLocked(passiveProviderManager); - mPassiveProvider = passiveProvider; + mPassiveProvider = new PassiveProvider(passiveProviderManager); + passiveProviderManager.attachLocked(mPassiveProvider); if (GnssLocationProvider.isSupported()) { // Create a gps location provider - LocationProvider gnssProviderManager = new LocationProvider( - LocationManager.GPS_PROVIDER); + LocationProvider gnssProviderManager = new LocationProvider(GPS_PROVIDER, true); + mRealProviders.add(gnssProviderManager); + addProviderLocked(gnssProviderManager); + GnssLocationProvider gnssProvider = new GnssLocationProvider(mContext, gnssProviderManager, - mLocationHandler.getLooper()); + mHandler.getLooper()); + gnssProviderManager.attachLocked(gnssProvider); mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider(); mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider(); mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider(); mGnssStatusProvider = gnssProvider.getGnssStatusProvider(); mNetInitiatedListener = gnssProvider.getNetInitiatedListener(); - addProviderLocked(gnssProviderManager); - mRealProviders.put(LocationManager.GPS_PROVIDER, gnssProviderManager); mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider(); mGnssNavigationMessageProvider = gnssProvider.getGnssNavigationMessageProvider(); mGpsGeofenceProxy = gnssProvider.getGpsGeofenceProxy(); @@ -634,9 +681,7 @@ public class LocationManagerService extends ILocationManager.Stub { ensureFallbackFusedProviderPresentLocked(pkgs); // bind to network provider - - LocationProvider networkProviderManager = new LocationProvider( - LocationManager.NETWORK_PROVIDER); + LocationProvider networkProviderManager = new LocationProvider(NETWORK_PROVIDER, true); LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( mContext, networkProviderManager, @@ -645,16 +690,15 @@ public class LocationManagerService extends ILocationManager.Stub { com.android.internal.R.string.config_networkLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (networkProvider != null) { - mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProviderManager); - mProxyProviders.add(networkProvider); + mRealProviders.add(networkProviderManager); addProviderLocked(networkProviderManager); + networkProviderManager.attachLocked(networkProvider); } else { Slog.w(TAG, "no network location provider found"); } // bind to fused provider - LocationProvider fusedProviderManager = new LocationProvider( - LocationManager.FUSED_PROVIDER); + LocationProvider fusedProviderManager = new LocationProvider(FUSED_PROVIDER); LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind( mContext, fusedProviderManager, @@ -663,9 +707,9 @@ public class LocationManagerService extends ILocationManager.Stub { com.android.internal.R.string.config_fusedLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (fusedProvider != null) { + mRealProviders.add(fusedProviderManager); addProviderLocked(fusedProviderManager); - mProxyProviders.add(fusedProvider); - mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedProviderManager); + fusedProviderManager.attachLocked(fusedProvider); } else { Slog.e(TAG, "no fused location provider found", new IllegalStateException("Location service needs a fused location provider")); @@ -715,9 +759,6 @@ public class LocationManagerService extends ILocationManager.Stub { for (String testProviderString : testProviderStrings) { String fragments[] = testProviderString.split(","); String name = fragments[0].trim(); - if (mProvidersByName.get(name) != null) { - throw new IllegalArgumentException("Provider \"" + name + "\" already exists"); - } ProviderProperties properties = new ProviderProperties( Boolean.parseBoolean(fragments[1]) /* requiresNetwork */, Boolean.parseBoolean(fragments[2]) /* requiresSatellite */, @@ -728,28 +769,37 @@ public class LocationManagerService extends ILocationManager.Stub { Boolean.parseBoolean(fragments[7]) /* supportsBearing */, Integer.parseInt(fragments[8]) /* powerRequirement */, Integer.parseInt(fragments[9]) /* accuracy */); - addTestProviderLocked(name, properties); + LocationProvider testProviderManager = new LocationProvider(name); + addProviderLocked(testProviderManager); + new MockProvider(testProviderManager, properties); } } - /** - * Called when the device's active user changes. - * - * @param userId the new active user's UserId - */ - private void switchUser(int userId) { + @GuardedBy("mLock") + private void onUserChangedLocked(int userId) { if (mCurrentUserId == userId) { return; } - mBlacklist.switchUser(userId); - mLocationHandler.removeMessages(MSG_LOCATION_CHANGED); - synchronized (mLock) { - mLastLocation.clear(); - mLastLocationCoarseInterval.clear(); - updateUserProfiles(userId); - updateProvidersSettingsLocked(); - mCurrentUserId = userId; + + // this call has the side effect of forcing a write to the LOCATION_MODE setting in an OS + // upgrade case, and ensures that if anyone checks the LOCATION_MODE setting directly, they + // will see it in an appropriate state (at least after that user becomes foreground for the + // first time...) + isLocationEnabledForUser(userId); + + // let providers know the current user is on the way out before changing the user + for (LocationProvider p : mProviders) { + p.onUserChangingLocked(); } + + mCurrentUserId = userId; + onUserProfilesChangedLocked(); + + mBlacklist.switchUser(userId); + + // if the user changes, per-user settings may also have changed + onLocationModeChangedLocked(false); + onProviderAllowedChangedLocked(false); } private static final class Identity { @@ -767,158 +817,380 @@ public class LocationManagerService extends ILocationManager.Stub { private class LocationProvider implements AbstractLocationProvider.LocationProviderManager { private final String mName; - private AbstractLocationProvider mProvider; - // whether the provider is enabled in location settings - private boolean mSettingsEnabled; + // whether this provider should respect LOCATION_PROVIDERS_ALLOWED (ie gps and network) + private final boolean mIsManagedBySettings; - // whether the provider considers itself enabled - private volatile boolean mEnabled; + // remember to clear binder identity before invoking any provider operation + @GuardedBy("mLock") + @Nullable protected AbstractLocationProvider mProvider; - @Nullable - private volatile ProviderProperties mProperties; + @GuardedBy("mLock") + private boolean mUseable; // combined state + @GuardedBy("mLock") + private boolean mAllowed; // state of LOCATION_PROVIDERS_ALLOWED + @GuardedBy("mLock") + private boolean mEnabled; // state of provider + + @GuardedBy("mLock") + @Nullable private ProviderProperties mProperties; private LocationProvider(String name) { + this(name, false); + } + + private LocationProvider(String name, boolean isManagedBySettings) { mName = name; - // TODO: initialize settings enabled? + mIsManagedBySettings = isManagedBySettings; + + mProvider = null; + mUseable = false; + mAllowed = !mIsManagedBySettings; + mEnabled = false; + mProperties = null; } - @Override - public void onAttachProvider(AbstractLocationProvider provider, boolean initiallyEnabled) { + @GuardedBy("mLock") + public void attachLocked(AbstractLocationProvider provider) { + checkNotNull(provider); checkState(mProvider == null); - - // the provider is not yet fully constructed at this point, so we may not do anything - // except save a reference for later use here. do not call any provider methods. mProvider = provider; - mEnabled = initiallyEnabled; - mProperties = null; + + onUseableChangedLocked(); } public String getName() { return mName; } - public boolean isEnabled() { - return mSettingsEnabled && mEnabled; + @GuardedBy("mLock") + @Nullable + public String getPackageLocked() { + if (mProvider == null) { + return null; + } else if (mProvider instanceof LocationProviderProxy) { + // safe to not clear binder context since this doesn't call into the actual provider + return ((LocationProviderProxy) mProvider).getConnectedPackageName(); + } else { + return mContext.getPackageName(); + } + } + + public boolean isMock() { + return false; } + @GuardedBy("mLock") + public boolean isPassiveLocked() { + return mProvider == mPassiveProvider; + } + + @GuardedBy("mLock") @Nullable - public ProviderProperties getProperties() { + public ProviderProperties getPropertiesLocked() { return mProperties; } - public void setRequest(ProviderRequest request, WorkSource workSource) { - mProvider.setRequest(request, workSource); + @GuardedBy("mLock") + public void setRequestLocked(ProviderRequest request, WorkSource workSource) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + mProvider.setRequest(request, workSource); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + @GuardedBy("mLock") + public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(mName + " provider:"); - pw.println(" setting=" + mSettingsEnabled); + if (isMock()) { + pw.println(" mock=true"); + } + pw.println(" attached=" + (mProvider != null)); + if (mIsManagedBySettings) { + pw.println(" allowed=" + mAllowed); + } pw.println(" enabled=" + mEnabled); + pw.println(" useable=" + mUseable); pw.println(" properties=" + mProperties); - mProvider.dump(fd, pw, args); + + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + mProvider.dump(fd, pw, args); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } - public long getStatusUpdateTime() { - return mProvider.getStatusUpdateTime(); + @GuardedBy("mLock") + public long getStatusUpdateTimeLocked() { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + return mProvider.getStatusUpdateTime(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } else { + return 0; + } } - public int getStatus(Bundle extras) { - return mProvider.getStatus(extras); + @GuardedBy("mLock") + public int getStatusLocked(Bundle extras) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + return mProvider.getStatus(extras); + } finally { + Binder.restoreCallingIdentity(identity); + } + } else { + return AVAILABLE; + } } - public void sendExtraCommand(String command, Bundle extras) { - mProvider.sendExtraCommand(command, extras); + @GuardedBy("mLock") + public void sendExtraCommandLocked(String command, Bundle extras) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + mProvider.sendExtraCommand(command, extras); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } // called from any thread @Override public void onReportLocation(Location location) { - runOnHandler(() -> LocationManagerService.this.reportLocation(location, - mProvider == mPassiveProvider)); + // 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(() -> { + synchronized (mLock) { + handleLocationChangedLocked(location, this); + } + }); } // called from any thread @Override public void onReportLocation(List<Location> locations) { - runOnHandler(() -> LocationManagerService.this.reportLocationBatch(locations)); + // move calls coming from below LMS onto a different thread to avoid deadlock + runInternal(() -> { + synchronized (mLock) { + LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER); + if (gpsProvider == null || !gpsProvider.isUseableLocked()) { + Slog.w(TAG, "reportLocationBatch() called without user permission"); + return; + } + + if (mGnssBatchingCallback == null) { + Slog.e(TAG, "reportLocationBatch() called without active Callback"); + return; + } + + try { + mGnssBatchingCallback.onLocationBatch(locations); + } catch (RemoteException e) { + Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e); + } + } + }); } // called from any thread @Override public void onSetEnabled(boolean enabled) { - runOnHandler(() -> { - if (enabled == mEnabled) { - return; - } - - mEnabled = enabled; - - if (!mSettingsEnabled) { - // this provider was disabled in settings anyways, so a change to it's own - // enabled status won't have any affect. - return; - } - - // traditionally clients can listen for changes to the LOCATION_PROVIDERS_ALLOWED - // setting to detect when providers are enabled or disabled (even though they aren't - // supposed to). to continue to support this we must force a change to this setting. - // we use the fused provider because this is forced to be always enabled in settings - // anyways, and so won't have any visible effect beyond triggering content observers - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "+" + LocationManager.FUSED_PROVIDER, mCurrentUserId); - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-" + LocationManager.FUSED_PROVIDER, mCurrentUserId); - + // move calls coming from below LMS onto a different thread to avoid deadlock + runInternal(() -> { synchronized (mLock) { - if (!enabled) { - // If any provider has been disabled, clear all last locations for all - // providers. This is to be on the safe side in case a provider has location - // derived from this disabled provider. - mLastLocation.clear(); - mLastLocationCoarseInterval.clear(); + if (enabled == mEnabled) { + return; } - updateProviderListenersLocked(mName); - } + mEnabled = enabled; + + // update provider allowed settings to reflect enabled status + if (mIsManagedBySettings) { + if (mEnabled && !mAllowed) { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "+" + mName, + mCurrentUserId); + } else if (!mEnabled && mAllowed) { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "-" + mName, + mCurrentUserId); + } + } - mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION), - UserHandle.ALL); + onUseableChangedLocked(); + } }); } @Override public void onSetProperties(ProviderProperties properties) { - runOnHandler(() -> mProperties = properties); + // move calls coming from below LMS onto a different thread to avoid deadlock + runInternal(() -> { + synchronized (mLock) { + mProperties = properties; + } + }); } - private void setSettingsEnabled(boolean enabled) { - synchronized (mLock) { - if (mSettingsEnabled == enabled) { + @GuardedBy("mLock") + public void onLocationModeChangedLocked() { + onUseableChangedLocked(); + } + + private boolean isAllowed() { + return isAllowedForUser(mCurrentUserId); + } + + private boolean isAllowedForUser(int userId) { + String allowedProviders = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + userId); + return TextUtils.delimitedStringContains(allowedProviders, ',', mName); + } + + @GuardedBy("mLock") + public void onAllowedChangedLocked() { + if (mIsManagedBySettings) { + boolean allowed = isAllowed(); + if (allowed == mAllowed) { return; } + mAllowed = allowed; - mSettingsEnabled = enabled; - if (!mSettingsEnabled) { - // if any provider has been disabled, clear all last locations for all - // providers. this is to be on the safe side in case a provider has location - // derived from this disabled provider. - mLastLocation.clear(); - mLastLocationCoarseInterval.clear(); - updateProviderListenersLocked(mName); - } else if (mEnabled) { - updateProviderListenersLocked(mName); + // make a best effort to keep the setting matching the real enabled state of the + // provider so that legacy applications aren't broken. + if (mAllowed && !mEnabled) { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "-" + mName, + mCurrentUserId); } + + onUseableChangedLocked(); + } + } + + @GuardedBy("mLock") + public boolean isUseableLocked() { + return isUseableForUserLocked(mCurrentUserId); + } + + @GuardedBy("mLock") + public boolean isUseableForUserLocked(int userId) { + return userId == mCurrentUserId && mUseable; + } + + @GuardedBy("mLock") + public void onUseableChangedLocked() { + // if any property that contributes to "useability" here changes state, it MUST result + // in a direct or indrect call to onUseableChangedLocked. this allows the provider to + // guarantee that it will always eventually reach the correct state. + boolean useable = mProvider != null + && mProviders.contains(this) && isLocationEnabled() && mAllowed && mEnabled; + if (useable == mUseable) { + return; + } + mUseable = useable; + + if (!mUseable) { + // If any provider has been disabled, clear all last locations for all + // providers. This is to be on the safe side in case a provider has location + // derived from this disabled provider. + mLastLocation.clear(); + mLastLocationCoarseInterval.clear(); } + + updateProviderUseableLocked(this); + } + + @GuardedBy("mLock") + public void onUserChangingLocked() { + // when the user is about to change, we set this provider to un-useable, and notify all + // of the current user clients. when the user is finished changing, useability will be + // updated back via onLocationModeChanged() and onAllowedChanged(). + mUseable = false; + updateProviderUseableLocked(this); } - private void runOnHandler(Runnable runnable) { - if (Looper.myLooper() == mLocationHandler.getLooper()) { + // 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 { - mLocationHandler.post(runnable); + mHandler.post(runnable); + } + } + } + + private class MockLocationProvider extends LocationProvider { + + private MockLocationProvider(String name) { + super(name); + } + + @Override + public void attachLocked(AbstractLocationProvider provider) { + checkState(provider instanceof MockProvider); + super.attachLocked(provider); + } + + public boolean isMock() { + return true; + } + + @GuardedBy("mLock") + public void setEnabledLocked(boolean enabled) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + ((MockProvider) mProvider).setEnabled(enabled); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @GuardedBy("mLock") + public void setLocationLocked(Location location) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + ((MockProvider) mProvider).setLocation(location); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @GuardedBy("mLock") + public void setStatusLocked(int status, Bundle extras, long updateTime) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + ((MockProvider) mProvider).setStatus(status, extras, updateTime); + } finally { + Binder.restoreCallingIdentity(identity); + } } } } @@ -1022,19 +1294,18 @@ public class LocationManagerService extends ILocationManager.Stub { // See if receiver has any enabled update records. Also note if any update records // are high power (has a high power provider with an interval under a threshold). for (UpdateRecord updateRecord : mUpdateRecords.values()) { - if (isAllowedByUserSettingsLockedForUser(updateRecord.mProvider, - mCurrentUserId)) { - requestingLocation = true; - LocationManagerService.LocationProvider locationProvider - = mProvidersByName.get(updateRecord.mProvider); - ProviderProperties properties = locationProvider != null - ? locationProvider.getProperties() : null; - if (properties != null - && properties.mPowerRequirement == Criteria.POWER_HIGH - && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { - requestingHighPowerLocation = true; - break; - } + LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider); + if (provider == null || !provider.isUseableLocked()) { + continue; + } + + requestingLocation = true; + ProviderProperties properties = provider.getPropertiesLocked(); + if (properties != null + && properties.mPowerRequirement == Criteria.POWER_HIGH + && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { + requestingHighPowerLocation = true; + break; } } } @@ -1122,7 +1393,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler, + mPendingIntent.send(mContext, 0, statusChanged, this, mHandler, getResolutionPermission(mAllowedResolutionLevel), PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); // call this after broadcasting so we do not increment @@ -1158,7 +1429,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler, + mPendingIntent.send(mContext, 0, locationChanged, this, mHandler, getResolutionPermission(mAllowedResolutionLevel), PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); // call this after broadcasting so we do not increment @@ -1201,7 +1472,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler, + mPendingIntent.send(mContext, 0, providerIntent, this, mHandler, getResolutionPermission(mAllowedResolutionLevel), PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); // call this after broadcasting so we do not increment @@ -1273,16 +1544,16 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (receiver) { // so wakelock calls will succeed long identity = Binder.clearCallingIdentity(); - receiver.decrementPendingBroadcastsLocked(); - Binder.restoreCallingIdentity(identity); + try { + receiver.decrementPendingBroadcastsLocked(); + } finally { + Binder.restoreCallingIdentity(identity); + } } } } } - /** - * Returns the year of the GNSS hardware. - */ @Override public int getGnssYearOfHardware() { if (mGnssSystemInfoProvider != null) { @@ -1292,10 +1563,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - - /** - * Returns the model name of the GNSS hardware. - */ @Override @Nullable public String getGnssHardwareModelName() { @@ -1306,32 +1573,24 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Runs some checks for GNSS (FINE) level permissions, used by several methods which directly - * (try to) access GNSS information at this layer. - */ private boolean hasGnssPermissions(String packageName) { - int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkResolutionLevelIsSufficientForProviderUse( - allowedResolutionLevel, - LocationManager.GPS_PROVIDER); + synchronized (mLock) { + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + checkResolutionLevelIsSufficientForProviderUseLocked( + allowedResolutionLevel, + GPS_PROVIDER); - int pid = Binder.getCallingPid(); - int uid = Binder.getCallingUid(); - long identity = Binder.clearCallingIdentity(); - boolean hasLocationAccess; - try { - hasLocationAccess = checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); - } finally { - Binder.restoreCallingIdentity(identity); + int pid = Binder.getCallingPid(); + int uid = Binder.getCallingUid(); + long identity = Binder.clearCallingIdentity(); + try { + return checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); + } finally { + Binder.restoreCallingIdentity(identity); + } } - - return hasLocationAccess; } - /** - * Returns the GNSS batching size, if available. - */ @Override public int getGnssBatchSize(String packageName) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -1344,10 +1603,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Adds a callback for GNSS Batching events, if permissions allow, which are transported - * to potentially multiple listeners by the BatchedLocationCallbackTransport above this. - */ @Override public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -1357,18 +1612,20 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } - mGnssBatchingCallback = callback; - mGnssBatchingDeathCallback = new LinkedCallback(callback); - try { - callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */); - } catch (RemoteException e) { - // if the remote process registering the listener is already dead, just swallow the - // exception and return - Log.e(TAG, "Remote listener already died.", e); - return false; - } + synchronized (mLock) { + mGnssBatchingCallback = callback; + mGnssBatchingDeathCallback = new LinkedCallback(callback); + try { + callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */); + } catch (RemoteException e) { + // if the remote process registering the listener is already dead, just swallow the + // exception and return + Log.e(TAG, "Remote listener already died.", e); + return false; + } - return true; + return true; + } } private class LinkedCallback implements IBinder.DeathRecipient { @@ -1391,27 +1648,22 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Removes callback for GNSS batching - */ @Override public void removeGnssBatchingCallback() { - try { - mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback, - 0 /* flags */); - } catch (NoSuchElementException e) { - // if the death callback isn't connected (it should be...), log error, swallow the - // exception and return - Log.e(TAG, "Couldn't unlink death callback.", e); + synchronized (mLock) { + try { + mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback, + 0 /* flags */); + } catch (NoSuchElementException e) { + // if the death callback isn't connected (it should be...), log error, swallow the + // exception and return + Log.e(TAG, "Couldn't unlink death callback.", e); + } + mGnssBatchingCallback = null; + mGnssBatchingDeathCallback = null; } - mGnssBatchingCallback = null; - mGnssBatchingDeathCallback = null; } - - /** - * Starts GNSS batching, if available. - */ @Override public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -1421,20 +1673,20 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } - if (mGnssBatchingInProgress) { - // Current design does not expect multiple starts to be called repeatedly - Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch"); - // Try to clean up anyway, and continue - stopGnssBatch(); - } + synchronized (mLock) { + if (mGnssBatchingInProgress) { + // Current design does not expect multiple starts to be called repeatedly + Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch"); + // Try to clean up anyway, and continue + stopGnssBatch(); + } - mGnssBatchingInProgress = true; - return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull); + mGnssBatchingInProgress = true; + return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull); + } } - /** - * Flushes a GNSS batch in progress - */ + @Override public void flushGnssBatch(String packageName) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -1445,117 +1697,66 @@ public class LocationManagerService extends ILocationManager.Stub { return; } - if (!mGnssBatchingInProgress) { - Log.w(TAG, "flushGnssBatch called with no batch in progress"); - } + synchronized (mLock) { + if (!mGnssBatchingInProgress) { + Log.w(TAG, "flushGnssBatch called with no batch in progress"); + } - if (mGnssBatchingProvider != null) { - mGnssBatchingProvider.flush(); + if (mGnssBatchingProvider != null) { + mGnssBatchingProvider.flush(); + } } } - /** - * Stops GNSS batching - */ @Override public boolean stopGnssBatch() { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, "Location Hardware permission not granted to access hardware batching"); - if (mGnssBatchingProvider != null) { - mGnssBatchingInProgress = false; - return mGnssBatchingProvider.stop(); - } else { - return false; - } - } - - @Override - public void reportLocationBatch(List<Location> locations) { - checkCallerIsProvider(); - - // Currently used only for GNSS locations - update permissions check if changed - if (isAllowedByUserSettingsLockedForUser(LocationManager.GPS_PROVIDER, mCurrentUserId)) { - if (mGnssBatchingCallback == null) { - Slog.e(TAG, "reportLocationBatch() called without active Callback"); - return; - } - try { - mGnssBatchingCallback.onLocationBatch(locations); - } catch (RemoteException e) { - Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e); + synchronized (mLock) { + if (mGnssBatchingProvider != null) { + mGnssBatchingInProgress = false; + return mGnssBatchingProvider.stop(); + } else { + return false; } - } else { - Slog.w(TAG, "reportLocationBatch() called without user permission, locations blocked"); } } + @GuardedBy("mLock") private void addProviderLocked(LocationProvider provider) { + Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null); + mProviders.add(provider); - mProvidersByName.put(provider.getName(), provider); - } - private void removeProviderLocked(LocationProvider provider) { - mProviders.remove(provider); - mProvidersByName.remove(provider.getName()); + provider.onAllowedChangedLocked(); // allowed state may change while provider was inactive + provider.onUseableChangedLocked(); } - /** - * Returns "true" if access to the specified location provider is allowed by the specified - * user's settings. Access to all location providers is forbidden to non-location-provider - * processes belonging to background users. - * - * @param provider the name of the location provider - * @param userId the user id to query - */ - private boolean isAllowedByUserSettingsLockedForUser(String provider, int userId) { - if (LocationManager.PASSIVE_PROVIDER.equals(provider)) { - return isLocationEnabledForUser(userId); - } - if (LocationManager.FUSED_PROVIDER.equals(provider)) { - return isLocationEnabledForUser(userId); - } - synchronized (mLock) { - if (mMockProviders.containsKey(provider)) { - return isLocationEnabledForUser(userId); + @GuardedBy("mLock") + private void removeProviderLocked(LocationProvider provider) { + if (mProviders.remove(provider)) { + long identity = Binder.clearCallingIdentity(); + try { + provider.onUseableChangedLocked(); + } finally { + Binder.restoreCallingIdentity(identity); } } - - long identity = Binder.clearCallingIdentity(); - try { - // Use system settings - ContentResolver cr = mContext.getContentResolver(); - String allowedProviders = Settings.Secure.getStringForUser( - cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId); - return TextUtils.delimitedStringContains(allowedProviders, ',', provider); - } finally { - Binder.restoreCallingIdentity(identity); - } } - - /** - * Returns "true" if access to the specified location provider is allowed by the specified - * user's settings. Access to all location providers is forbidden to non-location-provider - * processes belonging to background users. - * - * @param provider the name of the location provider - * @param uid the requestor's UID - * @param userId the user id to query - */ - private boolean isAllowedByUserSettingsLocked(String provider, int uid, int userId) { - if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) { - return false; + @GuardedBy("mLock") + @Nullable + private LocationProvider getLocationProviderLocked(String providerName) { + for (LocationProvider provider : mProviders) { + if (providerName.equals(provider.getName())) { + return provider; + } } - return isAllowedByUserSettingsLockedForUser(provider, userId); + + return null; } - /** - * Returns the permission string associated with the specified resolution level. - * - * @param resolutionLevel the resolution level - * @return the permission string - */ private String getResolutionPermission(int resolutionLevel) { switch (resolutionLevel) { case RESOLUTION_LEVEL_FINE: @@ -1567,13 +1768,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Returns the resolution level allowed to the given PID/UID pair. - * - * @param pid the PID - * @param uid the UID - * @return resolution level allowed to the pid/uid pair - */ private int getAllowedResolutionLevel(int pid, int uid) { if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, pid, uid) == PERMISSION_GRANTED) { @@ -1586,39 +1780,22 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Returns the resolution level allowed to the caller - * - * @return resolution level allowed to caller - */ private int getCallerAllowedResolutionLevel() { return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid()); } - /** - * Throw SecurityException if specified resolution level is insufficient to use geofences. - * - * @param allowedResolutionLevel resolution level allowed to caller - */ private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) { if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) { throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission"); } } - /** - * Return the minimum resolution level required to use the specified location provider. - * - * @param provider the name of the location provider - * @return minimum resolution level required for provider - */ - private int getMinimumResolutionLevelForProviderUse(String provider) { - if (LocationManager.GPS_PROVIDER.equals(provider) || - LocationManager.PASSIVE_PROVIDER.equals(provider)) { + @GuardedBy("mLock") + private int getMinimumResolutionLevelForProviderUseLocked(String provider) { + if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) { // gps and passive providers require FINE permission return RESOLUTION_LEVEL_FINE; - } else if (LocationManager.NETWORK_PROVIDER.equals(provider) || - LocationManager.FUSED_PROVIDER.equals(provider)) { + } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) { // network and fused providers are ok with COARSE or FINE return RESOLUTION_LEVEL_COARSE; } else { @@ -1627,7 +1804,7 @@ public class LocationManagerService extends ILocationManager.Stub { continue; } - ProviderProperties properties = lp.getProperties(); + ProviderProperties properties = lp.getPropertiesLocked(); if (properties != null) { if (properties.mRequiresSatellite) { // provider requiring satellites require FINE permission @@ -1643,16 +1820,10 @@ public class LocationManagerService extends ILocationManager.Stub { return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE } - /** - * Throw SecurityException if specified resolution level is insufficient to use the named - * location provider. - * - * @param allowedResolutionLevel resolution level allowed to caller - * @param providerName the name of the location provider - */ - private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel, + @GuardedBy("mLock") + private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel, String providerName) { - int requiredResolutionLevel = getMinimumResolutionLevelForProviderUse(providerName); + int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName); if (allowedResolutionLevel < requiredResolutionLevel) { switch (requiredResolutionLevel) { case RESOLUTION_LEVEL_FINE: @@ -1668,20 +1839,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages - * for battery). - */ - private void checkDeviceStatsAllowed() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.UPDATE_DEVICE_STATS, null); - } - - private void checkUpdateAppOpsAllowed() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.UPDATE_APP_OPS_STATS, null); - } - public static int resolutionLevelToOp(int allowedResolutionLevel) { if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) { if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) { @@ -1739,19 +1896,17 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public List<String> getAllProviders() { - ArrayList<String> out; synchronized (mLock) { - out = new ArrayList<>(mProviders.size()); + ArrayList<String> providers = new ArrayList<>(mProviders.size()); for (LocationProvider provider : mProviders) { String name = provider.getName(); - if (LocationManager.FUSED_PROVIDER.equals(name)) { + if (FUSED_PROVIDER.equals(name)) { continue; } - out.add(name); + providers.add(name); } + return providers; } - if (D) Log.d(TAG, "getAllProviders()=" + out); - return out; } /** @@ -1762,37 +1917,28 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public List<String> getProviders(Criteria criteria, boolean enabledOnly) { int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - ArrayList<String> out; - int uid = Binder.getCallingUid(); - long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - out = new ArrayList<>(mProviders.size()); - for (LocationProvider provider : mProviders) { - String name = provider.getName(); - if (LocationManager.FUSED_PROVIDER.equals(name)) { - continue; - } - if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) { - if (enabledOnly - && !isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) { - continue; - } - if (criteria != null - && !android.location.LocationProvider.propertiesMeetCriteria( - name, provider.getProperties(), criteria)) { - continue; - } - out.add(name); - } + synchronized (mLock) { + ArrayList<String> providers = new ArrayList<>(mProviders.size()); + for (LocationProvider provider : mProviders) { + String name = provider.getName(); + if (FUSED_PROVIDER.equals(name)) { + continue; + } + if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) { + continue; + } + if (enabledOnly && !provider.isUseableLocked()) { + continue; + } + if (criteria != null + && !android.location.LocationProvider.propertiesMeetCriteria( + name, provider.getPropertiesLocked(), criteria)) { + continue; } + providers.add(name); } - } finally { - Binder.restoreCallingIdentity(identity); + return providers; } - - if (D) Log.d(TAG, "getProviders()=" + out); - return out; } /** @@ -1804,71 +1950,36 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public String getBestProvider(Criteria criteria, boolean enabledOnly) { - String result; - List<String> providers = getProviders(criteria, enabledOnly); - if (!providers.isEmpty()) { - result = pickBest(providers); - if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); - return result; + if (providers.isEmpty()) { + providers = getProviders(null, enabledOnly); } - providers = getProviders(null, enabledOnly); + if (!providers.isEmpty()) { - result = pickBest(providers); - if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); - return result; + if (providers.contains(GPS_PROVIDER)) { + return GPS_PROVIDER; + } else if (providers.contains(NETWORK_PROVIDER)) { + return NETWORK_PROVIDER; + } else { + return providers.get(0); + } } - if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + null); return null; } - private String pickBest(List<String> providers) { - if (providers.contains(LocationManager.GPS_PROVIDER)) { - return LocationManager.GPS_PROVIDER; - } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) { - return LocationManager.NETWORK_PROVIDER; - } else { - return providers.get(0); - } - } - - @Override - public boolean providerMeetsCriteria(String provider, Criteria criteria) { - LocationProvider p = mProvidersByName.get(provider); - if (p == null) { - throw new IllegalArgumentException("provider=" + provider); - } - - boolean result = android.location.LocationProvider.propertiesMeetCriteria( - p.getName(), p.getProperties(), criteria); - if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result); - return result; - } - - private void updateProvidersSettingsLocked() { - for (LocationProvider p : mProviders) { - p.setSettingsEnabled(isAllowedByUserSettingsLockedForUser(p.getName(), mCurrentUserId)); - } - - mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION), - UserHandle.ALL); - } - - private void updateProviderListenersLocked(String provider) { - LocationProvider p = mProvidersByName.get(provider); - if (p == null) return; - - boolean enabled = p.isEnabled(); + @GuardedBy("mLock") + private void updateProviderUseableLocked(LocationProvider provider) { + boolean useable = provider.isUseableLocked(); ArrayList<Receiver> deadReceivers = null; - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); if (records != null) { for (UpdateRecord record : records) { - if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { + if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { // Sends a notification message to the receiver - if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) { + if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) { if (deadReceivers == null) { deadReceivers = new ArrayList<>(); } @@ -1887,25 +1998,37 @@ public class LocationManagerService extends ILocationManager.Stub { applyRequirementsLocked(provider); } - private void applyRequirementsLocked(String provider) { - LocationProvider p = mProvidersByName.get(provider); - if (p == null) return; + @GuardedBy("mLock") + private void applyRequirementsLocked(String providerName) { + LocationProvider provider = getLocationProviderLocked(providerName); + if (provider != null) { + applyRequirementsLocked(provider); + } + } - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); + @GuardedBy("mLock") + private void applyRequirementsLocked(LocationProvider provider) { + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); WorkSource worksource = new WorkSource(); ProviderRequest providerRequest = new ProviderRequest(); - ContentResolver resolver = mContext.getContentResolver(); - long backgroundThrottleInterval = Settings.Global.getLong( - resolver, - Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, - DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS); + long backgroundThrottleInterval; - if (p.isEnabled() && records != null && !records.isEmpty()) { + long identity = Binder.clearCallingIdentity(); + try { + backgroundThrottleInterval = Settings.Global.getLong( + mContext.getContentResolver(), + Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, + DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS); + } finally { + Binder.restoreCallingIdentity(identity); + } + + if (provider.isUseableLocked() && records != null && !records.isEmpty()) { // initialize the low power mode to true and set to false if any of the records requires providerRequest.lowPowerMode = true; for (UpdateRecord record : records) { - if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { + if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { if (checkLocationAccess( record.mReceiver.mIdentity.mPid, record.mReceiver.mIdentity.mUid, @@ -1945,7 +2068,8 @@ public class LocationManagerService extends ILocationManager.Stub { // under that threshold. long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; for (UpdateRecord record : records) { - if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { + if (isCurrentProfileLocked( + UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { LocationRequest locationRequest = record.mRequest; // Don't assign battery blame for update records whose @@ -1972,7 +2096,7 @@ public class LocationManagerService extends ILocationManager.Stub { } if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest); - p.setRequest(providerRequest, worksource); + provider.setRequestLocked(providerRequest, worksource); } /** @@ -1995,34 +2119,11 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public String[] getBackgroundThrottlingWhitelist() { synchronized (mLock) { - return mBackgroundThrottlePackageWhitelist.toArray( - new String[0]); + return mBackgroundThrottlePackageWhitelist.toArray(new String[0]); } } - private void updateBackgroundThrottlingWhitelistLocked() { - String setting = Settings.Global.getString( - mContext.getContentResolver(), - Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST); - if (setting == null) { - setting = ""; - } - - mBackgroundThrottlePackageWhitelist.clear(); - mBackgroundThrottlePackageWhitelist.addAll( - SystemConfig.getInstance().getAllowUnthrottledLocation()); - mBackgroundThrottlePackageWhitelist.addAll( - Arrays.asList(setting.split(","))); - } - - private void updateLastLocationMaxAgeLocked() { - mLastLocationMaxAgeMs = - Settings.Global.getLong( - mContext.getContentResolver(), - Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS, - DEFAULT_LAST_LOCATION_MAX_AGE_MS); - } - + @GuardedBy("mLock") private boolean isThrottlingExemptLocked(Identity identity) { if (identity.mUid == Process.SYSTEM_UID) { return true; @@ -2032,8 +2133,8 @@ public class LocationManagerService extends ILocationManager.Stub { return true; } - for (LocationProviderProxy provider : mProxyProviders) { - if (identity.mPackageName.equals(provider.getConnectedPackageName())) { + for (LocationProvider provider : mProviders) { + if (identity.mPackageName.equals(provider.getPackageLocked())) { return true; } } @@ -2119,6 +2220,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } + @GuardedBy("mLock") private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { IBinder binder = listener.asBinder(); @@ -2137,6 +2239,7 @@ public class LocationManagerService extends ILocationManager.Stub { return receiver; } + @GuardedBy("mLock") private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { Receiver receiver = mReceivers.get(intent); @@ -2202,67 +2305,65 @@ public class LocationManagerService extends ILocationManager.Stub { throw new SecurityException("invalid package name: " + packageName); } - private void checkPendingIntent(PendingIntent intent) { - if (intent == null) { - throw new IllegalArgumentException("invalid pending intent: " + null); - } - } - - private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent, - int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { - if (intent == null && listener == null) { - throw new IllegalArgumentException("need either listener or intent"); - } else if (intent != null && listener != null) { - throw new IllegalArgumentException("cannot register both listener and intent"); - } else if (intent != null) { - checkPendingIntent(intent); - return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps); - } else { - return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps); - } - } - @Override public void requestLocationUpdates(LocationRequest request, ILocationListener listener, PendingIntent intent, String packageName) { - if (request == null) request = DEFAULT_LOCATION_REQUEST; - checkPackageName(packageName); - int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, - request.getProvider()); - WorkSource workSource = request.getWorkSource(); - if (workSource != null && !workSource.isEmpty()) { - checkDeviceStatsAllowed(); - } - boolean hideFromAppOps = request.getHideFromAppOps(); - if (hideFromAppOps) { - checkUpdateAppOpsAllowed(); - } - boolean callerHasLocationHardwarePermission = - mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) - == PERMISSION_GRANTED; - LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel, - callerHasLocationHardwarePermission); - - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - // providers may use public location API's, need to clear identity - long identity = Binder.clearCallingIdentity(); - try { - // We don't check for MODE_IGNORED here; we will do that when we go to deliver - // a location. - checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); + synchronized (mLock) { + if (request == null) request = DEFAULT_LOCATION_REQUEST; + checkPackageName(packageName); + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, + request.getProvider()); + WorkSource workSource = request.getWorkSource(); + if (workSource != null && !workSource.isEmpty()) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.UPDATE_DEVICE_STATS, null); + } + boolean hideFromAppOps = request.getHideFromAppOps(); + if (hideFromAppOps) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.UPDATE_APP_OPS_STATS, null); + } + boolean callerHasLocationHardwarePermission = + mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) + == PERMISSION_GRANTED; + LocationRequest sanitizedRequest = createSanitizedRequest(request, + allowedResolutionLevel, + callerHasLocationHardwarePermission); + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); - synchronized (mLock) { - Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid, - packageName, workSource, hideFromAppOps); - requestLocationUpdatesLocked(sanitizedRequest, recevier, uid, packageName); + long identity = Binder.clearCallingIdentity(); + try { + + // We don't check for MODE_IGNORED here; we will do that when we go to deliver + // a location. + checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); + + if (intent == null && listener == null) { + throw new IllegalArgumentException("need either listener or intent"); + } else if (intent != null && listener != null) { + throw new IllegalArgumentException( + "cannot register both listener and intent"); + } + + Receiver receiver; + if (intent != null) { + receiver = getReceiverLocked(intent, pid, uid, packageName, workSource, + hideFromAppOps); + } else { + receiver = getReceiverLocked(listener, pid, uid, packageName, workSource, + hideFromAppOps); + } + requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName); + } finally { + Binder.restoreCallingIdentity(identity); } - } finally { - Binder.restoreCallingIdentity(identity); } } + @GuardedBy("mLock") private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver, int uid, String packageName) { // Figure out the provider. Either its explicitly request (legacy use cases), or @@ -2273,7 +2374,7 @@ public class LocationManagerService extends ILocationManager.Stub { throw new IllegalArgumentException("provider name must not be null"); } - LocationProvider provider = mProvidersByName.get(name); + LocationProvider provider = getLocationProviderLocked(name); if (provider == null) { throw new IllegalArgumentException("provider doesn't exist: " + name); } @@ -2292,7 +2393,7 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - if (provider.isEnabled()) { + if (provider.isUseableLocked()) { applyRequirementsLocked(name); } else { // Notify the listener that updates are currently disabled @@ -2308,14 +2409,23 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName) { checkPackageName(packageName); - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + int uid = Binder.getCallingUid(); + + if (intent == null && listener == null) { + throw new IllegalArgumentException("need either listener or intent"); + } else if (intent != null && listener != null) { + throw new IllegalArgumentException("cannot register both listener and intent"); + } synchronized (mLock) { - Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, - packageName, null, false); + Receiver receiver; + if (intent != null) { + receiver = getReceiverLocked(intent, pid, uid, packageName, null, false); + } else { + receiver = getReceiverLocked(listener, pid, uid, packageName, null, false); + } - // providers may use public location API's, need to clear identity long identity = Binder.clearCallingIdentity(); try { removeUpdatesLocked(receiver); @@ -2325,6 +2435,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } + @GuardedBy("mLock") private void removeUpdatesLocked(Receiver receiver) { if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver))); @@ -2356,51 +2467,53 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private void applyAllProviderRequirementsLocked() { - for (LocationProvider p : mProviders) { - applyRequirementsLocked(p.getName()); - } - } - @Override - public Location getLastLocation(LocationRequest request, String packageName) { - if (D) Log.d(TAG, "getLastLocation: " + request); - if (request == null) request = DEFAULT_LOCATION_REQUEST; - int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkPackageName(packageName); - checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, - request.getProvider()); - // no need to sanitize this request, as only the provider name is used - - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long identity = Binder.clearCallingIdentity(); - try { - if (mBlacklist.isBlacklisted(packageName)) { - if (D) { - Log.d(TAG, "not returning last loc for blacklisted app: " + - packageName); + public Location getLastLocation(LocationRequest r, String packageName) { + if (D) Log.d(TAG, "getLastLocation: " + r); + synchronized (mLock) { + LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST; + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + checkPackageName(packageName); + checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, + request.getProvider()); + // no need to sanitize this request, as only the provider name is used + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); + try { + if (mBlacklist.isBlacklisted(packageName)) { + if (D) { + Log.d(TAG, "not returning last loc for blacklisted app: " + + packageName); + } + return null; } - return null; - } - if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) { - if (D) { - Log.d(TAG, "not returning last loc for no op app: " + - packageName); + if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) { + if (D) { + Log.d(TAG, "not returning last loc for no op app: " + + packageName); + } + return null; } - return null; - } - synchronized (mLock) { // Figure out the provider. Either its explicitly request (deprecated API's), // or use the fused provider String name = request.getProvider(); if (name == null) name = LocationManager.FUSED_PROVIDER; - LocationProvider provider = mProvidersByName.get(name); + LocationProvider provider = getLocationProviderLocked(name); if (provider == null) return null; - if (!isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) return null; + // only the current user or location providers may get location this way + if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isLocationProviderLocked( + uid)) { + return null; + } + + if (!provider.isUseableLocked()) { + return null; + } Location location; if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) { @@ -2416,9 +2529,12 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't return stale location to apps with foreground-only location permission. String op = resolutionLevelToOpStr(allowedResolutionLevel); - long locationAgeMs = SystemClock.elapsedRealtime() - - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI; - if ((locationAgeMs > mLastLocationMaxAgeMs) + long locationAgeMs = SystemClock.elapsedRealtime() + - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI; + if ((locationAgeMs > Settings.Global.getLong( + mContext.getContentResolver(), + Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS, + DEFAULT_LAST_LOCATION_MAX_AGE_MS)) && (mAppOps.unsafeCheckOp(op, uid, packageName) == AppOpsManager.MODE_FOREGROUND)) { return null; @@ -2433,24 +2549,13 @@ public class LocationManagerService extends ILocationManager.Stub { } else { return new Location(location); } + return null; + } finally { + Binder.restoreCallingIdentity(identity); } - return null; - } finally { - Binder.restoreCallingIdentity(identity); } } - /** - * Provides an interface to inject and set the last location if location is not available - * currently. - * - * This helps in cases where the product (Cars for example) has saved the last known location - * before powering off. This interface lets the client inject the saved location while the GPS - * chipset is getting its first fix, there by improving user experience. - * - * @param location - Location object to inject - * @return true if update was successful, false if not - */ @Override public boolean injectLocation(Location location) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -2464,38 +2569,23 @@ public class LocationManagerService extends ILocationManager.Stub { } return false; } - LocationProvider p = null; - String provider = location.getProvider(); - if (provider != null) { - p = mProvidersByName.get(provider); - } - if (p == null) { - if (D) { - Log.d(TAG, "injectLocation(): unknown provider"); - } - return false; - } + synchronized (mLock) { - if (!isAllowedByUserSettingsLockedForUser(provider, mCurrentUserId)) { - if (D) { - Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId); - } + LocationProvider provider = getLocationProviderLocked(location.getProvider()); + if (provider == null || !provider.isUseableLocked()) { return false; - } else { - // NOTE: If last location is already available, location is not injected. If - // provider's normal source (like a GPS chipset) have already provided an output, - // there is no need to inject this location. - if (mLastLocation.get(provider) == null) { - updateLastLocationLocked(location, provider); - } else { - if (D) { - Log.d(TAG, "injectLocation(): Location exists. Not updating"); - } - return false; - } } + + // NOTE: If last location is already available, location is not injected. If + // provider's normal source (like a GPS chipset) have already provided an output + // there is no need to inject this location. + if (mLastLocation.get(provider.getName()) != null) { + return false; + } + + updateLastLocationLocked(location, provider.getName()); + return true; } - return true; } @Override @@ -2504,39 +2594,49 @@ public class LocationManagerService extends ILocationManager.Stub { if (request == null) request = DEFAULT_LOCATION_REQUEST; int allowedResolutionLevel = getCallerAllowedResolutionLevel(); checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel); - checkPendingIntent(intent); + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + null); + } checkPackageName(packageName); - checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, - request.getProvider()); - // Require that caller can manage given document - boolean callerHasLocationHardwarePermission = - mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) - == PERMISSION_GRANTED; - LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel, - callerHasLocationHardwarePermission); + synchronized (mLock) { + checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, + request.getProvider()); + // Require that caller can manage given document + boolean callerHasLocationHardwarePermission = + mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) + == PERMISSION_GRANTED; + LocationRequest sanitizedRequest = createSanitizedRequest(request, + allowedResolutionLevel, + callerHasLocationHardwarePermission); - if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent); + if (D) { + Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent); + } - // geo-fence manager uses the public location API, need to clear identity - int uid = Binder.getCallingUid(); - // TODO: http://b/23822629 - if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) { - // temporary measure until geofences work for secondary users - Log.w(TAG, "proximity alerts are currently available only to the primary user"); - return; - } - long identity = Binder.clearCallingIdentity(); - try { - mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel, - uid, packageName); - } finally { - Binder.restoreCallingIdentity(identity); + // geo-fence manager uses the public location API, need to clear identity + int uid = Binder.getCallingUid(); + // TODO: http://b/23822629 + if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) { + // temporary measure until geofences work for secondary users + Log.w(TAG, "proximity alerts are currently available only to the primary user"); + return; + } + long identity = Binder.clearCallingIdentity(); + try { + mGeofenceManager.addFence(sanitizedRequest, geofence, intent, + allowedResolutionLevel, + uid, packageName); + } finally { + Binder.restoreCallingIdentity(identity); + } } } @Override public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) { - checkPendingIntent(intent); + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + null); + } checkPackageName(packageName); if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent); @@ -2641,6 +2741,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (mLock) { Identity callerIdentity = new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName); + // TODO(b/120481270): Register for client death notification and update map. mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity); long identity = Binder.clearCallingIdentity(); @@ -2670,25 +2771,26 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean sendExtraCommand(String provider, String command, Bundle extras) { - if (provider == null) { + public boolean sendExtraCommand(String providerName, String command, Bundle extras) { + if (providerName == null) { // throw NullPointerException to remain compatible with previous implementation throw new NullPointerException(); } - checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), - provider); + synchronized (mLock) { + checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(), + providerName); - // and check for ACCESS_LOCATION_EXTRA_COMMANDS - if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) - != PERMISSION_GRANTED)) { - throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission"); - } + // and check for ACCESS_LOCATION_EXTRA_COMMANDS + if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) + != PERMISSION_GRANTED)) { + throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission"); + } - synchronized (mLock) { - LocationProvider p = mProvidersByName.get(provider); - if (p == null) return false; + LocationProvider provider = getLocationProviderLocked(providerName); + if (provider != null) { + provider.sendExtraCommandLocked(command, extras); + } - p.sendExtraCommand(command, extras); return true; } } @@ -2707,44 +2809,29 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * @return null if the provider does not exist - * @throws SecurityException if the provider is not allowed to be - * accessed by the caller - */ @Override - public ProviderProperties getProviderProperties(String provider) { - checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), - provider); - - LocationProvider p; + public ProviderProperties getProviderProperties(String providerName) { synchronized (mLock) { - p = mProvidersByName.get(provider); - } + checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(), + providerName); - if (p == null) return null; - return p.getProperties(); + LocationProvider provider = getLocationProviderLocked(providerName); + if (provider == null) { + return null; + } + return provider.getPropertiesLocked(); + } } - /** - * @return null if the provider does not exist - * @throws SecurityException if the provider is not allowed to be - * accessed by the caller - */ @Override public String getNetworkProviderPackage() { - LocationProvider p; synchronized (mLock) { - p = mProvidersByName.get(LocationManager.NETWORK_PROVIDER); - } - - if (p == null) { - return null; - } - if (p.mProvider instanceof LocationProviderProxy) { - return ((LocationProviderProxy) p.mProvider).getConnectedPackageName(); + LocationProvider provider = getLocationProviderLocked(NETWORK_PROVIDER); + if (provider == null) { + return null; + } + return provider.getPackageLocked(); } - return null; } @Override @@ -2780,241 +2867,96 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Returns the current location enabled/disabled status for a user - * - * @param userId the id of the user - * @return true if location is enabled - */ - @Override - public boolean isLocationEnabledForUser(int userId) { - // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); - - long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - final String allowedProviders = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - userId); - if (allowedProviders == null) { - return false; - } - final List<String> providerList = Arrays.asList(allowedProviders.split(",")); - for (String provider : mRealProviders.keySet()) { - if (provider.equals(LocationManager.PASSIVE_PROVIDER) - || provider.equals(LocationManager.FUSED_PROVIDER)) { - continue; - } - if (providerList.contains(provider)) { - return true; - } - } - return false; - } - } finally { - Binder.restoreCallingIdentity(identity); - } + private boolean isLocationEnabled() { + return isLocationEnabledForUser(mCurrentUserId); } - /** - * Enable or disable location for a user - * - * @param enabled true to enable location, false to disable location - * @param userId the id of the user - */ @Override - public void setLocationEnabledForUser(boolean enabled, int userId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, - "Requires WRITE_SECURE_SETTINGS permission"); - + public boolean isLocationEnabledForUser(int userId) { // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + "Requires INTERACT_ACROSS_USERS permission"); + } long identity = Binder.clearCallingIdentity(); try { - synchronized (mLock) { - final Set<String> allRealProviders = mRealProviders.keySet(); - // Update all providers on device plus gps and network provider when disabling - // location - Set<String> allProvidersSet = new ArraySet<>(allRealProviders.size() + 2); - allProvidersSet.addAll(allRealProviders); - // When disabling location, disable gps and network provider that could have been - // enabled by location mode api. - if (!enabled) { - allProvidersSet.add(LocationManager.GPS_PROVIDER); - allProvidersSet.add(LocationManager.NETWORK_PROVIDER); - } - if (allProvidersSet.isEmpty()) { - return; - } - // to ensure thread safety, we write the provider name with a '+' or '-' - // and let the SettingsProvider handle it rather than reading and modifying - // the list of enabled providers. - final String prefix = enabled ? "+" : "-"; - StringBuilder locationProvidersAllowed = new StringBuilder(); - for (String provider : allProvidersSet) { - if (provider.equals(LocationManager.PASSIVE_PROVIDER) - || provider.equals(LocationManager.FUSED_PROVIDER)) { - continue; - } - locationProvidersAllowed.append(prefix); - locationProvidersAllowed.append(provider); - locationProvidersAllowed.append(","); - } - // Remove the trailing comma - locationProvidersAllowed.setLength(locationProvidersAllowed.length() - 1); - Settings.Secure.putStringForUser( + boolean enabled; + try { + enabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, + userId) != Settings.Secure.LOCATION_MODE_OFF; + } catch (Settings.SettingNotFoundException e) { + // OS upgrade case where mode isn't set yet + enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - locationProvidersAllowed.toString(), - userId); - } + userId)); + + try { + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, + enabled + ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY + : Settings.Secure.LOCATION_MODE_OFF, + userId); + } catch (RuntimeException ex) { + // any problem with writing should not be propagated + Slog.e(TAG, "error updating location mode", ex); + } + } + return enabled; } finally { Binder.restoreCallingIdentity(identity); } } - /** - * Returns the current enabled/disabled status of a location provider and user - * - * @param providerName name of the provider - * @param userId the id of the user - * @return true if the provider exists and is enabled - */ @Override public boolean isProviderEnabledForUser(String providerName, int userId) { // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); - - if (!isLocationEnabledForUser(userId)) { - return false; + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + "Requires INTERACT_ACROSS_USERS permission"); } // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, // so we discourage its use - if (LocationManager.FUSED_PROVIDER.equals(providerName)) return false; - - long identity = Binder.clearCallingIdentity(); - try { - LocationProvider provider; - synchronized (mLock) { - provider = mProvidersByName.get(providerName); - } - return provider != null && provider.isEnabled(); - } finally { - Binder.restoreCallingIdentity(identity); - } - } + if (FUSED_PROVIDER.equals(providerName)) return false; - /** - * Enable or disable a single location provider. - * - * @param provider name of the provider - * @param enabled true to enable the provider. False to disable the provider - * @param userId the id of the user to set - * @return true if the value was set, false on errors - */ - @Override - public boolean setProviderEnabledForUser(String provider, boolean enabled, int userId) { - return false; - } - - /** - * Method for checking INTERACT_ACROSS_USERS permission if specified user id is not the same as - * current user id - * - * @param userId the user id to get or set value - */ - private void checkInteractAcrossUsersPermission(int userId) { - int uid = Binder.getCallingUid(); - if (UserHandle.getUserId(uid) != userId) { - if (ActivityManager.checkComponentPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, uid, -1, true) - != PERMISSION_GRANTED) { - throw new SecurityException("Requires INTERACT_ACROSS_USERS permission"); - } + synchronized (mLock) { + LocationProvider provider = getLocationProviderLocked(providerName); + return provider != null && provider.isUseableForUserLocked(userId); } } - /** - * Returns "true" if the UID belongs to a bound location provider. - * - * @param uid the uid - * @return true if uid belongs to a bound location provider - */ - private boolean isUidALocationProvider(int uid) { + @GuardedBy("mLock") + private boolean isLocationProviderLocked(int uid) { if (uid == Process.SYSTEM_UID) { return true; } - if (mGeocodeProvider != null) { - if (doesUidHavePackage(uid, mGeocodeProvider.getConnectedPackageName())) return true; - } - for (LocationProviderProxy proxy : mProxyProviders) { - if (doesUidHavePackage(uid, proxy.getConnectedPackageName())) return true; - } - return false; - } - private void checkCallerIsProvider() { - if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER) - == PERMISSION_GRANTED) { - return; - } - - // Previously we only used the INSTALL_LOCATION_PROVIDER - // check. But that is system or signature - // protection level which is not flexible enough for - // providers installed oustide the system image. So - // also allow providers with a UID matching the - // currently bound package name - - if (isUidALocationProvider(Binder.getCallingUid())) { - return; - } - - throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " + - "or UID of a currently bound location provider"); - } - - /** - * Returns true if the given package belongs to the given uid. - */ - private boolean doesUidHavePackage(int uid, String packageName) { - if (packageName == null) { - return false; - } String[] packageNames = mPackageManager.getPackagesForUid(uid); if (packageNames == null) { return false; } - for (String name : packageNames) { - if (packageName.equals(name)) { + for (LocationProvider provider : mProviders) { + String packageName = provider.getPackageLocked(); + if (packageName == null) { + continue; + } + if (ArrayUtils.contains(packageNames, packageName)) { return true; } } return false; } - @Override - public void reportLocation(Location location, boolean passive) { - checkCallerIsProvider(); - - if (!location.isComplete()) { - Log.w(TAG, "Dropping incomplete location: " + location); - return; - } - - mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location); - Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location); - m.arg1 = (passive ? 1 : 0); - mLocationHandler.sendMessageAtFrontOfQueue(m); - } - - - private static boolean shouldBroadcastSafe( + @GuardedBy("mLock") + private static boolean shouldBroadcastSafeLocked( Location loc, Location lastLoc, UpdateRecord record, long now) { // Always broadcast the first update if (lastLoc == null) { @@ -3046,26 +2988,36 @@ public class LocationManagerService extends ILocationManager.Stub { return record.mRealRequest.getExpireAt() >= now; } - private void handleLocationChangedLocked(Location location, boolean passive) { + @GuardedBy("mLock") + private void handleLocationChangedLocked(Location location, LocationProvider provider) { + if (!mProviders.contains(provider)) { + return; + } + if (!location.isComplete()) { + Log.w(TAG, "Dropping incomplete location: " + location); + return; + } + + if (!provider.isPassiveLocked()) { + // notify passive provider of the new location + mPassiveProvider.updateLocation(location); + } + if (D) Log.d(TAG, "incoming location: " + location); long now = SystemClock.elapsedRealtime(); - String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider()); - // Skip if the provider is unknown. - LocationProvider p = mProvidersByName.get(provider); - if (p == null) return; - updateLastLocationLocked(location, provider); + updateLastLocationLocked(location, provider.getName()); // mLastLocation should have been updated from the updateLastLocationLocked call above. - Location lastLocation = mLastLocation.get(provider); + Location lastLocation = mLastLocation.get(provider.getName()); if (lastLocation == null) { Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed"); return; } // Update last known coarse interval location if enough time has passed. - Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider); + Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName()); if (lastLocationCoarseInterval == null) { lastLocationCoarseInterval = new Location(location); - mLastLocationCoarseInterval.put(provider, lastLocationCoarseInterval); + mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); } long timeDiffNanos = location.getElapsedRealtimeNanos() - lastLocationCoarseInterval.getElapsedRealtimeNanos(); @@ -3079,7 +3031,7 @@ public class LocationManagerService extends ILocationManager.Stub { lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); // Skip if there are no UpdateRecords for this provider. - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); if (records == null || records.size() == 0) return; // Fetch coarse location @@ -3088,13 +3040,6 @@ public class LocationManagerService extends ILocationManager.Stub { coarseLocation = mLocationFudger.getOrCreate(noGPSLocation); } - // Fetch latest status update time - long newStatusUpdateTime = p.getStatusUpdateTime(); - - // Get latest status - Bundle extras = new Bundle(); - int status = p.getStatus(extras); - ArrayList<Receiver> deadReceivers = null; ArrayList<UpdateRecord> deadUpdateRecords = null; @@ -3104,8 +3049,8 @@ public class LocationManagerService extends ILocationManager.Stub { boolean receiverDead = false; int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid); - if (!isCurrentProfile(receiverUserId) - && !isUidALocationProvider(receiver.mIdentity.mUid)) { + if (!isCurrentProfileLocked(receiverUserId) + && !isLocationProviderLocked(receiver.mIdentity.mUid)) { if (D) { Log.d(TAG, "skipping loc update for background user " + receiverUserId + " (current user: " + mCurrentUserId + ", app: " + @@ -3142,7 +3087,8 @@ public class LocationManagerService extends ILocationManager.Stub { } if (notifyLocation != null) { Location lastLoc = r.mLastFixBroadcast; - if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now)) { + if ((lastLoc == null) + || shouldBroadcastSafeLocked(notifyLocation, lastLoc, r, now)) { if (lastLoc == null) { lastLoc = new Location(notifyLocation); r.mLastFixBroadcast = lastLoc; @@ -3150,7 +3096,8 @@ public class LocationManagerService extends ILocationManager.Stub { lastLoc.set(notifyLocation); } if (!receiver.callLocationChangedLocked(notifyLocation)) { - Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver); + Slog.w(TAG, "RemoteException calling onLocationChanged on " + + receiver); receiverDead = true; } r.mRealRequest.decrementNumUpdates(); @@ -3161,12 +3108,16 @@ public class LocationManagerService extends ILocationManager.Stub { // guarded behind this setting now. should be removed completely post-Q if (Settings.Global.getInt(mContext.getContentResolver(), LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) { + long newStatusUpdateTime = provider.getStatusUpdateTimeLocked(); + Bundle extras = new Bundle(); + int status = provider.getStatusLocked(extras); + long prevStatusUpdateTime = r.mLastStatusBroadcast; if ((newStatusUpdateTime > prevStatusUpdateTime) && (prevStatusUpdateTime != 0 || status != AVAILABLE)) { r.mLastStatusBroadcast = newStatusUpdateTime; - if (!receiver.callStatusChangedLocked(provider, status, extras)) { + if (!receiver.callStatusChangedLocked(provider.getName(), status, extras)) { receiverDead = true; Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver); } @@ -3205,12 +3156,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Updates last location with the given location - * - * @param location new location to update - * @param provider Location provider to update for - */ + @GuardedBy("mLock") private void updateLastLocationLocked(Location location, String provider) { Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); Location lastNoGPSLocation; @@ -3229,75 +3175,6 @@ public class LocationManagerService extends ILocationManager.Stub { lastLocation.set(location); } - private class LocationWorkerHandler extends Handler { - public LocationWorkerHandler(Looper looper) { - super(looper, null, true); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_LOCATION_CHANGED: - handleLocationChanged((Location) msg.obj, msg.arg1 == 1); - break; - } - } - } - - private boolean isMockProvider(String provider) { - synchronized (mLock) { - return mMockProviders.containsKey(provider); - } - } - - private void handleLocationChanged(Location location, boolean passive) { - // create a working copy of the incoming Location so that the service can modify it without - // disturbing the caller's copy - Location myLocation = new Location(location); - String provider = myLocation.getProvider(); - - // set "isFromMockProvider" bit if location came from a mock provider. we do not clear this - // bit if location did not come from a mock provider because passive/fused providers can - // forward locations from mock providers, and should not grant them legitimacy in doing so. - if (!myLocation.isFromMockProvider() && isMockProvider(provider)) { - myLocation.setIsFromMockProvider(true); - } - - synchronized (mLock) { - if (!passive) { - // notify passive provider of the new location - mPassiveProvider.updateLocation(myLocation); - } - handleLocationChangedLocked(myLocation, passive); - } - } - - private final PackageMonitor mPackageMonitor = new PackageMonitor() { - @Override - public void onPackageDisappeared(String packageName, int reason) { - // remove all receivers associated with this package name - synchronized (mLock) { - ArrayList<Receiver> deadReceivers = null; - - for (Receiver receiver : mReceivers.values()) { - if (receiver.mIdentity.mPackageName.equals(packageName)) { - if (deadReceivers == null) { - deadReceivers = new ArrayList<>(); - } - deadReceivers.add(receiver); - } - } - - // perform removal outside of mReceivers loop - if (deadReceivers != null) { - for (Receiver receiver : deadReceivers) { - removeUpdatesLocked(receiver); - } - } - } - } - }; - // Geocoder @Override @@ -3343,63 +3220,60 @@ public class LocationManagerService extends ILocationManager.Stub { return; } - if (LocationManager.PASSIVE_PROVIDER.equals(name)) { + if (PASSIVE_PROVIDER.equals(name)) { throw new IllegalArgumentException("Cannot mock the passive location provider"); } - long identity = Binder.clearCallingIdentity(); synchronized (mLock) { - // remove the real provider if we are replacing GPS or network provider - if (LocationManager.GPS_PROVIDER.equals(name) - || LocationManager.NETWORK_PROVIDER.equals(name) - || LocationManager.FUSED_PROVIDER.equals(name)) { - LocationProvider p = mProvidersByName.get(name); - if (p != null) { - removeProviderLocked(p); + long identity = Binder.clearCallingIdentity(); + try { + LocationProvider oldProvider = getLocationProviderLocked(name); + if (oldProvider != null) { + if (oldProvider.isMock()) { + throw new IllegalArgumentException( + "Provider \"" + name + "\" already exists"); + } + + removeProviderLocked(oldProvider); } - } - addTestProviderLocked(name, properties); - } - Binder.restoreCallingIdentity(identity); - } - private void addTestProviderLocked(String name, ProviderProperties properties) { - if (mProvidersByName.get(name) != null) { - throw new IllegalArgumentException("Provider \"" + name + "\" already exists"); + MockLocationProvider mockProviderManager = new MockLocationProvider(name); + addProviderLocked(mockProviderManager); + mockProviderManager.attachLocked(new MockProvider(mockProviderManager, properties)); + } finally { + Binder.restoreCallingIdentity(identity); + } } - - LocationProvider provider = new LocationProvider(name); - MockProvider mockProvider = new MockProvider(provider, properties); - - addProviderLocked(provider); - mMockProviders.put(name, mockProvider); - mLastLocation.put(name, null); - mLastLocationCoarseInterval.put(name, null); } @Override - public void removeTestProvider(String provider, String opPackageName) { + public void removeTestProvider(String name, String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { - MockProvider mockProvider = mMockProviders.remove(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); - } - long identity = Binder.clearCallingIdentity(); try { - removeProviderLocked(mProvidersByName.get(provider)); + LocationProvider testProvider = getLocationProviderLocked(name); + if (testProvider == null || !testProvider.isMock()) { + throw new IllegalArgumentException("Provider \"" + name + "\" unknown"); + } + + removeProviderLocked(testProvider); // reinstate real provider if available - LocationProvider realProvider = mRealProviders.get(provider); + LocationProvider realProvider = null; + for (LocationProvider provider : mRealProviders) { + if (name.equals(provider.getName())) { + realProvider = provider; + break; + } + } + if (realProvider != null) { addProviderLocked(realProvider); } - mLastLocation.put(provider, null); - mLastLocationCoarseInterval.put(provider, null); } finally { Binder.restoreCallingIdentity(identity); } @@ -3407,78 +3281,60 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public void setTestProviderLocation(String provider, Location loc, String opPackageName) { + public void setTestProviderLocation(String providerName, Location location, + String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { - MockProvider mockProvider = mMockProviders.get(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); + LocationProvider testProvider = getLocationProviderLocked(providerName); + if (testProvider == null || !testProvider.isMock()) { + throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); } - // Ensure that the location is marked as being mock. There's some logic to do this in - // handleLocationChanged(), but it fails if loc has the wrong provider (bug 33091107). - Location mock = new Location(loc); - mock.setIsFromMockProvider(true); - - if (!TextUtils.isEmpty(loc.getProvider()) && !provider.equals(loc.getProvider())) { - // The location has an explicit provider that is different from the mock provider - // name. The caller may be trying to fool us via bug 33091107. + String locationProvider = location.getProvider(); + if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) { + // The location has an explicit provider that is different from the mock + // provider name. The caller may be trying to fool us via b/33091107. EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), - provider + "!=" + loc.getProvider()); + providerName + "!=" + location.getProvider()); } - // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required - long identity = Binder.clearCallingIdentity(); - try { - mockProvider.setLocation(mock); - } finally { - Binder.restoreCallingIdentity(identity); - } + ((MockLocationProvider) testProvider).setLocationLocked(location); } } @Override - public void setTestProviderEnabled(String provider, boolean enabled, String opPackageName) { + public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { - MockProvider mockProvider = mMockProviders.get(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); - } - long identity = Binder.clearCallingIdentity(); - try { - mockProvider.setEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); + LocationProvider testProvider = getLocationProviderLocked(providerName); + if (testProvider == null || !testProvider.isMock()) { + throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); } + + ((MockLocationProvider) testProvider).setEnabledLocked(enabled); } } @Override - public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime, - String opPackageName) { + public void setTestProviderStatus(String providerName, int status, Bundle extras, + long updateTime, String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { - MockProvider mockProvider = mMockProviders.get(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); + LocationProvider testProvider = getLocationProviderLocked(providerName); + if (testProvider == null || !testProvider.isMock()) { + throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); } - mockProvider.setStatus(status, extras, updateTime); - } - } - private void log(String log) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.d(TAG, log); + ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime); } } @@ -3494,6 +3350,7 @@ public class LocationManagerService extends ILocationManager.Stub { return; } pw.println("Current Location Manager state:"); + pw.println(" Location Mode: " + isLocationEnabled()); pw.println(" Location Listeners:"); for (Receiver receiver : mReceivers.values()) { pw.println(" " + receiver); @@ -3548,12 +3405,6 @@ public class LocationManagerService extends ILocationManager.Stub { pw.append(" "); mBlacklist.dump(pw); - if (mMockProviders.size() > 0) { - pw.println(" Mock Providers:"); - for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) { - i.getValue().dump(fd, pw, args); - } - } if (mLocationControllerExtraPackage != null) { pw.println(" Location controller extra package: " + mLocationControllerExtraPackage @@ -3574,7 +3425,7 @@ public class LocationManagerService extends ILocationManager.Stub { return; } for (LocationProvider provider : mProviders) { - provider.dump(fd, pw, args); + provider.dumpLocked(fd, pw, args); } if (mGnssBatchingInProgress) { pw.println(" GNSS batching in progress"); diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index f27d37315ada..8adc416fda95 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -16,6 +16,7 @@ package com.android.server; +import android.annotation.Nullable; import android.content.Context; import android.os.Environment; import android.os.Handler; @@ -29,6 +30,7 @@ import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; @@ -46,8 +48,10 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Set; /** * Monitors the health of packages on the system and notifies interested observers when packages @@ -58,7 +62,7 @@ public class PackageWatchdog { // Duration to count package failures before it resets to 0 private static final int TRIGGER_DURATION_MS = 60000; // Number of package failures within the duration above before we notify observers - private static final int TRIGGER_FAILURE_COUNT = 5; + static final int TRIGGER_FAILURE_COUNT = 5; private static final int DB_VERSION = 1; private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog"; private static final String TAG_PACKAGE = "package"; @@ -75,20 +79,13 @@ public class PackageWatchdog { // Handler to run package cleanup runnables private final Handler mTimerHandler; private final Handler mIoHandler; - // Contains (observer-name -> external-observer-handle) that have been registered during the - // current boot. - // It is populated when observers call #registerHealthObserver and it does not survive reboots. - @GuardedBy("mLock") - final ArrayMap<String, PackageHealthObserver> mRegisteredObservers = new ArrayMap<>(); - // Contains (observer-name -> internal-observer-handle) that have ever been registered from + // Contains (observer-name -> observer-handle) that have ever been registered from // previous boots. Observers with all packages expired are periodically pruned. // It is saved to disk on system shutdown and repouplated on startup so it survives reboots. @GuardedBy("mLock") - final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>(); + private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>(); // File containing the XML data of monitored packages /data/system/package-watchdog.xml - private final AtomicFile mPolicyFile = - new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"), - "package-watchdog.xml")); + private final AtomicFile mPolicyFile; // Runnable to prune monitored packages that have expired private final Runnable mPackageCleanup; // Last SystemClock#uptimeMillis a package clean up was executed. @@ -98,14 +95,32 @@ public class PackageWatchdog { // 0 if mPackageCleanup not running. private long mDurationAtLastReschedule; + // TODO(zezeozue): Remove redundant context param private PackageWatchdog(Context context) { mContext = context; + mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"), + "package-watchdog.xml")); mTimerHandler = new Handler(Looper.myLooper()); mIoHandler = BackgroundThread.getHandler(); mPackageCleanup = this::rescheduleCleanup; loadFromFile(); } + /** + * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers + * and creates package-watchdog.xml in an apps data directory. + */ + @VisibleForTesting + PackageWatchdog(Context context, Looper looper) { + mContext = context; + mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); + mTimerHandler = new Handler(looper); + mIoHandler = mTimerHandler; + mPackageCleanup = this::rescheduleCleanup; + loadFromFile(); + } + + /** Creates or gets singleton instance of PackageWatchdog. */ public static PackageWatchdog getInstance(Context context) { synchronized (PackageWatchdog.class) { @@ -124,7 +139,10 @@ public class PackageWatchdog { */ public void registerHealthObserver(PackageHealthObserver observer) { synchronized (mLock) { - mRegisteredObservers.put(observer.getName(), observer); + ObserverInternal internalObserver = mAllObservers.get(observer.getName()); + if (internalObserver != null) { + internalObserver.mRegisteredObserver = observer; + } if (mDurationAtLastReschedule == 0) { // Nothing running, schedule rescheduleCleanup(); @@ -143,7 +161,7 @@ public class PackageWatchdog { * or {@code durationMs} is less than 1 */ public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames, - int durationMs) { + long durationMs) { if (packageNames.isEmpty() || durationMs < 1) { throw new IllegalArgumentException("Observation not started, no packages specified" + "or invalid duration"); @@ -180,11 +198,32 @@ public class PackageWatchdog { public void unregisterHealthObserver(PackageHealthObserver observer) { synchronized (mLock) { mAllObservers.remove(observer.getName()); - mRegisteredObservers.remove(observer.getName()); } saveToFileAsync(); } + /** + * Returns packages observed by {@code observer} + * + * @return an empty set if {@code observer} has some packages observerd from a previous boot + * but has not registered itself in the current boot to receive notifications. Returns null + * if there are no active packages monitored from any boot. + */ + @Nullable + public Set<String> getPackages(PackageHealthObserver observer) { + synchronized (mLock) { + for (int i = 0; i < mAllObservers.size(); i++) { + if (observer.getName().equals(mAllObservers.keyAt(i))) { + if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) { + return mAllObservers.valueAt(i).mPackages.keySet(); + } + return Collections.emptySet(); + } + } + } + return null; + } + // TODO(zezeozue:) Accept current versionCodes of failing packages? /** * Called when a process fails either due to a crash or ANR. @@ -198,33 +237,35 @@ public class PackageWatchdog { public void onPackageFailure(String[] packages) { ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>(); synchronized (mLock) { - if (mRegisteredObservers.isEmpty()) { + if (mAllObservers.isEmpty()) { return; } for (int pIndex = 0; pIndex < packages.length; pIndex++) { + // Observers interested in receiving packageName failures + List<PackageHealthObserver> observersToNotify = new ArrayList<>(); for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) { - // Observers interested in receiving packageName failures - List<PackageHealthObserver> observersToNotify = new ArrayList<>(); - PackageHealthObserver activeObserver = - mRegisteredObservers.get(mAllObservers.valueAt(oIndex).mName); - if (activeObserver != null) { - observersToNotify.add(activeObserver); - } - - // Save interested observers and notify them outside the lock - if (!observersToNotify.isEmpty()) { - packagesToReport.put(packages[pIndex], observersToNotify); + PackageHealthObserver registeredObserver = + mAllObservers.valueAt(oIndex).mRegisteredObserver; + if (registeredObserver != null) { + observersToNotify.add(registeredObserver); } } + // Save interested observers and notify them outside the lock + if (!observersToNotify.isEmpty()) { + packagesToReport.put(packages[pIndex], observersToNotify); + } } } // Notify observers for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) { List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex); + String packageName = packages[pIndex]; for (int oIndex = 0; oIndex < observers.size(); oIndex++) { - if (observers.get(oIndex).onHealthCheckFailed(packages[pIndex])) { + PackageHealthObserver observer = observers.get(oIndex); + if (mAllObservers.get(observer.getName()).onPackageFailure(packageName) + && observer.onHealthCheckFailed(packageName)) { // Observer has handled, do not notify others break; } @@ -275,10 +316,12 @@ public class PackageWatchdog { // O if mPackageCleanup not running long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0 ? 0 : uptimeMs - mUptimeAtLastRescheduleMs; - // O if mPackageCleanup not running + // Less than O if mPackageCleanup unexpectedly didn't run yet even though + // and we are past the last duration scheduled to run long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs; - - if (mUptimeAtLastRescheduleMs == 0 || nextDurationToScheduleMs < remainingDurationMs) { + if (mUptimeAtLastRescheduleMs == 0 + || remainingDurationMs <= 0 + || nextDurationToScheduleMs < remainingDurationMs) { // First schedule or an earlier reschedule pruneObservers(elapsedDurationMs); mTimerHandler.removeCallbacks(mPackageCleanup); @@ -305,6 +348,7 @@ public class PackageWatchdog { } } Slog.v(TAG, "Earliest package time is " + shortestDurationMs); + return shortestDurationMs; } @@ -409,6 +453,8 @@ public class PackageWatchdog { static class ObserverInternal { public final String mName; public final ArrayMap<String, MonitoredPackage> mPackages; + @Nullable + public PackageHealthObserver mRegisteredObserver; ObserverInternal(String name, List<MonitoredPackage> packages) { mName = name; diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING new file mode 100644 index 000000000000..93e1dd36797a --- /dev/null +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + { + "include-annotation": "android.platform.test.annotations.Presubmit" + }, + { + "exclude-annotation": "android.support.test.filters.FlakyTest" + } + ] + } + ] +} diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 5d4c5c359a2b..ea6d435c7ba5 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -141,6 +141,7 @@ public class VibratorService extends IVibratorService.Stub private boolean mLowPowerMode; private int mHapticFeedbackIntensity; private int mNotificationIntensity; + private int mRingIntensity; native static boolean vibratorExists(); native static void vibratorInit(); @@ -428,6 +429,10 @@ public class VibratorService extends IVibratorService.Stub Settings.System.getUriFor(Settings.System.NOTIFICATION_VIBRATION_INTENSITY), true, mSettingObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY), + true, mSettingObserver, UserHandle.USER_ALL); + mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -534,12 +539,6 @@ public class VibratorService extends IVibratorService.Stub return; } verifyIncomingUid(uid); - if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) - > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { - Slog.e(TAG, "Ignoring incoming vibration as process with uid = " - + uid + " is background"); - return; - } if (!verifyVibrationEffect(effect)) { return; } @@ -577,6 +576,13 @@ public class VibratorService extends IVibratorService.Stub } Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg, reason); + if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) + > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + && vib.isHapticFeedback()) { + Slog.e(TAG, "Ignoring incoming vibration as process with uid = " + + uid + " is background"); + return; + } linkVibration(vib); long ident = Binder.clearCallingIdentity(); try { @@ -747,7 +753,9 @@ public class VibratorService extends IVibratorService.Stub } private int getCurrentIntensityLocked(Vibration vib) { - if (vib.isNotification() || vib.isRingtone()){ + if (vib.isRingtone()) { + return mRingIntensity; + } else if (vib.isNotification()) { return mNotificationIntensity; } else if (vib.isHapticFeedback()) { return mHapticFeedbackIntensity; @@ -769,7 +777,9 @@ public class VibratorService extends IVibratorService.Stub } final int defaultIntensity; - if (vib.isNotification() || vib.isRingtone()) { + if (vib.isRingtone()) { + defaultIntensity = mVibrator.getDefaultRingVibrationIntensity(); + } else if (vib.isNotification()) { defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity(); } else if (vib.isHapticFeedback()) { defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity(); @@ -932,6 +942,9 @@ public class VibratorService extends IVibratorService.Stub mNotificationIntensity = Settings.System.getIntForUser(mContext.getContentResolver(), Settings.System.NOTIFICATION_VIBRATION_INTENSITY, mVibrator.getDefaultNotificationVibrationIntensity(), UserHandle.USER_CURRENT); + mRingIntensity = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.RING_VIBRATION_INTENSITY, + mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT); } @Override @@ -1280,6 +1293,7 @@ public class VibratorService extends IVibratorService.Stub pw.println(" mLowPowerMode=" + mLowPowerMode); pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity); pw.println(" mNotificationIntensity=" + mNotificationIntensity); + pw.println(" mRingIntensity=" + mRingIntensity); pw.println(""); pw.println(" Previous vibrations:"); for (VibrationInfo info : mPreviousVibrations) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 83812adf173c..bab9a6535376 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1686,6 +1686,10 @@ public final class ActiveServices { } } + if ((flags & Context.BIND_RESTRICT_ASSOCIATIONS) != 0) { + mAm.requireAllowedAssociationsLocked(s.appInfo.packageName); + } + mAm.startAssociationLocked(callerApp.uid, callerApp.processName, callerApp.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode, s.instanceName, s.processName); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6b9d144e0498..26141f7d7066 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -50,6 +50,7 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.IServiceManager.DUMP_FLAG_PROTO; import static android.os.Process.BLUETOOTH_UID; import static android.os.Process.FIRST_APPLICATION_UID; +import static android.os.Process.NETWORK_STACK_UID; import static android.os.Process.NFC_UID; import static android.os.Process.PHONE_UID; import static android.os.Process.PROC_CHAR; @@ -328,7 +329,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.TriFunction; import com.android.server.AlarmManagerInternal; -import com.android.server.AppOpsService; +import com.android.server.appop.AppOpsService; import com.android.server.AttributeCache; import com.android.server.DeviceIdleController; import com.android.server.DisplayThread; @@ -2430,6 +2431,20 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * Ensures that the given package name has an explicit set of allowed associations. + * If it does not, give it an empty set. + */ + void requireAllowedAssociationsLocked(String packageName) { + if (mAllowedAssociations == null) { + mAllowedAssociations = new ArrayMap<>( + SystemConfig.getInstance().getAllowedAssociations()); + } + if (mAllowedAssociations.get(packageName) == null) { + mAllowedAssociations.put(packageName, new ArraySet<>()); + } + } + + /** * Returns true if the package {@code pkg1} running under user handle {@code uid1} is * allowed association with the package {@code pkg2} running under user handle {@code uid2}. * <p> If either of the packages are running as part of the core system, then the @@ -2437,7 +2452,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ boolean validateAssociationAllowedLocked(String pkg1, int uid1, String pkg2, int uid2) { if (mAllowedAssociations == null) { - mAllowedAssociations = SystemConfig.getInstance().getAllowedAssociations(); + mAllowedAssociations = new ArrayMap<>( + SystemConfig.getInstance().getAllowedAssociations()); } // Interactions with the system uid are always allowed, since that is the core system // that everyone needs to be able to interact with. Also allow reflexive associations @@ -14343,6 +14359,7 @@ public class ActivityManagerService extends IActivityManager.Stub case BLUETOOTH_UID: case NFC_UID: case SE_UID: + case NETWORK_STACK_UID: isCallerSystem = true; break; default: diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 3cdf09e967fe..7ede6dcd41bd 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.appop; import static android.app.AppOpsManager.OP_PLAY_AUDIO; +import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.UID_STATE_BACKGROUND; import static android.app.AppOpsManager.UID_STATE_CACHED; import static android.app.AppOpsManager.UID_STATE_FOREGROUND; @@ -35,8 +36,7 @@ import android.app.ActivityManager; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; -import android.app.AppOpsManager.HistoricalOpEntry; -import android.app.AppOpsManager.HistoricalPackageOps; +import android.app.AppOpsManager.HistoricalOps; import android.app.AppOpsManagerInternal; import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.content.BroadcastReceiver; @@ -48,7 +48,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.media.AudioAttributes; @@ -59,6 +58,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -95,6 +95,8 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.LocalServices; +import com.android.server.LockGuard; import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; @@ -213,6 +215,8 @@ public class AppOpsService extends IAppOpsService.Stub { @VisibleForTesting final SparseArray<UidState> mUidStates = new SparseArray<>(); + private final HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); + long mLastRealtime; /* @@ -654,6 +658,7 @@ public class AppOpsService extends IAppOpsService.Stub { public void systemReady() { mConstants.startMonitoring(mContext.getContentResolver()); + mHistoricalRegistry.systemReady(mContext.getContentResolver()); synchronized (this) { boolean changed = false; @@ -1012,113 +1017,99 @@ public class AppOpsService extends IAppOpsService.Stub { } @Override - public @Nullable ParceledListSlice getAllHistoricalPackagesOps(@Nullable String[] opNames, - long beginTimeMillis, long endTimeMillis) { + public void getHistoricalOps(int uid, @NonNull String packageName, + @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @NonNull RemoteCallback callback) { + Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0, + "uid must be " + Process.INVALID_UID + " or non negative"); Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis, "beginTimeMillis must be non negative and lesser than endTimeMillis"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + checkValidOpsOrNull(opNames); mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid(), "getAllHistoricalPackagesOps"); + Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); - ArrayList<HistoricalPackageOps> historicalPackageOpsList = null; + if (mHistoricalRegistry.getMode() == AppOpsManager.HISTORICAL_MODE_DISABLED) { + // TODO (bug:122218838): Remove once the feature fully enabled. + getHistoricalPackagesOpsCompat(uid, packageName, opNames, beginTimeMillis, + endTimeMillis, callback); + } else { + // Must not hold the appops lock + mHistoricalRegistry.getHistoricalOps(uid, packageName, opNames, + beginTimeMillis, endTimeMillis, callback); + } + } - final int uidStateCount = mUidStates.size(); - for (int i = 0; i < uidStateCount; i++) { - final UidState uidState = mUidStates.valueAt(i); - if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) { - continue; + private void getHistoricalPackagesOpsCompat(int uid, @NonNull String packageName, + @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @NonNull RemoteCallback callback) { + synchronized (AppOpsService.this) { + final HistoricalOps ops = new HistoricalOps(beginTimeMillis, endTimeMillis); + if (opNames == null) { + opNames = AppOpsManager.getOpStrs(); } - final ArrayMap<String, Ops> packages = uidState.pkgOps; - final int packageCount = packages.size(); - for (int j = 0; j < packageCount; j++) { - final Ops pkgOps = packages.valueAt(j); - final AppOpsManager.HistoricalPackageOps historicalPackageOps = - createHistoricalPackageOps(uidState.uid, pkgOps, opNames, - beginTimeMillis, endTimeMillis); - if (historicalPackageOps != null) { - if (historicalPackageOpsList == null) { - historicalPackageOpsList = new ArrayList<>(); + final int uidStateCount = mUidStates.size(); + for (int uidIdx = 0; uidIdx < uidStateCount; uidIdx++) { + final UidState uidState = mUidStates.valueAt(uidIdx); + if (uidState.pkgOps == null || uidState.pkgOps.isEmpty() + || (uid != Process.INVALID_UID && uid != uidState.uid)) { + continue; + } + final ArrayMap<String, Ops> packages = uidState.pkgOps; + final int packageCount = packages.size(); + for (int pkgIdx = 0; pkgIdx < packageCount; pkgIdx++) { + final Ops pkgOps = packages.valueAt(pkgIdx); + if (packageName != null && !packageName.equals(pkgOps.packageName)) { + continue; + } + final int opCount = opNames.length; + for (int opIdx = 0; opIdx < opCount; opIdx++) { + final String opName = opNames[opIdx]; + if (!ArrayUtils.contains(opNames, opName)) { + continue; + } + final int opCode = AppOpsManager.strOpToOp(opName); + final Op op = pkgOps.get(opCode); + if (op == null) { + continue; + } + final int stateCount = AppOpsManager._NUM_UID_STATE; + for (int stateIdx = 0; stateIdx < stateCount; stateIdx++) { + if (op.rejectTime[stateIdx] != 0) { + ops.increaseRejectCount(opCode, uidState.uid, + pkgOps.packageName, stateIdx, 1); + } else if (op.time[stateIdx] != 0) { + ops.increaseAccessCount(opCode, uidState.uid, + pkgOps.packageName, stateIdx, 1); + } + } } - historicalPackageOpsList.add(historicalPackageOps); } } + final Bundle payload = new Bundle(); + payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, ops); + callback.sendResult(payload); } - - if (historicalPackageOpsList == null) { - return null; - } - - return new ParceledListSlice<>(historicalPackageOpsList); - } - - private static @Nullable HistoricalPackageOps createHistoricalPackageOps(int uid, - @Nullable Ops pkgOps, @Nullable String[] opNames, long beginTimeMillis, - long endTimeMillis) { - // TODO: Implement historical data collection - if (pkgOps == null) { - return null; - } - - final HistoricalPackageOps historicalPackageOps = new HistoricalPackageOps(uid, - pkgOps.packageName); - - if (opNames == null) { - opNames = AppOpsManager.getOpStrs(); - } - for (String opName : opNames) { - addHistoricOpEntry(AppOpsManager.strOpToOp(opName), pkgOps, historicalPackageOps); - } - - return historicalPackageOps; } @Override - public @Nullable HistoricalPackageOps getHistoricalPackagesOps(int uid, - @NonNull String packageName, @Nullable String[] opNames, - long beginTimeMillis, long endTimeMillis) { - Preconditions.checkNotNull(packageName, - "packageName cannot be null"); + public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName, + @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @NonNull RemoteCallback callback) { + Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0, + "uid must be " + Process.INVALID_UID + " or non negative"); Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis, "beginTimeMillis must be non negative and lesser than endTimeMillis"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + checkValidOpsOrNull(opNames); mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalPackagesOps"); - - final String resolvedPackageName = resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return null; - } - - // TODO: Implement historical data collection - final Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* edit */, - false /* uidMismatchExpected */); - return createHistoricalPackageOps(uid, pkgOps, opNames, beginTimeMillis, endTimeMillis); - } - - private static void addHistoricOpEntry(int opCode, @NonNull Ops ops, - @NonNull HistoricalPackageOps outHistoricalPackageOps) { - final Op op = ops.get(opCode); - if (op == null) { - return; - } - - final HistoricalOpEntry historicalOpEntry = new HistoricalOpEntry(opCode); + Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); - // TODO: Keep per UID state duration - for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) { - final int acceptCount; - final int rejectCount; - if (op.rejectTime[uidState] == 0) { - acceptCount = 1; - rejectCount = 0; - } else { - acceptCount = 0; - rejectCount = 1; - } - historicalOpEntry.addEntry(uidState, acceptCount, rejectCount, 0); - } - - outHistoricalPackageOps.addEntry(historicalOpEntry); + // Must not hold the appops lock + mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNames, + beginTimeMillis, endTimeMillis, callback); } @Override @@ -1884,6 +1875,8 @@ public class AppOpsService extends IAppOpsService.Stub { + packageName); op.rejectTime[uidState.state] = System.currentTimeMillis(); scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode); + mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName, + uidState.state); return uidMode; } } else { @@ -1895,12 +1888,16 @@ public class AppOpsService extends IAppOpsService.Stub { + packageName); op.rejectTime[uidState.state] = System.currentTimeMillis(); scheduleOpNotedIfNeededLocked(op.op, uid, packageName, mode); + mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName, + uidState.state); return mode; } } if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid + " package " + packageName); op.time[uidState.state] = System.currentTimeMillis(); + mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName, + uidState.state); op.rejectTime[uidState.state] = 0; op.proxyUid = proxyUid; op.proxyPackageName = proxyPackageName; @@ -2035,6 +2032,8 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + resolvedPackageName); op.rejectTime[uidState.state] = System.currentTimeMillis(); + mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName, + uidState.state); return uidMode; } } else { @@ -2046,6 +2045,8 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + resolvedPackageName); op.rejectTime[uidState.state] = System.currentTimeMillis(); + mHistoricalRegistry.incrementOpRejected(op.op, uid, packageName, + uidState.state); return mode; } } @@ -2054,6 +2055,8 @@ public class AppOpsService extends IAppOpsService.Stub { if (op.startNesting == 0) { op.startRealtime = SystemClock.elapsedRealtime(); op.time[uidState.state] = System.currentTimeMillis(); + mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName, + uidState.state); op.rejectTime[uidState.state] = 0; op.duration = -1; scheduleOpActiveChangedIfNeededLocked(code, uid, packageName, true); @@ -2216,6 +2219,8 @@ public class AppOpsService extends IAppOpsService.Stub { if (op.startNesting <= 1 || finishNested) { if (op.startNesting == 1 || finishNested) { op.duration = (int)(SystemClock.elapsedRealtime() - op.startRealtime); + mHistoricalRegistry.increaseOpAccessDuration(op.op, op.uid, op.packageName, + op.uidState.state, op.duration); op.time[op.uidState.state] = System.currentTimeMillis(); } else { Slog.w(TAG, "Finishing op nesting under-run: uid " + op.uid + " pkg " @@ -2344,7 +2349,7 @@ public class AppOpsService extends IAppOpsService.Stub { ApplicationInfo appInfo = ActivityThread.getPackageManager() .getApplicationInfo(packageName, PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, UserHandle.getUserId(uid)); if (appInfo != null) { pkgUid = appInfo.uid; @@ -3391,6 +3396,8 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println(" Limit output to data associated with the given app op mode."); pw.println(" --package [PACKAGE]"); pw.println(" Limit output to data associated with the given package name."); + pw.println(" --watchers"); + pw.println(" Only output the watcher sections."); } private void dumpTimesLocked(PrintWriter pw, String firstPrefix, String prefix, long[] times, @@ -3425,10 +3432,11 @@ public class AppOpsService extends IAppOpsService.Stub { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; - int dumpOp = -1; + int dumpOp = OP_NONE; String dumpPackage = null; - int dumpUid = -1; + int dumpUid = Process.INVALID_UID; int dumpMode = -1; + boolean dumpWatchers = false; if (args != null) { for (int i=0; i<args.length; i++) { @@ -3476,6 +3484,8 @@ public class AppOpsService extends IAppOpsService.Stub { if (dumpMode < 0) { return; } + } else if ("--watchers".equals(arg)) { + dumpWatchers = true; } else if (arg.length() > 0 && arg.charAt(0) == '-'){ pw.println("Unknown option: " + arg); return; @@ -3496,7 +3506,8 @@ public class AppOpsService extends IAppOpsService.Stub { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); final Date date = new Date(); boolean needSep = false; - if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null) { + if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null + && !dumpWatchers) { pw.println(" Profile owners:"); for (int poi = 0; poi < mProfileOwners.size(); poi++) { pw.print(" User #"); @@ -3517,7 +3528,7 @@ public class AppOpsService extends IAppOpsService.Stub { ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i); for (int j=0; j<callbacks.size(); j++) { final ModeCallback cb = callbacks.valueAt(j); - if (dumpPackage != null && cb.mWatchingUid >= 0 + if (dumpPackage != null && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { continue; } @@ -3561,7 +3572,7 @@ public class AppOpsService extends IAppOpsService.Stub { boolean printedHeader = false; for (int i=0; i<mModeWatchers.size(); i++) { final ModeCallback cb = mModeWatchers.valueAt(i); - if (dumpPackage != null && cb.mWatchingUid >= 0 + if (dumpPackage != null && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { continue; } @@ -3587,7 +3598,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) { continue; } - if (dumpPackage != null && cb.mWatchingUid >= 0 + if (dumpPackage != null && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { continue; } @@ -3627,7 +3638,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) { continue; } - if (dumpPackage != null && cb.mWatchingUid >= 0 + if (dumpPackage != null && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { continue; } @@ -3655,7 +3666,7 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println(cb); } } - if (mClients.size() > 0 && dumpMode < 0) { + if (mClients.size() > 0 && dumpMode < 0 && !dumpWatchers) { needSep = true; boolean printedHeader = false; for (int i=0; i<mClients.size(); i++) { @@ -3692,7 +3703,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } if (mAudioRestrictions.size() > 0 && dumpOp < 0 && dumpPackage != null - && dumpMode < 0) { + && dumpMode < 0 && !dumpWatchers) { boolean printedHeader = false; for (int o=0; o<mAudioRestrictions.size(); o++) { final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o)); @@ -3725,6 +3736,9 @@ public class AppOpsService extends IAppOpsService.Stub { final SparseIntArray opModes = uidState.opModes; final ArrayMap<String, Ops> pkgOps = uidState.pkgOps; + if (dumpWatchers) { + continue; + } if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) { boolean hasOp = dumpOp < 0 || (uidState.opModes != null && uidState.opModes.indexOfKey(dumpOp) >= 0); @@ -3739,8 +3753,8 @@ public class AppOpsService extends IAppOpsService.Stub { } if (pkgOps != null) { for (int pkgi = 0; - (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size(); - pkgi++) { + (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size(); + pkgi++) { Ops ops = pkgOps.valueAt(pkgi); if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) { hasOp = true; @@ -3880,18 +3894,34 @@ public class AppOpsService extends IAppOpsService.Stub { for (int i = 0; i < userRestrictionCount; i++) { IBinder token = mOpUserRestrictions.keyAt(i); ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i); - pw.println(" User restrictions for token " + token + ":"); + boolean printedTokenHeader = false; + + if (dumpMode >= 0 || dumpWatchers) { + continue; + } final int restrictionCount = restrictionState.perUserRestrictions != null ? restrictionState.perUserRestrictions.size() : 0; - if (restrictionCount > 0) { - pw.println(" Restricted ops:"); + if (restrictionCount > 0 && dumpPackage == null) { + boolean printedOpsHeader = false; for (int j = 0; j < restrictionCount; j++) { int userId = restrictionState.perUserRestrictions.keyAt(j); boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j); if (restrictedOps == null) { continue; } + if (dumpOp >= 0 && (dumpOp >= restrictedOps.length + || !restrictedOps[dumpOp])) { + continue; + } + if (!printedTokenHeader) { + pw.println(" User restrictions for token " + token + ":"); + printedTokenHeader = true; + } + if (!printedOpsHeader) { + pw.println(" Restricted ops:"); + printedOpsHeader = true; + } StringBuilder restrictedOpsValue = new StringBuilder(); restrictedOpsValue.append("["); final int restrictedOpCount = restrictedOps.length; @@ -3911,17 +3941,46 @@ public class AppOpsService extends IAppOpsService.Stub { final int excludedPackageCount = restrictionState.perUserExcludedPackages != null ? restrictionState.perUserExcludedPackages.size() : 0; - if (excludedPackageCount > 0) { - pw.println(" Excluded packages:"); + if (excludedPackageCount > 0 && dumpOp < 0) { + boolean printedPackagesHeader = false; for (int j = 0; j < excludedPackageCount; j++) { int userId = restrictionState.perUserExcludedPackages.keyAt(j); String[] packageNames = restrictionState.perUserExcludedPackages.valueAt(j); + if (packageNames == null) { + continue; + } + boolean hasPackage; + if (dumpPackage != null) { + hasPackage = false; + for (String pkg : packageNames) { + if (dumpPackage.equals(pkg)) { + hasPackage = true; + break; + } + } + } else { + hasPackage = true; + } + if (!hasPackage) { + continue; + } + if (!printedTokenHeader) { + pw.println(" User restrictions for token " + token + ":"); + printedTokenHeader = true; + } + if (!printedPackagesHeader) { + pw.println(" Excluded packages:"); + printedPackagesHeader = true; + } pw.print(" "); pw.print("user: "); pw.print(userId); pw.print(" packages: "); pw.println(Arrays.toString(packageNames)); } } } } + + // Must not hold the appops lock + mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpOp); } private static final class Restriction { @@ -4042,6 +4101,47 @@ public class AppOpsService extends IAppOpsService.Stub { return false; } + @Override + public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode, + long baseSnapshotInterval, int compressionStep) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "setHistoryParameters"); + // Must not hold the appops lock + mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep); + } + + @Override + public void offsetHistory(long offsetMillis) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "offsetHistory"); + // Must not hold the appops lock + mHistoricalRegistry.offsetHistory(offsetMillis); + } + + @Override + public void addHistoricalOps(HistoricalOps ops) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "addHistoricalOps"); + // Must not hold the appops lock + mHistoricalRegistry.addHistoricalOps(ops); + } + + @Override + public void resetHistoryParameters() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "resetHistoryParameters"); + // Must not hold the appops lock + mHistoricalRegistry.resetHistoryParameters(); + } + + @Override + public void clearHistory() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "clearHistory"); + // Must not hold the appops lock + mHistoricalRegistry.clearHistory(); + } + private void removeUidsForUserLocked(int userHandle) { for (int i = mUidStates.size() - 1; i >= 0; --i) { final int uid = mUidStates.keyAt(i); @@ -4112,6 +4212,16 @@ public class AppOpsService extends IAppOpsService.Stub { return packageNames; } + private static void checkValidOpsOrNull(String[] opNames) { + if (opNames != null) { + for (String opName : opNames) { + if (AppOpsManager.strOpToOp(opName) == AppOpsManager.OP_NONE) { + throw new IllegalArgumentException("Unknown op: " + opName); + } + } + } + } + private final class ClientRestrictionState implements DeathRecipient { private final IBinder token; SparseArray<boolean[]> perUserRestrictions; diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java new file mode 100644 index 000000000000..8d7811fc8e78 --- /dev/null +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -0,0 +1,1495 @@ +/* + * 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.appop; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.app.AppOpsManager.HistoricalMode; +import android.app.AppOpsManager.HistoricalOp; +import android.app.AppOpsManager.HistoricalOps; +import android.app.AppOpsManager.HistoricalPackageOps; +import android.app.AppOpsManager.HistoricalUidOps; +import android.app.AppOpsManager.UidState; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Message; +import android.os.Process; +import android.os.RemoteCallback; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.ArraySet; +import android.util.Slog; +import android.util.TimeUtils; +import android.util.Xml; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.AtomicDirectory; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.XmlUtils; +import com.android.internal.util.function.pooled.PooledLambda; +import com.android.server.FgThread; +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.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * This class managers historical app op state. This includes reading, persistence, + * accounting, querying. + * <p> + * The history is kept forever in multiple files. Each file time contains the + * relative offset from the current time which time is encoded in the file name. + * The files contain historical app op state snapshots which have times that + * are relative to the time of the container file. + * + * The data in the files are stored in a logarithmic fashion where where every + * subsequent file would contain data for ten times longer interval with ten + * times more time distance between snapshots. Hence, the more time passes + * the lesser the fidelity. + * <p> + * For example, the first file would contain data for 1 days with snapshots + * every 0.1 days, the next file would contain data for the period 1 to 10 + * days with snapshots every 1 days, and so on. + * <p> + * THREADING AND LOCKING: Reported ops must be processed as quickly as possible. + * We keep ops pending to be persisted in memory and write to disk on a background + * thread. Hence, methods that report op changes are locking only the in memory + * state guarded by the mInMemoryLock which happens to be the app ops service lock + * avoiding a lock addition on the critical path. When a query comes we need to + * evaluate it based off both in memory and on disk state. This means they need to + * be frozen with respect to each other and not change from the querying caller's + * perspective. To achieve this we add a dedicated mOnDiskLock to guard the on + * disk state. To have fast critical path we need to limit the locking of the + * mInMemoryLock, thus for operations that touch in memory and on disk state one + * must grab first the mOnDiskLock and then the mInMemoryLock and limit the + * in memory lock to extraction of relevant data. Locking order is critical to + * avoid deadlocks. The convention is that xxxDLocked suffix means the method + * must be called with the mOnDiskLock lock, xxxMLocked suffix means the method + * must be called with the mInMemoryLock, xxxDMLocked suffix means the method + * must be called with the mOnDiskLock and mInMemoryLock locks acquired in that + * exact order. + */ +// TODO (bug:122218838): Make sure we handle start of epoch time +// TODO (bug:122218838): Validate changed time is handled correctly +final class HistoricalRegistry { + private static final boolean DEBUG = false; + + private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName(); + + private static final String PARAMETER_DELIMITER = ","; + private static final String PARAMETER_ASSIGNMENT = "="; + + @GuardedBy("mLock") + private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>(); + + // Lock for read/write access to on disk state + private final Object mOnDiskLock = new Object(); + + //Lock for read/write access to in memory state + private final @NonNull Object mInMemoryLock; + + private static final int MSG_WRITE_PENDING_HISTORY = 1; + + // See mMode + private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_DISABLED; + + // See mBaseSnapshotInterval + private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15); + + // See mIntervalCompressionMultiplier + private static final long DEFAULT_COMPRESSION_STEP = 10; + + /** + * Whether history is enabled. + */ + @GuardedBy("mInMemoryLock") + private int mMode = AppOpsManager.HISTORICAL_MODE_DISABLED; + + /** + * This granularity has been chosen to allow clean delineation for intervals + * humans understand, 15 min, 60, min, a day, a week, a month (30 days). + */ + @GuardedBy("mInMemoryLock") + private long mBaseSnapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MILLIS; + + /** + * The compression between steps. Each subsequent step is this much longer + * in terms of duration and each snapshot is this much more apart from the + * previous step. + */ + @GuardedBy("mInMemoryLock") + private long mIntervalCompressionMultiplier = DEFAULT_COMPRESSION_STEP; + + // The current ops to which to add statistics. + @GuardedBy("mInMemoryLock") + private @Nullable HistoricalOps mCurrentHistoricalOps; + + // The time we should write the next snapshot. + @GuardedBy("mInMemoryLock") + private long mNextPersistDueTimeMillis; + + // How much to offset the history on the next write. + @GuardedBy("mInMemoryLock") + private long mPendingHistoryOffsetMillis; + + // Object managing persistence (read/write) + @GuardedBy("mOnDiskLock") + private Persistence mPersistence = new Persistence(mBaseSnapshotInterval, + mIntervalCompressionMultiplier); + + HistoricalRegistry(@NonNull Object lock) { + mInMemoryLock = lock; + if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) { + synchronized (mInMemoryLock) { + // When starting always adjust history to now. + mPendingHistoryOffsetMillis = System.currentTimeMillis() + - mPersistence.getLastPersistTimeMillisDLocked(); + } + } + } + + void systemReady(@NonNull ContentResolver resolver) { + updateParametersFromSetting(resolver); + final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS); + resolver.registerContentObserver(uri, false, new ContentObserver( + FgThread.getHandler()) { + @Override + public void onChange(boolean selfChange) { + updateParametersFromSetting(resolver); + } + }); + } + + private void updateParametersFromSetting(@NonNull ContentResolver resolver) { + final String setting = Settings.Global.getString(resolver, + Settings.Global.APPOP_HISTORY_PARAMETERS); + if (setting == null) { + return; + } + String modeValue = null; + String baseSnapshotIntervalValue = null; + String intervalMultiplierValue = null; + final String[] parameters = setting.split(PARAMETER_DELIMITER); + for (String parameter : parameters) { + final String[] parts = parameter.split(PARAMETER_ASSIGNMENT); + if (parts.length == 2) { + final String key = parts[0].trim(); + switch (key) { + case Settings.Global.APPOP_HISTORY_MODE: { + modeValue = parts[1].trim(); + } break; + case Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS: { + baseSnapshotIntervalValue = parts[1].trim(); + } break; + case Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER: { + intervalMultiplierValue = parts[1].trim(); + } break; + default: { + Slog.w(LOG_TAG, "Unknown parameter: " + parameter); + } + } + } + } + if (modeValue != null && baseSnapshotIntervalValue != null + && intervalMultiplierValue != null) { + try { + final int mode = AppOpsManager.parseHistoricalMode(modeValue); + final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue); + final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue); + setHistoryParameters(mode, baseSnapshotInterval,intervalCompressionMultiplier); + return; + } catch (NumberFormatException ignored) {} + } + Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS + + "=" + setting + " resetting!"); + } + + void dump(String prefix, PrintWriter pw, int filterUid, + String filterPackage, int filterOp) { + synchronized (mOnDiskLock) { + synchronized (mInMemoryLock) { + pw.println(); + pw.print(prefix); + pw.print("History:"); + + pw.print(" mode="); + pw.println(AppOpsManager.historicalModeToString(mMode)); + + final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ", + pw, filterUid, filterPackage, filterOp); + final long nowMillis = System.currentTimeMillis(); + + // Dump in memory state first + final HistoricalOps currentOps = getUpdatedPendingHistoricalOpsMLocked( + nowMillis); + makeRelativeToEpochStart(currentOps, nowMillis); + currentOps.accept(visitor); + + final List<HistoricalOps> ops = mPersistence.readHistoryDLocked(); + if (ops != null) { + // TODO (bug:122218838): Make sure this is properly dumped + final long remainingToFillBatchMillis = mNextPersistDueTimeMillis + - nowMillis - mBaseSnapshotInterval; + final int opCount = ops.size(); + for (int i = 0; i < opCount; i++) { + final HistoricalOps op = ops.get(i); + op.offsetBeginAndEndTime(remainingToFillBatchMillis); + makeRelativeToEpochStart(op, nowMillis); + op.accept(visitor); + } + } else { + pw.println(" Empty"); + } + } + } + } + + @HistoricalMode int getMode() { + synchronized (mInMemoryLock) { + return mMode; + } + } + + @Nullable void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName, + @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @NonNull RemoteCallback callback) { + final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis); + mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames, + beginTimeMillis, endTimeMillis); + final Bundle payload = new Bundle(); + payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); + callback.sendResult(payload); + } + + @Nullable void getHistoricalOps(int uid, @NonNull String packageName, + @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @NonNull RemoteCallback callback) { + final long currentTimeMillis = System.currentTimeMillis(); + if (endTimeMillis == Long.MAX_VALUE) { + endTimeMillis = currentTimeMillis; + } + + // Argument times are based off epoch start while our internal store is + // based off now, so take this into account. + final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0); + final long inMemoryAdjEndTimeMillis = Math.max(currentTimeMillis - beginTimeMillis, 0); + final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis, + inMemoryAdjEndTimeMillis); + + synchronized (mOnDiskLock) { + final List<HistoricalOps> pendingWrites; + final HistoricalOps currentOps; + synchronized (mInMemoryLock) { + currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis); + if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis() + || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) { + // Some of the current batch falls into the query, so extract that. + final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps); + currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis, + inMemoryAdjEndTimeMillis); + result.merge(currentOpsCopy); + } + pendingWrites = new ArrayList<>(mPendingWrites); + mPendingWrites.clear(); + } + + // If the query was only for in-memory state - done. + if (inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis()) { + // If there is a write in flight we need to force it now + persistPendingHistory(pendingWrites); + // Collect persisted state. + final long onDiskAndInMemoryOffsetMillis = currentTimeMillis + - mNextPersistDueTimeMillis + mBaseSnapshotInterval; + final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis + - onDiskAndInMemoryOffsetMillis, 0); + final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis + - onDiskAndInMemoryOffsetMillis, 0); + mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames, + onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis); + } + + // Rebase the result time to be since epoch. + result.setBeginAndEndTime(beginTimeMillis, endTimeMillis); + + // Send back the result. + final Bundle payload = new Bundle(); + payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); + callback.sendResult(payload); + } + } + + void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, + @UidState int uidState) { + synchronized (mInMemoryLock) { + if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { + getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis()) + .increaseAccessCount(op, uid, packageName, uidState, 1); + + } + } + } + + void incrementOpRejected(int op, int uid, @NonNull String packageName, + @UidState int uidState) { + synchronized (mInMemoryLock) { + if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { + getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis()) + .increaseRejectCount(op, uid, packageName, uidState, 1); + } + } + } + + void increaseOpAccessDuration(int op, int uid, @NonNull String packageName, + @UidState int uidState, long increment) { + synchronized (mInMemoryLock) { + if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { + getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis()) + .increaseAccessDuration(op, uid, packageName, uidState, increment); + } + } + } + + void setHistoryParameters(@HistoricalMode int mode, + long baseSnapshotInterval, long intervalCompressionMultiplier) { + synchronized (mOnDiskLock) { + synchronized (mInMemoryLock) { + boolean resampleHistory = false; + Slog.i(LOG_TAG, "New history parameters: mode:" + + AppOpsManager.historicalModeToString(mMode) + " baseSnapshotInterval:" + + baseSnapshotInterval + " intervalCompressionMultiplier:" + + intervalCompressionMultiplier); + if (mMode != mode) { + mMode = mode; + if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) { + clearHistoryOnDiskLocked(); + } + } + if (mBaseSnapshotInterval != baseSnapshotInterval) { + mBaseSnapshotInterval = baseSnapshotInterval; + resampleHistory = true; + } + if (mIntervalCompressionMultiplier != intervalCompressionMultiplier) { + mIntervalCompressionMultiplier = intervalCompressionMultiplier; + resampleHistory = true; + } + if (resampleHistory) { + resampleHistoryOnDiskInMemoryDMLocked(0); + } + } + } + } + + void offsetHistory(long offsetMillis) { + synchronized (mOnDiskLock) { + synchronized (mInMemoryLock) { + final List<HistoricalOps> history = mPersistence.readHistoryDLocked(); + clearHistory(); + if (history != null) { + final int historySize = history.size(); + for (int i = 0; i < historySize; i++) { + final HistoricalOps ops = history.get(i); + ops.offsetBeginAndEndTime(offsetMillis); + } + if (offsetMillis < 0) { + pruneFutureOps(history); + } + mPersistence.persistHistoricalOpsDLocked(history); + } + } + } + } + + void addHistoricalOps(HistoricalOps ops) { + final List<HistoricalOps> pendingWrites; + synchronized (mInMemoryLock) { + // The history files start from mBaseSnapshotInterval - take this into account. + ops.offsetBeginAndEndTime(mBaseSnapshotInterval); + mPendingWrites.offerFirst(ops); + pendingWrites = new ArrayList<>(mPendingWrites); + mPendingWrites.clear(); + } + persistPendingHistory(pendingWrites); + } + + private void resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis) { + mPersistence = new Persistence(mBaseSnapshotInterval, mIntervalCompressionMultiplier); + offsetHistory(offsetMillis); + } + + void resetHistoryParameters() { + setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS, + DEFAULT_COMPRESSION_STEP); + } + + void clearHistory() { + synchronized (mOnDiskLock) { + clearHistoryOnDiskLocked(); + } + } + + private void clearHistoryOnDiskLocked() { + BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY); + synchronized (mInMemoryLock) { + mCurrentHistoricalOps = null; + mNextPersistDueTimeMillis = System.currentTimeMillis(); + mPendingWrites.clear(); + } + mPersistence.clearHistoryDLocked(); + } + + private @NonNull HistoricalOps getUpdatedPendingHistoricalOpsMLocked(long now) { + if (mCurrentHistoricalOps != null) { + final long remainingTimeMillis = mNextPersistDueTimeMillis - now; + if (remainingTimeMillis > mBaseSnapshotInterval) { + // If time went backwards we need to push history to the future with the + // overflow over our snapshot interval. If time went forward do nothing + // as we would naturally push history into the past on the next write. + mPendingHistoryOffsetMillis = remainingTimeMillis - mBaseSnapshotInterval; + } + final long elapsedTimeMillis = mBaseSnapshotInterval - remainingTimeMillis; + mCurrentHistoricalOps.setEndTime(elapsedTimeMillis); + if (remainingTimeMillis > 0) { + if (DEBUG) { + Slog.i(LOG_TAG, "Returning current in-memory state"); + } + return mCurrentHistoricalOps; + } + if (mCurrentHistoricalOps.isEmpty()) { + mCurrentHistoricalOps.setBeginAndEndTime(0, 0); + mNextPersistDueTimeMillis = now + mBaseSnapshotInterval; + return mCurrentHistoricalOps; + } + // The current batch is full, so persist taking into account overdue persist time. + mCurrentHistoricalOps.offsetBeginAndEndTime(mBaseSnapshotInterval); + mCurrentHistoricalOps.setBeginTime(mCurrentHistoricalOps.getEndTimeMillis() + - mBaseSnapshotInterval); + final long overdueTimeMillis = Math.abs(remainingTimeMillis); + mCurrentHistoricalOps.offsetBeginAndEndTime(overdueTimeMillis); + schedulePersistHistoricalOpsMLocked(mCurrentHistoricalOps); + } + // The current batch is in the future, i.e. not complete yet. + mCurrentHistoricalOps = new HistoricalOps(0, 0); + mNextPersistDueTimeMillis = now + mBaseSnapshotInterval; + if (DEBUG) { + Slog.i(LOG_TAG, "Returning new in-memory state"); + } + return mCurrentHistoricalOps; + } + + private void persistPendingHistory() { + final List<HistoricalOps> pendingWrites; + synchronized (mOnDiskLock) { + synchronized (mInMemoryLock) { + pendingWrites = new ArrayList<>(mPendingWrites); + mPendingWrites.clear(); + if (mPendingHistoryOffsetMillis != 0) { + resampleHistoryOnDiskInMemoryDMLocked(mPendingHistoryOffsetMillis); + mPendingHistoryOffsetMillis = 0; + } + } + persistPendingHistory(pendingWrites); + } + } + private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) { + synchronized (mOnDiskLock) { + BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY); + if (pendingWrites.isEmpty()) { + return; + } + final int opCount = pendingWrites.size(); + // Pending writes are offset relative to each other, so take this + // into account to persist everything in one shot - single write. + for (int i = 0; i < opCount; i++) { + final HistoricalOps current = pendingWrites.get(i); + if (i > 0) { + final HistoricalOps previous = pendingWrites.get(i - 1); + current.offsetBeginAndEndTime(previous.getBeginTimeMillis()); + } + } + mPersistence.persistHistoricalOpsDLocked(pendingWrites); + } + } + + private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) { + final Message message = PooledLambda.obtainMessage( + HistoricalRegistry::persistPendingHistory, HistoricalRegistry.this); + message.what = MSG_WRITE_PENDING_HISTORY; + BackgroundThread.getHandler().sendMessage(message); + mPendingWrites.offerFirst(ops); + } + + private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) { + ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(), + nowMillis- ops.getBeginTimeMillis()); + } + + private void pruneFutureOps(@NonNull List<HistoricalOps> ops) { + final int opCount = ops.size(); + for (int i = opCount - 1; i >= 0; i--) { + final HistoricalOps op = ops.get(i); + if (op.getEndTimeMillis() <= mBaseSnapshotInterval) { + ops.remove(i); + } else if (op.getBeginTimeMillis() < mBaseSnapshotInterval) { + final double filterScale = (double) (op.getEndTimeMillis() - mBaseSnapshotInterval) + / (double) op.getDurationMillis(); + Persistence.spliceFromBeginning(op, filterScale); + } + } + } + + private static final class Persistence { + private static final boolean DEBUG = false; + + private static final String LOG_TAG = Persistence.class.getSimpleName(); + + private static final String HISTORY_FILE_SUFFIX = ".xml"; + + private static final String TAG_HISTORY = "history"; + private static final String TAG_OPS = "ops"; + private static final String TAG_UID = "uid"; + private static final String TAG_PACKAGE = "package"; + private static final String TAG_OP = "op"; + private static final String TAG_STATE = "state"; + + private static final String ATTR_VERSION = "version"; + private static final String ATTR_NAME = "name"; + private static final String ATTR_ACCESS_COUNT = "accessCount"; + private static final String ATTR_REJECT_COUNT = "rejectCount"; + private static final String ATTR_ACCESS_DURATION = "accessDuration"; + private static final String ATTR_BEGIN_TIME = "beginTime"; + private static final String ATTR_END_TIME = "endTime"; + private static final String ATTR_OVERFLOW = "overflow"; + + private static final int CURRENT_VERSION = 1; + + private final long mBaseSnapshotInterval; + private final long mIntervalCompressionMultiplier; + + Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier) { + mBaseSnapshotInterval = baseSnapshotInterval; + mIntervalCompressionMultiplier = intervalCompressionMultiplier; + } + + private final AtomicDirectory mHistoricalAppOpsDir = new AtomicDirectory( + new File(new File(Environment.getDataSystemDeDirectory(), "appops"), "history")); + + private File generateFile(@NonNull File baseDir, int depth) { + final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth); + return new File(baseDir, Long.toString(globalBeginMillis) + HISTORY_FILE_SUFFIX); + } + + void clearHistoryDLocked() { + mHistoricalAppOpsDir.delete(); + } + + void persistHistoricalOpsDLocked(@NonNull List<HistoricalOps> ops) { + if (DEBUG) { + Slog.i(LOG_TAG, "Persisting ops:\n" + opsToDebugString(ops)); + enforceOpsWellFormed(ops); + } + try { + final File newBaseDir = mHistoricalAppOpsDir.startWrite(); + final File oldBaseDir = mHistoricalAppOpsDir.getBackupDirectory(); + handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops, 0); + mHistoricalAppOpsDir.finishWrite(); + } catch (Throwable t) { + Slog.wtf(LOG_TAG, "Failed to write historical app ops, restoring backup", t); + mHistoricalAppOpsDir.failWrite(); + } + } + + @Nullable List<HistoricalOps> readHistoryRawDLocked() { + return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/, + null /*filterPackageName*/, null /*filterOpNames*/, + 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/); + } + + @Nullable List<HistoricalOps> readHistoryDLocked() { + final List<HistoricalOps> result = readHistoryRawDLocked(); + // Take into account in memory state duration. + if (result != null) { + final int opCount = result.size(); + for (int i = 0; i < opCount; i++) { + result.get(i).offsetBeginAndEndTime(mBaseSnapshotInterval); + } + } + return result; + } + + long getLastPersistTimeMillisDLocked() { + try { + final File baseDir = mHistoricalAppOpsDir.startRead(); + final File file = generateFile(baseDir, 0); + if (file.exists()) { + return file.lastModified(); + } + mHistoricalAppOpsDir.finishRead(); + } catch (IOException e) { + Slog.wtf("Error reading historical app ops. Deleting history.", e); + mHistoricalAppOpsDir.delete(); + } + return 0; + } + + private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps, + int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames, + long filterBeingMillis, long filterEndMillis) { + final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid, + filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis); + if (readOps != null) { + final int readCount = readOps.size(); + for (int i = 0; i < readCount; i++) { + final HistoricalOps readOp = readOps.get(i); + currentOps.merge(readOp); + } + } + } + + private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked( + int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames, + long filterBeginTimeMillis, long filterEndTimeMillis) { + try { + final File baseDir = mHistoricalAppOpsDir.startRead(); + final File[] files = baseDir.listFiles(); + if (files == null) { + return null; + } + final ArraySet<File> historyFiles = new ArraySet<>(files.length); + for (File file : files) { + if (file.isFile() && file.getName().endsWith(HISTORY_FILE_SUFFIX)) { + historyFiles.add(file); + } + } + final long[] globalContentOffsetMillis = {0}; + final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked( + baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis, + filterEndTimeMillis, globalContentOffsetMillis, null /*outOps*/, + 0 /*depth*/, historyFiles); + mHistoricalAppOpsDir.finishRead(); + return ops; + } catch (IOException | XmlPullParserException e) { + Slog.wtf("Error reading historical app ops. Deleting history.", e); + mHistoricalAppOpsDir.delete(); + } + return null; + } + + private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked( + @NonNull File baseDir, int filterUid, @NonNull String filterPackageName, + @Nullable String[] filterOpNames, long filterBeginTimeMillis, + long filterEndTimeMillis, @NonNull long[] globalContentOffsetMillis, + @Nullable LinkedList<HistoricalOps> outOps, int depth, + @NonNull ArraySet<File> historyFiles) + throws IOException, XmlPullParserException { + final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier, + depth) * mBaseSnapshotInterval; + final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier, + depth + 1) * mBaseSnapshotInterval; + + filterBeginTimeMillis = Math.max(filterBeginTimeMillis - previousIntervalEndMillis, 0); + filterEndTimeMillis = filterEndTimeMillis - previousIntervalEndMillis; + + // Read historical data at this level + final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir, + previousIntervalEndMillis, currentIntervalEndMillis, filterUid, + filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis, + globalContentOffsetMillis, depth, historyFiles); + + // Empty is a special signal to stop diving + if (readOps != null && readOps.isEmpty()) { + return outOps; + } + + // Collect older historical data from subsequent levels + outOps = collectHistoricalOpsRecursiveDLocked( + baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis, + filterEndTimeMillis, globalContentOffsetMillis, outOps, depth + 1, + historyFiles); + + // Make older historical data relative to the current historical level + if (outOps != null) { + final int opCount = outOps.size(); + for (int i = 0; i < opCount; i++) { + final HistoricalOps collectedOp = outOps.get(i); + collectedOp.offsetBeginAndEndTime(currentIntervalEndMillis); + } + } + + if (readOps != null) { + if (outOps == null) { + outOps = new LinkedList<>(); + } + // Add the read ops to output + final int opCount = readOps.size(); + for (int i = opCount - 1; i >= 0; i--) { + outOps.offerFirst(readOps.get(i)); + } + } + + return outOps; + } + + private boolean createHardLinkToExistingFile(@NonNull File fromFile, @NonNull File toFile) + throws IOException { + if (!fromFile.exists()) { + return false; + } + Files.createLink(toFile.toPath(), fromFile.toPath()); + return true; + } + + private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir, + @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, int depth) + throws IOException, XmlPullParserException { + final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier, + depth) * mBaseSnapshotInterval; + final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier, + depth + 1) * mBaseSnapshotInterval; + + if (passedOps == null || passedOps.isEmpty()) { + // If there is an old file we need to copy it over to the new state. + final File oldFile = generateFile(oldBaseDir, depth); + final File newFile = generateFile(newBaseDir, depth); + if (createHardLinkToExistingFile(oldFile, newFile)) { + handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, + passedOps, depth + 1); + } + return; + } + + if (DEBUG) { + enforceOpsWellFormed(passedOps); + } + + // Normalize passed ops time to be based off this interval start + final int passedOpCount = passedOps.size(); + for (int i = 0; i < passedOpCount; i++) { + final HistoricalOps passedOp = passedOps.get(i); + passedOp.offsetBeginAndEndTime(-previousIntervalEndMillis); + } + + if (DEBUG) { + enforceOpsWellFormed(passedOps); + } + + // Read persisted ops for this interval + final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir, + previousIntervalEndMillis, currentIntervalEndMillis, + Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/, + null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/, + Long.MAX_VALUE /*filterEndTimeMillis*/, null, depth, + null /*historyFiles*/); + + if (DEBUG) { + enforceOpsWellFormed(existingOps); + } + + // Offset existing ops to account for elapsed time + final int existingOpCount = existingOps.size(); + if (existingOpCount > 0) { + // Compute elapsed time + final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1) + .getEndTimeMillis(); + for (int i = 0; i < existingOpCount; i++) { + final HistoricalOps existingOp = existingOps.get(i); + existingOp.offsetBeginAndEndTime(elapsedTimeMillis); + } + } + + if (DEBUG) { + enforceOpsWellFormed(existingOps); + } + + final long slotDurationMillis = previousIntervalEndMillis; + + // Consolidate passed ops at the current slot duration ensuring each snapshot is + // full. To achieve this we put all passed and existing ops in a list and will + // merge them to ensure each represents a snapshot at the current granularity. + final List<HistoricalOps> allOps = new LinkedList<>(); + allOps.addAll(passedOps); + allOps.addAll(existingOps); + + if (DEBUG) { + enforceOpsWellFormed(allOps); + } + + // Compute ops to persist and overflow ops + List<HistoricalOps> persistedOps = null; + List<HistoricalOps> overflowedOps = null; + + // We move a snapshot into the next level only if the start time is + // after the end of the current interval. This avoids rewriting all + // files to propagate the information added to the history on every + // iteration. Instead, we would rewrite the next level file only if + // an entire snapshot from the previous level is being propagated. + // The trade off is that we need to store how much the last snapshot + // of the current interval overflows past the interval end. We write + // the overflow data to avoid parsing all snapshots on query. + long intervalOverflowMillis = 0; + final int opCount = allOps.size(); + for (int i = 0; i < opCount; i++) { + final HistoricalOps op = allOps.get(i); + final HistoricalOps persistedOp; + final HistoricalOps overflowedOp; + if (op.getEndTimeMillis() <= currentIntervalEndMillis) { + persistedOp = op; + overflowedOp = null; + } else if (op.getBeginTimeMillis() < currentIntervalEndMillis) { + persistedOp = op; + intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis; + if (intervalOverflowMillis > previousIntervalEndMillis) { + final double splitScale = (double) intervalOverflowMillis + / op.getDurationMillis(); + overflowedOp = spliceFromEnd(op, splitScale); + intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis; + } else { + overflowedOp = null; + } + } else { + persistedOp = null; + overflowedOp = op; + } + if (persistedOp != null) { + if (persistedOps == null) { + persistedOps = new ArrayList<>(); + } + persistedOps.add(persistedOp); + } + if (overflowedOp != null) { + if (overflowedOps == null) { + overflowedOps = new ArrayList<>(); + } + overflowedOps.add(overflowedOp); + } + } + + if (DEBUG) { + enforceOpsWellFormed(persistedOps); + enforceOpsWellFormed(overflowedOps); + } + + if (persistedOps != null) { + normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis); + final File newFile = generateFile(newBaseDir, depth); + writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile); + if (DEBUG) { + Slog.i(LOG_TAG, "Persisted at depth: " + depth + + " ops:\n" + opsToDebugString(persistedOps)); + enforceOpsWellFormed(persistedOps); + } + } + + handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, + overflowedOps, depth + 1); + } + + private @NonNull List<HistoricalOps> readHistoricalOpsLocked(File baseDir, + long intervalBeginMillis, long intervalEndMillis, int filterUid, + @Nullable String filterPackageName, @Nullable String[] filterOpNames, + long filterBeginTimeMillis, long filterEndTimeMillis, + @Nullable long[] cumulativeOverflowMillis, int depth, + @NonNull ArraySet<File> historyFiles) + throws IOException, XmlPullParserException { + final File file = generateFile(baseDir, depth); + if (historyFiles != null) { + historyFiles.remove(file); + } + if (filterBeginTimeMillis >= filterEndTimeMillis + || filterEndTimeMillis < intervalBeginMillis) { + // Don't go deeper + return Collections.emptyList(); + } + if (filterBeginTimeMillis >= (intervalEndMillis + + ((intervalEndMillis - intervalBeginMillis) / mIntervalCompressionMultiplier) + + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0)) + || !file.exists()) { + if (historyFiles == null || historyFiles.isEmpty()) { + // Don't go deeper + return Collections.emptyList(); + } else { + // Keep diving + return null; + } + } + return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames, + filterBeginTimeMillis, filterEndTimeMillis, cumulativeOverflowMillis); + } + + private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file, + int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames, + long filterBeginTimeMillis, long filterEndTimeMillis, + @Nullable long[] cumulativeOverflowMillis) + throws IOException, XmlPullParserException { + if (DEBUG) { + Slog.i(LOG_TAG, "Reading ops from:" + file); + } + List<HistoricalOps> allOps = null; + try (FileInputStream stream = new FileInputStream(file)) { + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, StandardCharsets.UTF_8.name()); + XmlUtils.beginDocument(parser, TAG_HISTORY); + final long overflowMillis = XmlUtils.readLongAttribute(parser, ATTR_OVERFLOW, 0); + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_OPS.equals(parser.getName())) { + final HistoricalOps ops = readeHistoricalOpsDLocked(parser, + filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis, + filterEndTimeMillis, cumulativeOverflowMillis); + if (ops == null) { + continue; + } + if (ops.isEmpty()) { + XmlUtils.skipCurrentTag(parser); + continue; + } + if (allOps == null) { + allOps = new ArrayList<>(); + } + allOps.add(ops); + } + } + if (cumulativeOverflowMillis != null) { + cumulativeOverflowMillis[0] += overflowMillis; + } + } catch (FileNotFoundException e) { + Slog.i(LOG_TAG, "No history file: " + file.getName()); + return Collections.emptyList(); + } + if (DEBUG) { + if (allOps != null) { + Slog.i(LOG_TAG, "Read from file: " + file + "ops:\n" + + opsToDebugString(allOps)); + enforceOpsWellFormed(allOps); + } + } + return allOps; + } + + private @Nullable HistoricalOps readeHistoricalOpsDLocked( + @NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName, + @Nullable String[] filterOpNames, long filterBeginTimeMillis, + long filterEndTimeMillis, @Nullable long[] cumulativeOverflowMillis) + throws IOException, XmlPullParserException { + final long beginTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_BEGIN_TIME, 0) + + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0); + final long endTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_END_TIME, 0) + + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0); + // Keep reading as subsequent records may start matching + if (filterEndTimeMillis < beginTimeMillis) { + return null; + } + // Stop reading as subsequent records will not match + if (filterBeginTimeMillis > endTimeMillis) { + return new HistoricalOps(0, 0); + } + final long filteredBeginTimeMillis = Math.max(beginTimeMillis, filterBeginTimeMillis); + final long filteredEndTimeMillis = Math.min(endTimeMillis, filterEndTimeMillis); + final double filterScale = (double) (filteredEndTimeMillis - filteredBeginTimeMillis) + / (double) (endTimeMillis - beginTimeMillis); + HistoricalOps ops = null; + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_UID.equals(parser.getName())) { + final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser, + filterUid, filterPackageName, filterOpNames, filterScale); + if (ops == null) { + ops = returnedOps; + } + } + } + if (ops != null) { + ops.setBeginAndEndTime(filteredBeginTimeMillis, filteredEndTimeMillis); + } + return ops; + } + + private @Nullable HistoricalOps readHistoricalUidOpsDLocked( + @Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid, + @Nullable String filterPackageName, @Nullable String[] filterOpNames, + double filterScale) throws IOException, XmlPullParserException { + final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME); + if (filterUid != Process.INVALID_UID && filterUid != uid) { + XmlUtils.skipCurrentTag(parser); + return null; + } + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_PACKAGE.equals(parser.getName())) { + final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops, + uid, parser, filterPackageName, filterOpNames, filterScale); + if (ops == null) { + ops = returnedOps; + } + } + } + return ops; + } + + private @Nullable HistoricalOps readHistoricalPackageOpsDLocked( + @Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser, + @Nullable String filterPackageName, @Nullable String[] filterOpNames, + double filterScale) throws IOException, XmlPullParserException { + final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME); + if (filterPackageName != null && !filterPackageName.equals(packageName)) { + XmlUtils.skipCurrentTag(parser); + return null; + } + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_OP.equals(parser.getName())) { + final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, + packageName, parser, filterOpNames, filterScale); + if (ops == null) { + ops = returnedOps; + } + } + } + return ops; + } + + private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops, + int uid, String packageName, @NonNull XmlPullParser parser, + @Nullable String[] filterOpNames, double filterScale) + throws IOException, XmlPullParserException { + final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME); + if (filterOpNames != null && !ArrayUtils.contains(filterOpNames, + AppOpsManager.opToName(op))) { + XmlUtils.skipCurrentTag(parser); + return null; + } + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_STATE.equals(parser.getName())) { + final HistoricalOps returnedOps = readUidStateDLocked(ops, uid, + packageName, op, parser, filterScale); + if (ops == null) { + ops = returnedOps; + } + } + } + return ops; + } + + private @Nullable HistoricalOps readUidStateDLocked(@Nullable HistoricalOps ops, + int uid, String packageName, int op, @NonNull XmlPullParser parser, + double filterScale) throws IOException { + final int uidState = XmlUtils.readIntAttribute(parser, ATTR_NAME); + long accessCount = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_COUNT, 0); + if (accessCount > 0) { + if (!Double.isNaN(filterScale)) { + accessCount = (long) HistoricalOps.round( + (double) accessCount * filterScale); + } + if (ops == null) { + ops = new HistoricalOps(0, 0); + } + ops.increaseAccessCount(op, uid, packageName, uidState, accessCount); + } + long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0); + if (rejectCount > 0) { + if (!Double.isNaN(filterScale)) { + rejectCount = (long) HistoricalOps.round( + (double) rejectCount * filterScale); + } + if (ops == null) { + ops = new HistoricalOps(0, 0); + } + ops.increaseRejectCount(op, uid, packageName, uidState, rejectCount); + } + long accessDuration = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0); + if (accessDuration > 0) { + if (!Double.isNaN(filterScale)) { + accessDuration = (long) HistoricalOps.round( + (double) accessDuration * filterScale); + } + if (ops == null) { + ops = new HistoricalOps(0, 0); + } + ops.increaseAccessDuration(op, uid, packageName, uidState, accessDuration); + } + return ops; + } + + private void writeHistoricalOpsDLocked(@Nullable List<HistoricalOps> allOps, + long intervalOverflowMillis, @NonNull File file) throws IOException { + final FileOutputStream output = mHistoricalAppOpsDir.openWrite(file); + try { + final XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(output, StandardCharsets.UTF_8.name()); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", + true); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_HISTORY); + serializer.attribute(null, ATTR_VERSION, String.valueOf(CURRENT_VERSION)); + if (intervalOverflowMillis != 0) { + serializer.attribute(null, ATTR_OVERFLOW, + Long.toString(intervalOverflowMillis)); + } + if (allOps != null) { + final int opsCount = allOps.size(); + for (int i = 0; i < opsCount; i++) { + final HistoricalOps ops = allOps.get(i); + writeHistoricalOpDLocked(ops, serializer); + } + } + serializer.endTag(null, TAG_HISTORY); + serializer.endDocument(); + mHistoricalAppOpsDir.closeWrite(output); + } catch (IOException e) { + mHistoricalAppOpsDir.failWrite(output); + throw e; + } + } + + private void writeHistoricalOpDLocked(@NonNull HistoricalOps ops, + @NonNull XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_OPS); + serializer.attribute(null, ATTR_BEGIN_TIME, Long.toString(ops.getBeginTimeMillis())); + serializer.attribute(null, ATTR_END_TIME, Long.toString(ops.getEndTimeMillis())); + final int uidCount = ops.getUidCount(); + for (int i = 0; i < uidCount; i++) { + final HistoricalUidOps uidOp = ops.getUidOpsAt(i); + writeHistoricalUidOpsDLocked(uidOp, serializer); + } + serializer.endTag(null, TAG_OPS); + } + + private void writeHistoricalUidOpsDLocked(@NonNull HistoricalUidOps uidOps, + @NonNull XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_UID); + serializer.attribute(null, ATTR_NAME, Integer.toString(uidOps.getUid())); + final int packageCount = uidOps.getPackageCount(); + for (int i = 0; i < packageCount; i++) { + final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(i); + writeHistoricalPackageOpsDLocked(packageOps, serializer); + } + serializer.endTag(null, TAG_UID); + } + + private void writeHistoricalPackageOpsDLocked(@NonNull HistoricalPackageOps packageOps, + @NonNull XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_PACKAGE); + serializer.attribute(null, ATTR_NAME, packageOps.getPackageName()); + final int opCount = packageOps.getOpCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalOp op = packageOps.getOpAt(i); + writeHistoricalOpDLocked(op, serializer); + } + serializer.endTag(null, TAG_PACKAGE); + } + + private void writeHistoricalOpDLocked(@NonNull HistoricalOp op, + @NonNull XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_OP); + serializer.attribute(null, ATTR_NAME, Integer.toString(op.getOpCode())); + for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) { + writeUidStateOnLocked(op, uidState, serializer); + } + serializer.endTag(null, TAG_OP); + } + + private void writeUidStateOnLocked(@NonNull HistoricalOp op, @UidState int uidState, + @NonNull XmlSerializer serializer) throws IOException { + final long accessCount = op.getAccessCount(uidState); + final long rejectCount = op.getRejectCount(uidState); + final long accessDuration = op.getAccessDuration(uidState); + if (accessCount == 0 && rejectCount == 0 && accessDuration == 0) { + return; + } + serializer.startTag(null, TAG_STATE); + serializer.attribute(null, ATTR_NAME, Integer.toString(uidState)); + if (accessCount > 0) { + serializer.attribute(null, ATTR_ACCESS_COUNT, Long.toString(accessCount)); + } + if (rejectCount > 0) { + serializer.attribute(null, ATTR_REJECT_COUNT, Long.toString(rejectCount)); + } + if (accessDuration > 0) { + serializer.attribute(null, ATTR_ACCESS_DURATION, Long.toString(accessDuration)); + } + serializer.endTag(null, TAG_STATE); + } + + private static void enforceOpsWellFormed(@NonNull List<HistoricalOps> ops) { + if (ops == null) { + return; + } + HistoricalOps previous; + HistoricalOps current = null; + final int opsCount = ops.size(); + for (int i = 0; i < opsCount; i++) { + previous = current; + current = ops.get(i); + if (current.isEmpty()) { + throw new IllegalStateException("Empty ops:\n" + + opsToDebugString(ops)); + } + if (current.getEndTimeMillis() < current.getBeginTimeMillis()) { + throw new IllegalStateException("Begin after end:\n" + + opsToDebugString(ops)); + } + if (previous != null) { + if (previous.getEndTimeMillis() > current.getBeginTimeMillis()) { + throw new IllegalStateException("Intersecting ops:\n" + + opsToDebugString(ops)); + } + if (previous.getBeginTimeMillis() > current.getBeginTimeMillis()) { + throw new IllegalStateException("Non increasing ops:\n" + + opsToDebugString(ops)); + } + } + } + } + + private long computeGlobalIntervalBeginMillis(int depth) { + long beginTimeMillis = 0; + for (int i = 0; i < depth + 1; i++) { + beginTimeMillis += Math.pow(mIntervalCompressionMultiplier, i); + } + return beginTimeMillis * mBaseSnapshotInterval; + } + + private static @NonNull HistoricalOps spliceFromEnd(@NonNull HistoricalOps ops, + double spliceRatio) { + if (DEBUG) { + Slog.w(LOG_TAG, "Splicing from end:" + ops + " ratio:" + spliceRatio); + } + final HistoricalOps splice = ops.spliceFromEnd(spliceRatio); + if (DEBUG) { + Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice); + } + return splice; + } + + + private static @NonNull HistoricalOps spliceFromBeginning(@NonNull HistoricalOps ops, + double spliceRatio) { + if (DEBUG) { + Slog.w(LOG_TAG, "Splicing from beginning:" + ops + " ratio:" + spliceRatio); + } + final HistoricalOps splice = ops.spliceFromBeginning(spliceRatio); + if (DEBUG) { + Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice); + } + return splice; + } + + private static void normalizeSnapshotForSlotDuration(@NonNull List<HistoricalOps> ops, + long slotDurationMillis) { + if (DEBUG) { + Slog.i(LOG_TAG, "Normalizing for slot duration: " + slotDurationMillis + + " ops:\n" + opsToDebugString(ops)); + enforceOpsWellFormed(ops); + } + long slotBeginTimeMillis; + final int opCount = ops.size(); + for (int processedIdx = opCount - 1; processedIdx >= 0; processedIdx--) { + final HistoricalOps processedOp = ops.get(processedIdx); + slotBeginTimeMillis = Math.max(processedOp.getEndTimeMillis() + - slotDurationMillis, 0); + for (int candidateIdx = processedIdx - 1; candidateIdx >= 0; candidateIdx--) { + final HistoricalOps candidateOp = ops.get(candidateIdx); + final long candidateSlotIntersectionMillis = candidateOp.getEndTimeMillis() + - Math.min(slotBeginTimeMillis, processedOp.getBeginTimeMillis()); + if (candidateSlotIntersectionMillis <= 0) { + break; + } + final float candidateSplitRatio = candidateSlotIntersectionMillis + / (float) candidateOp.getDurationMillis(); + if (Float.compare(candidateSplitRatio, 1.0f) >= 0) { + ops.remove(candidateIdx); + processedIdx--; + processedOp.merge(candidateOp); + } else { + final HistoricalOps endSplice = spliceFromEnd(candidateOp, + candidateSplitRatio); + if (endSplice != null) { + processedOp.merge(endSplice); + } + if (candidateOp.isEmpty()) { + ops.remove(candidateIdx); + processedIdx--; + } + } + } + } + if (DEBUG) { + Slog.i(LOG_TAG, "Normalized for slot duration: " + slotDurationMillis + + " ops:\n" + opsToDebugString(ops)); + enforceOpsWellFormed(ops); + } + } + + private static @NonNull String opsToDebugString(@NonNull List<HistoricalOps> ops) { + StringBuilder builder = new StringBuilder(); + final int opCount = ops.size(); + for (int i = 0; i < opCount; i++) { + builder.append(" "); + builder.append(ops.get(i)); + if (i < opCount - 1) { + builder.append('\n'); + } + } + return builder.toString(); + } + } + + private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor { + private final long mNow = System.currentTimeMillis(); + + private final SimpleDateFormat mDateFormatter = new SimpleDateFormat( + "yyyy-MM-dd HH:mm:ss.SSS"); + private final Date mDate = new Date(); + + private final @NonNull String mOpsPrefix; + private final @NonNull String mUidPrefix; + private final @NonNull String mPackagePrefix; + private final @NonNull String mEntryPrefix; + private final @NonNull String mUidStatePrefix; + private final @NonNull PrintWriter mWriter; + private final int mFilterUid; + private final String mFilterPackage; + private final int mFilterOp; + + StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, + int filterUid, String filterPackage, int filterOp) { + mOpsPrefix = prefix + " "; + mUidPrefix = mOpsPrefix + " "; + mPackagePrefix = mUidPrefix + " "; + mEntryPrefix = mPackagePrefix + " "; + mUidStatePrefix = mEntryPrefix + " "; + mWriter = writer; + mFilterUid = filterUid; + mFilterPackage = filterPackage; + mFilterOp = filterOp; + } + + @Override + public void visitHistoricalOps(HistoricalOps ops) { + mWriter.println(); + mWriter.print(mOpsPrefix); + mWriter.println("snapshot:"); + mWriter.print(mUidPrefix); + mWriter.print("begin = "); + mDate.setTime(ops.getBeginTimeMillis()); + mWriter.print(mDateFormatter.format(mDate)); + mWriter.print(" ("); + TimeUtils.formatDuration(ops.getBeginTimeMillis() - mNow, mWriter); + mWriter.println(")"); + mWriter.print(mUidPrefix); + mWriter.print("end = "); + mDate.setTime(ops.getEndTimeMillis()); + mWriter.print(mDateFormatter.format(mDate)); + mWriter.print(" ("); + TimeUtils.formatDuration(ops.getEndTimeMillis() - mNow, mWriter); + mWriter.println(")"); + } + + @Override + public void visitHistoricalUidOps(HistoricalUidOps ops) { + if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) { + return; + } + mWriter.println(); + mWriter.print(mUidPrefix); + mWriter.print("Uid "); + UserHandle.formatUid(mWriter, ops.getUid()); + mWriter.println(":"); + } + + @Override + public void visitHistoricalPackageOps(HistoricalPackageOps ops) { + if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) { + return; + } + mWriter.print(mPackagePrefix); + mWriter.print("Package "); + mWriter.print(ops.getPackageName()); + mWriter.println(":"); + } + + @Override + public void visitHistoricalOp(HistoricalOp ops) { + if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) { + return; + } + mWriter.print(mEntryPrefix); + mWriter.print(AppOpsManager.opToName(ops.getOpCode())); + mWriter.println(":"); + for (int uidState = 0; uidState < AppOpsManager._NUM_UID_STATE; uidState++) { + boolean printedUidState = false; + final long accessCount = ops.getAccessCount(uidState); + if (accessCount > 0) { + if (!printedUidState) { + mWriter.print(mUidStatePrefix); + mWriter.print(AppOpsManager.uidStateToString(uidState)); + mWriter.print("["); + printedUidState = true; + } + mWriter.print("access="); + mWriter.print(accessCount); + } + final long rejectCount = ops.getRejectCount(uidState); + if (rejectCount > 0) { + if (!printedUidState) { + mWriter.print(mUidStatePrefix); + mWriter.print(AppOpsManager.uidStateToString(uidState)); + mWriter.print("["); + printedUidState = true; + } else { + mWriter.print(","); + } + mWriter.print("reject="); + mWriter.print(rejectCount); + } + final long accessDuration = ops.getAccessDuration(uidState); + if (accessDuration > 0) { + if (!printedUidState) { + mWriter.print(mUidStatePrefix); + mWriter.print(AppOpsManager.uidStateToString(uidState)); + printedUidState = true; + } else { + mWriter.print(","); + } + mWriter.print("duration="); + mWriter.print(accessDuration); + } + if (printedUidState) { + mWriter.println("]"); + } + } + } + } +} diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 905f82693980..9d6628cf0c40 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -206,7 +206,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin switch (event) { case AudioManager.RECORD_CONFIG_EVENT_STOP: // return failure if an unknown recording session stopped - configChanged = (mRecordConfigs.remove(new Integer(session)) != null); + configChanged = (mRecordConfigs.remove(new Integer(portId)) != null); if (configChanged) { sEventLogger.log(new RecordingEvent(event, uid, session, source, null)); } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 36ca4dcbea33..41fedc5fab45 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -132,10 +132,13 @@ public class BiometricService extends SystemService { Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED); private final Uri FACE_UNLOCK_APP_ENABLED = Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED); + private final Uri FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION = + Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION); private final ContentResolver mContentResolver; private boolean mFaceEnabledOnKeyguard; private boolean mFaceEnabledForApps; + private boolean mFaceAlwaysRequireConfirmation; /** * Creates a content observer. @@ -158,10 +161,15 @@ public class BiometricService extends SystemService { false /* notifyForDescendents */, this /* observer */, UserHandle.USER_CURRENT); + mContentResolver.registerContentObserver(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, + false /* notifyForDescendents */, + this /* observer */, + UserHandle.USER_CURRENT); // Update the value immediately onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED); onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED); + onChange(true /* selfChange */, FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION); } @Override @@ -185,6 +193,13 @@ public class BiometricService extends SystemService { Settings.Secure.FACE_UNLOCK_APP_ENABLED, 1 /* default */, UserHandle.USER_CURRENT) != 0; + } else if (FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION.equals(uri)) { + mFaceAlwaysRequireConfirmation = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, + 0 /* default */, + UserHandle.USER_CURRENT) != 0; } } @@ -195,6 +210,10 @@ public class BiometricService extends SystemService { boolean getFaceEnabledForApps() { return mFaceEnabledForApps; } + + boolean getFaceAlwaysRequireConfirmation() { + return mFaceAlwaysRequireConfirmation; + } } private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient { @@ -332,10 +351,7 @@ public class BiometricService extends SystemService { if (!runningTasks.isEmpty()) { final String topPackage = runningTasks.get(0).topActivity.getPackageName(); if (mCurrentAuthSession != null - && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName) - && mCurrentAuthSession.mState != STATE_AUTH_STARTED) { - // We only care about this state, since <Biometric>Service will - // cancel any client that's still in STATE_AUTH_STARTED + && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)) { mStatusBarService.hideBiometricDialog(); mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); mCurrentAuthSession.mClientReceiver.onError( @@ -395,7 +411,7 @@ public class BiometricService extends SystemService { // Notify SysUI that the biometric has been authenticated. SysUI already knows // the implicit/explicit state and will react accordingly. - mStatusBarService.onBiometricAuthenticated(); + mStatusBarService.onBiometricAuthenticated(true); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } @@ -412,17 +428,20 @@ public class BiometricService extends SystemService { return; } - mStatusBarService.onBiometricHelp(getContext().getResources().getString( - com.android.internal.R.string.biometric_not_recognized)); - if (requireConfirmation) { + mStatusBarService.onBiometricAuthenticated(false); + + // TODO: This logic will need to be updated if BP is multi-modal + if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) { + // Pause authentication. onBiometricAuthenticated(false) causes the + // dialog to show a "try again" button for passive modalities. mCurrentAuthSession.mState = STATE_AUTH_PAUSED; - mStatusBarService.showBiometricTryAgain(); // Cancel authentication. Skip the token/package check since we are // cancelling from system server. The interface is permission protected so // this is fine. cancelInternal(null /* token */, null /* package */, false /* fromClient */); } + mCurrentAuthSession.mClientReceiver.onAuthenticationFailed(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); @@ -442,8 +461,9 @@ public class BiometricService extends SystemService { if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) { if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) { mStatusBarService.onBiometricError(message); - mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) { + mActivityTaskManager.unregisterTaskStackListener( + mTaskStackListener); mCurrentAuthSession.mClientReceiver.onError(error, message); mCurrentAuthSession.mState = STATE_AUTH_IDLE; mCurrentAuthSession = null; @@ -452,9 +472,14 @@ public class BiometricService extends SystemService { // Send errors after the dialog is dismissed. mHandler.postDelayed(() -> { try { - mCurrentAuthSession.mClientReceiver.onError(error, message); - mCurrentAuthSession.mState = STATE_AUTH_IDLE; - mCurrentAuthSession = null; + if (mCurrentAuthSession != null) { + mActivityTaskManager.unregisterTaskStackListener( + mTaskStackListener); + mCurrentAuthSession.mClientReceiver.onError(error, + message); + mCurrentAuthSession.mState = STATE_AUTH_IDLE; + mCurrentAuthSession = null; + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } @@ -518,6 +543,11 @@ public class BiometricService extends SystemService { @Override public void onDialogDismissed(int reason) throws RemoteException { + if (mCurrentAuthSession == null) { + Slog.e(TAG, "onDialogDismissed: " + reason + ", auth session null"); + return; + } + if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) { // Positive button is used by passive modalities as a "confirm" button, // do not send to client @@ -579,8 +609,10 @@ public class BiometricService extends SystemService { } if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) { - final boolean mContinuing = mCurrentAuthSession != null - && mCurrentAuthSession.mState == STATE_AUTH_PAUSED; + final boolean continuing = mCurrentAuthSession != null && + (mCurrentAuthSession.mState == STATE_AUTH_PAUSED + || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED); + mCurrentAuthSession = mPendingAuthSession; mPendingAuthSession = null; @@ -602,7 +634,7 @@ public class BiometricService extends SystemService { modality |= pair.getKey(); } - if (!mContinuing) { + if (!continuing) { mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle, mInternalReceiver, modality, requireConfirmation, userId); mActivityTaskManager.registerTaskStackListener(mTaskStackListener); @@ -706,7 +738,8 @@ public class BiometricService extends SystemService { mCurrentModality = modality; - // Actually start authentication + // Start preparing for authentication. Authentication starts when + // all modalities requested have invoked onReadyForAuthentication. authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, modality); }); @@ -725,6 +758,9 @@ public class BiometricService extends SystemService { IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, int callingUid, int callingPid, int callingUserId, int modality) { try { + boolean requireConfirmation = bundle.getBoolean( + BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */); + // Generate random cookies to pass to the services that should prepare to start // authenticating. Store the cookie here and wait for all services to "ack" // with the cookie. Once all cookies are received, we can show the prompt @@ -748,7 +784,10 @@ public class BiometricService extends SystemService { Slog.w(TAG, "Iris unsupported"); } if ((modality & TYPE_FACE) != 0) { - mFaceService.prepareForAuthentication(true /* requireConfirmation */, + // Check if the user has forced confirmation to be required in Settings. + requireConfirmation = requireConfirmation + || mSettingObserver.getFaceAlwaysRequireConfirmation(); + mFaceService.prepareForAuthentication(requireConfirmation, token, sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid, callingPid, callingUserId); } diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java index 32219aa5955f..ecc3d2da8226 100644 --- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java +++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java @@ -819,8 +819,6 @@ public abstract class BiometricServiceBase extends SystemService // Should be done on a handler thread - not on the Binder's thread. private void startAuthentication(AuthenticationClientImpl client, String opPackageName) { - updateActiveGroup(client.getGroupId(), opPackageName); - if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")"); int lockoutMode = getLockoutMode(); diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 72f73f6aaf67..5a9c1aca081a 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -133,10 +133,11 @@ public class FaceService extends BiometricServiceBase { } @Override // Binder call - public void authenticate(final IBinder token, final long opId, + public void authenticate(final IBinder token, final long opId, int userId, final IFaceServiceReceiver receiver, final int flags, final String opPackageName) { checkPermission(USE_BIOMETRIC_INTERNAL); + updateActiveGroup(userId, opPackageName); final boolean restricted = isRestricted(); final AuthenticationClientImpl client = new FaceAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), @@ -156,7 +157,7 @@ public class FaceService extends BiometricServiceBase { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(wrapperReceiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie, - true /* requireConfirmation */); + requireConfirmation); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 3895ef78b357..1613dc97225b 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -159,6 +159,7 @@ public class FingerprintService extends BiometricServiceBase { public void authenticate(final IBinder token, final long opId, final int groupId, final IFingerprintServiceReceiver receiver, final int flags, final String opPackageName) { + updateActiveGroup(groupId, opPackageName); final boolean restricted = isRestricted(); final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(), mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver), diff --git a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java index 24865bcd9a09..6fa98b8e8ad7 100644 --- a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java +++ b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java @@ -21,22 +21,6 @@ package com.android.server.connectivity; * @hide */ public class ConnectivityConstants { - // IPC constants - public static final String ACTION_NETWORK_CONDITIONS_MEASURED = - "android.net.conn.NETWORK_CONDITIONS_MEASURED"; - public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type"; - public static final String EXTRA_NETWORK_TYPE = "extra_network_type"; - public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received"; - public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal"; - public static final String EXTRA_CELL_ID = "extra_cellid"; - public static final String EXTRA_SSID = "extra_ssid"; - public static final String EXTRA_BSSID = "extra_bssid"; - /** real time since boot */ - public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms"; - public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms"; - - public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS = - "android.permission.ACCESS_NETWORK_CONDITIONS"; // Penalty applied to scores of Networks that have not been validated. public static final int UNVALIDATED_SCORE_PENALTY = 40; diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java index b8f057db290a..d8bb635f2ce8 100644 --- a/services/core/java/com/android/server/connectivity/DnsManager.java +++ b/services/core/java/com/android/server/connectivity/DnsManager.java @@ -18,10 +18,9 @@ package com.android.server.connectivity; import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; -import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; -import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES; import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES; +import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES; import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS; import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT; import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE; @@ -35,6 +34,7 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkUtils; import android.net.Uri; +import android.net.shared.PrivateDnsConfig; import android.os.Binder; import android.os.INetworkManagementService; import android.os.UserHandle; @@ -43,10 +43,7 @@ import android.text.TextUtils; import android.util.Pair; import android.util.Slog; -import com.android.server.connectivity.MockableSystemProperties; - import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -54,10 +51,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; import java.util.Set; -import java.util.StringJoiner; +import java.util.stream.Collectors; /** @@ -123,43 +118,6 @@ public class DnsManager { private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8; private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64; - public static class PrivateDnsConfig { - public final boolean useTls; - public final String hostname; - public final InetAddress[] ips; - - public PrivateDnsConfig() { - this(false); - } - - public PrivateDnsConfig(boolean useTls) { - this.useTls = useTls; - this.hostname = ""; - this.ips = new InetAddress[0]; - } - - public PrivateDnsConfig(String hostname, InetAddress[] ips) { - this.useTls = !TextUtils.isEmpty(hostname); - this.hostname = useTls ? hostname : ""; - this.ips = (ips != null) ? ips : new InetAddress[0]; - } - - public PrivateDnsConfig(PrivateDnsConfig cfg) { - useTls = cfg.useTls; - hostname = cfg.hostname; - ips = cfg.ips; - } - - public boolean inStrictMode() { - return useTls && !TextUtils.isEmpty(hostname); - } - - public String toString() { - return PrivateDnsConfig.class.getSimpleName() + - "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}"; - } - } - public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) { final String mode = getPrivateDnsMode(cr); diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 262184b0b12d..54c89aa04111 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -16,9 +16,8 @@ package com.android.server.connectivity; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; - import android.content.Context; +import android.net.INetworkMonitor; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; @@ -29,7 +28,6 @@ import android.net.NetworkState; import android.os.Handler; import android.os.INetworkManagementService; import android.os.Messenger; -import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; @@ -37,11 +35,8 @@ import android.util.SparseArray; import com.android.internal.util.AsyncChannel; import com.android.internal.util.WakeupMessage; import com.android.server.ConnectivityService; -import com.android.server.connectivity.NetworkMonitor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Comparator; import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; @@ -126,7 +121,6 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public LinkProperties linkProperties; // This should only be modified via ConnectivityService.updateCapabilities(). public NetworkCapabilities networkCapabilities; - public final NetworkMonitor networkMonitor; public final NetworkMisc networkMisc; // Indicates if netd has been told to create this Network. From this point on the appropriate // routing rules are setup and routes are added so packets can begin flowing over the Network. @@ -239,6 +233,9 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // Used by ConnectivityService to keep track of 464xlat. public Nat464Xlat clatd; + // Set after asynchronous creation of the NetworkMonitor. + private volatile INetworkMonitor mNetworkMonitor; + private static final String TAG = ConnectivityService.class.getSimpleName(); private static final boolean VDBG = false; private final ConnectivityService mConnService; @@ -247,7 +244,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, - NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) { + NetworkMisc misc, ConnectivityService connService) { this.messenger = messenger; asyncChannel = ac; network = net; @@ -258,10 +255,16 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mConnService = connService; mContext = context; mHandler = handler; - networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest); networkMisc = misc; } + /** + * Inform NetworkAgentInfo that a new NetworkMonitor was created. + */ + public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) { + mNetworkMonitor = networkMonitor; + } + public ConnectivityService connService() { return mConnService; } @@ -278,6 +281,15 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { return network; } + /** + * Get the INetworkMonitor in this NetworkAgentInfo. + * + * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called. + */ + public INetworkMonitor networkMonitor() { + return mNetworkMonitor; + } + // Functions for manipulating the requests satisfied by this network. // // These functions must only called on ConnectivityService's main thread. diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java index 73d3d9591529..b332c47ce281 100644 --- a/services/core/java/com/android/server/display/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/ColorDisplayService.java @@ -18,7 +18,9 @@ package com.android.server.display; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; +import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION; +import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TypeEvaluator; @@ -31,6 +33,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.database.ContentObserver; import android.hardware.display.ColorDisplayManager; import android.hardware.display.IColorDisplayManager; @@ -39,6 +42,7 @@ import android.opengl.Matrix; import android.os.Binder; import android.os.Handler; import android.os.Looper; +import android.os.Message; import android.os.UserHandle; import android.provider.Settings.Secure; import android.provider.Settings.System; @@ -61,6 +65,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.format.DateTimeParseException; +import java.util.Arrays; /** * Controls the display's color transforms. @@ -83,6 +88,10 @@ public final class ColorDisplayService extends SystemService { Matrix.setIdentityM(MATRIX_IDENTITY, 0); } + private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 0; + private static final int MSG_APPLY_NIGHT_DISPLAY_ANIMATED = 1; + private static final int MSG_APPLY_GLOBAL_SATURATION = 2; + /** * Evaluator used to animate color matrix transitions. */ @@ -161,6 +170,53 @@ public final class ColorDisplayService extends SystemService { } }; + private final TintController mGlobalSaturationTintController = new TintController() { + + private float[] mMatrixGlobalSaturation = new float[16]; + + @Override + public void setUp(Context context, boolean needsLinear) { + } + + @Override + public float[] getMatrix() { + return Arrays.copyOf(mMatrixGlobalSaturation, mMatrixGlobalSaturation.length); + } + + @Override + public void setMatrix(int saturationLevel) { + if (saturationLevel < 0) { + saturationLevel = 0; + } else if (saturationLevel > 100) { + saturationLevel = 100; + } + Slog.d(TAG, "Setting saturation level: " + saturationLevel); + + if (saturationLevel == 100) { + Matrix.setIdentityM(mMatrixGlobalSaturation, 0); + } else { + float saturation = saturationLevel * 0.1f; + float desaturation = 1.0f - saturation; + float[] luminance = {0.231f * desaturation, 0.715f * desaturation, + 0.072f * desaturation}; + mMatrixGlobalSaturation[0] = luminance[0] + saturation; + mMatrixGlobalSaturation[1] = luminance[0]; + mMatrixGlobalSaturation[2] = luminance[0]; + mMatrixGlobalSaturation[4] = luminance[1]; + mMatrixGlobalSaturation[5] = luminance[1] + saturation; + mMatrixGlobalSaturation[6] = luminance[1]; + mMatrixGlobalSaturation[8] = luminance[2]; + mMatrixGlobalSaturation[9] = luminance[2]; + mMatrixGlobalSaturation[10] = luminance[2] + saturation; + } + } + + @Override + public int getLevel() { + return LEVEL_COLOR_MATRIX_SATURATION; + } + }; + private final Handler mHandler; private int mCurrentUser = UserHandle.USER_NULL; @@ -178,7 +234,7 @@ public final class ColorDisplayService extends SystemService { public ColorDisplayService(Context context) { super(context); - mHandler = new Handler(Looper.getMainLooper()); + mHandler = new TintHandler(Looper.getMainLooper()); } @Override @@ -904,6 +960,23 @@ public final class ColorDisplayService extends SystemService { void onDisplayWhiteBalanceStatusChanged(boolean enabled); } + private final class TintHandler extends Handler { + + TintHandler(Looper looper) { + super(looper, null, true /* async */); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_APPLY_GLOBAL_SATURATION: + mGlobalSaturationTintController.setMatrix(msg.arg1); + applyTint(mGlobalSaturationTintController, false); + break; + } + } + } + private final class BinderService extends IColorDisplayManager.Stub { @Override @@ -915,5 +988,27 @@ public final class ColorDisplayService extends SystemService { Binder.restoreCallingIdentity(token); } } + + @Override + public boolean setSaturationLevel(int level) { + final boolean hasTransformsPermission = getContext() + .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + == PackageManager.PERMISSION_GRANTED; + final boolean hasLegacyPermission = getContext() + .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION) + == PackageManager.PERMISSION_GRANTED; + if (!hasTransformsPermission && !hasLegacyPermission) { + throw new SecurityException("Permission required to set display saturation level"); + } + final long token = Binder.clearCallingIdentity(); + try { + final Message message = mHandler.obtainMessage(MSG_APPLY_GLOBAL_SATURATION); + message.arg1 = level; + mHandler.sendMessage(message); + } finally { + Binder.restoreCallingIdentity(token); + } + return true; + } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b1ba05c035b3..4a17c6591449 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -724,27 +724,6 @@ public final class DisplayManagerService extends SystemService { } } - private void setSaturationLevelInternal(float level) { - if (level < 0 || level > 1) { - throw new IllegalArgumentException("Saturation level must be between 0 and 1"); - } - float[] matrix = (level == 1.0f ? null : computeSaturationMatrix(level)); - DisplayTransformManager dtm = LocalServices.getService(DisplayTransformManager.class); - dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION, matrix); - } - - private static float[] computeSaturationMatrix(float saturation) { - float desaturation = 1.0f - saturation; - float[] luminance = {0.231f * desaturation, 0.715f * desaturation, 0.072f * desaturation}; - float[] matrix = { - luminance[0] + saturation, luminance[0], luminance[0], 0, - luminance[1], luminance[1] + saturation, luminance[1], 0, - luminance[2], luminance[2], luminance[2] + saturation, 0, - 0, 0, 0, 1 - }; - return matrix; - } - private int createVirtualDisplayInternal(IVirtualDisplayCallback callback, IMediaProjection projection, int callingUid, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags, String uniqueId) { @@ -1836,19 +1815,6 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call - public void setSaturationLevel(float level) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.CONTROL_DISPLAY_SATURATION, - "Permission required to set display saturation level"); - final long token = Binder.clearCallingIdentity(); - try { - setSaturationLevelInternal(level); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - @Override // Binder call public int createVirtualDisplay(IVirtualDisplayCallback callback, IMediaProjection projection, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags, diff --git a/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java index ed17de5e8c35..18d328d3f971 100644 --- a/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/ArcInitiationActionFromAvr.java @@ -21,14 +21,15 @@ import android.hardware.tv.cec.V1_0.SendMessageResult; * Feature action that handles Audio Return Channel initiated by AVR devices. */ public class ArcInitiationActionFromAvr extends HdmiCecFeatureAction { - // TODO(shubang): add tests - // State in which waits for ARC response. private static final int STATE_WAITING_FOR_INITIATE_ARC_RESPONSE = 1; private static final int STATE_ARC_INITIATED = 2; // the required maximum response time specified in CEC 9.2 private static final int TIMEOUT_MS = 1000; + private static final int MAX_RETRY_COUNT = 5; + + private int mSendRequestActiveSourceRetryCount = 0; ArcInitiationActionFromAvr(HdmiCecLocalDevice source) { super(source); @@ -56,7 +57,12 @@ public class ArcInitiationActionFromAvr extends HdmiCecFeatureAction { return true; case Constants.MESSAGE_REPORT_ARC_INITIATED: mState = STATE_ARC_INITIATED; - finish(); + if (audioSystem().getActiveSource().physicalAddress != getSourcePath() + && audioSystem().isSystemAudioActivated()) { + sendRequestActiveSource(); + } else { + finish(); + } return true; } return false; @@ -91,4 +97,19 @@ public class ArcInitiationActionFromAvr extends HdmiCecFeatureAction { finish(); } + protected void sendRequestActiveSource() { + sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()), + result -> { + if (result != SendMessageResult.SUCCESS) { + if (mSendRequestActiveSourceRetryCount < MAX_RETRY_COUNT) { + mSendRequestActiveSourceRetryCount++; + sendRequestActiveSource(); + } else { + finish(); + } + } else { + finish(); + } + }); + } } diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java index 7e7332180f8a..eb7c0cd61132 100644 --- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java @@ -50,6 +50,9 @@ public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction { case Constants.MESSAGE_REPORT_ARC_TERMINATED: mState = STATE_ARC_TERMINATED; audioSystem().setArcStatus(false); + if (audioSystem().getLocalActivePort() == Constants.CEC_SWITCH_ARC) { + audioSystem().routeToInputFromPortId(audioSystem().getRoutingPort()); + } finish(); return true; } diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index ea01a0b4253e..6f5a19612895 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -256,6 +256,41 @@ final class Constants { static final int USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 1; static final int NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON = 2; + // Port id to record local active port for Routing Control features + // They are used to map to corresponding Inputs + // Current interface is only implemented for specific device. + // Developers can add more port number and map them to corresponding inputs on demand. + @IntDef({ + CEC_SWITCH_HOME, + CEC_SWITCH_HDMI1, + CEC_SWITCH_HDMI2, + CEC_SWITCH_HDMI3, + CEC_SWITCH_HDMI4, + CEC_SWITCH_HDMI5, + CEC_SWITCH_HDMI6, + CEC_SWITCH_HDMI7, + CEC_SWITCH_HDMI8, + CEC_SWITCH_ARC, + CEC_SWITCH_BLUETOOTH, + CEC_SWITCH_OPTICAL, + CEC_SWITCH_AUX + }) + @interface LocalActivePort {} + static final int CEC_SWITCH_HOME = 0; + static final int CEC_SWITCH_HDMI1 = 1; + static final int CEC_SWITCH_HDMI2 = 2; + static final int CEC_SWITCH_HDMI3 = 3; + static final int CEC_SWITCH_HDMI4 = 4; + static final int CEC_SWITCH_HDMI5 = 5; + static final int CEC_SWITCH_HDMI6 = 6; + static final int CEC_SWITCH_HDMI7 = 7; + static final int CEC_SWITCH_HDMI8 = 8; + static final int CEC_SWITCH_ARC = 17; + static final int CEC_SWITCH_BLUETOOTH = 18; + static final int CEC_SWITCH_OPTICAL = 19; + static final int CEC_SWITCH_AUX = 20; + static final int CEC_SWITCH_PORT_MAX = 21; + static final String PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM = "persist.sys.hdmi.addr.audiosystem"; static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback"; @@ -274,15 +309,48 @@ final class Constants { static final String PROPERTY_SET_MENU_LANGUAGE = "ro.hdmi.set_menu_language"; /** + * Property to save the ARC port id on system audio device. + * <p>When ARC is initiated, this port will be used to turn on ARC. + */ + static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT = + "ro.hdmi.property_sytem_audio_device_arc_port"; + + /** * Property to disable muting logic in System Audio Control handling. Default is true. * * <p>True means enabling muting logic. * <p>False means never mute device. */ - // TODO(OEM): set to true to disable muting. static final String PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE = "ro.hdmi.property_system_audio_mode_muting_enable"; + /** + * When set to true the HdmiControlService will never request a Logical Address for the + * playback device type. Default is false. + * + * <p> This is useful when HDMI CEC multiple device types is not supported by the cec driver + */ + static final String PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS = + "ro.hdmi.property_hdmi_cec_never_claim_playback_logical_address"; + + /** + * A comma separated list of logical addresses that HdmiControlService + * will never assign local CEC devices to. + * + * <p> This is useful when HDMI CEC hardware module can't assign multiple logical addresses + * in the range same range of 0-7 or 8-15. + */ + static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES = + "ro.hdmi.property_hdmi_cec_never_assign_logical_addresses"; + + /** + * Property to indicate if the current device is a cec switch device. + * + * <p> Default is false. + */ + static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH = + "ro.hdmi.property_is_device_hdmi_cec_switch"; + // Set to false to allow playback device to go to suspend mode even // when it's an active source. True by default. static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake"; @@ -323,13 +391,6 @@ final class Constants { "persist.sys.hdmi.property_sytem_audio_mode_audio_port"; /** - * Property to save the ARC port id on system audio device. - * <p>When ARC is initiated, this port will be used to turn on ARC. - */ - static final String PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT = - "persist.sys.hdmi.property_sytem_audio_device_arc_port"; - - /** * Property to indicate if a CEC audio device should forward volume keys when system audio mode * is off. * diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index de6cdd48fb09..e777ce8166ac 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -22,20 +22,25 @@ import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; +import android.os.SystemProperties; import android.util.Slog; import android.util.SparseArray; + import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; + +import libcore.util.EmptyArray; + import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; import java.util.List; -import java.util.function.Predicate; import java.util.concurrent.ArrayBlockingQueue; -import libcore.util.EmptyArray; +import java.util.function.Predicate; + import sun.util.locale.LanguageTag; /** @@ -112,10 +117,18 @@ final class HdmiCecController { private final NativeWrapper mNativeWrapperImpl; + /** List of logical addresses that should not be assigned to the current device. + * + * <p>Parsed from {@link Constants#PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES} + */ + private final List<Integer> mNeverAssignLogicalAddresses; + // Private constructor. Use HdmiCecController.create(). private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) { mService = service; mNativeWrapperImpl = nativeWrapper; + mNeverAssignLogicalAddresses = mService.getIntList(SystemProperties.get( + Constants.PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES)); } /** @@ -208,7 +221,8 @@ final class HdmiCecController { for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) { int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS; if (curAddress != Constants.ADDR_UNREGISTERED - && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) { + && deviceType == HdmiUtils.getTypeFromAddress(curAddress) + && !mNeverAssignLogicalAddresses.contains(curAddress)) { boolean acked = false; for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) { if (sendPollMessage(curAddress, curAddress, 1)) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java index 11faa56f704a..2da698be56be 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java @@ -252,6 +252,10 @@ abstract class HdmiCecFeatureAction { return (HdmiCecLocalDevicePlayback) mSource; } + protected final HdmiCecLocalDeviceSource source() { + return (HdmiCecLocalDeviceSource) mSource; + } + protected final HdmiCecLocalDeviceTv tv() { return (HdmiCecLocalDeviceTv) mSource; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 7860122ed9ea..6e1b0181adbf 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -18,19 +18,24 @@ package com.android.server.hdmi; import android.annotation.Nullable; import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.os.SystemClock; import android.util.Slog; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; + import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.hdmi.Constants.LocalActivePort; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -51,6 +56,11 @@ abstract class HdmiCecLocalDevice { // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior. // When it expires, we can assume <User Control Release> is received. private static final int FOLLOWER_SAFETY_TIMEOUT = 550; + /** + * Return value of {@link #getLocalPortFromPhysicalAddress(int)} + */ + private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1; + private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0; protected final HdmiControlService mService; protected final int mDeviceType; @@ -125,9 +135,6 @@ abstract class HdmiCecLocalDevice { return s.toString(); } } - // Logical address of the active source. - @GuardedBy("mLock") - protected final ActiveSource mActiveSource = new ActiveSource(); // Active routing path. Physical address of the active source but not all the time, such as // when the new active source does not claim itself to be one. Note that we don't keep @@ -434,10 +441,14 @@ abstract class HdmiCecLocalDevice { return true; } + // Audio System device with no Playback device type + // needs to refactor this function if it's also a switch protected boolean handleRoutingChange(HdmiCecMessage message) { return false; } + // Audio System device with no Playback device type + // needs to refactor this function if it's also a switch protected boolean handleRoutingInformation(HdmiCecMessage message) { return false; } @@ -855,9 +866,7 @@ abstract class HdmiCecLocalDevice { } ActiveSource getActiveSource() { - synchronized (mLock) { - return mActiveSource; - } + return mService.getActiveSource(); } void setActiveSource(ActiveSource newActive) { @@ -869,10 +878,7 @@ abstract class HdmiCecLocalDevice { } void setActiveSource(int logicalAddress, int physicalAddress) { - synchronized (mLock) { - mActiveSource.logicalAddress = logicalAddress; - mActiveSource.physicalAddress = physicalAddress; - } + mService.setActiveSource(logicalAddress, physicalAddress); mService.setLastInputForMhl(Constants.INVALID_PORT_ID); } @@ -1017,6 +1023,19 @@ abstract class HdmiCecLocalDevice { return Constants.ADDR_INVALID; } + @ServiceThreadOnly + void invokeCallback(IHdmiControlCallback callback, int result) { + assertRunOnServiceThread(); + if (callback == null) { + return; + } + try { + callback.onComplete(result); + } catch (RemoteException e) { + Slog.e(TAG, "Invoking callback failed:" + e); + } + } + void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) { mService.sendCecCommand( HdmiCecMessageBuilder.buildUserControlPressed(mAddress, targetAddress, cecKeycode)); @@ -1030,7 +1049,71 @@ abstract class HdmiCecLocalDevice { pw.println("mAddress: " + mAddress); pw.println("mPreferredAddress: " + mPreferredAddress); pw.println("mDeviceInfo: " + mDeviceInfo); - pw.println("mActiveSource: " + mActiveSource); + pw.println("mActiveSource: " + getActiveSource()); pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath)); } + + /** + * Method to parse target physical address to the port number on the current device. + * + * <p>This check assumes target address is valid. + * @param targetPhysicalAddress is the physical address of the target device + * @return + * If the target device is under the current device, return the port number of current device + * that the target device is connected to. + * + * <p>If the target device has the same physical address as the current device, return + * {@link #TARGET_SAME_PHYSICAL_ADDRESS}. + * + * <p>If the target device is not under the current device, return + * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}. + */ + protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) { + int myPhysicalAddress = mService.getPhysicalAddress(); + if (myPhysicalAddress == targetPhysicalAddress) { + return TARGET_SAME_PHYSICAL_ADDRESS; + } + int finalMask = 0xF000; + int mask; + int port = 0; + for (mask = 0x0F00; mask > 0x000F; mask >>= 4) { + if ((myPhysicalAddress & mask) == 0) { + port = mask & targetPhysicalAddress; + break; + } else { + finalMask |= mask; + } + } + if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) { + while (mask != 0x000F) { + mask >>= 4; + port >>= 4; + } + return port; + } + return TARGET_NOT_UNDER_LOCAL_DEVICE; + } + + /** Calculates the physical address for {@code activePortId}. + * + * <p>This method assumes current device physical address is valid. + * <p>If the current device is already the leaf of the whole CEC system + * and can't have devices under it, will return its own physical address. + * + * @param activePortId is the local active port Id + * @return the calculated physical address of the port + */ + protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) { + int myPhysicalAddress = mService.getPhysicalAddress(); + int finalMask = activePortId << 8; + int mask; + for (mask = 0x0F00; mask > 0x000F; mask >>= 4) { + if ((myPhysicalAddress & mask) == 0) { + break; + } else { + finalMask >>= 4; + } + } + return finalMask | myPhysicalAddress; + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 7358eaa587e3..8cfe47f3d75e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -20,23 +20,28 @@ import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; import android.annotation.Nullable; +import android.content.Intent; +import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioSystem; +import android.media.tv.TvContract; import android.os.SystemProperties; -import android.provider.Settings.Global; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.Constants.AudioCodec; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; +import java.util.HashMap; + /** * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android * system. */ -public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { +public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { private static final String TAG = "HdmiCecLocalDeviceAudioSystem"; @@ -51,25 +56,20 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { private boolean mTvSystemAudioModeSupport; - // Whether the auido system will turn TV off when it's powering off - private boolean mAutoTvOff; - // Whether the auido system will broadcast standby to the system when it's powering off - private boolean mAutoDeviceOff; - // Whether ARC is available or not. "true" means that ARC is established between TV and // AVR as audio receiver. @ServiceThreadOnly private boolean mArcEstablished = false; - /** - * Return value of {@link #getLocalPortFromPhysicalAddress(int)} - */ - private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1; - private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0; + // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput + // when ARC is using TvInput. + private boolean mArcIntentUsed = SystemProperties + .get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, "0").contains("tvinput"); - // Local active port number used for Routing Control. - // Default 0 means HOME is the current active path. Temp solution only. - // TODO(amyjojo): adding system constants for Atom inputs port and TIF mapping. - private int mLocalActivePath = 0; + // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to + // accept input switching request from HDMI devices. Requests for which the corresponding + // input ID is not yet registered by TV input framework need to be buffered for delayed + // processing. + private final HashMap<Integer, String> mTvInputs = new HashMap<>(); protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); @@ -77,12 +77,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { // TODO(amyjojo) make System Audio Control controllable by users /*mSystemAudioControlFeatureEnabled = mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);*/ - // TODO(b/80297700): set read-only property in config instead of setting here - SystemProperties.set(Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, "false"); - mAutoDeviceOff = mService.readBooleanSetting( - Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true); - mAutoTvOff = mService.readBooleanSetting( - Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED, true); + // TODO(amyjojo): make the map ro property. + mTvInputs.put(Constants.CEC_SWITCH_HDMI1, + "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5"); + mTvInputs.put(Constants.CEC_SWITCH_HDMI2, + "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6"); + mTvInputs.put(Constants.CEC_SWITCH_HDMI3, + "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7"); } @Override @@ -97,21 +98,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { mSystemAudioActivated ? "true" : "false"); } terminateSystemAudioMode(); - - HdmiLogger.debug(TAG + " onStandby, initiatedByCec:" + initiatedByCec - + ", mAutoDeviceOff: " + mAutoDeviceOff + ", mAutoTvOff: " + mAutoTvOff); - if (!mService.isControlEnabled() || initiatedByCec) { - return; - } - if (mAutoDeviceOff) { - mService.sendCecCommand( - HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST)); - } else if (mAutoTvOff) { - mService.sendCecCommand( - HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV)); - } - return; - } @Override @@ -143,43 +129,17 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) { if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON) || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON) - && lastSystemAudioControlStatus)) { + && lastSystemAudioControlStatus)) { addAndStartAction(new SystemAudioInitiationActionFromAvr(this)); } } - @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { - assertRunOnServiceThread(); - int logicalAddress = message.getSource(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); - if (!mActiveSource.equals(activeSource)) { - setActiveSource(activeSource); - } - return true; - } - - @Override - @ServiceThreadOnly - protected boolean handleSetStreamPath(HdmiCecMessage message) { - assertRunOnServiceThread(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - // If current device is the target path, playback device should handle it. - // If the path is under the current device, should switch - int port = getLocalPortFromPhysicalAddress(physicalAddress); - if (port > 0) { - routeToPort(port); - } - return true; - } - @Override @ServiceThreadOnly protected int getPreferredAddress() { assertRunOnServiceThread(); return SystemProperties.getInt( - Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED); + Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED); } @Override @@ -249,6 +209,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleRequestArcInitiate(HdmiCecMessage message) { assertRunOnServiceThread(); + removeAction(ArcInitiationActionFromAvr.class); if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); } else if (!isDirectConnectToTv()) { @@ -270,6 +231,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { HdmiLogger.debug("ARC is not established between TV and AVR device"); mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); } else { + removeAction(ArcTerminationActionFromAvr.class); addAndStartAction(new ArcTerminationActionFromAvr(this)); } return true; @@ -323,7 +285,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { for (int i = 0; i < params.length; i++) { byte val = params[i]; audioFormatCodes[i] = - val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE; + val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE; } return audioFormatCodes; } @@ -333,7 +295,23 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) { assertRunOnServiceThread(); boolean systemAudioStatusOn = message.getParams().length != 0; - if (!setSystemAudioMode(systemAudioStatusOn)) { + // Check if the request comes from a non-TV device. + // Need to check if TV supports System Audio Control + // if non-TV device tries to turn on the feature + if (message.getSource() != Constants.ADDR_TV) { + if (systemAudioStatusOn) { + handleSystemAudioModeOnFromNonTvDevice(message); + return true; + } + } else { + // If TV request the feature on + // cache TV supporting System Audio Control + // until Audio System loses its physical address. + setTvSystemAudioModeSupport(true); + } + // If TV or Audio System does not support the feature, + // will send abort command. + if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); return true; } @@ -348,7 +326,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { assertRunOnServiceThread(); - if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) { + if (!checkSupportAndSetSystemAudioMode( + HdmiUtils.parseCommandParamSystemAudioStatus(message))) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); } return true; @@ -358,7 +337,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); - if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) { + if (!checkSupportAndSetSystemAudioMode( + HdmiUtils.parseCommandParamSystemAudioStatus(message))) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); } return true; @@ -390,7 +370,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { private void notifyArcStatusToAudioService(boolean enabled) { // Note that we don't set any name to ARC. mService.getAudioManager() - .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", ""); + .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", ""); } private void reportAudioStatus(HdmiCecMessage message) { @@ -406,7 +386,16 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { mAddress, message.getSource(), scaledVolume, mute)); } - protected boolean setSystemAudioMode(boolean newSystemAudioMode) { + /** + * Method to check if device support System Audio Control. If so, wake up device if necessary. + * + * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode + * @param newSystemAudioMode turning feature on or off. True is on. False is off. + * @return true or false. + * + * <p>False when device does not support the feature. Otherwise returns true. + */ + protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) { if (!isSystemAudioControlFeatureEnabled()) { HdmiLogger.debug( "Cannot turn " @@ -422,6 +411,17 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) { mService.wakeUp(); } + setSystemAudioMode(newSystemAudioMode); + return true; + } + + /** + * Real work to turn on or off System Audio Mode. + * + * Use {@link #checkSupportAndSetSystemAudioMode(boolean)} + * if trying to turn on or off the feature. + */ + private void setSystemAudioMode(boolean newSystemAudioMode) { int targetPhysicalAddress = getActiveSource().physicalAddress; int port = getLocalPortFromPhysicalAddress(targetPhysicalAddress); if (newSystemAudioMode && port >= 0) { @@ -449,48 +449,19 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { mService.announceSystemAudioModeChange(newSystemAudioMode); } } - return true; - } - - /** - * Method to parse target physical address to the port number on the current device. - * - * <p>This check assumes target address is valid. - * @param targetPhysicalAddress is the physical address of the target device - * @return - * <p>If the target device is under the current device, return the port number of current device - * that the target device is connected to. - * - * <p>If the target device has the same physical address as the current device, return - * {@link #TARGET_SAME_PHYSICAL_ADDRESS}. - * - * <p>If the target device is not under the current device, return - * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}. - */ - protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) { - int myPhysicalAddress = mService.getPhysicalAddress(); - if (myPhysicalAddress == targetPhysicalAddress) { - return TARGET_SAME_PHYSICAL_ADDRESS; - } - int finalMask = 0xF000; - int mask; - int port = 0; - for (mask = 0x0F00; mask > 0x000F; mask >>= 4) { - if ((myPhysicalAddress & mask) == 0) { - port = mask & targetPhysicalAddress; - break; - } else { - finalMask |= mask; - } - } - if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) { - while (mask != 0x000F) { - mask >>= 4; - port >>= 4; + // Init arc whenever System Audio Mode is on + // Terminate arc when System Audio Mode is off + // Since some TVs don't request ARC on with System Audio Mode on request + if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true) + && isDirectConnectToTv()) { + if (newSystemAudioMode && !isArcEnabled()) { + removeAction(ArcInitiationActionFromAvr.class); + addAndStartAction(new ArcInitiationActionFromAvr(this)); + } else if (!newSystemAudioMode && isArcEnabled()) { + removeAction(ArcTerminationActionFromAvr.class); + addAndStartAction(new ArcTerminationActionFromAvr(this)); } - return port; } - return TARGET_NOT_UNDER_LOCAL_DEVICE; } protected void switchToAudioInput() { @@ -515,6 +486,33 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { } } + @ServiceThreadOnly + void doManualPortSwitching(int portId, IHdmiControlCallback callback) { + assertRunOnServiceThread(); + // TODO: validate port ID + if (portId == getLocalActivePort()) { + invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); + return; + } + getActiveSource().invalidate(); + if (!mService.isControlEnabled()) { + setLocalActivePort(portId); + invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); + return; + } + int oldPath = getLocalActivePort() != Constants.CEC_SWITCH_HOME + ? getActivePathOnSwitchFromActivePortId(getLocalActivePort()) + : getDeviceInfo().getPhysicalAddress(); + int newPath = getActivePathOnSwitchFromActivePortId(portId); + if (oldPath == newPath) { + return; + } + setLocalActivePort(portId); + HdmiCecMessage routingChange = + HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); + mService.sendCecCommand(routingChange); + } + boolean isSystemAudioControlFeatureEnabled() { synchronized (mLock) { return mSystemAudioControlFeatureEnabled; @@ -534,7 +532,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { return; } - if (setSystemAudioMode(false)) { + if (checkSupportAndSetSystemAudioMode(false)) { // send <Set System Audio Mode> [“Off”] mService.sendCecCommand( HdmiCecMessageBuilder.buildSetSystemAudioMode( @@ -557,6 +555,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { * <p>The result of the query may be cached until Audio device type is put in standby or loses * its physical address. */ + // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic. void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) { if (!mTvSystemAudioModeSupport) { addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback)); @@ -565,6 +564,37 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { } } + /** + * Handler of System Audio Mode Request on from non TV device + */ + void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) { + if (!isSystemAudioControlFeatureEnabled()) { + HdmiLogger.debug( + "Cannot turn on" + "system audio mode " + + "because the System Audio Control feature is disabled."); + mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return; + } + // Wake up device if it is still on standby + if (mService.isPowerStandbyOrTransient()) { + mService.wakeUp(); + } + // Check if TV supports System Audio Control. + // Handle broadcasting setSystemAudioMode on or aborting message on callback. + queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() { + public void onResult(boolean supported) { + if (supported) { + setSystemAudioMode(true); + mService.sendCecCommand( + HdmiCecMessageBuilder.buildSetSystemAudioMode( + mAddress, Constants.ADDR_BROADCAST, true)); + } else { + mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + } + } + }); + } + void setTvSystemAudioModeSupport(boolean supported) { mTvSystemAudioModeSupport = supported; } @@ -576,26 +606,123 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { } } - @ServiceThreadOnly - protected void setAutoTvOff(boolean autoTvOff) { - assertRunOnServiceThread(); - mAutoTvOff = autoTvOff; + @Override + protected void switchInputOnReceivingNewActivePath(int physicalAddress) { + int port = getLocalPortFromPhysicalAddress(physicalAddress); + if (isSystemAudioActivated() && port < 0) { + // If system audio mode is on and the new active source is not under the current device, + // Will switch to ARC input. + // TODO(b/115637145): handle system aduio without ARC + routeToInputFromPortId(Constants.CEC_SWITCH_ARC); + } else if (mIsSwitchDevice && port >= 0) { + // If current device is a switch and the new active source is under it, + // will switch to the corresponding active path. + routeToInputFromPortId(port); + } + } + + protected void routeToInputFromPortId(int portId) { + if (mArcIntentUsed) { + routeToTvInputFromPortId(portId); + } else { + // TODO(): implement input switching for devices not using TvInput. + } + } + + protected void routeToTvInputFromPortId(int portId) { + if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) { + HdmiLogger.debug("Invalid port number for Tv Input switching."); + return; + } + // Wake up if the current device if ready to route. + if (mService.isPowerStandbyOrTransient()) { + mService.wakeUp(); + } + if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) { + switchToHomeTvInput(); + } else if (portId == Constants.CEC_SWITCH_ARC) { + switchToTvInput(SystemProperties.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT)); + setLocalActivePort(portId); + return; + } else { + String uri = mTvInputs.get(portId); + if (uri != null) { + switchToTvInput(mTvInputs.get(portId)); + } else { + HdmiLogger.debug("Port number does not match any Tv Input."); + return; + } + } + + setLocalActivePort(portId); + setRoutingPort(portId); + } + + // For device to switch to specific TvInput with corresponding URI. + private void switchToTvInput(String uri) { + mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW, + TvContract.buildChannelUriForPassthroughInput(uri)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + + // For device using TvInput to switch to Home. + private void switchToHomeTvInput() { + Intent activityIntent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_NO_ANIMATION); + mService.getContext().startActivity(activityIntent); } @Override - @ServiceThreadOnly - void setAutoDeviceOff(boolean autoDeviceOff) { - assertRunOnServiceThread(); - mAutoDeviceOff = autoDeviceOff; + protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) { + int port = getLocalPortFromPhysicalAddress(physicalAddress); + // Routing change or information sent from switches under the current device can be ignored. + if (port > 0) { + return; + } + // When other switches route to some other devices not under the current device, + // check system audio mode status and do ARC switch if needed. + if (port < 0 && isSystemAudioActivated()) { + handleRoutingChangeAndInformationForSystemAudio(); + return; + } + // When other switches route to the current device + // and the current device is also a switch. + if (port == 0) { + handleRoutingChangeAndInformationForSwitch(message); + } } - private void routeToPort(int portId) { - // TODO(AMYJOJO): route to specific input of the port - mLocalActivePath = portId; + // Handle the system audio(ARC) part of the logic on receiving routing change or information. + private void handleRoutingChangeAndInformationForSystemAudio() { + // TODO(b/115637145): handle system aduio without ARC + routeToInputFromPortId(Constants.CEC_SWITCH_ARC); } - @VisibleForTesting - protected int getLocalActivePath() { - return mLocalActivePath; + // Handle the routing control part of the logic on receiving routing change or information. + private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) { + if (getRoutingPort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) { + routeToInputFromPortId(Constants.CEC_SWITCH_HOME); + mService.setAndBroadcastActiveSourceFromOneDeviceType( + message.getSource(), mService.getPhysicalAddress()); + return; + } + + int routingInformationPath = + getActivePathOnSwitchFromActivePortId(getRoutingPort()); + // If current device is already the leaf of the whole HDMI system, will do nothing. + if (routingInformationPath == mService.getPhysicalAddress()) { + HdmiLogger.debug("Current device can't assign valid physical address" + + "to devices under it any more. " + + "It's physical address is " + routingInformationPath); + return; + } + // Otherwise will switch to the current active port and broadcast routing information. + mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingInformation( + mAddress, routingInformationPath)); + routeToInputFromPortId(getRoutingPort()); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index d45b00bd904b..07db9716497c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -21,11 +21,11 @@ import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.os.PowerManager; import android.os.PowerManager.WakeLock; -import android.os.RemoteException; import android.os.SystemProperties; import android.provider.Settings.Global; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.LocalePicker; import com.android.internal.app.LocalePicker.LocaleInfo; import com.android.internal.util.IndentingPrintWriter; @@ -35,12 +35,10 @@ import java.io.UnsupportedEncodingException; import java.util.List; import java.util.Locale; -import java.util.List; - /** * Represent a logical device of type Playback residing in Android system. */ -final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { +public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { private static final String TAG = "HdmiCecLocalDevicePlayback"; private static final boolean WAKE_ON_HOTPLUG = @@ -49,8 +47,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { private static final boolean SET_MENU_LANGUAGE = SystemProperties.getBoolean(Constants.PROPERTY_SET_MENU_LANGUAGE, false); - private boolean mIsActiveSource = false; - // Used to keep the device awake while it is the active source. For devices that // cannot wake up via CEC commands, this address the inconvenience of having to // turn them on. True by default, and can be disabled (i.e. device can go to sleep @@ -62,6 +58,11 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { // If true, turn off TV upon standby. False by default. private boolean mAutoTvOff; + // Local active port number used for Routing Control. + // Default 0 means HOME is the current active path. Temp solution only. + // TODO(amyjojo): adding system constants for input ports to TIF mapping. + private int mLocalActivePath = 0; + HdmiCecLocalDevicePlayback(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_PLAYBACK); @@ -100,25 +101,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } @ServiceThreadOnly - void oneTouchPlay(IHdmiControlCallback callback) { - assertRunOnServiceThread(); - List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class); - if (!actions.isEmpty()) { - Slog.i(TAG, "oneTouchPlay already in progress"); - actions.get(0).addCallback(callback); - return; - } - OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV, - callback); - if (action == null) { - Slog.w(TAG, "Cannot initiate oneTouchPlay"); - invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION); - return; - } - addAndStartAction(action); - } - - @ServiceThreadOnly void queryDisplayStatus(IHdmiControlCallback callback) { assertRunOnServiceThread(); List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class); @@ -137,16 +119,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { addAndStartAction(action); } - @ServiceThreadOnly - private void invokeCallback(IHdmiControlCallback callback, int result) { - assertRunOnServiceThread(); - try { - callback.onComplete(result); - } catch (RemoteException e) { - Slog.e(TAG, "Invoking callback failed:" + e); - } - } - @Override @ServiceThreadOnly void onHotplug(int portId, boolean connected) { @@ -189,7 +161,8 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } @ServiceThreadOnly - void setActiveSource(boolean on) { + @VisibleForTesting + void setIsActiveSource(boolean on) { assertRunOnServiceThread(); mIsActiveSource = on; if (on) { @@ -227,21 +200,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { return !getWakeLock().isHeld(); } - @Override - @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { - assertRunOnServiceThread(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - mayResetActiveSource(physicalAddress); - return true; // Broadcast message. - } - - private void mayResetActiveSource(int physicalAddress) { - if (physicalAddress != mService.getPhysicalAddress()) { - setActiveSource(false); - } - } - @ServiceThreadOnly protected boolean handleUserControlPressed(HdmiCecMessage message) { assertRunOnServiceThread(); @@ -250,42 +208,7 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } @Override - @ServiceThreadOnly - protected boolean handleSetStreamPath(HdmiCecMessage message) { - assertRunOnServiceThread(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - maySetActiveSource(physicalAddress); - maySendActiveSource(message.getSource()); - wakeUpIfActiveSource(); - return true; // Broadcast message. - } - - // Samsung model we tested sends <Routing Change> and <Request Active Source> - // in a row, and then changes the input to the internal source if there is no - // <Active Source> in response. To handle this, we'll set ActiveSource aggressively. - @Override - @ServiceThreadOnly - protected boolean handleRoutingChange(HdmiCecMessage message) { - assertRunOnServiceThread(); - int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2); - maySetActiveSource(newPath); - return true; // Broadcast message. - } - - @Override - @ServiceThreadOnly - protected boolean handleRoutingInformation(HdmiCecMessage message) { - assertRunOnServiceThread(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - maySetActiveSource(physicalAddress); - return true; // Broadcast message. - } - - private void maySetActiveSource(int physicalAddress) { - setActiveSource(physicalAddress == mService.getPhysicalAddress()); - } - - private void wakeUpIfActiveSource() { + protected void wakeUpIfActiveSource() { if (!mIsActiveSource) { return; } @@ -296,7 +219,8 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } } - private void maySendActiveSource(int dest) { + @Override + protected void maySendActiveSource(int dest) { if (mIsActiveSource) { mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( mAddress, mService.getPhysicalAddress())); @@ -306,14 +230,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } } - @Override - @ServiceThreadOnly - protected boolean handleRequestActiveSource(HdmiCecMessage message) { - assertRunOnServiceThread(); - maySendActiveSource(message.getSource()); - return true; // Broadcast message. - } - @ServiceThreadOnly protected boolean handleSetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); @@ -361,16 +277,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected void sendStandby(int deviceId) { - assertRunOnServiceThread(); - - // Playback device can send <Standby> to TV only. Ignore the parameter. - int targetAddress = Constants.ADDR_TV; - mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress)); - } - - @Override - @ServiceThreadOnly protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { super.disableDevice(initiatedByCec, callback); @@ -379,10 +285,20 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource( mAddress, mService.getPhysicalAddress())); } - setActiveSource(false); + setIsActiveSource(false); checkIfPendingActionsCleared(); } + private void routeToPort(int portId) { + // TODO(AMYJOJO): route to specific input of the port + mLocalActivePath = portId; + } + + @VisibleForTesting + protected int getLocalActivePath() { + return mLocalActivePath; + } + @Override protected void dump(final IndentingPrintWriter pw) { super.dump(pw); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java new file mode 100644 index 000000000000..6532e16cffe5 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -0,0 +1,276 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; +import android.os.SystemProperties; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.hdmi.Constants.LocalActivePort; +import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; + +import java.util.List; + +/** + * Represent a logical source device residing in Android system. + */ +abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { + + private static final String TAG = "HdmiCecLocalDeviceSource"; + + // Indicate if current device is Active Source or not + @VisibleForTesting + protected boolean mIsActiveSource = false; + + // Device has cec switch functionality or not. + // Default is false. + protected boolean mIsSwitchDevice = SystemProperties.getBoolean( + Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); + + // Routing port number used for Routing Control. + // This records the default routing port or the previous valid routing port. + // Default is HOME input. + // Note that we don't save active path here because for source device, + // new Active Source physical address might not match the active path + @GuardedBy("mLock") + @LocalActivePort + private int mRoutingPort = Constants.CEC_SWITCH_HOME; + + // This records the current input of the device. + // When device is switched to ARC input, mRoutingPort does not record it + // since it's not an HDMI port used for Routing Control. + // mLocalActivePort will record whichever input we switch to to keep tracking on + // the current input status of the device. + // This can help prevent duplicate switching and provide status information. + @GuardedBy("mLock") + @LocalActivePort + protected int mLocalActivePort = Constants.CEC_SWITCH_HOME; + + protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) { + super(service, deviceType); + } + + @Override + @ServiceThreadOnly + void onHotplug(int portId, boolean connected) { + assertRunOnServiceThread(); + mCecMessageCache.flushAll(); + // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3. + if (mService.isPowerStandbyOrTransient()) { + mService.wakeUp(); + } + } + + @Override + @ServiceThreadOnly + protected void sendStandby(int deviceId) { + assertRunOnServiceThread(); + + // Send standby to TV only for now + int targetAddress = Constants.ADDR_TV; + mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress)); + } + + @ServiceThreadOnly + void oneTouchPlay(IHdmiControlCallback callback) { + assertRunOnServiceThread(); + List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class); + if (!actions.isEmpty()) { + Slog.i(TAG, "oneTouchPlay already in progress"); + actions.get(0).addCallback(callback); + return; + } + OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV, + callback); + if (action == null) { + Slog.w(TAG, "Cannot initiate oneTouchPlay"); + invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION); + return; + } + addAndStartAction(action); + } + + @ServiceThreadOnly + protected boolean handleActiveSource(HdmiCecMessage message) { + assertRunOnServiceThread(); + int logicalAddress = message.getSource(); + int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); + ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); + if (!getActiveSource().equals(activeSource)) { + setActiveSource(activeSource); + } + setIsActiveSource(physicalAddress == mService.getPhysicalAddress()); + switchInputOnReceivingNewActivePath(physicalAddress); + return true; + } + + @Override + @ServiceThreadOnly + protected boolean handleRequestActiveSource(HdmiCecMessage message) { + assertRunOnServiceThread(); + maySendActiveSource(message.getSource()); + return true; + } + + @Override + @ServiceThreadOnly + protected boolean handleSetStreamPath(HdmiCecMessage message) { + assertRunOnServiceThread(); + int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); + // If current device is the target path, set to Active Source. + // If the path is under the current device, should switch + if (physicalAddress == mService.getPhysicalAddress() && mService.isPlaybackDevice()) { + setAndBroadcastActiveSource(message, physicalAddress); + } + switchInputOnReceivingNewActivePath(physicalAddress); + return true; + } + + @Override + @ServiceThreadOnly + protected boolean handleRoutingChange(HdmiCecMessage message) { + assertRunOnServiceThread(); + int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2); + // if the current device is a pure playback device + if (!mIsSwitchDevice + && newPath == mService.getPhysicalAddress() + && mService.isPlaybackDevice()) { + setAndBroadcastActiveSource(message, newPath); + } + handleRoutingChangeAndInformation(newPath, message); + return true; + } + + @Override + @ServiceThreadOnly + protected boolean handleRoutingInformation(HdmiCecMessage message) { + assertRunOnServiceThread(); + int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); + // if the current device is a pure playback device + if (!mIsSwitchDevice + && physicalAddress == mService.getPhysicalAddress() + && mService.isPlaybackDevice()) { + setAndBroadcastActiveSource(message, physicalAddress); + } + handleRoutingChangeAndInformation(physicalAddress, message); + return true; + } + + // Method to switch Input with the new Active Path. + // All the devices with Switch functionality should implement this. + protected void switchInputOnReceivingNewActivePath(int physicalAddress) { + // do nothing + } + + // Source device with Switch functionality should implement this method. + // TODO(): decide which type will handle the routing when multi device type is supported + protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) { + // do nothing + } + + // Active source claiming needs to be handled in Service + // since service can decide who will be the active source when the device supports + // multiple device types in this method. + // This method should only be called when the device can be the active source. + protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) { + mService.setAndBroadcastActiveSource( + message, physicalAddress, getDeviceInfo().getDeviceType()); + } + + @ServiceThreadOnly + void setIsActiveSource(boolean on) { + assertRunOnServiceThread(); + mIsActiveSource = on; + } + + protected void wakeUpIfActiveSource() { + if (!mIsActiveSource) { + return; + } + // Wake up the device if the power is in standby mode + if (mService.isPowerStandbyOrTransient()) { + mService.wakeUp(); + } + return; + } + + protected void maySendActiveSource(int dest) { + if (mIsActiveSource) { + mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( + mAddress, mService.getPhysicalAddress())); + } + } + + /** + * Set {@link #mRoutingPort} to a specific {@link LocalActivePort} to record the current active + * CEC Routing Control related port. + * + * @param portId The portId of the new routing port. + */ + @VisibleForTesting + protected void setRoutingPort(@LocalActivePort int portId) { + synchronized (mLock) { + mRoutingPort = portId; + } + } + + /** + * Get {@link #mRoutingPort}. This is useful when the device needs to route to the last valid + * routing port. + */ + @LocalActivePort + protected int getRoutingPort() { + synchronized (mLock) { + return mRoutingPort; + } + } + + /** + * Get {@link #mLocalActivePort}. This is useful when device needs to know the current active + * port. + */ + @LocalActivePort + protected int getLocalActivePort() { + synchronized (mLock) { + return mLocalActivePort; + } + } + + /** + * Set {@link #mLocalActivePort} to a specific {@link LocalActivePort} to record the current + * active port. + * + * <p>It does not have to be a Routing Control related port. For example it can be + * set to {@link Constants#CEC_SWITCH_ARC} but this port is System Audio related. + * + * @param activePort The portId of the new active port. + */ + protected void setLocalActivePort(@LocalActivePort int activePort) { + synchronized (mLock) { + mLocalActivePort = activePort; + } + } + + // Check if the device is trying to switch to the same input that is active right now. + // This can help avoid redundant port switching. + protected boolean isSwitchingToTheSameInput(@LocalActivePort int activePort) { + return activePort == getLocalActivePort(); + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 25ca27836aa7..b91d8c637c79 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -41,17 +41,18 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager.TvInputCallback; -import android.os.RemoteException; import android.provider.Settings.Global; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; + import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; @@ -307,7 +308,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { private void handleSelectInternalSource() { assertRunOnServiceThread(); // Seq #18 - if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) { + if (mService.isControlEnabled() && getActiveSource().logicalAddress != mAddress) { updateActiveSource(mAddress, mService.getPhysicalAddress()); if (mSkipRoutingControl) { mSkipRoutingControl = false; @@ -329,7 +330,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { void updateActiveSource(ActiveSource newActive) { assertRunOnServiceThread(); // Seq #14 - if (mActiveSource.equals(newActive)) { + if (getActiveSource().equals(newActive)) { return; } setActiveSource(newActive); @@ -402,7 +403,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); return; } - mActiveSource.invalidate(); + getActiveSource().invalidate(); if (!mService.isControlEnabled()) { setActivePortId(portId); invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); @@ -452,17 +453,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return Constants.ADDR_INVALID; } - private static void invokeCallback(IHdmiControlCallback callback, int result) { - if (callback == null) { - return; - } - try { - callback.onComplete(result); - } catch (RemoteException e) { - Slog.e(TAG, "Invoking callback failed:" + e); - } - } - @Override @ServiceThreadOnly protected boolean handleActiveSource(HdmiCecMessage message) { @@ -518,7 +508,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } else { // No HDMI port to switch to was found. Notify the input change listers to // switch to the lastly shown internal input. - mActiveSource.invalidate(); + getActiveSource().invalidate(); setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE); } @@ -685,7 +675,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { byte[] params = message.getParams(); int currentPath = HdmiUtils.twoBytesToInt(params); if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) { - mActiveSource.invalidate(); + getActiveSource().invalidate(); removeAction(RoutingControlAction.class); int newPath = HdmiUtils.twoBytesToInt(params, 2); addAndStartAction(new RoutingControlAction(this, newPath, true, null)); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 941c321d484a..3f949bab8a2e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -365,6 +365,20 @@ public class HdmiCecMessageBuilder { } /** + * Build <Routing Information> command. + * + * <p>This is a broadcast message sent to all devices on the bus. + * + * @param src source address of command + * @param physicalAddress physical address of the new active routing path + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildRoutingInformation(int src, int physicalAddress) { + return buildCommand(src, Constants.ADDR_BROADCAST, + Constants.MESSAGE_ROUTING_INFORMATION, physicalAddressToParam(physicalAddress)); + } + + /** * Build <Give Device Power Status> command. * * @param src source address of command diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index e3a4084ab7f3..833091df5f1c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -140,6 +140,14 @@ public class HdmiControlService extends SystemService { static final int STANDBY_SCREEN_OFF = 0; static final int STANDBY_SHUTDOWN = 1; + // Logical address of the active source. + @GuardedBy("mLock") + protected final ActiveSource mActiveSource = new ActiveSource(); + + private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr = + SystemProperties.getBoolean( + Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false); + /** * Interface to report send result. */ @@ -413,7 +421,7 @@ public class HdmiControlService extends SystemService { mSettingsObserver = new SettingsObserver(mHandler); } - private static List<Integer> getIntList(String string) { + protected static List<Integer> getIntList(String string) { ArrayList<Integer> list = new ArrayList<>(); TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(','); splitter.setString(string); @@ -592,11 +600,6 @@ public class HdmiControlService extends SystemService { } // No need to propagate to HAL. break; - case Global.HDMI_CONTROL_AUTO_TV_OFF_ENABLED: - if (isAudioSystemDevice()) { - audioSystem().setAutoTvOff(enabled); - } - break; case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED: if (isTvDeviceEnabled()) { tv().setSystemAudioControlFeatureEnabled(enabled); @@ -639,6 +642,10 @@ public class HdmiControlService extends SystemService { // A container for [Device type, Local device info]. ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); for (int type : mLocalDevices) { + if (type == HdmiDeviceInfo.DEVICE_PLAYBACK + && isHdmiCecNeverClaimPlaybackLogicAddr) { + continue; + } HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); if (localDevice == null) { localDevice = HdmiCecLocalDevice.create(this, type); @@ -1001,8 +1008,16 @@ public class HdmiControlService extends SystemService { assertRunOnServiceThread(); if (connected && !isTvDevice()) { + if (getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT && isSwitchDevice()) { + initPortInfo(); + HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx."); + } ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); for (int type : mLocalDevices) { + if (type == HdmiDeviceInfo.DEVICE_PLAYBACK + && isHdmiCecNeverClaimPlaybackLogicAddr) { + continue; + } HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); if (localDevice == null) { localDevice = HdmiCecLocalDevice.create(this, type); @@ -1382,17 +1397,24 @@ public class HdmiControlService extends SystemService { return; } HdmiCecLocalDeviceTv tv = tv(); - if (tv == null) { - if (!mAddressAllocated) { - mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect( - HdmiControlService.this, portId, callback)); - return; - } - Slog.w(TAG, "Local tv device not available"); - invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); + if (tv != null) { + tv.doManualPortSwitching(portId, callback); + return; + } + HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); + if (audioSystem != null) { + audioSystem.doManualPortSwitching(portId, callback); + return; + } + + if (!mAddressAllocated) { + mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect( + HdmiControlService.this, portId, callback)); return; } - tv.doManualPortSwitching(portId, callback); + Slog.w(TAG, "Local device not available"); + invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); + return; } }); } @@ -1651,6 +1673,9 @@ public class HdmiControlService extends SystemService { } HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); if (device == null) { + device = audioSystem(); + } + if (device == null) { Slog.w(TAG, "Local device not available"); return; } @@ -1830,9 +1855,13 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private void oneTouchPlay(final IHdmiControlCallback callback) { assertRunOnServiceThread(); - HdmiCecLocalDevicePlayback source = playback(); + HdmiCecLocalDeviceSource source = playback(); if (source == null) { - Slog.w(TAG, "Local playback device not available"); + source = audioSystem(); + } + + if (source == null) { + Slog.w(TAG, "Local source device not available"); invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); return; } @@ -2095,11 +2124,20 @@ public class HdmiControlService extends SystemService { return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); } + boolean isPlaybackDevice() { + return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK); + } + + boolean isSwitchDevice() { + return SystemProperties.getBoolean( + Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false); + } + boolean isTvDeviceEnabled() { return isTvDevice() && tv() != null; } - private HdmiCecLocalDevicePlayback playback() { + protected HdmiCecLocalDevicePlayback playback() { return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); } @@ -2470,6 +2508,77 @@ public class HdmiControlService extends SystemService { setLastInputForMhl(Constants.INVALID_PORT_ID); } + ActiveSource getActiveSource() { + synchronized (mLock) { + return mActiveSource; + } + } + + void setActiveSource(int logicalAddress, int physicalAddress) { + synchronized (mLock) { + mActiveSource.logicalAddress = logicalAddress; + mActiveSource.physicalAddress = physicalAddress; + } + } + + // This method should only be called when the device can be the active source + // and all the device types call into this method. + // For example, when receiving broadcast messages, all the device types will call this + // method but only one of them will be the Active Source. + protected void setAndBroadcastActiveSource( + HdmiCecMessage message, int physicalAddress, int deviceType) { + // If the device has both playback and audio system logical addresses, + // playback will claim active source. Otherwise audio system will. + if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) { + HdmiCecLocalDevicePlayback playback = playback(); + playback.setIsActiveSource(true); + playback.wakeUpIfActiveSource(); + playback.maySendActiveSource(message.getSource()); + setActiveSource(playback.mAddress, physicalAddress); + } + + if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { + HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); + if (playback() != null) { + audioSystem.setIsActiveSource(false); + } else { + audioSystem.setIsActiveSource(true); + audioSystem.wakeUpIfActiveSource(); + audioSystem.maySendActiveSource(message.getSource()); + setActiveSource(audioSystem.mAddress, physicalAddress); + } + } + } + + // This method should only be called when the device can be the active source + // and only one of the device types calls into this method. + // For example, when receiving One Touch Play, only playback device handles it + // and this method updates Active Source in all the device types sharing the same + // Physical Address. + protected void setAndBroadcastActiveSourceFromOneDeviceType( + int sourceAddress, int physicalAddress) { + // If the device has both playback and audio system logical addresses, + // playback will claim active source. Otherwise audio system will. + HdmiCecLocalDevicePlayback playback = playback(); + HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); + if (playback != null) { + playback.setIsActiveSource(true); + playback.wakeUpIfActiveSource(); + playback.maySendActiveSource(sourceAddress); + if (audioSystem != null) { + audioSystem.setIsActiveSource(false); + } + setActiveSource(playback.mAddress, physicalAddress); + } else { + if (audioSystem != null) { + audioSystem.setIsActiveSource(true); + audioSystem.wakeUpIfActiveSource(); + audioSystem.maySendActiveSource(sourceAddress); + setActiveSource(audioSystem.mAddress, physicalAddress); + } + } + } + @ServiceThreadOnly void setLastInputForMhl(int portId) { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index 5c66316da60c..41bf01f842cd 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -16,8 +16,8 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; -import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; +import android.hardware.hdmi.IHdmiControlCallback; import android.os.RemoteException; import android.util.Slog; @@ -55,7 +55,7 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { private int mPowerStatusCounter = 0; // Factory method. Ensures arguments are valid. - static OneTouchPlayAction create(HdmiCecLocalDevicePlayback source, + static OneTouchPlayAction create(HdmiCecLocalDeviceSource source, int targetAddress, IHdmiControlCallback callback) { if (source == null || callback == null) { Slog.e(TAG, "Wrong arguments"); @@ -83,9 +83,17 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { } private void broadcastActiveSource() { - sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath())); - // Because only playback device can create this action, it's safe to cast. - playback().setActiveSource(true); + // Because only source device can create this action, it's safe to cast. + HdmiCecLocalDeviceSource source = source(); + source.mService.setAndBroadcastActiveSourceFromOneDeviceType( + mTargetAddress, getSourcePath()); + // Set local active port to HOME when One Touch Play. + // Active Port and Current Input are handled by the switch functionality device. + if (source.mService.audioSystem() != null) { + source = source.mService.audioSystem(); + } + source.setRoutingPort(Constants.CEC_SWITCH_HOME); + source.setLocalActivePort(Constants.CEC_SWITCH_HOME); } private void queryDevicePowerStatus() { diff --git a/services/core/java/com/android/server/hdmi/SelectRequestBuffer.java b/services/core/java/com/android/server/hdmi/SelectRequestBuffer.java index 75986c7ba1c1..ba16260bbfb7 100644 --- a/services/core/java/com/android/server/hdmi/SelectRequestBuffer.java +++ b/services/core/java/com/android/server/hdmi/SelectRequestBuffer.java @@ -56,6 +56,10 @@ public class SelectRequestBuffer { return mService.tv(); } + protected HdmiCecLocalDeviceAudioSystem audioSystem() { + return mService.audioSystem(); + } + protected boolean isLocalDeviceReady() { if (tv() == null) { Slog.e(TAG, "Local tv device not available"); @@ -105,7 +109,15 @@ public class SelectRequestBuffer { public void process() { if (isLocalDeviceReady()) { Slog.v(TAG, "calling delayed portSelect id:" + mId); - tv().doManualPortSwitching(mId, mCallback); + HdmiCecLocalDeviceTv tv = tv(); + if (tv != null) { + tv.doManualPortSwitching(mId, mCallback); + return; + } + HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); + if (audioSystem != null) { + audioSystem.doManualPortSwitching(mId, mCallback); + } } } } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java index 2fdcb5106595..b6ebcd7c3ec2 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; import android.hardware.tv.cec.V1_0.SendMessageResult; + import com.android.internal.annotations.VisibleForTesting; /** @@ -40,7 +41,7 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { @Override boolean start() { - if (audioSystem().mActiveSource.physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) { + if (audioSystem().getActiveSource().physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) { mState = STATE_WAITING_FOR_ACTIVE_SOURCE; addTimer(mState, HdmiConfig.TIMEOUT_MS); sendRequestActiveSource(); @@ -59,10 +60,8 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { return false; } mActionTimer.clearTimerMessage(); - int physicalAddress = HdmiUtils.twoBytesToInt(cmd.getParams()); - if (physicalAddress != getSourcePath()) { - audioSystem().setActiveSource(cmd.getSource(), physicalAddress); - } + // Broadcast message is also handled by other device types + audioSystem().handleActiveSource(cmd); mState = STATE_WAITING_FOR_TV_SUPPORT; queryTvSystemAudioModeSupport(); return true; @@ -91,7 +90,7 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { mSendRequestActiveSourceRetryCount++; sendRequestActiveSource(); } else { - audioSystem().setSystemAudioMode(false); + audioSystem().checkSupportAndSetSystemAudioMode(false); finish(); } } @@ -106,7 +105,7 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { mSendSetSystemAudioModeRetryCount++; sendSetSystemAudioMode(on, dest); } else { - audioSystem().setSystemAudioMode(false); + audioSystem().checkSupportAndSetSystemAudioMode(false); finish(); } } @@ -115,7 +114,16 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { private void handleActiveSourceTimeout() { HdmiLogger.debug("Cannot get active source."); - audioSystem().setSystemAudioMode(false); + // If not able to find Active Source and the current device has playbcak functionality, + // claim Active Source and start to query TV system audio mode support. + if (audioSystem().mService.isPlaybackDevice()) { + audioSystem().mService.setAndBroadcastActiveSourceFromOneDeviceType( + Constants.ADDR_BROADCAST, getSourcePath()); + mState = STATE_WAITING_FOR_TV_SUPPORT; + queryTvSystemAudioModeSupport(); + } else { + audioSystem().checkSupportAndSetSystemAudioMode(false); + } finish(); } @@ -123,12 +131,12 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { audioSystem().queryTvSystemAudioModeSupport( supported -> { if (supported) { - if (audioSystem().setSystemAudioMode(true)) { + if (audioSystem().checkSupportAndSetSystemAudioMode(true)) { sendSetSystemAudioMode(true, Constants.ADDR_BROADCAST); } finish(); } else { - audioSystem().setSystemAudioMode(false); + audioSystem().checkSupportAndSetSystemAudioMode(false); finish(); } }); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index e7c3c7bbe21b..df28f30d9127 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -213,8 +213,6 @@ public class InputManagerService extends IInputManager.Stub private static native void nativeSetFocusedApplication(long ptr, int displayId, InputApplicationHandle application); private static native void nativeSetFocusedDisplay(long ptr, int displayId); - private static native boolean nativeTransferTouchFocus(long ptr, - InputChannel fromChannel, InputChannel toChannel); private static native void nativeSetPointerSpeed(long ptr, int speed); private static native void nativeSetShowTouches(long ptr, boolean enabled); private static native void nativeSetInteractive(long ptr, boolean interactive); @@ -1485,29 +1483,6 @@ public class InputManagerService extends IInputManager.Stub nativeSetSystemUiVisibility(mPtr, visibility); } - /** - * Atomically transfers touch focus from one window to another as identified by - * their input channels. It is possible for multiple windows to have - * touch focus if they support split touch dispatch - * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this - * method only transfers touch focus of the specified window without affecting - * other windows that may also have touch focus at the same time. - * @param fromChannel The channel of a window that currently has touch focus. - * @param toChannel The channel of the window that should receive touch focus in - * place of the first. - * @return True if the transfer was successful. False if the window with the - * specified channel did not actually have touch focus at the time of the request. - */ - public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { - if (fromChannel == null) { - throw new IllegalArgumentException("fromChannel must not be null."); - } - if (toChannel == null) { - throw new IllegalArgumentException("toChannel must not be null."); - } - return nativeTransferTouchFocus(mPtr, fromChannel, toChannel); - } - @Override // Binder call public void tryPointerSpeed(int speed) { if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED, @@ -1785,15 +1760,14 @@ public class InputManagerService extends IInputManager.Stub } // Native callback - private void notifyFocusChanged(IBinder token) { - if (mFocusedWindow != token) { - if (mFocusedWindowHasCapture) { - setPointerCapture(false); - } - if (token instanceof IWindow) { - mFocusedWindow = (IWindow) token; - } + private void notifyFocusChanged(IBinder oldToken, IBinder newToken) { + if (mFocusedWindow.asBinder() == newToken) { + Log.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow=" + mFocusedWindow); + return; } + + setPointerCapture(false); + mFocusedWindow = IWindow.Stub.asInterface(newToken); } // Native callback. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 326479039ac8..d4b8eb2db007 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -16,7 +16,15 @@ 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; + +import java.util.Collections; +import java.util.List; /** * Input method manager local system service interface. @@ -39,9 +47,25 @@ public abstract class InputMethodManagerInternal { 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. + * @return A list of {@link InputMethodInfo}. VR-only IMEs are already excluded. + */ + public abstract List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId); + + /** + * Returns the list of installed input methods that are enabled for the specified user. + * + * @param userId The user ID to be queried. + * @return A list of {@link InputMethodInfo} that are enabled for {@code userId}. + */ + public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId); + + /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. */ - public static final InputMethodManagerInternal NOP = + private static final InputMethodManagerInternal NOP = new InputMethodManagerInternal() { @Override public void setInteractive(boolean interactive) { @@ -54,5 +78,25 @@ public abstract class InputMethodManagerInternal { @Override public void startVrInputMethodNoCheck(ComponentName componentName) { } + + @Override + public List<InputMethodInfo> getInputMethodListAsUser(int userId) { + return Collections.emptyList(); + } + + @Override + public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) { + return Collections.emptyList(); + } }; + + /** + * @return Global instance if exists. Otherwise, a dummy no-op instance. + */ + @NonNull + public static InputMethodManagerInternal get() { + final InputMethodManagerInternal instance = + LocalServices.getService(InputMethodManagerInternal.class); + return instance != null ? instance : NOP; + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d45869e07586..5fa3f52c6302 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -20,6 +20,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.inputmethod.InputMethodSystemProperty.PER_PROFILE_IME_ENABLED; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -1603,7 +1604,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() { + private boolean calledFromValidUserLocked(boolean allowCrossProfileAccess) { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserId(uid); if (DEBUG) { @@ -1613,7 +1614,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid() + InputMethodUtils.getApiCallStack()); } - if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) { + if (uid == Process.SYSTEM_UID) { + return true; + } + if (userId == mSettings.getCurrentUserId()) { + return true; + } + if (allowCrossProfileAccess && mSettings.isCurrentProfile(userId)) { return true; } @@ -1674,40 +1681,93 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodInfo> getInputMethodList() { - return getInputMethodList(false /* isVrOnly */); + 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(false /* isVrOnly */, resolvedUserIds[0]); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } @Override public List<InputMethodInfo> getVrInputMethodList() { - return getInputMethodList(true /* isVrOnly */); - } - - private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) { + final int callingUserId = UserHandle.getCallingUserId(); synchronized (mMethodMap) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { + final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, + mSettings.getCurrentUserId(), null); + if (resolvedUserIds.length != 1) { return Collections.emptyList(); } - ArrayList<InputMethodInfo> methodList = new ArrayList<>(); - for (InputMethodInfo info : mMethodList) { - - if (info.isVrOnly() == isVrOnly) { - methodList.add(info); - } + final long ident = Binder.clearCallingIdentity(); + try { + return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]); + } finally { + Binder.restoreCallingIdentity(ident); } - return methodList; } } @Override public List<InputMethodInfo> getEnabledInputMethodList() { + final int callingUserId = UserHandle.getCallingUserId(); synchronized (mMethodMap) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { + final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, + mSettings.getCurrentUserId(), null); + if (resolvedUserIds.length != 1) { return Collections.emptyList(); } + final long ident = Binder.clearCallingIdentity(); + try { + return getEnabledInputMethodListLocked(resolvedUserIds[0]); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @GuardedBy("mMethodMap") + private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly, + @UserIdInt int userId) { + final ArrayList<InputMethodInfo> methodList; + if (userId == mSettings.getCurrentUserId()) { + // Create a copy. + methodList = new ArrayList<>(mMethodList); + } else { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, + methodList); + } + methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly); + return methodList; + } + + @GuardedBy("mMethodMap") + private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) { + if (userId == mSettings.getCurrentUserId()) { return mSettings.getEnabledInputMethodListLocked(); } + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, + methodList); + final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), + mContext.getContentResolver(), methodMap, methodList, userId, true); + return settings.getEnabledInputMethodListLocked(); } /** @@ -1717,11 +1777,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlySelectedSubtypes) { + final int callingUserId = UserHandle.getCallingUserId(); synchronized (mMethodMap) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { + final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, + mSettings.getCurrentUserId(), null); + if (resolvedUserIds.length != 1) { return Collections.emptyList(); } + final long ident = Binder.clearCallingIdentity(); + try { + return getEnabledInputMethodSubtypeListLocked(imiId, + allowsImplicitlySelectedSubtypes, resolvedUserIds[0]); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @GuardedBy("mMethodMap") + private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId, + boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) { + if (userId == mSettings.getCurrentUserId()) { final InputMethodInfo imi; if (imiId == null && mCurMethodId != null) { imi = mMethodMap.get(mCurMethodId); @@ -1734,6 +1810,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mSettings.getEnabledInputMethodSubtypeListLocked( mContext, imi, allowsImplicitlySelectedSubtypes); } + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, + methodList); + final InputMethodInfo imi = methodMap.get(imiId); + if (imi == null) { + return Collections.emptyList(); + } + final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), + mContext.getContentResolver(), methodMap, methodList, userId, true); + return settings.getEnabledInputMethodSubtypeListLocked( + mContext, imi, allowsImplicitlySelectedSubtypes); } /** @@ -2434,7 +2525,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) { synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return; } final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); @@ -2450,7 +2541,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) { synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return false; } final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span); @@ -2617,7 +2708,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ResultReceiver resultReceiver) { int uid = Binder.getCallingUid(); synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return false; } final long ident = Binder.clearCallingIdentity(); @@ -2702,7 +2793,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ResultReceiver resultReceiver) { int uid = Binder.getCallingUid(); synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return false; } final long ident = Binder.clearCallingIdentity(); @@ -2809,10 +2900,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute, IInputContext inputContext, @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) { + final int userId = UserHandle.getUserId(Binder.getCallingUid()); InputBindResult res = null; synchronized (mMethodMap) { // Needs to check the validity before clearing calling identity - final boolean calledFromValidUser = calledFromValidUserLocked(); + // Note that cross-profile access is always allowed here to allow profile-switching. + final boolean calledFromValidUser = calledFromValidUserLocked(true); final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); final long ident = Binder.clearCallingIdentity(); @@ -2864,6 +2957,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return InputBindResult.INVALID_USER; } + if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) { + switchUserLocked(userId); + } + if (mCurFocusedWindow == windowToken) { if (DEBUG) { Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client @@ -3032,7 +3129,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void showInputMethodPickerFromClient( IInputMethodClient client, int auxiliarySubtypeMode) { synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return; } if(!canShowInputMethodPickerLocked(client)) { @@ -3103,7 +3200,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub IInputMethodClient client, String inputMethodId) { synchronized (mMethodMap) { // TODO(yukawa): Should we verify the display ID? - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return; } executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( @@ -3218,7 +3315,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public InputMethodSubtype getLastInputMethodSubtype() { synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return null; } final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); @@ -3256,7 +3353,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return; } if (!mSystemReady) { @@ -4102,7 +4199,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public InputMethodSubtype getCurrentInputMethodSubtype() { synchronized (mMethodMap) { // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return null; } return getCurrentInputMethodSubtypeLocked(); @@ -4152,7 +4249,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()) { + if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) { return false; } if (subtype != null && mCurMethodId != null) { @@ -4167,6 +4264,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { + synchronized (mMethodMap) { + return getInputMethodListLocked(false, userId); + } + } + + private List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) { + synchronized (mMethodMap) { + return getEnabledInputMethodListLocked(userId); + } + } + private static final class LocalServiceImpl extends InputMethodManagerInternal { @NonNull private final InputMethodManagerService mService; @@ -4192,6 +4301,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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); + } + + @Override + public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) { + return mService.getEnabledInputMethodListAsUser(userId); + } } @BinderThread @@ -4545,6 +4664,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) { boolean all = false; boolean brief = false; + int userIdToBeResolved = UserHandle.USER_CURRENT; while (true) { final String nextOption = shellCommand.getNextOption(); if (nextOption == null) { @@ -4557,19 +4677,34 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case "-s": brief = true; break; + case "-u": + case "--user": + userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired()); + break; } } - final List<InputMethodInfo> methods = all ? - getInputMethodList() : getEnabledInputMethodList(); - final PrintWriter pr = shellCommand.getOutPrintWriter(); - final Printer printer = x -> pr.println(x); - final int N = methods.size(); - for (int i = 0; i < N; ++i) { - if (brief) { - pr.println(methods.get(i).getId()); - } else { - pr.print(methods.get(i).getId()); pr.println(":"); - methods.get(i).dump(printer, " "); + synchronized (mMethodMap) { + final PrintWriter pr = shellCommand.getOutPrintWriter(); + final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, + mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); + for (int userId : userIds) { + final List<InputMethodInfo> methods = all + ? getInputMethodListLocked(false, userId) + : getEnabledInputMethodListLocked(userId); + if (userIds.length > 1) { + pr.print("User #"); + pr.print(userId); + pr.println(":"); + } + for (InputMethodInfo info : methods) { + if (brief) { + pr.println(info.getId()); + } else { + pr.print(info.getId()); + pr.println(":"); + info.dump(pr::println, " "); + } + } } } return ShellCommandResult.SUCCESS; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 1137bf967d24..88d1a9c0eb6c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -29,21 +29,27 @@ import android.content.res.Resources; import android.os.Build; import android.os.LocaleList; import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManagerInternal; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.IntArray; import android.util.Pair; import android.util.Printer; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; +import android.view.inputmethod.InputMethodSystemProperty; import android.view.textservice.SpellCheckerInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.StartInputFlags; +import com.android.server.LocalServices; import com.android.server.textservices.TextServicesManagerInternal; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; @@ -1286,4 +1292,62 @@ final class InputMethodUtils { return true; } + /** + * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a + * list of real user IDs. + * + * <p>This method also converts profile user ID to profile parent user ID unless + * {@link InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is {@code true}.</p> + * + * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and + * {@link UserHandle#USER_ALL} are also supported + * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT} + * is specified in {@code userIdToBeResolved}. + * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if + * no debug message is required. + * @return An integer array that contain user IDs. + */ + static int[] resolveUserId(@UserIdInt int userIdToBeResolved, + @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) { + final UserManagerInternal userManagerInternal = + LocalServices.getService(UserManagerInternal.class); + + if (userIdToBeResolved == UserHandle.USER_ALL) { + if (InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) { + return userManagerInternal.getUserIds(); + } + final IntArray result = new IntArray(); + for (int userId : userManagerInternal.getUserIds()) { + final int parentUserId = userManagerInternal.getProfileParentId(userId); + if (result.indexOf(parentUserId) < 0) { + result.add(parentUserId); + } + } + return result.toArray(); + } + + final int sourceUserId; + if (userIdToBeResolved == UserHandle.USER_CURRENT) { + sourceUserId = currentUserId; + } else if (userIdToBeResolved < 0) { + if (warningWriter != null) { + warningWriter.print("Pseudo user ID "); + warningWriter.print(userIdToBeResolved); + warningWriter.println(" is not supported."); + } + return new int[]{}; + } else if (userManagerInternal.exists(userIdToBeResolved)) { + sourceUserId = userIdToBeResolved; + } else { + if (warningWriter != null) { + warningWriter.print("User #"); + warningWriter.print(userIdToBeResolved); + warningWriter.println(" does not exit."); + } + return new int[]{}; + } + final int resolvedUserId = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED + ? sourceUserId : userManagerInternal.getProfileParentId(sourceUserId); + return new int[]{resolvedUserId}; + } } diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 98ed3ea92fa1..f304cebddb25 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -163,6 +163,18 @@ public final class MultiClientInputMethodManagerService { public void startVrInputMethodNoCheck(ComponentName componentName) { reportNotSupported(); } + + @Override + public List<InputMethodInfo> getInputMethodListAsUser( + @UserIdInt int userId) { + return userIdToInputMethodInfoMapper.getAsList(userId); + } + + @Override + public List<InputMethodInfo> getEnabledInputMethodListAsUser( + @UserIdInt int userId) { + return userIdToInputMethodInfoMapper.getAsList(userId); + } }); } diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java index 4c7c420214bd..b3f101848692 100644 --- a/services/core/java/com/android/server/location/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -29,7 +29,7 @@ import java.io.PrintWriter; import java.util.List; /** - * Location Manager's interface for location providers. + * Location Manager's interface for location providers. Always starts as disabled. * * @hide */ @@ -41,12 +41,6 @@ public abstract class AbstractLocationProvider { public interface LocationProviderManager { /** - * Called on location provider construction to make the location service aware of this - * provider and what it's initial enabled/disabled state should be. - */ - void onAttachProvider(AbstractLocationProvider locationProvider, boolean initiallyEnabled); - - /** * May be called to inform the location service of a change in this location provider's * enabled/disabled state. */ @@ -74,13 +68,7 @@ public abstract class AbstractLocationProvider { private final LocationProviderManager mLocationProviderManager; protected AbstractLocationProvider(LocationProviderManager locationProviderManager) { - this(locationProviderManager, true); - } - - protected AbstractLocationProvider(LocationProviderManager locationProviderManager, - boolean initiallyEnabled) { mLocationProviderManager = locationProviderManager; - mLocationProviderManager.onAttachProvider(this, initiallyEnabled); } /** diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 3c81a4569407..269767ac3be2 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -559,7 +559,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager, Looper looper) { - super(locationProviderManager, true); + super(locationProviderManager); mContext = context; @@ -652,6 +652,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements }, UserHandle.ALL, intentFilter, null, mHandler); setProperties(PROPERTIES); + setEnabled(true); } /** diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index dfcef70c8248..a6da8c5e7713 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -101,7 +101,7 @@ public class LocationProviderProxy extends AbstractLocationProvider { private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager, String action, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - super(locationProviderManager, false); + super(locationProviderManager); mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId, diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java index b7934d978bac..b7ccb26da64b 100644 --- a/services/core/java/com/android/server/location/LocationRequestStatistics.java +++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java @@ -123,6 +123,9 @@ public class LocationRequestStatistics { // in foreground. private long mForegroundDurationMs; + // Time when package last went dormant (stopped requesting location) + private long mLastStopElapsedTimeMs; + private PackageStatistics() { mInitialElapsedTimeMs = SystemClock.elapsedRealtime(); mNumActiveRequests = 0; @@ -131,6 +134,7 @@ public class LocationRequestStatistics { mSlowestIntervalMs = 0; mForegroundDurationMs = 0; mLastForegroundElapsedTimeMs = 0; + mLastStopElapsedTimeMs = 0; } private void startRequesting(long intervalMs) { @@ -167,8 +171,8 @@ public class LocationRequestStatistics { mNumActiveRequests--; if (mNumActiveRequests == 0) { - long lastDurationMs - = SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs; + mLastStopElapsedTimeMs = SystemClock.elapsedRealtime(); + long lastDurationMs = mLastStopElapsedTimeMs - mLastActivitationElapsedTimeMs; mTotalDurationMs += lastDurationMs; updateForeground(false); } @@ -206,6 +210,13 @@ public class LocationRequestStatistics { } /** + * Returns the time since the last request stopped in ms. + */ + public long getTimeSinceLastRequestStoppedMs() { + return SystemClock.elapsedRealtime() - mLastStopElapsedTimeMs; + } + + /** * Returns the fastest interval that has been tracked. */ public long getFastestIntervalMs() { @@ -244,6 +255,10 @@ public class LocationRequestStatistics { .append(" minutes"); if (isActive()) { s.append(": Currently active"); + } else { + s.append(": Last active ") + .append((getTimeSinceLastRequestStoppedMs() / 1000) / 60) + .append(" minutes ago"); } return s.toString(); } diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java index bfbebf74e93d..86bc9f313551 100644 --- a/services/core/java/com/android/server/location/MockProvider.java +++ b/services/core/java/com/android/server/location/MockProvider.java @@ -43,7 +43,7 @@ public class MockProvider extends AbstractLocationProvider { public MockProvider( LocationProviderManager locationProviderManager, ProviderProperties properties) { - super(locationProviderManager, true); + super(locationProviderManager); mEnabled = true; mLocation = null; @@ -52,6 +52,7 @@ public class MockProvider extends AbstractLocationProvider { mExtras = null; setProperties(properties); + setEnabled(true); } /** Sets the enabled state of this mock provider. */ @@ -63,8 +64,11 @@ public class MockProvider extends AbstractLocationProvider { /** Sets the location to report for this mock provider. */ public void setLocation(Location l) { mLocation = new Location(l); + if (!mLocation.isFromMockProvider()) { + mLocation.setIsFromMockProvider(true); + } if (mEnabled) { - reportLocation(l); + reportLocation(mLocation); } } diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index 70d64b02e4b4..30260b2fe14c 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -43,11 +43,12 @@ public class PassiveProvider extends AbstractLocationProvider { private boolean mReportLocation; public PassiveProvider(LocationProviderManager locationProviderManager) { - super(locationProviderManager, true); + super(locationProviderManager); mReportLocation = false; setProperties(PROPERTIES); + setEnabled(true); } @Override diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 8ecceb9664f5..d611a172d225 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -19,7 +19,6 @@ package com.android.server.media; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioManagerInternal; @@ -27,14 +26,15 @@ import android.media.AudioSystem; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; +import android.media.session.ControllerCallbackLink; import android.media.session.ISession; -import android.media.session.ISessionCallback; import android.media.session.ISessionController; -import android.media.session.ISessionControllerCallback; 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.PlaybackState; +import android.media.session.SessionCallbackLink; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -44,7 +44,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; -import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; import android.util.Log; @@ -55,6 +54,7 @@ import com.android.server.LocalServices; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; /** * This is the system implementation of a Session. Apps will interact with the @@ -84,7 +84,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private final Context mContext; private final Object mLock = new Object(); - private final ArrayList<ISessionControllerCallbackHolder> mControllerCallbackHolders = + private final ArrayList<ControllerCallbackLinkHolder> mControllerCallbackHolders = new ArrayList<>(); private long mFlags; @@ -97,7 +97,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { // may result in throwing an exception. private MediaMetadata mMetadata; private PlaybackState mPlaybackState; - private ParceledListSlice mQueue; + private List<QueueItem> mQueue; private CharSequence mQueueTitle; private int mRatingType; // End TransportPerformer fields @@ -120,7 +120,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private String mMetadataDescription; public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, - ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) { + SessionCallbackLink cb, String tag, MediaSessionService service, Looper handlerLooper) { mOwnerPid = ownerPid; mOwnerUid = ownerUid; mUserId = userId; @@ -249,7 +249,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { * @param useSuggested True to use adjustSuggestedStreamVolume instead of */ public void adjustVolume(String packageName, String opPackageName, int pid, int uid, - ISessionControllerCallback caller, boolean asSystemService, int direction, int flags, + ControllerCallbackLink caller, boolean asSystemService, int direction, int flags, boolean useSuggested) { int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND; if (isPlaybackActive() || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { @@ -291,7 +291,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } private void setVolumeTo(String packageName, String opPackageName, int pid, int uid, - ISessionControllerCallback caller, int value, int flags) { + ControllerCallbackLink caller, int value, int flags) { if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); final int volumeValue = value; @@ -440,7 +440,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } } - public ISessionCallback getCallback() { + public SessionCallbackLink getCallback() { return mSessionCb.mCb; } @@ -468,7 +468,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { + ", max=" + mMaxVolume + ", current=" + mCurrentVolume); pw.println(indent + "metadata: " + mMetadataDescription); pw.println(indent + "queueTitle=" + mQueueTitle + ", size=" - + (mQueue == null ? 0 : mQueue.getList().size())); + + (mQueue == null ? 0 : mQueue.size())); } @Override @@ -518,7 +518,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } private void logCallbackException( - String msg, ISessionControllerCallbackHolder holder, Exception e) { + String msg, ControllerCallbackLinkHolder holder, Exception e) { Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName + ", exception=" + e); } @@ -529,16 +529,18 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i); + ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i); try { - holder.mCallback.onPlaybackStateChanged(mPlaybackState); - } catch (DeadObjectException e) { - mControllerCallbackHolders.remove(i); - logCallbackException("Removed dead callback in pushPlaybackStateUpdate", - holder, e); - } catch (RemoteException e) { - logCallbackException("unexpected exception in pushPlaybackStateUpdate", - holder, e); + holder.mCallback.notifyPlaybackStateChanged(mPlaybackState); + } catch (RuntimeException e) { + if (e.getCause() instanceof DeadObjectException) { + mControllerCallbackHolders.remove(i); + logCallbackException("Removing dead callback in pushPlaybackStateUpdate", + holder, e); + } else { + logCallbackException("unexpected exception in pushPlaybackStateUpdate", + holder, e); + } } } } @@ -550,14 +552,18 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i); + ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i); try { - holder.mCallback.onMetadataChanged(mMetadata); - } catch (DeadObjectException e) { - logCallbackException("Removing dead callback in pushMetadataUpdate", holder, e); - mControllerCallbackHolders.remove(i); - } catch (RemoteException e) { - logCallbackException("unexpected exception in pushMetadataUpdate", holder, e); + holder.mCallback.notifyMetadataChanged(mMetadata); + } catch (RuntimeException e) { + if (e.getCause() instanceof DeadObjectException) { + mControllerCallbackHolders.remove(i); + logCallbackException("Removing dead callback in pushMetadataUpdate", + holder, e); + } else { + logCallbackException("unexpected exception in pushMetadataUpdate", + holder, e); + } } } } @@ -569,14 +575,17 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i); + ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i); try { - holder.mCallback.onQueueChanged(mQueue); - } catch (DeadObjectException e) { - mControllerCallbackHolders.remove(i); - logCallbackException("Removed dead callback in pushQueueUpdate", holder, e); - } catch (RemoteException e) { - logCallbackException("unexpected exception in pushQueueUpdate", holder, e); + holder.mCallback.notifyQueueChanged(mQueue); + } catch (RuntimeException e) { + if (e.getCause() instanceof DeadObjectException) { + mControllerCallbackHolders.remove(i); + logCallbackException("Removing dead callback in pushQueueUpdate", + holder, e); + } else { + logCallbackException("unexpected exception in pushQueueUpdate", holder, e); + } } } } @@ -588,16 +597,18 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i); + ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i); try { - holder.mCallback.onQueueTitleChanged(mQueueTitle); - } catch (DeadObjectException e) { - mControllerCallbackHolders.remove(i); - logCallbackException("Removed dead callback in pushQueueTitleUpdate", - holder, e); - } catch (RemoteException e) { - logCallbackException("unexpected exception in pushQueueTitleUpdate", - holder, e); + holder.mCallback.notifyQueueTitleChanged(mQueueTitle); + } catch (RuntimeException e) { + if (e.getCause() instanceof DeadObjectException) { + mControllerCallbackHolders.remove(i); + logCallbackException("Removing dead callback in pushQueueTitleUpdate", + holder, e); + } else { + logCallbackException("unexpected exception in pushQueueTitleUpdate", + holder, e); + } } } } @@ -609,14 +620,17 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i); + ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i); try { - holder.mCallback.onExtrasChanged(mExtras); - } catch (DeadObjectException e) { - mControllerCallbackHolders.remove(i); - logCallbackException("Removed dead callback in pushExtrasUpdate", holder, e); - } catch (RemoteException e) { - logCallbackException("unexpected exception in pushExtrasUpdate", holder, e); + holder.mCallback.notifyExtrasChanged(mExtras); + } catch (RuntimeException e) { + if (e.getCause() instanceof DeadObjectException) { + mControllerCallbackHolders.remove(i); + logCallbackException("Removing dead callback in pushExtrasUpdate", + holder, e); + } else { + logCallbackException("unexpected exception in pushExtrasUpdate", holder, e); + } } } } @@ -629,14 +643,17 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } PlaybackInfo info = mController.getVolumeAttributes(); for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i); + ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i); try { - holder.mCallback.onVolumeInfoChanged(info); - } catch (DeadObjectException e) { - mControllerCallbackHolders.remove(i); - logCallbackException("Removing dead callback in pushVolumeUpdate", holder, e); - } catch (RemoteException e) { - logCallbackException("Unexpected exception in pushVolumeUpdate", holder, e); + holder.mCallback.notifyVolumeInfoChanged(info); + } catch (RuntimeException e) { + if (e.getCause() instanceof DeadObjectException) { + mControllerCallbackHolders.remove(i); + logCallbackException("Removing dead callback in pushVolumeUpdate", + holder, e); + } else { + logCallbackException("unexpected exception in pushVolumeUpdate", holder, e); + } } } } @@ -648,14 +665,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i); + ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i); try { - holder.mCallback.onEvent(event, data); - } catch (DeadObjectException e) { - mControllerCallbackHolders.remove(i); - logCallbackException("Removing dead callback in pushEvent", holder, e); - } catch (RemoteException e) { - logCallbackException("unexpected exception in pushEvent", holder, e); + holder.mCallback.notifyEvent(event, data); + } catch (RuntimeException e) { + if (e.getCause() instanceof DeadObjectException) { + mControllerCallbackHolders.remove(i); + logCallbackException("Removing dead callback in pushEvent", holder, e); + } else { + logCallbackException("unexpected exception in pushEvent", holder, e); + } } } } @@ -669,14 +688,18 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i); + ControllerCallbackLinkHolder holder = mControllerCallbackHolders.get(i); try { - holder.mCallback.onSessionDestroyed(); - } catch (DeadObjectException e) { - logCallbackException("Removing dead callback in pushEvent", holder, e); - mControllerCallbackHolders.remove(i); - } catch (RemoteException e) { - logCallbackException("unexpected exception in pushEvent", holder, e); + holder.mCallback.notifySessionDestroyed(); + } catch (RuntimeException e) { + if (e.getCause() instanceof DeadObjectException) { + mControllerCallbackHolders.remove(i); + logCallbackException("Removing dead callback in pushSessionDestroyed", + holder, e); + } else { + logCallbackException("unexpected exception in pushSessionDestroyed", + holder, e); + } } } // After notifying clear all listeners @@ -716,10 +739,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return result == null ? state : result; } - private int getControllerHolderIndexForCb(ISessionControllerCallback cb) { - IBinder binder = cb.asBinder(); + private int getControllerHolderIndexForCb(ControllerCallbackLink cb) { + IBinder binder = cb.getBinder(); for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) { - if (binder.equals(mControllerCallbackHolders.get(i).mCallback.asBinder())) { + if (binder.equals(mControllerCallbackHolders.get(i).mCallback.getBinder())) { return i; } } @@ -843,7 +866,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void setQueue(ParceledListSlice queue) { + public void setQueue(List<QueueItem> queue) { synchronized (mLock) { mQueue = queue; } @@ -920,9 +943,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } class SessionCb { - private final ISessionCallback mCb; + private final SessionCallbackLink mCb; - public SessionCb(ISessionCallback cb) { + SessionCb(SessionCallbackLink cb) { mCb = cb; } @@ -930,224 +953,224 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) { try { if (asSystemService) { - mCb.onMediaButton(mContext.getPackageName(), Process.myPid(), + mCb.notifyMediaButton(mContext.getPackageName(), Process.myPid(), Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb); } else { - mCb.onMediaButton(packageName, pid, uid, + mCb.notifyMediaButton(packageName, pid, uid, createMediaButtonIntent(keyEvent), sequenceId, cb); } return true; - } catch (RemoteException e) { + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in sendMediaRequest.", e); } return false; } public boolean sendMediaButton(String packageName, int pid, int uid, - ISessionControllerCallback caller, boolean asSystemService, + ControllerCallbackLink caller, boolean asSystemService, KeyEvent keyEvent) { try { if (asSystemService) { - mCb.onMediaButton(mContext.getPackageName(), Process.myPid(), + mCb.notifyMediaButton(mContext.getPackageName(), Process.myPid(), Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), 0, null); } else { - mCb.onMediaButtonFromController(packageName, pid, uid, caller, + mCb.notifyMediaButtonFromController(packageName, pid, uid, caller, createMediaButtonIntent(keyEvent)); } return true; - } catch (RemoteException e) { + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in sendMediaRequest.", e); } return false; } public void sendCommand(String packageName, int pid, int uid, - ISessionControllerCallback caller, String command, Bundle args, ResultReceiver cb) { + ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) { try { - mCb.onCommand(packageName, pid, uid, caller, command, args, cb); - } catch (RemoteException e) { + mCb.notifyCommand(packageName, pid, uid, caller, command, args, cb); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in sendCommand.", e); } } public void sendCustomAction(String packageName, int pid, int uid, - ISessionControllerCallback caller, String action, + ControllerCallbackLink caller, String action, Bundle args) { try { - mCb.onCustomAction(packageName, pid, uid, caller, action, args); - } catch (RemoteException e) { + mCb.notifyCustomAction(packageName, pid, uid, caller, action, args); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in sendCustomAction.", e); } } public void prepare(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { try { - mCb.onPrepare(packageName, pid, uid, caller); - } catch (RemoteException e) { + mCb.notifyPrepare(packageName, pid, uid, caller); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in prepare.", e); } } public void prepareFromMediaId(String packageName, int pid, int uid, - ISessionControllerCallback caller, String mediaId, Bundle extras) { + ControllerCallbackLink caller, String mediaId, Bundle extras) { try { - mCb.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras); - } catch (RemoteException e) { + mCb.notifyPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in prepareFromMediaId.", e); } } public void prepareFromSearch(String packageName, int pid, int uid, - ISessionControllerCallback caller, String query, Bundle extras) { + ControllerCallbackLink caller, String query, Bundle extras) { try { - mCb.onPrepareFromSearch(packageName, pid, uid, caller, query, extras); - } catch (RemoteException e) { + mCb.notifyPrepareFromSearch(packageName, pid, uid, caller, query, extras); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in prepareFromSearch.", e); } } public void prepareFromUri(String packageName, int pid, int uid, - ISessionControllerCallback caller, Uri uri, Bundle extras) { + ControllerCallbackLink caller, Uri uri, Bundle extras) { try { - mCb.onPrepareFromUri(packageName, pid, uid, caller, uri, extras); - } catch (RemoteException e) { + mCb.notifyPrepareFromUri(packageName, pid, uid, caller, uri, extras); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in prepareFromUri.", e); } } - public void play(String packageName, int pid, int uid, ISessionControllerCallback caller) { + public void play(String packageName, int pid, int uid, ControllerCallbackLink caller) { try { - mCb.onPlay(packageName, pid, uid, caller); - } catch (RemoteException e) { + mCb.notifyPlay(packageName, pid, uid, caller); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in play.", e); } } public void playFromMediaId(String packageName, int pid, int uid, - ISessionControllerCallback caller, String mediaId, Bundle extras) { + ControllerCallbackLink caller, String mediaId, Bundle extras) { try { - mCb.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras); - } catch (RemoteException e) { + mCb.notifyPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in playFromMediaId.", e); } } public void playFromSearch(String packageName, int pid, int uid, - ISessionControllerCallback caller, String query, Bundle extras) { + ControllerCallbackLink caller, String query, Bundle extras) { try { - mCb.onPlayFromSearch(packageName, pid, uid, caller, query, extras); - } catch (RemoteException e) { + mCb.notifyPlayFromSearch(packageName, pid, uid, caller, query, extras); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in playFromSearch.", e); } } public void playFromUri(String packageName, int pid, int uid, - ISessionControllerCallback caller, Uri uri, Bundle extras) { + ControllerCallbackLink caller, Uri uri, Bundle extras) { try { - mCb.onPlayFromUri(packageName, pid, uid, caller, uri, extras); - } catch (RemoteException e) { + mCb.notifyPlayFromUri(packageName, pid, uid, caller, uri, extras); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in playFromUri.", e); } } public void skipToTrack(String packageName, int pid, int uid, - ISessionControllerCallback caller, long id) { + ControllerCallbackLink caller, long id) { try { - mCb.onSkipToTrack(packageName, pid, uid, caller, id); - } catch (RemoteException e) { + mCb.notifySkipToTrack(packageName, pid, uid, caller, id); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in skipToTrack", e); } } - public void pause(String packageName, int pid, int uid, ISessionControllerCallback caller) { + public void pause(String packageName, int pid, int uid, ControllerCallbackLink caller) { try { - mCb.onPause(packageName, pid, uid, caller); - } catch (RemoteException e) { + mCb.notifyPause(packageName, pid, uid, caller); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in pause.", e); } } - public void stop(String packageName, int pid, int uid, ISessionControllerCallback caller) { + public void stop(String packageName, int pid, int uid, ControllerCallbackLink caller) { try { - mCb.onStop(packageName, pid, uid, caller); - } catch (RemoteException e) { + mCb.notifyStop(packageName, pid, uid, caller); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in stop.", e); } } - public void next(String packageName, int pid, int uid, ISessionControllerCallback caller) { + public void next(String packageName, int pid, int uid, ControllerCallbackLink caller) { try { - mCb.onNext(packageName, pid, uid, caller); - } catch (RemoteException e) { + mCb.notifyNext(packageName, pid, uid, caller); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in next.", e); } } public void previous(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { try { - mCb.onPrevious(packageName, pid, uid, caller); - } catch (RemoteException e) { + mCb.notifyPrevious(packageName, pid, uid, caller); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in previous.", e); } } public void fastForward(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { try { - mCb.onFastForward(packageName, pid, uid, caller); - } catch (RemoteException e) { + mCb.notifyFastForward(packageName, pid, uid, caller); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in fastForward.", e); } } public void rewind(String packageName, int pid, int uid, - ISessionControllerCallback caller) { + ControllerCallbackLink caller) { try { - mCb.onRewind(packageName, pid, uid, caller); - } catch (RemoteException e) { + mCb.notifyRewind(packageName, pid, uid, caller); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in rewind.", e); } } - public void seekTo(String packageName, int pid, int uid, ISessionControllerCallback caller, + public void seekTo(String packageName, int pid, int uid, ControllerCallbackLink caller, long pos) { try { - mCb.onSeekTo(packageName, pid, uid, caller, pos); - } catch (RemoteException e) { + mCb.notifySeekTo(packageName, pid, uid, caller, pos); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in seekTo.", e); } } - public void rate(String packageName, int pid, int uid, ISessionControllerCallback caller, + public void rate(String packageName, int pid, int uid, ControllerCallbackLink caller, Rating rating) { try { - mCb.onRate(packageName, pid, uid, caller, rating); - } catch (RemoteException e) { + mCb.notifyRate(packageName, pid, uid, caller, rating); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in rate.", e); } } public void adjustVolume(String packageName, int pid, int uid, - ISessionControllerCallback caller, boolean asSystemService, int direction) { + ControllerCallbackLink caller, boolean asSystemService, int direction) { try { if (asSystemService) { - mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(), + mCb.notifyAdjustVolume(mContext.getPackageName(), Process.myPid(), Process.SYSTEM_UID, null, direction); } else { - mCb.onAdjustVolume(packageName, pid, uid, caller, direction); + mCb.notifyAdjustVolume(packageName, pid, uid, caller, direction); } - } catch (RemoteException e) { + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in adjustVolume.", e); } } public void setVolumeTo(String packageName, int pid, int uid, - ISessionControllerCallback caller, int value) { + ControllerCallbackLink caller, int value) { try { - mCb.onSetVolumeTo(packageName, pid, uid, caller, value); - } catch (RemoteException e) { + mCb.notifySetVolumeTo(packageName, pid, uid, caller, value); + } catch (RuntimeException e) { Slog.e(TAG, "Remote failure in setVolumeTo.", e); } } @@ -1161,34 +1184,34 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { class ControllerStub extends ISessionController.Stub { @Override - public void sendCommand(String packageName, ISessionControllerCallback caller, + public void sendCommand(String packageName, ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) { mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, command, args, cb); } @Override - public boolean sendMediaButton(String packageName, ISessionControllerCallback cb, + public boolean sendMediaButton(String packageName, ControllerCallbackLink cb, boolean asSystemService, KeyEvent keyEvent) { return mSessionCb.sendMediaButton(packageName, Binder.getCallingPid(), Binder.getCallingUid(), cb, asSystemService, keyEvent); } @Override - public void registerCallbackListener(String packageName, ISessionControllerCallback cb) { + public void registerCallbackListener(String packageName, ControllerCallbackLink cb) { synchronized (mLock) { // If this session is already destroyed tell the caller and // don't add them. if (mDestroyed) { try { - cb.onSessionDestroyed(); + cb.notifySessionDestroyed(); } catch (Exception e) { // ignored } return; } if (getControllerHolderIndexForCb(cb) < 0) { - mControllerCallbackHolders.add(new ISessionControllerCallbackHolder(cb, + mControllerCallbackHolders.add(new ControllerCallbackLinkHolder(cb, packageName, Binder.getCallingUid())); if (DEBUG) { Log.d(TAG, "registering controller callback " + cb + " from controller" @@ -1199,14 +1222,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void unregisterCallbackListener(ISessionControllerCallback cb) { + public void unregisterCallbackListener(ControllerCallbackLink cb) { synchronized (mLock) { int index = getControllerHolderIndexForCb(cb); if (index != -1) { mControllerCallbackHolders.remove(index); } if (DEBUG) { - Log.d(TAG, "unregistering callback " + cb.asBinder()); + Log.d(TAG, "unregistering callback " + cb.getBinder()); } } } @@ -1253,7 +1276,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public void adjustVolume(String packageName, String opPackageName, - ISessionControllerCallback caller, boolean asSystemService, int direction, + ControllerCallbackLink caller, boolean asSystemService, int direction, int flags) { int pid = Binder.getCallingPid(); int uid = Binder.getCallingUid(); @@ -1268,7 +1291,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public void setVolumeTo(String packageName, String opPackageName, - ISessionControllerCallback caller, int value, int flags) { + ControllerCallbackLink caller, int value, int flags) { int pid = Binder.getCallingPid(); int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); @@ -1281,110 +1304,110 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void prepare(String packageName, ISessionControllerCallback caller) { + public void prepare(String packageName, ControllerCallbackLink caller) { mSessionCb.prepare(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller); } @Override - public void prepareFromMediaId(String packageName, ISessionControllerCallback caller, + public void prepareFromMediaId(String packageName, ControllerCallbackLink caller, String mediaId, Bundle extras) { mSessionCb.prepareFromMediaId(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, mediaId, extras); } @Override - public void prepareFromSearch(String packageName, ISessionControllerCallback caller, + public void prepareFromSearch(String packageName, ControllerCallbackLink caller, String query, Bundle extras) { mSessionCb.prepareFromSearch(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, query, extras); } @Override - public void prepareFromUri(String packageName, ISessionControllerCallback caller, + public void prepareFromUri(String packageName, ControllerCallbackLink caller, Uri uri, Bundle extras) { mSessionCb.prepareFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, uri, extras); } @Override - public void play(String packageName, ISessionControllerCallback caller) { + public void play(String packageName, ControllerCallbackLink caller) { mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller); } @Override - public void playFromMediaId(String packageName, ISessionControllerCallback caller, + public void playFromMediaId(String packageName, ControllerCallbackLink caller, String mediaId, Bundle extras) { mSessionCb.playFromMediaId(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, mediaId, extras); } @Override - public void playFromSearch(String packageName, ISessionControllerCallback caller, + public void playFromSearch(String packageName, ControllerCallbackLink caller, String query, Bundle extras) { mSessionCb.playFromSearch(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, query, extras); } @Override - public void playFromUri(String packageName, ISessionControllerCallback caller, + public void playFromUri(String packageName, ControllerCallbackLink caller, Uri uri, Bundle extras) { mSessionCb.playFromUri(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, uri, extras); } @Override - public void skipToQueueItem(String packageName, ISessionControllerCallback caller, + public void skipToQueueItem(String packageName, ControllerCallbackLink caller, long id) { mSessionCb.skipToTrack(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, id); } @Override - public void pause(String packageName, ISessionControllerCallback caller) { + public void pause(String packageName, ControllerCallbackLink caller) { mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller); } @Override - public void stop(String packageName, ISessionControllerCallback caller) { + public void stop(String packageName, ControllerCallbackLink caller) { mSessionCb.stop(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller); } @Override - public void next(String packageName, ISessionControllerCallback caller) { + public void next(String packageName, ControllerCallbackLink caller) { mSessionCb.next(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller); } @Override - public void previous(String packageName, ISessionControllerCallback caller) { + public void previous(String packageName, ControllerCallbackLink caller) { mSessionCb.previous(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller); } @Override - public void fastForward(String packageName, ISessionControllerCallback caller) { + public void fastForward(String packageName, ControllerCallbackLink caller) { mSessionCb.fastForward(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller); } @Override - public void rewind(String packageName, ISessionControllerCallback caller) { + public void rewind(String packageName, ControllerCallbackLink caller) { mSessionCb.rewind(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller); } @Override - public void seekTo(String packageName, ISessionControllerCallback caller, long pos) { + public void seekTo(String packageName, ControllerCallbackLink caller, long pos) { mSessionCb.seekTo(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, pos); } @Override - public void rate(String packageName, ISessionControllerCallback caller, Rating rating) { + public void rate(String packageName, ControllerCallbackLink caller, Rating rating) { mSessionCb.rate(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, rating); } @Override - public void sendCustomAction(String packageName, ISessionControllerCallback caller, + public void sendCustomAction(String packageName, ControllerCallbackLink caller, String action, Bundle args) { mSessionCb.sendCustomAction(packageName, Binder.getCallingPid(), Binder.getCallingUid(), caller, action, args); @@ -1403,7 +1426,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public ParceledListSlice getQueue() { + public List<QueueItem> getQueue() { synchronized (mLock) { return mQueue; } @@ -1432,12 +1455,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } } - private class ISessionControllerCallbackHolder { - private final ISessionControllerCallback mCallback; + private class ControllerCallbackLinkHolder { + private final ControllerCallbackLink mCallback; private final String mPackageName; private final int mUid; - ISessionControllerCallbackHolder(ISessionControllerCallback callback, String packageName, + ControllerCallbackLinkHolder(ControllerCallbackLink callback, String packageName, int uid) { mCallback = callback; mPackageName = packageName; diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 7f2e047d7b99..ce0e72b85cc3 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -48,10 +48,10 @@ import android.media.session.ICallback; import android.media.session.IOnMediaKeyListener; import android.media.session.IOnVolumeKeyLongPressListener; import android.media.session.ISession; -import android.media.session.ISessionCallback; import android.media.session.ISessionManager; import android.media.session.MediaSession; import android.media.session.MediaSessionManager; +import android.media.session.SessionCallbackLink; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -114,10 +114,11 @@ public class MediaSessionService extends SystemService implements Monitor { @GuardedBy("mLock") private final ArrayList<SessionsListenerRecord> mSessionsListeners = new ArrayList<SessionsListenerRecord>(); + // Map user id as index to list of Session2Tokens // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in // one place. @GuardedBy("mLock") - private final List<Session2Token> mSession2Tokens = new ArrayList<>(); + private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>(); private KeyguardManager mKeyguardManager; private IAudioService mAudioService; @@ -305,10 +306,13 @@ public class MediaSessionService extends SystemService implements Monitor { updateUser(); } + // Called when the user with the userId is removed. @Override public void onStopUser(int userId) { if (DEBUG) Log.d(TAG, "onStopUser: " + userId); synchronized (mLock) { + // TODO: Also handle removing user in updateUser() because adding/switching user is + // handled in updateUser(). FullUserRecord user = getFullUserRecordLocked(userId); if (user != null) { if (user.mFullUserId == userId) { @@ -318,6 +322,7 @@ public class MediaSessionService extends SystemService implements Monitor { user.destroySessionsForUserLocked(userId); } } + mSession2TokensPerUser.remove(userId); updateUser(); } } @@ -363,6 +368,9 @@ public class MediaSessionService extends SystemService implements Monitor { mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id)); } } + if (mSession2TokensPerUser.get(userInfo.id) == null) { + mSession2TokensPerUser.put(userInfo.id, new ArrayList<>()); + } } } // Ensure that the current full user exists. @@ -372,6 +380,9 @@ public class MediaSessionService extends SystemService implements Monitor { Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId); mCurrentFullUserRecord = new FullUserRecord(currentFullUserId); mUserRecords.put(currentFullUserId, mCurrentFullUserRecord); + if (mSession2TokensPerUser.get(currentFullUserId) == null) { + mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>()); + } } mFullUserIds.put(currentFullUserId, currentFullUserId); } @@ -425,7 +436,7 @@ public class MediaSessionService extends SystemService implements Monitor { } try { - session.getCallback().asBinder().unlinkToDeath(session, 0); + session.getCallback().getBinder().unlinkToDeath(session, 0); } catch (Exception e) { // ignore exceptions while destroying a session. } @@ -511,7 +522,7 @@ public class MediaSessionService extends SystemService implements Monitor { } private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId, - String callerPackageName, ISessionCallback cb, String tag) throws RemoteException { + String callerPackageName, SessionCallbackLink cb, String tag) throws RemoteException { synchronized (mLock) { return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag); } @@ -525,7 +536,7 @@ public class MediaSessionService extends SystemService implements Monitor { * 4. It needs to be added to the relevant user record. */ private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId, - String callerPackageName, ISessionCallback cb, String tag) { + String callerPackageName, SessionCallbackLink cb, String tag) { FullUserRecord user = getFullUserRecordLocked(userId); if (user == null) { Log.wtf(TAG, "Request from invalid user: " + userId); @@ -535,7 +546,7 @@ public class MediaSessionService extends SystemService implements Monitor { final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId, callerPackageName, cb, tag, this, mHandler.getLooper()); try { - cb.asBinder().linkToDeath(session, 0); + cb.getBinder().linkToDeath(session, 0); } catch (RemoteException e) { throw new RuntimeException("Media Session owner died prematurely.", e); } @@ -732,9 +743,15 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(indent + "Restored MediaButtonReceiverComponentType: " + mRestoredMediaButtonReceiverComponentType); mPriorityStack.dump(pw, indent); - pw.println(indent + "Session2Tokens - " + mSession2Tokens.size()); - for (Session2Token session2Token : mSession2Tokens) { - pw.println(indent + " " + session2Token); + pw.println(indent + "Session2Tokens:"); + for (int i = 0; i < mSession2TokensPerUser.size(); i++) { + List<Session2Token> list = mSession2TokensPerUser.valueAt(i); + if (list == null || list.size() == 0) { + continue; + } + for (Session2Token token : list) { + pw.println(indent + " " + token); + } } } @@ -890,7 +907,7 @@ public class MediaSessionService extends SystemService implements Monitor { private boolean mVoiceButtonHandled = false; @Override - public ISession createSession(String packageName, ISessionCallback cb, String tag, + public ISession createSession(String packageName, SessionCallbackLink cb, String tag, int userId) throws RemoteException { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); @@ -956,6 +973,34 @@ public class MediaSessionService extends SystemService implements Monitor { } @Override + public List<Session2Token> getSession2Tokens(int userId) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + + try { + // Check that they can make calls on behalf of the user and + // get the final user id + int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, + true /* allowAll */, true /* requireFull */, "getSession2Tokens", + null /* optional packageName */); + List<Session2Token> result = new ArrayList<>(); + synchronized (mLock) { + if (resolvedUserId == UserHandle.USER_ALL) { + for (int i = 0; i < mSession2TokensPerUser.size(); i++) { + result.addAll(mSession2TokensPerUser.valueAt(i)); + } + } else { + result.addAll(mSession2TokensPerUser.get(userId)); + } + } + return result; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void addSessionsListener(IActiveSessionsListener listener, ComponentName componentName, int userId) throws RemoteException { final int pid = Binder.getCallingPid(); @@ -1965,14 +2010,16 @@ public class MediaSessionService extends SystemService implements Monitor { @Override public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { synchronized (mLock) { - mSession2Tokens.add(mToken); + int userId = UserHandle.getUserId(mToken.getUid()); + mSession2TokensPerUser.get(userId).add(mToken); } } @Override public void onDisconnected(MediaController2 controller) { synchronized (mLock) { - mSession2Tokens.remove(mToken); + int userId = UserHandle.getUserId(mToken.getUid()); + mSession2TokensPerUser.get(userId).remove(mToken); } } } diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java new file mode 100644 index 000000000000..e2415911e929 --- /dev/null +++ b/services/core/java/com/android/server/os/BugreportManagerService.java @@ -0,0 +1,43 @@ +/* + * 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.os; + +import android.content.Context; + +import com.android.server.SystemService; + +/** + * Service that provides a privileged API to capture and consume bugreports. + * + * @hide + */ +public class BugreportManagerService extends SystemService { + private static final String TAG = "BugreportManagerService"; + + private BugreportManagerServiceImpl mService; + + public BugreportManagerService(Context context) { + super(context); + } + + @Override + public void onStart() { + mService = new BugreportManagerServiceImpl(getContext()); + // TODO(b/111441001): Needs sepolicy to be submitted first. + // publishBinderService(Context.BUGREPORT_SERVICE, mService); + } +} diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java new file mode 100644 index 000000000000..faa4714a8697 --- /dev/null +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -0,0 +1,130 @@ +/* + * 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.os; + +import android.annotation.RequiresPermission; +import android.content.Context; +import android.os.BugreportParams; +import android.os.IDumpstate; +import android.os.IDumpstateListener; +import android.os.IDumpstateToken; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Slog; + +import java.io.FileDescriptor; + +// TODO(b/111441001): +// 1. Handle the case where another bugreport is in progress +// 2. Make everything threadsafe +// 3. Pass validation & other errors on listener + +/** + * Implementation of the service that provides a privileged API to capture and consume bugreports. + * + * <p>Delegates the actualy generation to a native implementation of {@code Dumpstate}. + */ +class BugreportManagerServiceImpl extends IDumpstate.Stub { + private static final String TAG = "BugreportManagerService"; + private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000; + + private IDumpstate mDs = null; + private final Context mContext; + + BugreportManagerServiceImpl(Context context) { + mContext = context; + } + + @Override + @RequiresPermission(android.Manifest.permission.DUMP) + public IDumpstateToken setListener(String name, IDumpstateListener listener, + boolean getSectionDetails) throws RemoteException { + // TODO(b/111441001): Figure out if lazy setting of listener should be allowed + // and if so how to handle it. + throw new UnsupportedOperationException("setListener is not allowed on this service"); + } + + + @Override + @RequiresPermission(android.Manifest.permission.DUMP) + public void startBugreport(FileDescriptor bugreportFd, FileDescriptor screenshotFd, + int bugreportMode, IDumpstateListener listener) throws RemoteException { + + validate(bugreportMode); + + mDs = getDumpstateService(); + if (mDs == null) { + Slog.w(TAG, "Unable to get bugreport service"); + // TODO(b/111441001): pass error on listener + return; + } + mDs.startBugreport(bugreportFd, screenshotFd, bugreportMode, listener); + } + + private boolean validate(@BugreportParams.BugreportMode int mode) { + if (mode != BugreportParams.BUGREPORT_MODE_FULL + && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE + && mode != BugreportParams.BUGREPORT_MODE_REMOTE + && mode != BugreportParams.BUGREPORT_MODE_WEAR + && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY + && mode != BugreportParams.BUGREPORT_MODE_WIFI) { + Slog.w(TAG, "Unknown bugreport mode: " + mode); + return false; + } + return true; + } + + /* + * Start and get a handle to the native implementation of {@code IDumpstate} which does the + * actual bugreport generation. + * + * <p>Generating bugreports requires root privileges. To limit the footprint + * of the root access, the actual generation in Dumpstate binary is accessed as a + * oneshot service 'bugreport'. + */ + private IDumpstate getDumpstateService() { + // Start bugreport service. + SystemProperties.set("ctl.start", "bugreport"); + + IDumpstate ds = null; + boolean timedOut = false; + int totalTimeWaitedMillis = 0; + int seedWaitTimeMillis = 500; + while (!timedOut) { + // Note that the binder service on the native side is "dumpstate". + ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate")); + if (ds != null) { + Slog.i(TAG, "Got bugreport service handle."); + break; + } + SystemClock.sleep(seedWaitTimeMillis); + Slog.i(TAG, + "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)"); + totalTimeWaitedMillis += seedWaitTimeMillis; + seedWaitTimeMillis *= 2; + timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS; + } + if (timedOut) { + Slog.w(TAG, + "Timed out waiting to get dumpstate service handle (" + + totalTimeWaitedMillis + "ms)"); + } + return ds; + } +} diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index 3b11525e7cda..8facce112b52 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -42,6 +42,7 @@ import android.content.pm.ServiceInfo; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.DebugUtils; import android.util.Log; import android.util.LogPrinter; import android.util.Pair; @@ -60,6 +61,7 @@ import java.util.Set; /** Resolves all Android component types [activities, services, providers and receivers]. */ public class ComponentResolver { + private static final boolean DEBUG = false; private static final String TAG = "PackageManager"; private static final boolean DEBUG_FILTERS = false; private static final boolean DEBUG_SHOW_INFO = false; @@ -1198,22 +1200,48 @@ public class ComponentResolver { return packageName.equals(info.activity.owner.packageName); } + private void log(String reason, ActivityIntentInfo info, int match, + int userId) { + Slog.w(TAG, reason + + "; match: " + + DebugUtils.flagsToString(IntentFilter.class, "MATCH_", match) + + "; userId: " + userId + + "; intent info: " + info); + } + @Override protected ResolveInfo newResult(PackageParser.ActivityIntentInfo info, int match, int userId) { - if (!sUserManager.exists(userId)) return null; + if (!sUserManager.exists(userId)) { + if (DEBUG) { + log("User doesn't exist", info, match, userId); + } + return null; + } if (!sPackageManagerInternal.isEnabledAndMatches(info.activity.info, mFlags, userId)) { + if (DEBUG) { + log("!PackageManagerInternal.isEnabledAndMatches; mFlags=" + + DebugUtils.flagsToString(PackageManager.class, "MATCH_", mFlags), + info, match, userId); + } return null; } final PackageParser.Activity activity = info.activity; PackageSetting ps = (PackageSetting) activity.owner.mExtras; if (ps == null) { + if (DEBUG) { + log("info.activity.owner.mExtras == null", info, match, userId); + } return null; } final PackageUserState userState = ps.readUserState(userId); ActivityInfo ai = PackageParser.generateActivityInfo(activity, mFlags, userState, userId); if (ai == null) { + if (DEBUG) { + log("Failed to create ActivityInfo based on " + info.activity, info, match, + userId); + } return null; } final boolean matchExplicitlyVisibleOnly = @@ -1227,15 +1255,31 @@ public class ComponentResolver { final boolean matchInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0; // throw out filters that aren't visible to ephemeral apps if (matchVisibleToInstantApp && !(componentVisible || userState.instantApp)) { + if (DEBUG) { + log("Filter(s) not visible to ephemeral apps" + + "; matchVisibleToInstantApp=" + matchVisibleToInstantApp + + "; matchInstantApp=" + matchInstantApp + + "; info.isVisibleToInstantApp()=" + info.isVisibleToInstantApp() + + "; matchExplicitlyVisibleOnly=" + matchExplicitlyVisibleOnly + + "; info.isExplicitlyVisibleToInstantApp()=" + + info.isExplicitlyVisibleToInstantApp(), + info, match, userId); + } return null; } // throw out instant app filters if we're not explicitly requesting them if (!matchInstantApp && userState.instantApp) { + if (DEBUG) { + log("Instant app filter is not explicitly requested", info, match, userId); + } return null; } // throw out instant app filters if updates are available; will trigger // instant app resolution if (userState.instantApp && ps.isUpdateAvailable()) { + if (DEBUG) { + log("Instant app update is available", info, match, userId); + } return null; } final ResolveInfo res = new ResolveInfo(); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 7ca39df39bb7..d0b20e815a42 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -329,6 +329,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements if (valid) { mSessions.put(session.sessionId, session); + if (session.isStaged()) { + mStagingManager.restoreSession(session); + } } else { // Since this is early during boot we don't send // any observer events about the session, but we @@ -533,7 +536,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false, - false, null, SessionInfo.INVALID_ID); + false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR); synchronized (mSessions) { mSessions.put(sessionId, session); @@ -1131,7 +1134,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } synchronized (mSessions) { - mSessions.remove(session.sessionId); + if (!session.isStaged() || !success) { + mSessions.remove(session.sessionId); + } addHistoricalSessionLocked(session); final File appIconFile = buildAppIconFile(session.sessionId); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 5cb6c342e5e3..516927fa7c49 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -151,6 +151,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_MULTI_PACKAGE = "multiPackage"; private static final String ATTR_PARENT_SESSION_ID = "parentSessionId"; private static final String ATTR_STAGED_SESSION = "stagedSession"; + private static final String ATTR_IS_READY = "isReady"; + private static final String ATTR_IS_FAILED = "isFailed"; + private static final String ATTR_IS_APPLIED = "isApplied"; + private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; private static final String ATTR_INSTALL_LOCATION = "installLocation"; @@ -408,7 +412,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { int sessionId, int userId, String installerPackageName, int installerUid, SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean sealed, - @Nullable int[] childSessionIds, int parentSessionId) { + @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, + boolean isFailed, boolean isApplied, int stagedSessionErrorCode) { mCallback = callback; mContext = context; mPm = pm; @@ -438,7 +443,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mPrepared = prepared; - + mStagedSessionReady = isReady; + mStagedSessionFailed = isFailed; + mStagedSessionApplied = isApplied; + mStagedSessionErrorCode = stagedSessionErrorCode; if (sealed) { synchronized (mLock) { try { @@ -2023,8 +2031,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void destroyInternal() { synchronized (mLock) { mSealed = true; - mDestroyed = true; - + if (!params.isStaged) { + mDestroyed = true; + } // Force shut down all bridges for (RevocableFileDescriptor fd : mFds) { fd.revoke(); @@ -2131,6 +2140,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged); + writeBooleanAttribute(out, ATTR_IS_READY, mStagedSessionReady); + writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed); + writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied); + writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode); // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after // we've read all sessions. writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId); @@ -2269,10 +2282,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath()); params.appIconLastModified = appIconFile.lastModified(); } + 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); + return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerPackageName, installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed, - EMPTY_CHILD_SESSION_ARRAY, parentSessionId); + EMPTY_CHILD_SESSION_ARRAY, 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 33019627efda..2dc543712f0c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -13562,7 +13562,8 @@ public class PackageManagerService extends IPackageManager.Stub } Signature[] callerSignature; - Object obj = mSettings.getSettingLPr(callingUid); + final int appId = UserHandle.getAppId(callingUid); + final Object obj = mSettings.getSettingLPr(appId); if (obj != null) { if (obj instanceof SharedUserSetting) { callerSignature = diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 23293567be46..c297c62dfece 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -18,6 +18,7 @@ package com.android.server.pm; import android.annotation.NonNull; import android.apex.ApexInfo; +import android.apex.ApexInfoList; import android.apex.IApexService; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; @@ -52,9 +53,6 @@ public class StagingManager { private final PackageManagerService mPm; private final Handler mBgHandler; - // STOPSHIP: This is a temporary mock implementation of staged sessions. This variable - // shouldn't be needed at all. - // TODO(b/118865310): Implement staged sessions logic. @GuardedBy("mStagedSessions") private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>(); @@ -127,28 +125,55 @@ public class StagingManager { return false; } - void commitSession(@NonNull PackageInstallerSession sessionInfo) { - updateStoredSession(sessionInfo); + private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) { + final IApexService apex = IApexService.Stub.asInterface( + ServiceManager.getService("apexservice")); + boolean success; + try { + success = apex.submitStagedSession(sessionId, apexInfoList); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + return false; + } + return success; + } - mBgHandler.post(() -> { - sessionInfo.setStagedSessionReady(); - - SessionInfo session = sessionInfo.generateInfo(false); - // For APEXes, we validate the signature here before we write the package to the - // staging directory. 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. - if ((sessionInfo.params.installFlags & PackageManager.INSTALL_APEX) != 0) { - if (!validateApexSignatureLocked(session.resolvedBaseCodePath, - session.appPackageName)) { - sessionInfo.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED); + 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; + } } } + } + if (success) { + session.setStagedSessionReady(); + } else { + session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED); + } + mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId); + } - mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(false), sessionInfo.userId); - }); + void commitSession(@NonNull PackageInstallerSession session) { + updateStoredSession(session); + mBgHandler.post(() -> preRebootVerification(session)); } void createSession(@NonNull PackageInstallerSession sessionInfo) { @@ -163,4 +188,11 @@ public class StagingManager { mStagedSessions.remove(sessionInfo.sessionId); } } + + void restoreSession(@NonNull PackageInstallerSession session) { + updateStoredSession(session); + // TODO(b/118865310): This method is called when PackageInstaller is re-instantiated, e.g. + // at reboot. Staging manager should at this point recover state from apexd and decide what + // to do with the session. + } } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index dd04652a29b3..aaa187468f8d 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -620,9 +620,6 @@ public class UserRestrictionsUtils { && callingUid != Process.SYSTEM_UID) { return true; } else if (String.valueOf(Settings.Secure.LOCATION_MODE_OFF).equals(value)) { - // Note LOCATION_MODE will be converted into LOCATION_PROVIDERS_ALLOWED - // in android.provider.Settings.Secure.putStringForUser(), so we shouldn't come - // here normally, but we still protect it here from a direct provider write. return false; } restriction = UserManager.DISALLOW_SHARE_LOCATION; diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING index e212b0481ed6..437ef7392a3a 100644 --- a/services/core/java/com/android/server/policy/TEST_MAPPING +++ b/services/core/java/com/android/server/policy/TEST_MAPPING @@ -10,7 +10,7 @@ "include-annotation": "android.platform.test.annotations.Presubmit" }, { - "exclude-annotation": "android.support.test.filters.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] }, diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java new file mode 100644 index 000000000000..45c975b26956 --- /dev/null +++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java @@ -0,0 +1,97 @@ +/* + * 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.policy.role; + +import android.annotation.NonNull; +import android.app.role.RoleManager; +import android.content.Context; +import android.os.Debug; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.telephony.SmsApplication; +import com.android.internal.util.CollectionUtils; +import com.android.server.role.RoleManagerService; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Logic to retrieve the various legacy(pre-Q) equivalents of role holders. + * + * Unlike {@link RoleManagerService} this is meant to be pretty high-level to allow for depending + * on all kinds of various systems that are historically involved in legacy role resolution, + * e.g. {@link SmsApplication} + * + * @see RoleManagerService#migrateRoleIfNecessary + */ +public class LegacyRoleResolutionPolicy implements RoleManagerService.RoleHoldersResolver { + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "LegacyRoleResolutionPol"; + + @NonNull + private final Context mContext; + + public LegacyRoleResolutionPolicy(Context context) { + mContext = context; + } + + @Override + public List<String> getRoleHolders(String roleName, int userId) { + switch (roleName) { + case RoleManager.ROLE_SMS: { + // Moved over from SmsApplication#getApplication + String result = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.SMS_DEFAULT_APPLICATION, userId); + + if (result == null) { + Collection<SmsApplication.SmsApplicationData> applications = + SmsApplication.getApplicationCollectionAsUser(mContext, userId); + SmsApplication.SmsApplicationData applicationData; + String defaultPackage = mContext.getResources() + .getString(com.android.internal.R.string.default_sms_application); + applicationData = + SmsApplication.getApplicationForPackage(applications, defaultPackage); + + if (applicationData == null) { + // Are there any applications? + if (applications.size() != 0) { + applicationData = + (SmsApplication.SmsApplicationData) applications.toArray()[0]; + } + } + if (DEBUG) { + Log.i(LOG_TAG, "Found default sms app: " + applicationData + + " among: " + applications + " from " + Debug.getCallers(4)); + } + SmsApplication.SmsApplicationData app = applicationData; + result = app == null ? null : app.mPackageName; + } + + return CollectionUtils.singletonOrEmpty(result); + } + default: { + Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName); + return Collections.emptyList(); + } + } + } +} diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS index 244ccb69e958..5cbe74c5986c 100644 --- a/services/core/java/com/android/server/power/OWNERS +++ b/services/core/java/com/android/server/power/OWNERS @@ -1,6 +1,4 @@ michaelwr@google.com santoscordon@google.com -per-file BatterySaverPolicy.java=omakoto@google.com -per-file ShutdownThread.java=fkupolov@google.com -per-file ThermalManagerService.java=wvw@google.com
\ No newline at end of file +per-file ThermalManagerService.java=wvw@google.com diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 565bb706a5d8..a02787308246 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -96,6 +96,7 @@ import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.batterysaver.BatterySaverController; +import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; import com.android.server.power.batterysaver.BatterySavingStats; diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java index 02689a90a98a..7d03d821d4c8 100644 --- a/services/core/java/com/android/server/power/ThermalManagerService.java +++ b/services/core/java/com/android/server/power/ThermalManagerService.java @@ -134,10 +134,14 @@ public class ThermalManagerService extends SystemService { if (!halConnected) { mHalWrapper = new ThermalHal20Wrapper(); halConnected = mHalWrapper.connectToHal(); - if (!halConnected) { - mHalWrapper = new ThermalHal11Wrapper(); - halConnected = mHalWrapper.connectToHal(); - } + } + if (!halConnected) { + mHalWrapper = new ThermalHal11Wrapper(); + halConnected = mHalWrapper.connectToHal(); + } + if (!halConnected) { + mHalWrapper = new ThermalHal10Wrapper(); + halConnected = mHalWrapper.connectToHal(); } mHalWrapper.setCallback(this::onTemperatureChangedCallback); if (!halConnected) { @@ -616,6 +620,81 @@ public class ThermalManagerService extends SystemService { } } + + static class ThermalHal10Wrapper extends ThermalHalWrapper { + /** Proxy object for the Thermal HAL 1.0 service. */ + @GuardedBy("mHalLock") + private android.hardware.thermal.V1_0.IThermal mThermalHal10 = null; + + @Override + protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, + int type) { + synchronized (mHalLock) { + List<Temperature> ret = new ArrayList<>(); + if (mThermalHal10 == null) { + return ret; + } + try { + mThermalHal10.getTemperatures( + (ThermalStatus status, + ArrayList<android.hardware.thermal.V1_0.Temperature> + temperatures) -> { + if (ThermalStatusCode.SUCCESS == status.code) { + for (android.hardware.thermal.V1_0.Temperature + temperature : temperatures) { + if (shouldFilter && type != temperature.type) { + continue; + } + // Thermal HAL 1.0 doesn't report current throttling status + ret.add(new Temperature( + temperature.currentValue, temperature.type, + temperature.name, + Temperature.THROTTLING_NONE)); + } + } else { + Slog.e(TAG, + "Couldn't get temperatures because of HAL error: " + + status.debugMessage); + } + + }); + } catch (RemoteException e) { + Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting...", e); + connectToHal(); + } + return ret; + } + } + + @Override + protected boolean connectToHal() { + synchronized (mHalLock) { + try { + mThermalHal10 = android.hardware.thermal.V1_0.IThermal.getService(); + mThermalHal10.linkToDeath(new DeathRecipient(), + THERMAL_HAL_DEATH_COOKIE); + Slog.i(TAG, + "Thermal HAL 1.0 service connected, no thermal call back will be " + + "called due to legacy API."); + } catch (NoSuchElementException | RemoteException e) { + Slog.e(TAG, + "Thermal HAL 1.0 service not connected."); + mThermalHal10 = null; + } + return (mThermalHal10 != null); + } + } + + @Override + protected void dump(PrintWriter pw, String prefix) { + synchronized (mHalLock) { + pw.print(prefix); + pw.println("ThermalHAL 1.0 connected: " + (mThermalHal10 != null ? "yes" + : "no")); + } + } + } + static class ThermalHal11Wrapper extends ThermalHalWrapper { /** Proxy object for the Thermal HAL 1.1 service. */ @GuardedBy("mHalLock") diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java index 6400c88b320f..1d74350de978 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java @@ -35,13 +35,13 @@ import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; import com.android.server.LocalServices; -import com.android.server.power.BatterySaverPolicy; -import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener; import com.android.server.power.PowerManagerService; +import com.android.server.power.batterysaver.BatterySaverPolicy.BatterySaverPolicyListener; import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState; import com.android.server.power.batterysaver.BatterySavingStats.DozeState; import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState; @@ -158,10 +158,9 @@ public class BatterySaverController implements BatterySaverPolicyListener { mBatterySavingStats = batterySavingStats; // Initialize plugins. - final ArrayList<Plugin> plugins = new ArrayList<>(); - plugins.add(new BatterySaverLocationPlugin(mContext)); - - mPlugins = plugins.toArray(new Plugin[plugins.size()]); + mPlugins = new Plugin[] { + new BatterySaverLocationPlugin(mContext) + }; } /** @@ -217,7 +216,7 @@ public class BatterySaverController implements BatterySaverPolicyListener { super(looper); } - public void postStateChanged(boolean sendBroadcast, int reason) { + void postStateChanged(boolean sendBroadcast, int reason) { obtainMessage(MSG_STATE_CHANGED, sendBroadcast ? ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, reason).sendToTarget(); } @@ -244,9 +243,8 @@ public class BatterySaverController implements BatterySaverPolicyListener { } } - /** - * Called by {@link PowerManagerService} to update the battery saver state. - */ + /** Enable or disable full battery saver. */ + @VisibleForTesting public void enableBatterySaver(boolean enable, int reason) { synchronized (mLock) { if (mEnabled == enable) { @@ -311,7 +309,7 @@ public class BatterySaverController implements BatterySaverPolicyListener { reason); mPreviouslyEnabled = mEnabled; - listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]); + listeners = mListeners.toArray(new LowPowerModeListener[0]); enabled = mEnabled; mIsInteractive = isInteractive; diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java index cedb54801dc8..48a041eaab28 100644 --- a/services/core/java/com/android/server/power/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.power; +package com.android.server.power.batterysaver; import android.content.ContentResolver; import android.content.Context; @@ -36,21 +36,19 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ConcurrentUtils; -import com.android.server.power.batterysaver.BatterySavingStats; -import com.android.server.power.batterysaver.CpuFrequencies; +import com.android.server.power.PowerManagerService; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** - * Class to decide whether to turn on battery saver mode for specific service + * Class to decide whether to turn on battery saver mode for specific services. * * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy. * Do not call out with the lock held, such as AccessibilityManager. (Settings provider is okay.) * - * Test: - atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java + * Test: atest com.android.server.power.batterysaver.BatterySaverPolicyTest.java */ public class BatterySaverPolicy extends ContentObserver { private static final String TAG = "BatterySaverPolicy"; @@ -200,8 +198,10 @@ public class BatterySaverPolicy extends ContentObserver { onChange(true, null); } + @VisibleForTesting public void addListener(BatterySaverPolicyListener listener) { synchronized (mLock) { + // TODO: set this in the constructor instead mListeners.add(listener); } } @@ -244,7 +244,7 @@ public class BatterySaverPolicy extends ContentObserver { // Update. updateConstantsLocked(setting, deviceSpecificSetting); - listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]); + listeners = mListeners.toArray(new BatterySaverPolicyListener[0]); } // Notify the listeners. diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java index a60f16d7ca27..b7f28da499e9 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java @@ -37,7 +37,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.server.EventLogTags; -import com.android.server.power.BatterySaverPolicy; import com.android.server.power.BatterySaverStateMachineProto; import java.io.PrintWriter; @@ -229,8 +228,8 @@ public class BatterySaverStateMachine { } /** - * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable}, - * it'll be first removed before a new one is posted. + * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable} is + * already registered, it'll be first removed before being re-posted. */ @VisibleForTesting void runOnBgThreadLazy(Runnable r, int delayMillis) { diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java index 821320559017..79b44ebd8645 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java @@ -29,7 +29,6 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.EventLogTags; import com.android.server.LocalServices; -import com.android.server.power.BatterySaverPolicy; import java.io.PrintWriter; import java.text.SimpleDateFormat; diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index f37ca12bbd7f..b4883371b468 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -35,6 +35,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; +import android.os.Binder; import android.os.Handler; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -92,6 +93,15 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @NonNull private final Object mLock = new Object(); + @NonNull + private final RoleHoldersResolver mLegacyRoleResolver; + + /** @see #getRoleHolders(String, int) */ + public interface RoleHoldersResolver { + /** @return a list of packages that hold a given role for a given user */ + List<String> getRoleHolders(String roleName, int userId); + } + /** * Maps user id to its state. */ @@ -118,9 +128,12 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @NonNull private final Handler mListenerHandler = FgThread.getHandler(); - public RoleManagerService(@NonNull Context context) { + public RoleManagerService(@NonNull Context context, + @NonNull RoleHoldersResolver legacyRoleResolver) { super(context); + mLegacyRoleResolver = legacyRoleResolver; + mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); @@ -175,10 +188,17 @@ public class RoleManagerService extends SystemService implements RoleUserState.C private void performInitialGrantsIfNecessary(@UserIdInt int userId) { RoleUserState userState; userState = getOrCreateUserState(userId); + String packagesHash = computeComponentStateHash(userId); String oldPackagesHash = userState.getPackagesHash(); boolean needGrant = !Objects.equals(packagesHash, oldPackagesHash); if (needGrant) { + + //TODO gradually add more role migrations statements here for remaining roles + // Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders + // for a given role before adding a migration statement for it here + migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId); + // Some vital packages state has changed since last role grant // Run grants again Slog.i(LOG_TAG, "Granting default permissions..."); @@ -205,6 +225,20 @@ public class RoleManagerService extends SystemService implements RoleUserState.C } } + private void migrateRoleIfNecessary(String role, @UserIdInt int userId) { + // Any role for which we have a record are already migrated + RoleUserState userState = getOrCreateUserState(userId); + if (!userState.isRoleAvailable(role)) { + userState.addRoleName(role); + List<String> roleHolders = mLegacyRoleResolver.getRoleHolders(role, userId); + Slog.i(LOG_TAG, "Migrating " + role + ", legacy holders: " + roleHolders); + int size = roleHolders.size(); + for (int i = 0; i < size; i++) { + userState.addRoleHolder(role, roleHolders.get(i)); + } + } + } + @Nullable private static String computeComponentStateHash(@UserIdInt int userId) { PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); @@ -372,6 +406,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @Nullable private ArraySet<String> getRoleHoldersInternal(@NonNull String roleName, @UserIdInt int userId) { + migrateRoleIfNecessary(roleName, userId); RoleUserState userState = getOrCreateUserState(userId); return userState.getRoleHolders(roleName); } @@ -530,6 +565,17 @@ public class RoleManagerService extends SystemService implements RoleUserState.C } @Override + public String getDefaultSmsPackage(int userId) { + long identity = Binder.clearCallingIdentity(); + try { + return CollectionUtils.firstOrNull( + getRoleHoldersAsUser(RoleManager.ROLE_SMS, userId)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), LOG_TAG, fout)) { diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java index 630a39caeb08..69e144951154 100644 --- a/services/core/java/com/android/server/role/RoleUserState.java +++ b/services/core/java/com/android/server/role/RoleUserState.java @@ -205,6 +205,28 @@ public class RoleUserState { } /** + * Adds the given role, effectively marking it as {@link #isRoleAvailable available} + * + * @param roleName the name of the role + * + * @return whether any changes were made + */ + public boolean addRoleName(@NonNull String roleName) { + synchronized (mLock) { + throwIfDestroyedLocked(); + + if (!mRoles.containsKey(roleName)) { + mRoles.put(roleName, new ArraySet<>()); + Slog.i(LOG_TAG, "Added new role: " + roleName); + scheduleWriteFileLocked(); + return true; + } else { + return false; + } + } + } + + /** * Set the names of all available roles. * * @param roleNames the names of all the available roles @@ -231,13 +253,7 @@ public class RoleUserState { int roleNamesSize = roleNames.size(); for (int i = 0; i < roleNamesSize; i++) { - String roleName = roleNames.get(i); - - if (!mRoles.containsKey(roleName)) { - mRoles.put(roleName, new ArraySet<>()); - Slog.i(LOG_TAG, "Added new role: " + roleName); - changed = true; - } + changed |= addRoleName(roleNames.get(i)); } if (changed) { diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index fc21adbdcca3..7c1e6198080d 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -598,11 +598,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onBiometricAuthenticated() { + public void onBiometricAuthenticated(boolean authenticated) { enforceBiometricDialog(); if (mBar != null) { try { - mBar.onBiometricAuthenticated(); + mBar.onBiometricAuthenticated(authenticated); } catch (RemoteException ex) { } } @@ -641,17 +641,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } - @Override - public void showBiometricTryAgain() { - enforceBiometricDialog(); - if (mBar != null) { - try { - mBar.showBiometricTryAgain(); - } catch (RemoteException ex) { - } - } - } - // TODO(b/117478341): make it aware of multi-display if needed. @Override public void disable(int what, IBinder token, String pkg) { diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 2cab63aa680a..ef771406805b 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -510,7 +510,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent()); willBind = mContext.bindServiceAsUser( serviceIntent, mConnection, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE + | Context.BIND_RESTRICT_ASSOCIATIONS, UserHandle.of(mUserId)); mBinding = willBind; } finally { diff --git a/services/core/java/com/android/server/textservices/TextServicesManagerService.java b/services/core/java/com/android/server/textservices/TextServicesManagerService.java index 65d5b10590fa..7236d79c55ab 100644 --- a/services/core/java/com/android/server/textservices/TextServicesManagerService.java +++ b/services/core/java/com/android/server/textservices/TextServicesManagerService.java @@ -16,8 +16,6 @@ package com.android.server.textservices; -import static android.view.textservice.TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -43,6 +41,7 @@ import android.service.textservice.SpellCheckerService; import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; +import android.view.inputmethod.InputMethodSystemProperty; import android.view.textservice.SpellCheckerInfo; import android.view.textservice.SpellCheckerSubtype; @@ -334,7 +333,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { mContext = context; mUserManager = mContext.getSystemService(UserManager.class); mSpellCheckerOwnerUserIdMap = new LazyIntToIntMap(callingUserId -> { - if (DISABLE_PER_PROFILE_SPELL_CHECKER) { + if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) { final long token = Binder.clearCallingIdentity(); try { final UserInfo parent = mUserManager.getProfileParent(callingUserId); @@ -355,7 +354,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { private void initializeInternalStateLocked(@UserIdInt int userId) { // When DISABLE_PER_PROFILE_SPELL_CHECKER is true, we make sure here that work profile users // will never have non-null TextServicesData for their user ID. - if (DISABLE_PER_PROFILE_SPELL_CHECKER + if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED && userId != mSpellCheckerOwnerUserIdMap.get(userId)) { return; } @@ -780,7 +779,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { private TextServicesData getDataFromCallingUserIdLocked(@UserIdInt int callingUserId) { final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(callingUserId); final TextServicesData data = mUserData.get(spellCheckerOwnerUserId); - if (DISABLE_PER_PROFILE_SPELL_CHECKER) { + if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) { if (spellCheckerOwnerUserId != callingUserId) { // Calling process is running under child profile. if (data == null) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 410f864b5893..fcc828461bc3 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -84,6 +84,7 @@ import android.service.wallpaper.WallpaperService; import android.system.ErrnoException; import android.system.Os; import android.util.EventLog; +import android.util.FeatureFlagUtils; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -2219,8 +2220,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { mInAmbientMode = inAmbientMode; final WallpaperData data = mWallpaperMap.get(mCurrentUserId); - if (data != null && data.connection != null && data.connection.mInfo != null - && data.connection.mInfo.supportsAmbientMode()) { + final boolean hasConnection = data != null && data.connection != null; + final WallpaperInfo info = hasConnection ? data.connection.mInfo : null; + + // The wallpaper info is null for image wallpaper, also use the engine in this case. + if (hasConnection && (info == null && isAodImageWallpaperEnabled() + || info != null && info.supportsAmbientMode())) { // TODO(multi-display) Extends this method with specific display. engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine; } else { @@ -2237,6 +2242,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + private boolean isAodImageWallpaperEnabled() { + return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED); + } + @Override public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) { checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6f8f85f1af26..f8f0d1cfcac8 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -390,6 +390,11 @@ final class ActivityRecord extends ConfigurationContainer { private boolean mTurnScreenOn; /** + * Current sequencing integer of the configuration, for skipping old activity configurations. + */ + private int mConfigurationSeq; + + /** * Temp configs used in {@link #ensureActivityConfiguration(int, boolean)} */ private final Configuration mTmpConfig = new Configuration(); @@ -2568,6 +2573,45 @@ final class ActivityRecord extends ConfigurationContainer { onRequestedOverrideConfigurationChanged(mTmpConfig); } + @Override + void resolveOverrideConfiguration(Configuration newParentConfiguration) { + super.resolveOverrideConfiguration(newParentConfiguration); + + // Assign configuration sequence number into hierarchy because there is a different way than + // ensureActivityConfiguration() in this class that uses configuration in WindowState during + // layout traversals. + mConfigurationSeq = Math.max(++mConfigurationSeq, 1); + getResolvedOverrideConfiguration().seq = mConfigurationSeq; + } + + @Override + public void onConfigurationChanged(Configuration newParentConfig) { + super.onConfigurationChanged(newParentConfig); + + // Configuration's equality doesn't consider seq so if only seq number changes in resolved + // override configuration. Therefore ConfigurationContainer doesn't change merged override + // configuration, but it's used to push configuration changes so explicitly update that. + if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) { + onMergedOverrideConfigurationChanged(); + } + + // TODO(b/80414790): Remove code below after unification. + // Same as above it doesn't notify configuration listeners, and consequently AppWindowToken + // can't get updated seq number. However WindowState's merged override configuration needs + // to have this seq number because that's also used for activity config pushes during layout + // traversal. Therefore explicitly update them here. + if (mAppWindowToken == null) { + return; + } + final Configuration appWindowTokenRequestedOverrideConfig = + mAppWindowToken.getRequestedOverrideConfiguration(); + if (appWindowTokenRequestedOverrideConfig.seq != getResolvedOverrideConfiguration().seq) { + appWindowTokenRequestedOverrideConfig.seq = + getResolvedOverrideConfiguration().seq; + mAppWindowToken.onMergedOverrideConfigurationChanged(); + } + } + /** Returns true if the configuration is compatible with this activity. */ boolean isConfigurationCompatible(Configuration config) { final int orientation = mAppWindowToken != null diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 0f286ce30ccd..d8644df3684c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -19,6 +19,7 @@ package com.android.server.wm; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.AppProtoEnums; import android.app.IActivityManager; import android.app.IApplicationThread; @@ -479,4 +480,10 @@ public abstract class ActivityTaskManagerInternal { public abstract void setProfilerInfo(ProfilerInfo profilerInfo); public abstract ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry(); + + /** + * Gets bitmap snapshot of the provided task id. + */ + public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, + boolean reducedResolution); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 42121ca08696..e5bf21a205e3 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -245,7 +245,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.server.AppOpsService; +import com.android.server.appop.AppOpsService; import com.android.server.AttributeCache; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -7012,5 +7012,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mStackSupervisor.getActivityMetricsLogger().getLaunchObserverRegistry(); } } + + @Override + public ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) { + synchronized (mGlobalLock) { + return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution); + } + } } } diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/wm/WindowTraceBuffer.java new file mode 100644 index 000000000000..936ee85697b8 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowTraceBuffer.java @@ -0,0 +1,181 @@ +/* + * 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; + +import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; +import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; +import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; + +import android.os.Trace; +import android.util.proto.ProtoOutputStream; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Buffer used for window tracing. + */ +abstract class WindowTraceBuffer { + private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; + + final Object mBufferSizeLock = new Object(); + final BlockingQueue<byte[]> mBuffer; + int mBufferSize; + private final int mBufferCapacity; + private final File mTraceFile; + + WindowTraceBuffer(int size, File traceFile) throws IOException { + mBufferCapacity = size; + mTraceFile = traceFile; + mBuffer = new LinkedBlockingQueue<>(); + + initTraceFile(); + } + + int getAvailableSpace() { + return mBufferCapacity - mBufferSize; + } + + /** + * Inserts the specified element into this buffer. + * + * This method is synchronized with {@code #take()} and {@code #clear()} + * for consistency. + * + * @param proto the element to add + * @return {@code true} if the inserted item was inserted into the buffer + * @throws IllegalStateException if the element cannot be added because it is larger + * than the buffer size. + */ + boolean add(ProtoOutputStream proto) throws InterruptedException { + byte[] protoBytes = proto.getBytes(); + int protoLength = protoBytes.length; + if (protoLength > mBufferCapacity) { + throw new IllegalStateException("Trace object too large for the buffer. Buffer size:" + + mBufferCapacity + " Object size: " + protoLength); + } + synchronized (mBufferSizeLock) { + boolean canAdd = canAdd(protoBytes); + if (canAdd) { + mBuffer.offer(protoBytes); + mBufferSize += protoLength; + } + return canAdd; + } + } + + void writeNextBufferElementToFile() throws IOException { + byte[] proto; + try { + proto = take(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + try { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile"); + try (OutputStream os = new FileOutputStream(mTraceFile, true)) { + os.write(proto); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + } + } + + /** + * Retrieves and removes the head of this queue, waiting if necessary + * until an element becomes available. + * + * This method is synchronized with {@code #add(ProtoOutputStream)} and {@code #clear()} + * for consistency. + * + * @return the head of this buffer, or {@code null} if this buffer is empty + */ + private byte[] take() throws InterruptedException { + byte[] item = mBuffer.take(); + synchronized (mBufferSizeLock) { + mBufferSize -= item.length; + return item; + } + } + + private void initTraceFile() throws IOException { + mTraceFile.delete(); + try (OutputStream os = new FileOutputStream(mTraceFile)) { + mTraceFile.setReadable(true, false); + ProtoOutputStream proto = new ProtoOutputStream(os); + proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); + proto.flush(); + } + } + + /** + * Checks if the element can be added to the buffer. The element is already certain to be + * smaller than the overall buffer size. + * + * @param protoBytes byte array representation of the Proto object to add + * @return <tt>true<</tt> if the element can be added to the buffer or not + */ + abstract boolean canAdd(byte[] protoBytes) throws InterruptedException; + + /** + * Flush all buffer content to the disk. + * + * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile} + */ + abstract void writeToDisk() throws IOException, InterruptedException; + + /** + * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceQueueBuffer} + */ + static class Builder { + private File mTraceFile; + private int mBufferCapacity; + + + Builder setTraceFile(File traceFile) { + mTraceFile = traceFile; + return this; + } + + Builder setBufferCapacity(int size) { + mBufferCapacity = size; + return this; + } + + File getFile() { + return mTraceFile; + } + + WindowTraceBuffer build() throws IOException { + if (mBufferCapacity <= 0) { + throw new IllegalStateException("Buffer capacity must be greater than 0."); + } + + if (mTraceFile == null) { + throw new IllegalArgumentException("A valid trace file must be specified."); + } + + return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile); + } + } +} diff --git a/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java new file mode 100644 index 000000000000..b7fc7ac8cb5e --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java @@ -0,0 +1,88 @@ +/* + * 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; + +import static android.os.Build.IS_USER; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.io.IOException; + +/** + * A buffer structure backed by a {@link java.util.concurrent.BlockingQueue} to store the first + * {@code #size size} bytes of window trace elements. + * Once the buffer is full it will no longer accepts new elements. + */ +class WindowTraceQueueBuffer extends WindowTraceBuffer { + private Thread mWriterThread; + private boolean mCancel; + + @VisibleForTesting + WindowTraceQueueBuffer(int size, File traceFile, boolean startWriterThread) throws IOException { + super(size, traceFile); + if (startWriterThread) { + initializeWriterThread(); + } + } + + WindowTraceQueueBuffer(int size, File traceFile) throws IOException { + this(size, traceFile, !IS_USER); + } + + private void initializeWriterThread() { + mCancel = false; + mWriterThread = new Thread(() -> { + try { + loop(); + } catch (IOException e) { + throw new IllegalStateException("Failed to execute trace write loop thread", e); + } + }, "window_tracing"); + mWriterThread.start(); + } + + private void loop() throws IOException { + while (!mCancel) { + writeNextBufferElementToFile(); + } + } + + private void restartWriterThread() throws InterruptedException { + if (mWriterThread != null) { + mCancel = true; + mWriterThread.interrupt(); + mWriterThread.join(); + initializeWriterThread(); + } + } + + @Override + boolean canAdd(byte[] protoBytes) { + long availableSpace = getAvailableSpace(); + return availableSpace >= protoBytes.length; + } + + @Override + void writeToDisk() throws InterruptedException { + while (!mBuffer.isEmpty()) { + mBufferSizeLock.wait(); + mBufferSizeLock.notify(); + } + restartWriterThread(); + } +} diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 8fa56bb065c6..63539c4f9fd9 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -17,31 +17,23 @@ package com.android.server.wm; import static android.os.Build.IS_USER; + import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY; -import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; -import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; -import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS; import static com.android.server.wm.WindowManagerTraceProto.WHERE; import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE; +import android.annotation.Nullable; import android.content.Context; import android.os.ShellCommand; import android.os.SystemClock; import android.os.Trace; -import android.annotation.Nullable; import android.util.Log; import android.util.proto.ProtoOutputStream; -import com.android.internal.annotations.VisibleForTesting; - import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.io.PrintWriter; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; /** * A class that allows window manager to dump its state continuously to a trace file, such that a @@ -49,35 +41,42 @@ import java.util.concurrent.BlockingQueue; */ class WindowTracing { + /** + * Maximum buffer size, currently defined as 512 KB + * Size was experimentally defined to fit between 100 to 150 elements. + */ + private static final int WINDOW_TRACE_BUFFER_SIZE = 512 * 1024; private static final String TAG = "WindowTracing"; - private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; private final Object mLock = new Object(); - private final File mTraceFile; - private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200); + private final WindowTraceBuffer.Builder mBufferBuilder; + + private WindowTraceBuffer mTraceBuffer; private boolean mEnabled; private volatile boolean mEnabledLockFree; WindowTracing(File file) { - mTraceFile = file; + mBufferBuilder = new WindowTraceBuffer.Builder() + .setTraceFile(file) + .setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE); } void startTrace(@Nullable PrintWriter pw) throws IOException { - if (IS_USER){ + if (IS_USER) { logAndPrintln(pw, "Error: Tracing is not supported on user builds."); return; } synchronized (mLock) { - logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); - mWriteQueue.clear(); - mTraceFile.delete(); - try (OutputStream os = new FileOutputStream(mTraceFile)) { - mTraceFile.setReadable(true, false); - ProtoOutputStream proto = new ProtoOutputStream(os); - proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); - proto.flush(); + logAndPrintln(pw, "Start tracing to " + mBufferBuilder.getFile() + "."); + if (mTraceBuffer != null) { + try { + mTraceBuffer.writeToDisk(); + } catch (InterruptedException e) { + logAndPrintln(pw, "Error: Unable to flush the previous buffer."); + } } + mTraceBuffer = mBufferBuilder.build(); mEnabled = mEnabledLockFree = true; } } @@ -91,67 +90,42 @@ class WindowTracing { } void stopTrace(@Nullable PrintWriter pw) { - if (IS_USER){ + if (IS_USER) { logAndPrintln(pw, "Error: Tracing is not supported on user builds."); return; } synchronized (mLock) { - logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); + logAndPrintln(pw, "Stop tracing to " + mBufferBuilder.getFile() + + ". Waiting for traces to flush."); mEnabled = mEnabledLockFree = false; - while (!mWriteQueue.isEmpty()) { + + synchronized (mLock) { if (mEnabled) { logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush."); throw new IllegalStateException("tracing enabled while waiting for flush."); } try { - mLock.wait(); - mLock.notify(); + mTraceBuffer.writeToDisk(); + } catch (IOException e) { + Log.e(TAG, "Unable to write buffer to file", e); } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + Log.e(TAG, "Unable to interrupt window tracing file write thread", e); } } - logAndPrintln(pw, "Trace written to " + mTraceFile + "."); + logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + "."); } } - void appendTraceEntry(ProtoOutputStream proto) { + private void appendTraceEntry(ProtoOutputStream proto) { if (!mEnabledLockFree) { return; } - if (!mWriteQueue.offer(proto)) { - Log.e(TAG, "Dropping window trace entry, queue full"); - } - } - - void loop() { - for (;;) { - loopOnce(); - } - } - - @VisibleForTesting - void loopOnce() { - ProtoOutputStream proto; try { - proto = mWriteQueue.take(); + mTraceBuffer.add(proto); } catch (InterruptedException e) { + Log.e(TAG, "Unable to add element to trace", e); Thread.currentThread().interrupt(); - return; - } - - synchronized (mLock) { - try { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile"); - try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) { - os.write(proto.getBytes()); - } - } catch (IOException e) { - Log.e(TAG, "Failed to write file " + mTraceFile, e); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - mLock.notify(); } } @@ -161,11 +135,7 @@ class WindowTracing { static WindowTracing createDefaultAndStartLooper(Context context) { File file = new File("/data/misc/wmtrace/wm_trace.pb"); - WindowTracing windowTracing = new WindowTracing(file); - if (!IS_USER){ - new Thread(windowTracing::loop, "window_tracing").start(); - } - return windowTracing; + return new WindowTracing(file); } int onShellCommand(ShellCommand shell, String cmd) { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 9ecafb3c70d6..c8c5e8f136af 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -107,6 +107,7 @@ cc_defaults { "android.hardware.gnss@1.0", "android.hardware.gnss@1.1", "android.hardware.gnss@2.0", + "android.hardware.input.classifier@1.0", "android.hardware.ir@1.0", "android.hardware.light@2.0", "android.hardware.power@1.0", diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 0929e20c9187..641200769cf0 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -244,7 +244,7 @@ public: const sp<IBinder>& token, const std::string& reason); virtual void notifyInputChannelBroken(const sp<IBinder>& token); - virtual void notifyFocusChanged(const sp<IBinder>& token); + virtual void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken); virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags); virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig); virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); @@ -738,7 +738,8 @@ void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) { } } -void NativeInputManager::notifyFocusChanged(const sp<IBinder>& token) { +void NativeInputManager::notifyFocusChanged(const sp<IBinder>& oldToken, + const sp<IBinder>& newToken) { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("notifyFocusChanged"); #endif @@ -746,12 +747,11 @@ void NativeInputManager::notifyFocusChanged(const sp<IBinder>& token) { JNIEnv* env = jniEnv(); - jobject tokenObj = javaObjectForIBinder(env, token); - if (tokenObj) { - env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyFocusChanged, - tokenObj); - checkAndClearExceptionFromCallback(env, "notifyFocusChanged"); - } + jobject oldTokenObj = javaObjectForIBinder(env, oldToken); + jobject newTokenObj = javaObjectForIBinder(env, newToken); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyFocusChanged, + oldTokenObj, newTokenObj); + checkAndClearExceptionFromCallback(env, "notifyFocusChanged"); } void NativeInputManager::getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) { @@ -1500,27 +1500,6 @@ static void nativeSetSystemUiVisibility(JNIEnv* /* env */, im->setSystemUiVisibility(visibility); } -static jboolean nativeTransferTouchFocus(JNIEnv* env, - jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) { - NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); - - sp<InputChannel> fromChannel = - android_view_InputChannel_getInputChannel(env, fromChannelObj); - sp<InputChannel> toChannel = - android_view_InputChannel_getInputChannel(env, toChannelObj); - - if (fromChannel == nullptr || toChannel == nullptr) { - return JNI_FALSE; - } - - if (im->getInputManager()->getDispatcher()-> - transferTouchFocus(fromChannel, toChannel)) { - return JNI_TRUE; - } else { - return JNI_FALSE; - } -} - static void nativeSetPointerSpeed(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint speed) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1708,8 +1687,6 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeSetInputDispatchMode }, { "nativeSetSystemUiVisibility", "(JI)V", (void*) nativeSetSystemUiVisibility }, - { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z", - (void*) nativeTransferTouchFocus }, { "nativeSetPointerSpeed", "(JI)V", (void*) nativeSetPointerSpeed }, { "nativeSetShowTouches", "(JZ)V", @@ -1785,7 +1762,7 @@ int register_android_server_InputManager(JNIEnv* env) { "notifyInputChannelBroken", "(Landroid/os/IBinder;)V"); GET_METHOD_ID(gServiceClassInfo.notifyFocusChanged, clazz, - "notifyFocusChanged", "(Landroid/os/IBinder;)V"); + "notifyFocusChanged", "(Landroid/os/IBinder;Landroid/os/IBinder;)V"); GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz, "notifyANR", diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7f6895a4cf83..76ae5ccd4eeb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -221,7 +221,7 @@ import android.view.IWindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSystemProperty; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -245,6 +245,7 @@ import com.android.server.LockGuard; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.pm.UserRestrictionsUtils; import com.android.server.storage.DeviceStorageMonitorInternal; @@ -5322,6 +5323,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final int callingUserId = mInjector.userHandleGetCallingUserId(); + ComponentName adminComponent = null; synchronized (getLockObject()) { // Make sure the caller has any active admin with the right policy or // the required permission. @@ -5332,8 +5334,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { android.Manifest.permission.LOCK_DEVICE); final long ident = mInjector.binderClearCallingIdentity(); try { - final ComponentName adminComponent = admin == null ? - null : admin.info.getComponent(); + adminComponent = admin == null ? null : admin.info.getComponent(); if (adminComponent != null) { // For Profile Owners only, callers with only permission not allowed. if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) { @@ -5385,6 +5386,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } DevicePolicyEventLogger .createEvent(DevicePolicyEnums.LOCK_NOW) + .setAdmin(adminComponent) .setInt(flags) .write(); } @@ -6101,7 +6103,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { delegates = getDelegatePackagesInternalLocked(scope, userId); } - if (delegates.size() != 1) { + if (delegates.size() == 0) { + return null; + } else if (delegates.size() > 1) { Slog.wtf(LOG_TAG, "More than one delegate holds " + scope); return null; } @@ -9188,21 +9192,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkNotNull(who, "ComponentName is null"); - // TODO When InputMethodManager supports per user calls remove - // this restriction. - if (!checkCallerIsCurrentUserOrProfile()) { + // TODO When InputMethodManager supports per user calls remove this restriction. + if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED + && !checkCallerIsCurrentUserOrProfile()) { return false; } - final int callingUserId = mInjector.userHandleGetCallingUserId(); if (packageList != null) { - // InputMethodManager fetches input methods for current user. - // So this can only be set when calling user is the current user - // or parent is current user in case of managed profiles. - InputMethodManager inputMethodManager = - mContext.getSystemService(InputMethodManager.class); - List<InputMethodInfo> enabledImes = inputMethodManager.getEnabledInputMethodList(); - + List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get() + .getEnabledInputMethodListAsUser(callingUserId); if (enabledImes != null) { List<String> enabledPackages = new ArrayList<String>(); for (InputMethodInfo ime : enabledImes) { @@ -9250,22 +9248,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public List getPermittedInputMethodsForCurrentUser() { enforceManageUsers(); - UserInfo currentUser; - try { - currentUser = mInjector.getIActivityManager().getCurrentUser(); - } catch (RemoteException e) { - Slog.e(LOG_TAG, "Failed to make remote calls to get current user", e); - // Activity managed is dead, just allow all IMEs - return null; - } - int userId = currentUser.id; + final int callingUserId = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { List<String> result = null; // If we have multiple profiles we return the intersection of the // permitted lists. This can happen in cases where we have a device // and profile owner. - int[] profileIds = mUserManager.getProfileIdsWithDisabled(userId); + int[] profileIds = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED + ? new int[]{callingUserId} + : mUserManager.getProfileIdsWithDisabled(callingUserId); for (int profileId : profileIds) { // Just loop though all admins, only device or profiles // owners can have permitted lists set. @@ -9286,9 +9278,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // If we have a permitted list add all system input methods. if (result != null) { - InputMethodManager inputMethodManager = - mContext.getSystemService(InputMethodManager.class); - List<InputMethodInfo> imes = inputMethodManager.getInputMethodList(); + List<InputMethodInfo> imes = + InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId); if (imes != null) { for (InputMethodInfo ime : imes) { ServiceInfo serviceInfo = ime.getServiceInfo(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java index d0ec0eeebd5c..699bec2c9329 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java @@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USE import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.admin.DeviceAdminReceiver; import android.content.ComponentName; import android.content.Context; @@ -30,15 +31,13 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; import android.util.ArraySet; import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSystemProperty; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.view.IInputMethodManager; +import com.android.server.inputmethod.InputMethodManagerInternal; import java.util.Arrays; import java.util.List; @@ -53,18 +52,38 @@ public class OverlayPackagesProvider { protected static final String TAG = "OverlayPackagesProvider"; private final PackageManager mPm; - private final IInputMethodManager mIInputMethodManager; private final Context mContext; + private final Injector mInjector; public OverlayPackagesProvider(Context context) { - this(context, getIInputMethodManager()); + this(context, new DefaultInjector()); } @VisibleForTesting - OverlayPackagesProvider(Context context, IInputMethodManager iInputMethodManager) { + interface Injector { + boolean isPerProfileImeEnabled(); + @NonNull + List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId); + } + + private static final class DefaultInjector implements Injector { + @Override + public boolean isPerProfileImeEnabled() { + return InputMethodSystemProperty.PER_PROFILE_IME_ENABLED; + } + + @NonNull + @Override + public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { + return InputMethodManagerInternal.get().getInputMethodListAsUser(userId); + } + } + + @VisibleForTesting + OverlayPackagesProvider(Context context, Injector injector) { mContext = context; mPm = checkNotNull(context.getPackageManager()); - mIInputMethodManager = checkNotNull(iInputMethodManager); + mInjector = checkNotNull(injector); } /** @@ -89,10 +108,12 @@ public class OverlayPackagesProvider { // Newly installed system apps are uninstalled when they are not required and are either // disallowed or have a launcher icon. nonRequiredApps.removeAll(getRequiredApps(provisioningAction, admin.getPackageName())); - // Don't delete the system input method packages in case of Device owner provisioning. - if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction) + if (mInjector.isPerProfileImeEnabled()) { + nonRequiredApps.removeAll(getSystemInputMethods(userId)); + } else if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction) || ACTION_PROVISION_MANAGED_USER.equals(provisioningAction)) { - nonRequiredApps.removeAll(getSystemInputMethods()); + // Don't delete the system input method packages in case of Device owner provisioning. + nonRequiredApps.removeAll(getSystemInputMethods(userId)); } nonRequiredApps.addAll(getDisallowedApps(provisioningAction)); return nonRequiredApps; @@ -114,16 +135,8 @@ public class OverlayPackagesProvider { return apps; } - private Set<String> getSystemInputMethods() { - // InputMethodManager is final so it cannot be mocked. - // So, we're using IInputMethodManager directly because it can be mocked. - final List<InputMethodInfo> inputMethods; - try { - inputMethods = mIInputMethodManager.getInputMethodList(); - } catch (RemoteException e) { - // Should not happen - return null; - } + private Set<String> getSystemInputMethods(int userId) { + final List<InputMethodInfo> inputMethods = mInjector.getInputMethodListAsUser(userId); final Set<String> systemInputMethods = new ArraySet<>(); for (InputMethodInfo inputMethodInfo : inputMethods) { ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo; @@ -149,11 +162,6 @@ public class OverlayPackagesProvider { return disallowedApps; } - private static IInputMethodManager getIInputMethodManager() { - final IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); - return IInputMethodManager.Stub.asInterface(b); - } - private Set<String> getRequiredAppsSet(String provisioningAction) { final int resId; switch (provisioningAction) { diff --git a/services/ipmemorystore/Android.bp b/services/ipmemorystore/Android.bp new file mode 100644 index 000000000000..013cf5616904 --- /dev/null +++ b/services/ipmemorystore/Android.bp @@ -0,0 +1,4 @@ +java_library_static { + name: "services.ipmemorystore", + srcs: ["java/**/*.java"], +} diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java new file mode 100644 index 000000000000..c9759bf6170f --- /dev/null +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net.ipmemorystore; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.net.IIpMemoryStore; +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.NetworkAttributesParcelable; + +/** + * Implementation for the IP memory store. + * This component offers specialized services for network components to store and retrieve + * knowledge about networks, and provides intelligence that groups level 2 networks together + * into level 3 networks. + * + * @hide + */ +public class IpMemoryStoreService extends IIpMemoryStore.Stub { + final Context mContext; + + public IpMemoryStoreService(@NonNull final Context context) { + mContext = context; + } + + /** + * Store network attributes for a given L2 key. + * + * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2 + * key and only care about grouping can pass a unique ID here like the ones + * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low + * relevance of such a network will lead to it being evicted soon if it's not + * refreshed. Use findL2Key to try and find a similar L2Key to these attributes. + * @param attributes The attributes for this network. + * @param listener A listener to inform of the completion of this call, or null if the client + * is not interested in learning about success/failure. + * Through the listener, returns the L2 key. This is useful if the L2 key was not specified. + * If the call failed, the L2 key will be null. + */ + @Override + public void storeNetworkAttributes(@NonNull final String l2Key, + @NonNull final NetworkAttributesParcelable attributes, + @Nullable final IOnStatusListener listener) { + // TODO : implement this + } + + /** + * Store a binary blob associated with an L2 key and a name. + * + * @param l2Key The L2 key for this network. + * @param clientId The ID of the client. + * @param name The name of this data. + * @param data The data to store. + * @param listener The listener that will be invoked to return the answer, or null if the + * is not interested in learning about success/failure. + * Through the listener, returns a status to indicate success or failure. + */ + @Override + public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId, + @NonNull final String name, @NonNull final Blob data, + @Nullable final IOnStatusListener listener) { + // TODO : implement this + } + + /** + * Returns the best L2 key associated with the attributes. + * + * This will find a record that would be in the same group as the passed attributes. This is + * useful to choose the key for storing a sample or private data when the L2 key is not known. + * If multiple records are group-close to these attributes, the closest match is returned. + * If multiple records have the same closeness, the one with the smaller (unicode codepoint + * order) L2 key is returned. + * If no record matches these attributes, null is returned. + * + * @param attributes The attributes of the network to find. + * @param listener The listener that will be invoked to return the answer. + * 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 + } + + /** + * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point + * to the same L3 network. Group-closeness is used to determine this. + * + * @param l2Key1 The key for the first network. + * @param l2Key2 The key for the second network. + * @param listener The listener that will be invoked to return the answer. + * 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 + } + + /** + * Retrieve the network attributes for a key. + * If no record is present for this key, this will return null attributes. + * + * @param l2Key The key of the network to query. + * @param listener The listener that will be invoked to return the answer. + * Through the listener, returns the network attributes and the L2 key associated with + * the query. + */ + @Override + public void retrieveNetworkAttributes(@NonNull final String l2Key, + @NonNull final IOnNetworkAttributesRetrieved listener) { + // TODO : implement this. + } + + /** + * Retrieve previously stored private data. + * If no data was stored for this L2 key and name this will return null. + * + * @param l2Key The L2 key. + * @param clientId The id of the client that stored this data. + * @param name The name of the data. + * @param listener The listener that will be invoked to return the answer. + * Through the listener, returns the private data if any or null if none, with the L2 key + * and the name of the data associated with the query. + */ + @Override + public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId, + @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) { + // TODO : implement this. + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4326c39c43a2..fef2db921dcf 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -102,10 +102,12 @@ import com.android.server.media.MediaUpdateService; import com.android.server.media.projection.MediaProjectionManagerService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.NetworkStatsService; +import com.android.server.net.ipmemorystore.IpMemoryStoreService; import com.android.server.net.watchlist.NetworkWatchlistService; import com.android.server.notification.NotificationManagerService; import com.android.server.oemlock.OemLockService; import com.android.server.om.OverlayManagerService; +import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.SchedulingPolicyService; import com.android.server.pm.BackgroundDexOptService; @@ -118,6 +120,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; import com.android.server.policy.PhoneWindowManager; +import com.android.server.policy.role.LegacyRoleResolutionPolicy; import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; import com.android.server.power.ThermalManagerService; @@ -260,6 +263,10 @@ public final class SystemServer { "com.android.server.accessibility.AccessibilityManagerService$Lifecycle"; private static final String ADB_SERVICE_CLASS = "com.android.server.adb.AdbService$Lifecycle"; + private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS = + "com.android.server.appprediction.AppPredictionManagerService"; + private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS = + "com.android.server.contentsuggestions.ContentSuggestionsManagerService"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; @@ -786,6 +793,11 @@ public final class SystemServer { traceBeginAndSlog("StartRollbackManagerService"); mSystemServiceManager.startService(RollbackManagerService.class); traceEnd(); + + // Service to capture bugreports. + traceBeginAndSlog("StartBugreportManagerService"); + mSystemServiceManager.startService(BugreportManagerService.class); + traceEnd(); } /** @@ -1020,6 +1032,18 @@ public final class SystemServer { Slog.e("System", "************ Failure starting core service", e); } + // Before things start rolling, be sure we have decided whether + // we are in safe mode. + final boolean safeMode = wm.detectSafeMode(); + if (safeMode) { + // If yes, immediately turn on the global setting for airplane mode. + // Note that this does not send broadcasts at this stage because + // subsystems are not yet up. We will send broadcasts later to ensure + // all listeners have the chance to react with special handling. + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 1); + } + StatusBarManagerService statusBar = null; INotificationManager notification = null; LocationManagerService location = null; @@ -1153,6 +1177,16 @@ public final class SystemServer { startContentCaptureService(context); + // App prediction manager service + traceBeginAndSlog("StartAppPredictionService"); + mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS); + traceEnd(); + + // Content suggestions manager service + traceBeginAndSlog("StartContentSuggestionsService"); + mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS); + traceEnd(); + // NOTE: ClipboardService indirectly depends on IntelligenceService traceBeginAndSlog("StartClipboardService"); mSystemServiceManager.startService(ClipboardService.class); @@ -1167,6 +1201,15 @@ public final class SystemServer { } traceEnd(); + traceBeginAndSlog("StartIpMemoryStoreService"); + try { + ServiceManager.addService(Context.IP_MEMORY_STORE_SERVICE, + new IpMemoryStoreService(context)); + } catch (Throwable e) { + reportWtf("starting IP Memory Store Service", e); + } + traceEnd(); + traceBeginAndSlog("StartIpSecService"); try { ipSecService = IpSecService.create(context); @@ -1786,9 +1829,6 @@ public final class SystemServer { mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class); traceEnd(); - // Before things start rolling, be sure we have decided whether - // we are in safe mode. - final boolean safeMode = wm.detectSafeMode(); if (safeMode) { traceBeginAndSlog("EnterSafeModeAndDisableJitCompilation"); mActivityManagerService.enterSafeMode(); @@ -1952,7 +1992,8 @@ public final class SystemServer { // Grants default permissions and defines roles traceBeginAndSlog("StartRoleManagerService"); - mSystemServiceManager.startService(RoleManagerService.class); + mSystemServiceManager.startService(new RoleManagerService( + mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext))); traceEnd(); // No dependency on Webview preparation in system server. But this should @@ -1985,6 +2026,20 @@ public final class SystemServer { reportWtf("starting System UI", e); } traceEnd(); + // Enable airplane mode in safe mode. setAirplaneMode() cannot be called + // earlier as it sends broadcasts to other services. + // TODO: This may actually be too late if radio firmware already started leaking + // RF before the respective services start. However, fixing this requires changes + // to radio firmware and interfaces. + if (safeMode) { + traceBeginAndSlog("EnableAirplaneModeInSafeMode"); + try { + connectivityF.setAirplaneMode(true); + } catch (Throwable e) { + reportWtf("enabling Airplane Mode during Safe Mode bootup", e); + } + traceEnd(); + } traceBeginAndSlog("MakeNetworkManagementServiceReady"); try { if (networkManagementF != null) { @@ -2179,7 +2234,7 @@ public final class SystemServer { windowManager.onSystemUiStarted(); } - private static void traceBeginAndSlog(String name) { + private static void traceBeginAndSlog(@NonNull String name) { Slog.i(TAG, name); BOOT_TIMINGS_TRACE_LOG.traceBegin(name); } diff --git a/services/net/Android.bp b/services/net/Android.bp index ae697b7f093a..3b4d6a75591f 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -11,10 +11,10 @@ java_library { ] } -// TODO: move to networking module with IpNeighborMonitor/ConnectivityPacketTracker and remove lib -java_library { - name: "frameworks-net-shared-utils", +filegroup { + name: "services-networkstack-shared-srcs", srcs: [ - "java/android/net/util/FdEventsReader.java", + "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient + "java/android/net/shared/*.java", ] -}
\ No newline at end of file +} diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java new file mode 100644 index 000000000000..463cf2af2897 --- /dev/null +++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import android.content.Context; +import android.net.NetworkCapabilities; +import android.provider.Settings; + +/** @hide */ +public class NetworkMonitorUtils { + + // Network conditions broadcast constants + public static final String ACTION_NETWORK_CONDITIONS_MEASURED = + "android.net.conn.NETWORK_CONDITIONS_MEASURED"; + public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type"; + public static final String EXTRA_NETWORK_TYPE = "extra_network_type"; + public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received"; + public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal"; + public static final String EXTRA_CELL_ID = "extra_cellid"; + public static final String EXTRA_SSID = "extra_ssid"; + public static final String EXTRA_BSSID = "extra_bssid"; + /** real time since boot */ + public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms"; + public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms"; + public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS = + "android.permission.ACCESS_NETWORK_CONDITIONS"; + + // TODO: once the URL is a resource overlay, remove and have the resource define the default + private static final String DEFAULT_HTTP_URL = + "http://connectivitycheck.gstatic.com/generate_204"; + + /** + * Get the captive portal server HTTP URL that is configured on the device. + */ + public static String getCaptivePortalServerHttpUrl(Context context) { + final String settingUrl = Settings.Global.getString( + context.getContentResolver(), + Settings.Global.CAPTIVE_PORTAL_HTTP_URL); + return settingUrl != null ? settingUrl : DEFAULT_HTTP_URL; + } + + /** + * Return whether validation is required for a network. + * @param dfltNetCap Default requested network capabilities. + * @param nc Network capabilities of the network to test. + */ + public static boolean isValidationRequired( + NetworkCapabilities dfltNetCap, NetworkCapabilities nc) { + // TODO: Consider requiring validation for DUN networks. + return dfltNetCap.satisfiedByNetworkCapabilities(nc); + } +} diff --git a/services/net/java/android/net/shared/PrivateDnsConfig.java b/services/net/java/android/net/shared/PrivateDnsConfig.java new file mode 100644 index 000000000000..41e0bad7d746 --- /dev/null +++ b/services/net/java/android/net/shared/PrivateDnsConfig.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import android.net.InetAddresses; +import android.net.PrivateDnsConfigParcel; +import android.text.TextUtils; + +import java.net.InetAddress; +import java.util.Arrays; + +/** @hide */ +public class PrivateDnsConfig { + public final boolean useTls; + public final String hostname; + public final InetAddress[] ips; + + public PrivateDnsConfig() { + this(false); + } + + public PrivateDnsConfig(boolean useTls) { + this.useTls = useTls; + this.hostname = ""; + this.ips = new InetAddress[0]; + } + + public PrivateDnsConfig(String hostname, InetAddress[] ips) { + this.useTls = !TextUtils.isEmpty(hostname); + this.hostname = useTls ? hostname : ""; + this.ips = (ips != null) ? ips : new InetAddress[0]; + } + + public PrivateDnsConfig(PrivateDnsConfig cfg) { + useTls = cfg.useTls; + hostname = cfg.hostname; + ips = cfg.ips; + } + + /** + * Indicates whether this is a strict mode private DNS configuration. + */ + public boolean inStrictMode() { + return useTls && !TextUtils.isEmpty(hostname); + } + + @Override + public String toString() { + return PrivateDnsConfig.class.getSimpleName() + + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}"; + } + + /** + * Create a stable AIDL-compatible parcel from the current instance. + */ + public PrivateDnsConfigParcel toParcel() { + final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel(); + parcel.hostname = hostname; + + final String[] parceledIps = new String[ips.length]; + for (int i = 0; i < ips.length; i++) { + parceledIps[i] = ips[i].getHostAddress(); + } + parcel.ips = parceledIps; + + return parcel; + } + + /** + * Build a configuration from a stable AIDL-compatible parcel. + */ + public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) { + final InetAddress[] ips = new InetAddress[parcel.ips.length]; + for (int i = 0; i < ips.length; i++) { + ips[i] = InetAddresses.parseNumericAddress(parcel.ips[i]); + } + + return new PrivateDnsConfig(parcel.hostname, ips); + } +} diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java index 8b7b59d20978..2cdb2b04f8f7 100644 --- a/services/net/java/android/net/util/SharedLog.java +++ b/services/net/java/android/net/util/SharedLog.java @@ -70,6 +70,10 @@ public class SharedLog { mComponent = component; } + public String getTag() { + return mTag; + } + /** * Create a SharedLog based on this log with an additional component prefix on each logged line. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index 148faada6381..6a153d5346ed 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -60,6 +60,7 @@ import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; @@ -81,6 +82,7 @@ import org.mockito.quality.Strictness; import java.util.ArrayList; +@Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class AlarmManagerServiceTest { diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 6a10ff487d2d..2cc338cb8674 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -70,7 +70,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; -import com.android.server.AppOpsService; +import com.android.server.appop.AppOpsService; import com.android.server.am.ProcessList.IsolatedUidRange; import com.android.server.am.ProcessList.IsolatedUidRangeAllocator; import com.android.server.wm.ActivityTaskManagerService; diff --git a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java index 05cb48b7d64d..8109b1c56207 100644 --- a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java +++ b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java @@ -25,7 +25,7 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; -import com.android.server.AppOpsService; +import com.android.server.appop.AppOpsService; import org.junit.Before; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java index 9626990efdcd..cbdc6c3faadd 100644 --- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java @@ -34,7 +34,7 @@ import android.test.mock.MockContentResolver; import androidx.test.filters.SmallTest; import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.AppOpsService; +import com.android.server.appop.AppOpsService; import org.junit.AfterClass; import org.junit.Before; diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java index 7e0dfcfd73f0..65c5781a9114 100644 --- a/services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.appops; +package com.android.server.appop; import static com.google.common.truth.Truth.assertThat; diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java index edd89f9e61d1..edd89f9e61d1 100644 --- a/services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java diff --git a/services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java index 4ae9bea7617c..36f84d053817 100644 --- a/services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server; +package com.android.server.appop; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; @@ -36,6 +36,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.appop.AppOpsService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java index aac96a1dd847..eb0c6279ea99 100644 --- a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.appop; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java index 34edd9fb020b..757a046e9133 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java @@ -32,7 +32,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; -import android.os.RemoteException; import android.test.AndroidTestCase; import android.test.mock.MockPackageManager; import android.view.inputmethod.InputMethodInfo; @@ -40,7 +39,6 @@ import android.view.inputmethod.InputMethodInfo; import androidx.test.InstrumentationRegistry; import com.android.internal.R; -import com.android.internal.view.IInputMethodManager; import org.junit.Before; import org.junit.Test; @@ -61,8 +59,8 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { private @Mock Resources mResources; - private @Mock - IInputMethodManager mIInputMethodManager; + @Mock + private OverlayPackagesProvider.Injector mInjector; private @Mock Context mTestContext; private Resources mRealResources; @@ -81,6 +79,7 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { InstrumentationRegistry.getTargetContext().getCacheDir()); setSystemInputMethods(); + setIsPerProfileModeEnabled(false); setRequiredAppsManagedDevice(); setVendorRequiredAppsManagedDevice(); setDisallowedAppsManagedDevice(); @@ -95,7 +94,7 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { setVendorDisallowedAppsManagedUser(); mRealResources = InstrumentationRegistry.getTargetContext().getResources(); - mHelper = new OverlayPackagesProvider(mTestContext, mIInputMethodManager); + mHelper = new OverlayPackagesProvider(mTestContext, mInjector); } @Test @@ -165,6 +164,15 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { } @Test + public void testProfileOwnerImesAreRequiredForPerProfileImeMode() { + setSystemAppsWithLauncher("app.a", "app.b"); + setSystemInputMethods("app.a"); + setIsPerProfileModeEnabled(true); + + verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_PROFILE, "app.b"); + } + + @Test public void testManagedUserImesAreRequired() { setSystemAppsWithLauncher("app.a", "app.b"); setSystemInputMethods("app.a"); @@ -333,11 +341,11 @@ public class OverlayPackagesProviderTest extends AndroidTestCase { InputMethodInfo inputMethodInfo = new InputMethodInfo(ri, false, null, null, 0, false); inputMethods.add(inputMethodInfo); } - try { - when(mIInputMethodManager.getInputMethodList()).thenReturn(inputMethods); - } catch (RemoteException e) { - fail(e.toString()); - } + when(mInjector.getInputMethodListAsUser(eq(TEST_USER_ID))).thenReturn(inputMethods); + } + + private void setIsPerProfileModeEnabled(boolean enabled) { + when(mInjector.isPerProfileImeEnabled()).thenReturn(enabled); } private void setSystemAppsWithLauncher(String... apps) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java new file mode 100644 index 000000000000..8b0e8abf069d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -0,0 +1,163 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.Nullable; +import android.app.Instrumentation; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.Looper; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; + +/** Tests for {@link ArcInitiationActionFromAvrTest} */ +@SmallTest +@RunWith(JUnit4.class) +public class ArcInitiationActionFromAvrTest { + + private HdmiDeviceInfo mDeviceInfoForTests; + private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem; + private HdmiCecController mHdmiCecController; + private HdmiControlService mHdmiControlService; + private FakeNativeWrapper mNativeWrapper; + private ArcInitiationActionFromAvr mAction; + + private TestLooper mTestLooper = new TestLooper(); + private boolean mSendCecCommandSuccess; + private boolean mShouldDispatchARCInitiated; + private boolean mArcInitSent; + private boolean mRequestActiveSourceSent; + private Instrumentation mInstrumentation; + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + + @Before + public void setUp() { + mDeviceInfoForTests = new HdmiDeviceInfo(1000, 1); + + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + + mHdmiControlService = + new HdmiControlService(mInstrumentation.getTargetContext()) { + @Override + void sendCecCommand( + HdmiCecMessage command, @Nullable SendMessageCallback callback) { + switch (command.getOpcode()) { + case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE: + if (callback != null) { + callback.onSendCompleted( + mSendCecCommandSuccess + ? SendMessageResult.SUCCESS + : SendMessageResult.NACK); + } + mRequestActiveSourceSent = true; + break; + case Constants.MESSAGE_INITIATE_ARC: + if (callback != null) { + callback.onSendCompleted( + mSendCecCommandSuccess + ? SendMessageResult.SUCCESS + : SendMessageResult.NACK); + } + mArcInitSent = true; + if (mShouldDispatchARCInitiated) { + mHdmiCecLocalDeviceAudioSystem.dispatchMessage( + HdmiCecMessageBuilder.buildReportArcInitiated( + Constants.ADDR_TV, + Constants.ADDR_AUDIO_SYSTEM)); + } + break; + default: + } + } + + @Override + boolean isPowerStandby() { + return false; + } + + @Override + boolean isAddressAllocated() { + return true; + } + + @Override + Looper getServiceLooper() { + return mTestLooper.getLooper(); + } + }; + + mHdmiCecLocalDeviceAudioSystem = + new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) { + @Override + HdmiDeviceInfo getDeviceInfo() { + return mDeviceInfoForTests; + } + + @Override + void setArcStatus(boolean enabled) { + // do nothing + } + + @Override + protected boolean isSystemAudioActivated() { + return true; + } + }; + + mHdmiCecLocalDeviceAudioSystem.init(); + Looper looper = mTestLooper.getLooper(); + mHdmiControlService.setIoLooper(looper); + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(this.mHdmiControlService, mNativeWrapper); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + mHdmiControlService.initPortInfo(); + mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); + + mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + } + + @Test + public void arcInitiation_requestActiveSource() { + mSendCecCommandSuccess = true; + mShouldDispatchARCInitiated = true; + mRequestActiveSourceSent = false; + mArcInitSent = false; + + mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + assertThat(mArcInitSent).isTrue(); + assertThat(mRequestActiveSourceSent).isTrue(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index da840be9bca7..93a09dc3ff04 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -18,6 +18,7 @@ package com.android.server.hdmi; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; + import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; @@ -25,16 +26,21 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3; import static com.android.server.hdmi.Constants.ADDR_SPECIFIC_USE; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; + import static junit.framework.Assert.assertEquals; import android.content.Context; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; + import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; + import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -136,6 +142,7 @@ public class HdmiCecControllerTest { assertEquals(ADDR_UNREGISTERED, mLogicalAddress); } + @Ignore("b/110413065 Support multiple device types 4 and 5.") @Test public void testAllocatLogicalAddress_PlaybackPreferredNotOccupied() { mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_PLAYBACK_1, mCallback); @@ -151,6 +158,7 @@ public class HdmiCecControllerTest { assertEquals(ADDR_PLAYBACK_2, mLogicalAddress); } + @Ignore("b/110413065 Support multiple device types 4 and 5.") @Test public void testAllocatLogicalAddress_PlaybackNoPreferredNotOcuppied() { mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_UNREGISTERED, mCallback); 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 bdede3354ef7..b47f269bc721 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -17,6 +17,8 @@ package com.android.server.hdmi; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; +import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; +import static com.android.server.hdmi.Constants.ADDR_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; @@ -34,6 +36,7 @@ import androidx.test.filters.SmallTest; import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -52,6 +55,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem; + private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback; private FakeNativeWrapper mNativeWrapper; private Looper mMyLooper; private TestLooper mTestLooper = new TestLooper(); @@ -64,82 +68,86 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Before public void setUp() { mHdmiControlService = - new HdmiControlService(InstrumentationRegistry.getTargetContext()) { - @Override - AudioManager getAudioManager() { - return new AudioManager() { - @Override - public int getStreamVolume(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicVolume; - default: - return 0; - } + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + AudioManager getAudioManager() { + return new AudioManager() { + @Override + public int getStreamVolume(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicVolume; + default: + return 0; } - - @Override - public boolean isStreamMute(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicMute; - default: - return false; - } + } + + @Override + public boolean isStreamMute(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicMute; + default: + return false; } - - @Override - public int getStreamMaxVolume(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicMaxVolume; - default: - return 100; - } + } + + @Override + public int getStreamMaxVolume(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicMaxVolume; + default: + return 100; } - - @Override - public void adjustStreamVolume( - int streamType, int direction, int flags) { - switch (streamType) { - case STREAM_MUSIC: - if (direction == AudioManager.ADJUST_UNMUTE) { - mMusicMute = false; - } else if (direction == AudioManager.ADJUST_MUTE) { - mMusicMute = true; - } - default: - } + } + + @Override + public void adjustStreamVolume( + int streamType, int direction, int flags) { + switch (streamType) { + case STREAM_MUSIC: + if (direction == AudioManager.ADJUST_UNMUTE) { + mMusicMute = false; + } else if (direction == AudioManager.ADJUST_MUTE) { + mMusicMute = true; + } + break; + default: } + } - @Override - public void setWiredDeviceConnectionState( - int type, int state, String address, String name) { - // Do nothing. - } - }; - } + @Override + public void setWiredDeviceConnectionState( + int type, int state, String address, String name) { + // Do nothing. + } + }; + } - @Override - void wakeUp() {} - - @Override - boolean isControlEnabled() { - return true; - } - }; + @Override + void wakeUp() {} + }; mMyLooper = mTestLooper.getLooper(); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService); + mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) { + @Override + void setIsActiveSource(boolean on) { + mIsActiveSource = on; + } + }; mHdmiCecLocalDeviceAudioSystem.init(); + mHdmiCecLocalDevicePlayback.init(); mHdmiControlService.setIoLooper(mMyLooper); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = - HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); + mLocalDevices.add(mHdmiCecLocalDevicePlayback); mHdmiControlService.initPortInfo(); // No TV device interacts with AVR so system audio control won't be turned on here mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); @@ -170,11 +178,12 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Test public void handleGiveSystemAudioModeStatus_originalOff() throws Exception { HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); + HdmiCecMessageBuilder.buildReportSystemAudioMode( + ADDR_AUDIO_SYSTEM, ADDR_TV, false); HdmiCecMessage messageGive = HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -190,9 +199,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false); assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( - MESSAGE_REQUEST_SAD_LCPM)) - .isTrue(); + mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + MESSAGE_REQUEST_SAD_LCPM)) + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -206,11 +215,11 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, Constants.ABORT_NOT_IN_CORRECT_MODE); - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false); assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( - MESSAGE_REQUEST_SAD_LCPM)) - .isEqualTo(true); + mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + MESSAGE_REQUEST_SAD_LCPM)) + .isEqualTo(true); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -224,11 +233,11 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, Constants.ABORT_UNABLE_TO_DETERMINE); - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true); assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( - MESSAGE_REQUEST_SAD_LCPM)) - .isEqualTo(true); + mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + MESSAGE_REQUEST_SAD_LCPM)) + .isEqualTo(true); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -244,17 +253,17 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); // Check if correctly turned on mNativeWrapper.clearResultMessages(); expectedMessage = - HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true); + HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true); assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); assertThat(mMusicMute).isFalse(); @@ -273,15 +282,15 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessageBuilder.buildSetSystemAudioMode( ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); mNativeWrapper.clearResultMessages(); expectedMessage = - HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); + HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); assertThat(mMusicMute).isTrue(); @@ -289,10 +298,8 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Test public void onStandbyAudioSystem_currentSystemAudioControlOn() throws Exception { - mHdmiCecLocalDeviceAudioSystem.setAutoDeviceOff(false); - mHdmiCecLocalDeviceAudioSystem.setAutoTvOff(false); // Set system audio control on first - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true); // Check if standby correctly turns off the feature mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF); mTestLooper.dispatchAll(); @@ -309,9 +316,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true); assertThat( - mHdmiCecLocalDeviceAudioSystem.getActions( - SystemAudioInitiationActionFromAvr.class)) - .isNotEmpty(); + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isNotEmpty(); } @Test @@ -320,9 +327,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false); assertThat( - mHdmiCecLocalDeviceAudioSystem.getActions( - SystemAudioInitiationActionFromAvr.class)) - .isEmpty(); + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isEmpty(); } @Test @@ -331,9 +338,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false); assertThat( - mHdmiCecLocalDeviceAudioSystem.getActions( - SystemAudioInitiationActionFromAvr.class)) - .isEmpty(); + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isEmpty(); } @Test @@ -342,9 +349,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true); assertThat( - mHdmiCecLocalDeviceAudioSystem.getActions( - SystemAudioInitiationActionFromAvr.class)) - .isNotEmpty(); + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isNotEmpty(); } @Test @@ -354,12 +361,12 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource)) - .isTrue(); + .isTrue(); } @Test public void terminateSystemAudioMode_systemAudioModeOff() throws Exception { - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false); assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse(); mMusicMute = false; HdmiCecMessage message = @@ -373,7 +380,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Test public void terminateSystemAudioMode_systemAudioModeOn() throws Exception { - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true); assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue(); mMusicMute = false; HdmiCecMessage expectedMessage = @@ -458,7 +465,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class)) - .isNotEmpty(); + .isNotEmpty(); } @Test @@ -473,7 +480,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class)) - .isNotEmpty(); + .isNotEmpty(); } @Test @@ -510,23 +517,98 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } + public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() { + HdmiCecMessage message = + HdmiCecMessageBuilder.buildSystemAudioModeRequest( + ADDR_TUNER_1, ADDR_AUDIO_SYSTEM, + mAvrPhysicalAddress, true); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, + ADDR_TUNER_1, + Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, + Constants.ABORT_REFUSED); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + } + @Test - public void onStandby_setAutoDeviceOff_true() throws Exception { + public void handleSystemAudioModeRequest_fromNonTV_tVSupport() { + HdmiCecMessage message = + HdmiCecMessageBuilder.buildSystemAudioModeRequest( + ADDR_TUNER_1, ADDR_AUDIO_SYSTEM, + mAvrPhysicalAddress, true); HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildStandby(ADDR_AUDIO_SYSTEM, ADDR_BROADCAST); - mHdmiCecLocalDeviceAudioSystem.setAutoDeviceOff(true); - mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF); + HdmiCecMessageBuilder.buildSetSystemAudioMode( + ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true); + mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true); + + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + } + @Test + public void handleActiveSource_activeSourceFromTV_swithToArc() { + mHdmiCecLocalDeviceAudioSystem.setArcStatus(true); + HdmiCecMessage message = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + ActiveSource expectedActiveSource = ActiveSource.of(ADDR_TV, 0x0000); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource()) + .isEqualTo(expectedActiveSource); + } + + @Ignore("b/110413065 Support multiple device types 4 and 5.") + @Test + public void handleRoutingChange_currentActivePortIsHome() { + HdmiCecMessage message = + HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, mAvrPhysicalAddress); + + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, mAvrPhysicalAddress); + ActiveSource expectedActiveSource = ActiveSource.of(ADDR_PLAYBACK_1, mAvrPhysicalAddress); + int expectedLocalActivePort = Constants.CEC_SWITCH_HOME; + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource()) + .isEqualTo(expectedActiveSource); + assertThat(mHdmiCecLocalDeviceAudioSystem.getRoutingPort()) + .isEqualTo(expectedLocalActivePort); + assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); + } + + @Test + public void handleRoutingInformation_currentActivePortIsHDMI1() { + HdmiCecMessage message = + HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x2000); + mHdmiCecLocalDeviceAudioSystem.setRoutingPort(Constants.CEC_SWITCH_HDMI1); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildRoutingInformation(ADDR_AUDIO_SYSTEM, 0x2100); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } + @Ignore("b/110413065 Support multiple device types 4 and 5.") @Test - public void handleSetStreamPath_underCurrentDevice() { - assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(0); + public void handleRoutingChange_homeIsActive_playbackSendActiveSource() { HdmiCecMessage message = - HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetStreamPath(message)).isTrue(); - assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(1); + HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x2000); + + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java new file mode 100644 index 000000000000..792c617b99ea --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -0,0 +1,94 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.Constants.ADDR_TV; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Looper; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; + +@SmallTest +@RunWith(JUnit4.class) +/** Tests for {@link HdmiCecLocalDevicePlayback} class. */ +public class HdmiCecLocalDevicePlaybackTest { + + private HdmiControlService mHdmiControlService; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback; + private FakeNativeWrapper mNativeWrapper; + private Looper mMyLooper; + private TestLooper mTestLooper = new TestLooper(); + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mPlaybackPhysicalAddress; + + @Before + public void setUp() { + mHdmiControlService = + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + void wakeUp() { + } + + @Override + boolean isControlEnabled() { + return true; + } + }; + + mMyLooper = mTestLooper.getLooper(); + mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService); + mHdmiCecLocalDevicePlayback.init(); + mHdmiControlService.setIoLooper(mMyLooper); + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + mLocalDevices.add(mHdmiCecLocalDevicePlayback); + mHdmiControlService.initPortInfo(); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + mNativeWrapper.clearResultMessages(); + mPlaybackPhysicalAddress = 0x2000; + mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress); + } + + @Ignore + @Test + public void handleSetStreamPath_underCurrentDevice() { + assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0); + HdmiCecMessage message = + HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + // TODO(amyjojo): Move set and get LocalActivePath to Control Service. + assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(1); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java index c7809d3f2f29..ef974f2e01bb 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java @@ -18,6 +18,7 @@ package com.android.server.hdmi; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_TV; + import static com.google.common.truth.Truth.assertThat; import android.hardware.hdmi.HdmiDeviceInfo; @@ -51,6 +52,14 @@ public class HdmiCecMessageBuilderTest { assertThat(message).isEqualTo(buildMessage("05:A4:06:01")); } + @Test + public void buildRoutingInformation() { + HdmiCecMessage message = + HdmiCecMessageBuilder.buildRoutingInformation( + ADDR_AUDIO_SYSTEM, 0x2100); + assertThat(message).isEqualTo(buildMessage("5F:81:21:00")); + } + /** * Build a CEC message from a hex byte string with bytes separated by {@code :}. * diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index d914b9a090a2..bd297eecf25c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -26,8 +26,10 @@ import android.hardware.tv.cec.V1_0.SendMessageResult; import android.media.AudioManager; import android.os.Looper; import android.os.test.TestLooper; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,6 +51,9 @@ public class SystemAudioInitiationActionFromAvrTest { private int mMsgRequestActiveSourceCount; private int mMsgSetSystemAudioModeCount; private int mQueryTvSystemAudioModeSupportCount; + private boolean mArcEnabled; + private boolean mIsPlaybackDevice; + private boolean mBroadcastActiveSource; @Before public void SetUp() { @@ -80,6 +85,8 @@ public class SystemAudioInitiationActionFromAvrTest { callback.onSendCompleted(SendMessageResult.NACK); } break; + case Constants.MESSAGE_INITIATE_ARC: + break; default: throw new IllegalArgumentException("Unexpected message"); } @@ -132,6 +139,17 @@ public class SystemAudioInitiationActionFromAvrTest { int getPhysicalAddress() { return 0; } + + @Override + boolean isPlaybackDevice() { + return mIsPlaybackDevice; + } + + @Override + public void setAndBroadcastActiveSourceFromOneDeviceType( + int sourceAddress, int physicalAddress) { + mBroadcastActiveSource = true; + } }; mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @@ -148,6 +166,11 @@ public class SystemAudioInitiationActionFromAvrTest { HdmiDeviceInfo getDeviceInfo() { return mDeviceInfoForTests; } + + @Override + void setArcStatus(boolean enabled) { + mArcEnabled = enabled; + } }; mHdmiCecLocalDeviceAudioSystem.init(); Looper looper = mTestLooper.getLooper(); @@ -159,7 +182,7 @@ public class SystemAudioInitiationActionFromAvrTest { resetTestVariables(); mShouldDispatchActiveSource = false; - assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress) + assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().physicalAddress) .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS); mHdmiCecLocalDeviceAudioSystem.addAndStartAction( @@ -171,7 +194,7 @@ public class SystemAudioInitiationActionFromAvrTest { assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(0); assertFalse(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()); - assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress) + assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().physicalAddress) .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS); } @@ -206,14 +229,15 @@ public class SystemAudioInitiationActionFromAvrTest { assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1); assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()); - assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress).isEqualTo(1002); + assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().physicalAddress) + .isEqualTo(1002); } @Test public void testKnownActiveSource() { resetTestVariables(); mTvSystemAudioModeSupport = true; - mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress = 1001; + mHdmiCecLocalDeviceAudioSystem.getActiveSource().physicalAddress = 1001; mHdmiCecLocalDeviceAudioSystem.addAndStartAction( new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem)); @@ -233,7 +257,7 @@ public class SystemAudioInitiationActionFromAvrTest { mTryCountBeforeSucceed = 3; assertThat(mTryCountBeforeSucceed) .isAtMost(SystemAudioInitiationActionFromAvr.MAX_RETRY_COUNT); - assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress) + assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().physicalAddress) .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS); mHdmiCecLocalDeviceAudioSystem.addAndStartAction( @@ -246,12 +270,32 @@ public class SystemAudioInitiationActionFromAvrTest { assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()); } + @Test + public void testIsPlaybackDevice_cannotReceiveActiveSource() { + resetTestVariables(); + mIsPlaybackDevice = true; + assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().physicalAddress) + .isEqualTo(Constants.INVALID_PHYSICAL_ADDRESS); + + mHdmiCecLocalDeviceAudioSystem.addAndStartAction( + new SystemAudioInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem)); + mTestLooper.dispatchAll(); + + assertThat(mMsgRequestActiveSourceCount).isEqualTo(1); + assertThat(mMsgSetSystemAudioModeCount).isEqualTo(1); + assertThat(mQueryTvSystemAudioModeSupportCount).isEqualTo(1); + assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue(); + assertThat(mBroadcastActiveSource).isTrue(); + } + private void resetTestVariables() { mMsgRequestActiveSourceCount = 0; mMsgSetSystemAudioModeCount = 0; mQueryTvSystemAudioModeSupportCount = 0; mTryCountBeforeSucceed = 0; - mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress = + mIsPlaybackDevice = false; + mBroadcastActiveSource = false; + mHdmiCecLocalDeviceAudioSystem.getActiveSource().physicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; } } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 561c61fb979f..0dff03f2f53e 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -47,6 +47,7 @@ import com.android.server.lights.LightsManager; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.PowerManagerService.Injector; import com.android.server.power.PowerManagerService.NativeWrapper; +import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySavingStats; import org.junit.Rule; diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java index acf511e38738..73eefcf618b0 100644 --- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.power; +package com.android.server.power.batterysaver; import static com.google.common.truth.Truth.assertThat; @@ -31,13 +31,12 @@ import android.util.ArrayMap; import com.android.frameworks.servicestests.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; -import com.android.server.power.batterysaver.BatterySavingStats; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * Tests for {@link com.android.server.power.BatterySaverPolicy} + * Tests for {@link com.android.server.power.batterysaver.BatterySaverPolicy} */ public class BatterySaverPolicyTest extends AndroidTestCase { private static final boolean BATTERY_SAVER_ON = true; @@ -62,7 +61,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase { private static final String BATTERY_SAVER_INCORRECT_CONSTANTS = "vi*,!=,,true"; private class BatterySaverPolicyForTest extends BatterySaverPolicy { - public BatterySaverPolicyForTest(Object lock, Context context, + BatterySaverPolicyForTest(Object lock, Context context, BatterySavingStats batterySavingStats) { super(lock, context, batterySavingStats); } @@ -269,15 +268,15 @@ public class BatterySaverPolicyTest extends AndroidTestCase { mBatterySaverPolicy.onChange(); assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}"); assertThat(mBatterySaverPolicy.getFileValues(false).toString()) - .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " + - "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}"); + .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " + + "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}"); mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3; mBatterySaverPolicy.onChange(); assertThat(mBatterySaverPolicy.getFileValues(true).toString()) - .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " + - "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}"); + .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " + + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}"); assertThat(mBatterySaverPolicy.getFileValues(false).toString()) .isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}"); @@ -287,9 +286,9 @@ public class BatterySaverPolicyTest extends AndroidTestCase { mBatterySaverPolicy.onChange(); assertThat(mBatterySaverPolicy.getFileValues(true).toString()) - .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " + - "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " + - "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}"); + .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " + + "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " + + "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}"); assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}"); } } 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 b6f181758d3a..8f9d2badce30 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -20,6 +20,9 @@ import static android.view.Display.DEFAULT_DISPLAY; 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.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM; import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; @@ -38,6 +41,7 @@ import static org.junit.Assert.assertTrue; import android.app.ActivityOptions; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.PauseActivityItem; +import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.MutableBoolean; @@ -210,4 +214,41 @@ public class ActivityRecordTests extends ActivityTestsBase { assertNull(mActivity.pendingOptions); assertNotNull(activity2.pendingOptions); } + + @Test + public void testNewOverrideConfigurationIncrementsSeq() { + final Configuration newConfig = new Configuration(); + + final int prevSeq = mActivity.getMergedOverrideConfiguration().seq; + mActivity.onRequestedOverrideConfigurationChanged(newConfig); + assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq); + } + + @Test + public void testNewParentConfigurationIncrementsSeq() { + final Configuration newConfig = new Configuration( + mTask.getRequestedOverrideConfiguration()); + newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT + ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT; + + final int prevSeq = mActivity.getMergedOverrideConfiguration().seq; + mTask.onRequestedOverrideConfigurationChanged(newConfig); + assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq); + } + + @Test + public void testNotifiesSeqIncrementToAppToken() { + final Configuration appWindowTokenRequestedOrientation = mock(Configuration.class); + mActivity.mAppWindowToken = mock(AppWindowToken.class); + doReturn(appWindowTokenRequestedOrientation).when(mActivity.mAppWindowToken) + .getRequestedOverrideConfiguration(); + + final Configuration newConfig = new Configuration(); + newConfig.orientation = Configuration.ORIENTATION_PORTRAIT; + + final int prevSeq = mActivity.getMergedOverrideConfiguration().seq; + mActivity.onRequestedOverrideConfigurationChanged(newConfig); + assertEquals(prevSeq + 1, appWindowTokenRequestedOrientation.seq); + verify(mActivity.mAppWindowToken).onMergedOverrideConfigurationChanged(); + } } 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 bd29d2a3a884..5589ca1be815 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -66,7 +66,7 @@ import android.view.Display; import android.view.DisplayInfo; import com.android.internal.app.IVoiceInteractor; -import com.android.server.AppOpsService; +import com.android.server.appop.AppOpsService; import com.android.server.AttributeCache; import com.android.server.ServiceThread; import com.android.server.am.ActivityManagerService; @@ -264,6 +264,8 @@ class ActivityTestsBase { .setOrientation(anyInt(), any(), any()); doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt()); doNothing().when(activity).removeWindowContainer(); + doReturn(mock(Configuration.class)).when(activity.mAppWindowToken) + .getRequestedOverrideConfiguration(); if (mTaskRecord != null) { mTaskRecord.addActivityToTop(activity); diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index f1c6eab2143d..bb3ab358e71a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -20,11 +20,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 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.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -125,7 +123,6 @@ public class DragDropControllerTests extends WindowTestsBase { mDisplayContent = spy(mDisplayContent); mWindow = createDropTargetWindow("Drag test window", 0); doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0); - when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true); synchronized (mWm.mGlobalLock) { mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); @@ -176,7 +173,6 @@ public class DragDropControllerTests extends WindowTestsBase { .setFormat(PixelFormat.TRANSLUCENT) .build(); - assertTrue(mWm.mInputManager.transferTouchFocus(null, null)); mToken = mTarget.performDrag( new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, data); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java index c343fe7d0675..8c6ac23c9202 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java @@ -18,7 +18,6 @@ package com.android.server.wm; 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.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; @@ -58,10 +57,6 @@ public class TaskPositioningControllerTests extends WindowTestsBase { assertNotNull(mWm.mTaskPositioningController); mTarget = mWm.mTaskPositioningController; - when(mWm.mInputManager.transferTouchFocus( - any(InputChannel.class), - any(InputChannel.class))).thenReturn(true); - mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); mWindow.mInputChannel = new InputChannel(); synchronized (mWm.mGlobalLock) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java new file mode 100644 index 000000000000..8d834974148c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java @@ -0,0 +1,115 @@ +/* + * 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; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; +import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; +import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; +import android.util.proto.ProtoOutputStream; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + + +/** + * Test class for {@link WindowTraceBuffer} and {@link WindowTraceQueueBuffer}. + * + * Build/Install/Run: + * atest WmTests:WindowTraceBufferTest + */ +@SmallTest +@Presubmit +public class WindowTraceBufferTest { + private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; + + private File mFile; + + @Before + public void setUp() throws Exception { + final Context testContext = getInstrumentation().getContext(); + mFile = testContext.getFileStreamPath("tracing_test.dat"); + mFile.delete(); + } + + @After + public void tearDown() throws Exception { + mFile.delete(); + } + + @Test + public void testTraceQueueBuffer_addItem() throws Exception { + ProtoOutputStream toWrite1 = getDummy(1); + ProtoOutputStream toWrite2 = getDummy(2); + ProtoOutputStream toWrite3 = getDummy(3); + byte[] toWrite1Bytes = toWrite1.getBytes(); + byte[] toWrite2Bytes = toWrite2.getBytes(); + byte[] toWrite3Bytes = toWrite3.getBytes(); + + final int objectSize = toWrite1.getBytes().length; + final int bufferCapacity = objectSize * 2; + + final WindowTraceBuffer buffer = buildQueueBuffer(bufferCapacity); + + buffer.add(toWrite1); + assertTrue("First element should be in the list", + buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes))); + + buffer.add(toWrite2); + assertTrue("First element should be in the list", + buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite1Bytes))); + assertTrue("Second element should be in the list", + buffer.mBuffer.stream().anyMatch(p -> Arrays.equals(p, toWrite2Bytes))); + + buffer.add(toWrite3); + + assertTrue("Third element should not be in the list", + buffer.mBuffer.stream().noneMatch(p -> Arrays.equals(p, toWrite3Bytes))); + + assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2); + assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity), + buffer.mBufferSize, bufferCapacity); + assertEquals("Buffer is full, available space should be 0", + buffer.getAvailableSpace(), 0); + } + + private ProtoOutputStream getDummy(int value) { + ProtoOutputStream toWrite = new ProtoOutputStream(); + toWrite.write(MAGIC_NUMBER, value); + toWrite.flush(); + + return toWrite; + } + + private WindowTraceBuffer buildQueueBuffer(int size) throws IOException { + return new WindowTraceQueueBuffer(size, mFile, false); + } +} diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 2820836282a1..dcaa49996d0b 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -239,6 +239,30 @@ public final class Call { "android.telecom.event.HANDOVER_FAILED"; public static class Details { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = { "DIRECTION_" }, + value = {DIRECTION_UNKNOWN, DIRECTION_INCOMING, DIRECTION_OUTGOING}) + public @interface CallDirection {} + + /** + * Indicates that the call is neither and incoming nor an outgoing call. This can be the + * case for calls reported directly by a {@link ConnectionService} in special cases such as + * call handovers. + */ + public static final int DIRECTION_UNKNOWN = -1; + + /** + * Indicates that the call is an incoming call. + */ + public static final int DIRECTION_INCOMING = 0; + + /** + * Indicates that the call is an outgoing call. + */ + public static final int DIRECTION_OUTGOING = 1; + /** Call can currently be put on hold or unheld. */ public static final int CAPABILITY_HOLD = 0x00000001; @@ -519,6 +543,7 @@ public final class Call { private final Bundle mIntentExtras; private final long mCreationTimeMillis; private final CallIdentification mCallIdentification; + private final @CallDirection int mCallDirection; /** * Whether the supplied capabilities supports the specified capability. @@ -838,6 +863,14 @@ public final class Call { return mCallIdentification; } + /** + * Indicates whether the call is an incoming or outgoing call. + * @return The call's direction. + */ + public @CallDirection int getCallDirection() { + return mCallDirection; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -859,7 +892,8 @@ public final class Call { areBundlesEqual(mExtras, d.mExtras) && areBundlesEqual(mIntentExtras, d.mIntentExtras) && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) && - Objects.equals(mCallIdentification, d.mCallIdentification); + Objects.equals(mCallIdentification, d.mCallIdentification) && + Objects.equals(mCallDirection, d.mCallDirection); } return false; } @@ -881,7 +915,8 @@ public final class Call { mExtras, mIntentExtras, mCreationTimeMillis, - mCallIdentification); + mCallIdentification, + mCallDirection); } /** {@hide} */ @@ -902,7 +937,8 @@ public final class Call { Bundle extras, Bundle intentExtras, long creationTimeMillis, - CallIdentification callIdentification) { + CallIdentification callIdentification, + int callDirection) { mTelecomCallId = telecomCallId; mHandle = handle; mHandlePresentation = handlePresentation; @@ -920,6 +956,7 @@ public final class Call { mIntentExtras = intentExtras; mCreationTimeMillis = creationTimeMillis; mCallIdentification = callIdentification; + mCallDirection = callDirection; } /** {@hide} */ @@ -941,7 +978,8 @@ public final class Call { parcelableCall.getExtras(), parcelableCall.getIntentExtras(), parcelableCall.getCreationTimeMillis(), - parcelableCall.getCallIdentification()); + parcelableCall.getCallIdentification(), + parcelableCall.getCallDirection()); } @Override diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java index 97af06c1d64c..87834fd5109d 100644 --- a/telecomm/java/android/telecom/CallIdentification.java +++ b/telecomm/java/android/telecom/CallIdentification.java @@ -250,8 +250,8 @@ public final class CallIdentification implements Parcelable { mDetails = details; mPhoto = photo; mNuisanceConfidence = nuisanceConfidence; - mCallScreeningAppName = callScreeningPackageName; - mCallScreeningPackageName = callScreeningAppName; + mCallScreeningAppName = callScreeningAppName; + mCallScreeningPackageName = callScreeningPackageName; } private String mName; @@ -430,4 +430,22 @@ public final class CallIdentification implements Parcelable { return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence, mCallScreeningAppName, mCallScreeningPackageName); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[CallId mName="); + sb.append(Log.pii(mName)); + sb.append(", mDesc="); + sb.append(mDescription); + sb.append(", mDet="); + sb.append(mDetails); + sb.append(", conf="); + sb.append(mNuisanceConfidence); + sb.append(", appName="); + sb.append(mCallScreeningAppName); + sb.append(", pkgName="); + sb.append(mCallScreeningPackageName); + return sb.toString(); + } } diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index be96b3cac6f6..826ad82dfbb2 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -21,6 +21,7 @@ import android.annotation.SdkConstant; import android.app.Service; import android.content.ComponentName; import android.content.Intent; +import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -33,8 +34,9 @@ import com.android.internal.telecom.ICallScreeningService; /** * This service can be implemented by the default dialer (see - * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before - * they are shown to a user. + * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow + * incoming calls before they are shown to a user. This service can also provide + * {@link CallIdentification} information for calls. * <p> * Below is an example manifest registration for a {@code CallScreeningService}. * <pre> @@ -56,6 +58,34 @@ import com.android.internal.telecom.ICallScreeningService; * information about a {@link Call.Details call} which will be shown to the user in the * Dialer app.</li> * </ol> + * <p> + * <h2>Becoming the {@link CallScreeningService}</h2> + * Telecom will bind to a single app chosen by the user which implements the + * {@link CallScreeningService} API when there are new incoming and outgoing calls. + * <p> + * The code snippet below illustrates how your app can request that it fills the call screening + * role. + * <pre> + * {@code + * private static final int REQUEST_ID = 1; + * + * public void requestRole() { + * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); + * Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING_APP"); + * startActivityForResult(intent, REQUEST_ID); + * } + * + * @Override + * public void onActivityResult(int requestCode, int resultCode, Intent data) { + * if (requestCode == REQUEST_ID) { + * if (resultCode == android.app.Activity.RESULT_OK) { + * // Your app is now the call screening app + * } else { + * // Your app is not the call screening app + * } + * } + * } + * </pre> */ public abstract class CallScreeningService extends Service { /** @@ -222,30 +252,46 @@ public abstract class CallScreeningService extends Service { } /** - * Called when a new incoming call is added. - * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)} - * should be called to allow or disallow the call. + * Called when a new incoming or outgoing call is added which is not in the user's contact list. + * <p> + * A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by + * calling + * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}. + * Your app can tell if a call is an incoming call by checking to see if + * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}. + * <p> + * For incoming or outgoing calls, the {@link CallScreeningService} can call + * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide + * {@link CallIdentification} for the call. * <p> * Note: The {@link Call.Details} instance provided to a call screening service will only have * the following properties set. The rest of the {@link Call.Details} properties will be set to * their default value or {@code null}. * <ul> - * <li>{@link Call.Details#getState()}</li> + * <li>{@link Call.Details#getCallDirection()}</li> * <li>{@link Call.Details#getConnectTimeMillis()}</li> * <li>{@link Call.Details#getCreationTimeMillis()}</li> * <li>{@link Call.Details#getHandle()}</li> * <li>{@link Call.Details#getHandlePresentation()}</li> * </ul> + * <p> + * Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme} + * is {@link PhoneAccount#SCHEME_TEL} are passed for call + * screening. Further, only calls which are not in the user's contacts are passed for + * screening. For outgoing calls, no post-dial digits are passed. * - * @param callDetails Information about a new incoming call, see {@link Call.Details}. + * @param callDetails Information about a new call, see {@link Call.Details}. */ public abstract void onScreenCall(@NonNull Call.Details callDetails); /** - * Responds to the given call, either allowing it or disallowing it. + * Responds to the given incoming call, either allowing it or disallowing it. * <p> * The {@link CallScreeningService} calls this method to inform the system whether the call * should be silently blocked or not. + * <p> + * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is + * {@link Call.Details#DIRECTION_INCOMING}. * * @param callDetails The call to allow. * <p> diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 1aeeca73c0b9..f5f0af7e4666 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -40,11 +40,30 @@ import java.util.Collections; import java.util.List; /** - * This service is implemented by any app that wishes to provide the user-interface for managing - * phone calls. Telecom binds to this service while there exists a live (active or incoming) call, - * and uses it to notify the in-call app of any live and recently disconnected calls. An app must - * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()}) - * before the telecom service will bind to its {@code InCallService} implementation. + * This service is implemented by an app that wishes to provide functionality for managing + * phone calls. + * <p> + * There are three types of apps which Telecom can bind to when there exists a live (active or + * incoming) call: + * <ol> + * <li>Default Dialer/Phone app - the default dialer/phone app is one which provides the + * in-call user interface while the device is in a call. A device is bundled with a system + * provided default dialer/phone app. The user may choose a single app to take over this role + * from the system app.</li> + * <li>Default Car-mode Dialer/Phone app - the default car-mode dialer/phone app is one which + * provides the in-call user interface while the device is in a call and the device is in car + * mode. The user may choose a single app to fill this role.</li> + * <li>Call Companion app - a call companion app is one which provides no user interface itself, + * but exposes call information to another display surface, such as a wearable device. The + * user may choose multiple apps to fill this role.</li> + * </ol> + * <p> + * Apps which wish to fulfill one of the above roles use the {@link android.app.role.RoleManager} + * to request that they fill the desired role. + * + * <h2>Becoming the Default Phone App</h2> + * An app filling the role of the default phone app provides a user interface while the device is in + * a call, and the device is not in car mode. * <p> * Below is an example manifest registration for an {@code InCallService}. The meta-data * {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular @@ -82,12 +101,34 @@ import java.util.List; * } * </pre> * <p> - * When a user installs your application and runs it for the first time, you should prompt the user - * to see if they would like your application to be the new default phone app. See the - * {@link TelecomManager#ACTION_CHANGE_DEFAULT_DIALER} intent documentation for more information on - * how to do this. + * When a user installs your application and runs it for the first time, you should use the + * {@link android.app.role.RoleManager} to prompt the user to see if they would like your app to + * be the new default phone app. + * <p id="requestRole"> + * The code below shows how your app can request to become the default phone/dialer app: + * <pre> + * {@code + * private static final int REQUEST_ID = 1; + * + * public void requestRole() { + * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); + * Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER); + * startActivityForResult(intent, REQUEST_ID); + * } + * + * @Override + * public void onActivityResult(int requestCode, int resultCode, Intent data) { + * if (requestCode == REQUEST_ID) { + * if (resultCode == android.app.Activity.RESULT_OK) { + * // Your app is now the default dialer app + * } else { + * // Your app is not the default dialer app + * } + * } + * } + * </pre> * <p id="incomingCallNotification"> - * <h2>Showing the Incoming Call Notification</h2> + * <h3>Showing the Incoming Call Notification</h3> * When your app receives a new incoming call via {@link InCallService#onCallAdded(Call)}, it is * responsible for displaying an incoming call UI for the incoming call. It should do this using * {@link android.app.NotificationManager} APIs to post a new incoming call notification. @@ -121,7 +162,7 @@ import java.util.List; * heads-up notification if the user is actively using the phone. When the user is not using the * phone, your full-screen incoming call UI is used instead. * For example: - * <pre><code> + * <pre><code>{@code * // Create an intent which triggers your fullscreen incoming call user interface. * Intent intent = new Intent(Intent.ACTION_MAIN, null); * intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); @@ -151,7 +192,49 @@ import java.util.List; * NotificationManager notificationManager = mContext.getSystemService( * NotificationManager.class); * notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build()); - * </code></pre> + * }</pre> + * <p> + * <h2>Becoming the Default Car-mode Phone App</h2> + * An app filling the role of the default car-mode dialer/phone app provides a user interface while + * the device is in a call, and in car mode. See + * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information about car mode. + * When the device is in car mode, Telecom binds to the default car-mode dialer/phone app instead + * of the usual dialer/phone app. + * <p> + * Similar to the requirements for becoming the default dialer/phone app, your app must declare a + * manifest entry for its {@link InCallService} implementation. Your manifest entry should ensure + * the following conditions are met: + * <ul> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li> + * <li>Set the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} metadata to + * {@code true}<li> + * <li>Your app must request the permission + * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li> + * </ul> + * <p> + * Your app should request to fill the role {@code android.app.role.CAR_MODE_DIALER_APP} in order to + * become the default (see <a href="#requestRole">above</a> for how to request your app fills this + * role). + * + * <h2>Becoming a Call Companion App</h2> + * An app which fills the companion app role does not directly provide a user interface while the + * device is in a call. Instead, it is typically used to relay information about calls to another + * display surface, such as a wearable device. + * <p> + * Similar to the requirements for becoming the default dialer/phone app, your app must declare a + * manifest entry for its {@link InCallService} implementation. Your manifest entry should + * ensure the following conditions are met: + * <ul> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} + * metadata.</li> + * <li>Your app must request the permission + * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li> + * </ul> + * <p> + * Your app should request to fill the role {@code android.app.role.CALL_COMPANION_APP} in order to + * become a call companion app (see <a href="#requestRole">above</a> for how to request your app + * fills this role). */ public abstract class InCallService extends Service { diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 911786e455c2..f7dec83c3ace 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import android.telecom.Call.Details.CallDirection; import java.util.ArrayList; import java.util.Collections; @@ -64,6 +65,7 @@ public final class ParcelableCall implements Parcelable { private final Bundle mExtras; private final long mCreationTimeMillis; private final CallIdentification mCallIdentification; + private final int mCallDirection; public ParcelableCall( String id, @@ -92,7 +94,8 @@ public final class ParcelableCall implements Parcelable { Bundle intentExtras, Bundle extras, long creationTimeMillis, - CallIdentification callIdentification) { + CallIdentification callIdentification, + int callDirection) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -120,6 +123,7 @@ public final class ParcelableCall implements Parcelable { mExtras = extras; mCreationTimeMillis = creationTimeMillis; mCallIdentification = callIdentification; + mCallDirection = callDirection; } /** The unique ID of the call. */ @@ -318,6 +322,13 @@ public final class ParcelableCall implements Parcelable { return mCallIdentification; } + /** + * Indicates whether the call is an incoming or outgoing call. + */ + public @CallDirection int getCallDirection() { + return mCallDirection; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static final Parcelable.Creator<ParcelableCall> CREATOR = @@ -356,6 +367,7 @@ public final class ParcelableCall implements Parcelable { ParcelableRttCall rttCall = source.readParcelable(classLoader); long creationTimeMillis = source.readLong(); CallIdentification callIdentification = source.readParcelable(classLoader); + int callDirection = source.readInt(); return new ParcelableCall( id, state, @@ -383,7 +395,8 @@ public final class ParcelableCall implements Parcelable { intentExtras, extras, creationTimeMillis, - callIdentification); + callIdentification, + callDirection); } @Override @@ -429,6 +442,7 @@ public final class ParcelableCall implements Parcelable { destination.writeParcelable(mRttCall, 0); destination.writeLong(mCreationTimeMillis); destination.writeParcelable(mCallIdentification, 0); + destination.writeInt(mCallDirection); } @Override diff --git a/telephony/java/android/telephony/DataSpecificRegistrationStates.java b/telephony/java/android/telephony/DataSpecificRegistrationStates.java index 5d809d0b7c36..d6a8065feabe 100644 --- a/telephony/java/android/telephony/DataSpecificRegistrationStates.java +++ b/telephony/java/android/telephony/DataSpecificRegistrationStates.java @@ -44,13 +44,19 @@ public class DataSpecificRegistrationStates implements Parcelable{ */ public final boolean isEnDcAvailable; + /** + * Provides network support info for LTE VoPS and LTE Emergency bearer support + */ + public final LteVopsSupportInfo lteVopsSupportInfo; + DataSpecificRegistrationStates( int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable, - boolean isEnDcAvailable) { + boolean isEnDcAvailable, LteVopsSupportInfo lteVops) { this.maxDataCalls = maxDataCalls; this.isDcNrRestricted = isDcNrRestricted; this.isNrAvailable = isNrAvailable; this.isEnDcAvailable = isEnDcAvailable; + this.lteVopsSupportInfo = lteVops; } private DataSpecificRegistrationStates(Parcel source) { @@ -58,6 +64,7 @@ public class DataSpecificRegistrationStates implements Parcelable{ isDcNrRestricted = source.readBoolean(); isNrAvailable = source.readBoolean(); isEnDcAvailable = source.readBoolean(); + lteVopsSupportInfo = LteVopsSupportInfo.CREATOR.createFromParcel(source); } @Override @@ -66,6 +73,7 @@ public class DataSpecificRegistrationStates implements Parcelable{ dest.writeBoolean(isDcNrRestricted); dest.writeBoolean(isNrAvailable); dest.writeBoolean(isEnDcAvailable); + lteVopsSupportInfo.writeToParcel(dest, flags); } @Override @@ -81,13 +89,15 @@ public class DataSpecificRegistrationStates implements Parcelable{ .append(" isDcNrRestricted = " + isDcNrRestricted) .append(" isNrAvailable = " + isNrAvailable) .append(" isEnDcAvailable = " + isEnDcAvailable) + .append(lteVopsSupportInfo.toString()) .append(" }") .toString(); } @Override public int hashCode() { - return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable, isEnDcAvailable); + return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable, isEnDcAvailable, + lteVopsSupportInfo); } @Override @@ -100,7 +110,8 @@ public class DataSpecificRegistrationStates implements Parcelable{ return this.maxDataCalls == other.maxDataCalls && this.isDcNrRestricted == other.isDcNrRestricted && this.isNrAvailable == other.isNrAvailable - && this.isEnDcAvailable == other.isEnDcAvailable; + && this.isEnDcAvailable == other.isEnDcAvailable + && this.lteVopsSupportInfo.equals(other.lteVopsSupportInfo); } public static final Parcelable.Creator<DataSpecificRegistrationStates> CREATOR = @@ -115,4 +126,4 @@ public class DataSpecificRegistrationStates implements Parcelable{ return new DataSpecificRegistrationStates[size]; } }; -}
\ No newline at end of file +} diff --git a/telephony/java/android/telephony/LteVopsSupportInfo.aidl b/telephony/java/android/telephony/LteVopsSupportInfo.aidl new file mode 100644 index 000000000000..598459853d1c --- /dev/null +++ b/telephony/java/android/telephony/LteVopsSupportInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable LteVopsSupportInfo; diff --git a/telephony/java/android/telephony/LteVopsSupportInfo.java b/telephony/java/android/telephony/LteVopsSupportInfo.java new file mode 100644 index 000000000000..0ae85c0dfa6c --- /dev/null +++ b/telephony/java/android/telephony/LteVopsSupportInfo.java @@ -0,0 +1,137 @@ +/* + * 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.telephony; + +import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Class stores information related to LTE network VoPS support + * @hide + */ +@SystemApi +public final class LteVopsSupportInfo implements Parcelable { + + /**@hide*/ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + value = {LTE_STATUS_NOT_AVAILABLE, LTE_STATUS_SUPPORTED, + LTE_STATUS_NOT_SUPPORTED}, prefix = "LTE_STATUS_") + public @interface LteVopsStatus {} + /** + * Indicates information not available from modem. + */ + public static final int LTE_STATUS_NOT_AVAILABLE = 1; + + /** + * Indicates network support the feature. + */ + public static final int LTE_STATUS_SUPPORTED = 2; + + /** + * Indicates network does not support the feature. + */ + public static final int LTE_STATUS_NOT_SUPPORTED = 3; + + @LteVopsStatus + private final int mVopsSupport; + @LteVopsStatus + private final int mEmcBearerSupport; + + public LteVopsSupportInfo(@LteVopsStatus int vops, @LteVopsStatus int emergency) { + mVopsSupport = vops; + mEmcBearerSupport = emergency; + } + + /** + * Provides the LTE VoPS support capability as described in: + * 3GPP 24.301 EPS network feature support -> IMS VoPS + */ + public @LteVopsStatus int getVopsSupport() { + return mVopsSupport; + } + + /** + * Provides the LTE Emergency bearer support capability as described in: + * 3GPP 24.301 EPS network feature support -> EMC BS + * 25.331 LTE RRC SIB1 : ims-EmergencySupport-r9 + */ + public @LteVopsStatus int getEmcBearerSupport() { + return mEmcBearerSupport; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mVopsSupport); + out.writeInt(mEmcBearerSupport); + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof LteVopsSupportInfo)) { + return false; + } + if (this == o) return true; + LteVopsSupportInfo other = (LteVopsSupportInfo) o; + return mVopsSupport == other.mVopsSupport + && mEmcBearerSupport == other.mEmcBearerSupport; + } + + @Override + public int hashCode() { + return Objects.hash(mVopsSupport, mEmcBearerSupport); + } + + /** + * @return string representation. + */ + @Override + public String toString() { + return ("LteVopsSupportInfo : " + + " mVopsSupport = " + mVopsSupport + + " mEmcBearerSupport = " + mEmcBearerSupport); + } + + public static final Creator<LteVopsSupportInfo> CREATOR = + new Creator<LteVopsSupportInfo>() { + @Override + public LteVopsSupportInfo createFromParcel(Parcel in) { + return new LteVopsSupportInfo(in); + } + + @Override + public LteVopsSupportInfo[] newArray(int size) { + return new LteVopsSupportInfo[size]; + } + }; + + private LteVopsSupportInfo(Parcel in) { + mVopsSupport = in.readInt(); + mEmcBearerSupport = in.readInt(); + } +} diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java index b00665e26ff2..ceb76b57ae0c 100644 --- a/telephony/java/android/telephony/NetworkRegistrationState.java +++ b/telephony/java/android/telephony/NetworkRegistrationState.java @@ -219,12 +219,13 @@ public class NetworkRegistrationState implements Parcelable { public NetworkRegistrationState(int domain, int transportType, int regState, int accessNetworkTechnology, int rejectCause, boolean emergencyOnly, int[] availableServices, @Nullable CellIdentity cellIdentity, int maxDataCalls, - boolean isDcNrRestricted, boolean isNrAvailable, boolean isEndcAvailable) { + boolean isDcNrRestricted, boolean isNrAvailable, boolean isEndcAvailable, + LteVopsSupportInfo lteVopsSupportInfo) { this(domain, transportType, regState, accessNetworkTechnology, rejectCause, emergencyOnly, availableServices, cellIdentity); mDataSpecificStates = new DataSpecificRegistrationStates( - maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable); + maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo); updateNrStatus(mDataSpecificStates); } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 9fc1b6f76e77..bf9bf9a1ba8f 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -170,7 +170,8 @@ public class ServiceState implements Parcelable { RIL_RADIO_TECHNOLOGY_GSM, RIL_RADIO_TECHNOLOGY_TD_SCDMA, RIL_RADIO_TECHNOLOGY_IWLAN, - RIL_RADIO_TECHNOLOGY_LTE_CA}) + RIL_RADIO_TECHNOLOGY_LTE_CA, + RIL_RADIO_TECHNOLOGY_NR}) public @interface RilRadioTechnology {} /** * Available radio technologies for GSM, UMTS and CDMA. diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index a1e8b199d2a0..51d5ab17ee16 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -174,6 +174,16 @@ public class SubscriptionInfo implements Parcelable { private boolean mIsGroupDisabled = false; /** + * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL + * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET. + * A profile on the eUICC can be defined as test, operational, provisioning, or unset. + * The profile class will be populated from the profile metadata if present. Otherwise, + * the profile class defaults to unset if there is no profile metadata or the subscription + * is not on an eUICC ({@link #isEmbedded} returns false). + */ + private int mProfileClass; + + /** * @hide */ public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, @@ -182,7 +192,8 @@ public class SubscriptionInfo implements Parcelable { @Nullable UiccAccessRule[] accessRules, String cardString) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, - false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID); + false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID, + SubscriptionManager.PROFILE_CLASS_DEFAULT); } /** @@ -192,10 +203,10 @@ public class SubscriptionInfo implements Parcelable { CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic, - @Nullable String groupUUID, boolean isMetered, int carrierId) { + @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, - isOpportunistic, groupUUID, isMetered, false, carrierId); + isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass); } /** @@ -206,7 +217,7 @@ public class SubscriptionInfo implements Parcelable { Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardString, int cardId, boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered, - boolean isGroupDisabled, int carrierid) { + boolean isGroupDisabled, int carrierid, int profileClass) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -229,6 +240,7 @@ public class SubscriptionInfo implements Parcelable { this.mIsMetered = isMetered; this.mIsGroupDisabled = isGroupDisabled; this.mCarrierId = carrierid; + this.mProfileClass = profileClass; } @@ -466,6 +478,15 @@ public class SubscriptionInfo implements Parcelable { } /** + * @return the profile class of this subscription. + * @hide + */ + @SystemApi + public @SubscriptionManager.ProfileClass int getProfileClass() { + return this.mProfileClass; + } + + /** * Checks whether the app with the given context is authorized to manage this subscription * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} * returns true). @@ -590,11 +611,12 @@ public class SubscriptionInfo implements Parcelable { boolean isMetered = source.readBoolean(); boolean isGroupDisabled = source.readBoolean(); int carrierid = source.readInt(); + int profileClass = source.readInt(); return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID, - isMetered, isGroupDisabled, carrierid); + isMetered, isGroupDisabled, carrierid, profileClass); } @Override @@ -627,6 +649,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeBoolean(mIsMetered); dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); + dest.writeInt(mProfileClass); } @Override @@ -662,7 +685,8 @@ public class SubscriptionInfo implements Parcelable { + " accessRules " + Arrays.toString(mAccessRules) + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID - + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled + "}"; + + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled + + " profileClass=" + mProfileClass + "}"; } @Override @@ -670,7 +694,7 @@ public class SubscriptionInfo implements Parcelable { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules, - mIsGroupDisabled, mCarrierId); + mIsGroupDisabled, mCarrierId, mProfileClass); } @Override @@ -705,6 +729,7 @@ public class SubscriptionInfo implements Parcelable { && Objects.equals(mCardId, toCompare.mCardId) && TextUtils.equals(mDisplayName, toCompare.mDisplayName) && TextUtils.equals(mCarrierName, toCompare.mCarrierName) - && Arrays.equals(mAccessRules, toCompare.mAccessRules); + && Arrays.equals(mAccessRules, toCompare.mAccessRules) + && mProfileClass == toCompare.mProfileClass; } } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 2c712a1c36e1..34f7abd79a70 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -22,6 +22,7 @@ import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.DurationMillisLong; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -62,6 +63,8 @@ import com.android.internal.telephony.ISub; import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.PhoneConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -599,6 +602,73 @@ public class SubscriptionManager { * @hide */ public static final String IS_METERED = "is_metered"; + + /** + * TelephonyProvider column name for the profile class of a subscription + * Only present if {@link #IS_EMBEDDED} is 1. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String PROFILE_CLASS = "profile_class"; + + /** + * Profile class of the subscription + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "PROFILE_CLASS_" }, value = { + PROFILE_CLASS_TESTING, + PROFILE_CLASS_PROVISIONING, + PROFILE_CLASS_OPERATIONAL, + PROFILE_CLASS_UNSET, + PROFILE_CLASS_DEFAULT + }) + public @interface ProfileClass {} + + /** + * A testing profile can be pre-loaded or downloaded onto + * the eUICC and provides connectivity to test equipment + * for the purpose of testing the device and the eUICC. It + * is not intended to store any operator credentials. + * @hide + */ + @SystemApi + public static final int PROFILE_CLASS_TESTING = 0; + + /** + * A provisioning profile is pre-loaded onto the eUICC and + * provides connectivity to a mobile network solely for the + * purpose of provisioning profiles. + * @hide + */ + @SystemApi + public static final int PROFILE_CLASS_PROVISIONING = 1; + + /** + * An operational profile can be pre-loaded or downloaded + * onto the eUICC and provides services provided by the + * operator. + * @hide + */ + @SystemApi + public static final int PROFILE_CLASS_OPERATIONAL = 2; + + /** + * The profile class is unset. This occurs when profile class + * info is not available. The subscription either has no profile + * metadata or the profile metadata did not encode profile class. + * @hide + */ + @SystemApi + public static final int PROFILE_CLASS_UNSET = -1; + + /** + * Default profile class + * @hide + */ + @SystemApi + public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET; + /** * Broadcast Action: The user has changed one of the default subs related to * data, phone calls, or sms</p> @@ -1102,17 +1172,33 @@ public class SubscriptionManager { @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public List<SubscriptionInfo> getActiveSubscriptionInfoList() { - List<SubscriptionInfo> result = null; + return getActiveSubscriptionInfoList(false); + } + + /** + * This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly + * is true, it will filter out the hidden subscriptions. + * + * @hide + */ + public List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) { + List<SubscriptionInfo> activeList = null; try { ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); if (iSub != null) { - result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName()); + activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName()); } } catch (RemoteException ex) { // ignore it } - return result; + + if (!userVisibleOnly || activeList == null) { + return activeList; + } else { + return activeList.stream().filter(subInfo -> !shouldHideSubscription(subInfo)) + .collect(Collectors.toList()); + } } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 648400509bac..b48a1ced61a4 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -76,8 +76,8 @@ import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; import com.android.internal.telephony.CellNetworkScanResult; -import com.android.internal.telephony.IAns; import com.android.internal.telephony.INumberVerificationCallback; +import com.android.internal.telephony.IOns; import com.android.internal.telephony.IPhoneSubInfo; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.ITelephonyRegistry; @@ -4651,8 +4651,8 @@ public class TelephonyManager { return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry")); } - private IAns getIAns() { - return IAns.Stub.asInterface(ServiceManager.getService("ians")); + private IOns getIOns() { + return IOns.Stub.asInterface(ServiceManager.getService("ions")); } // @@ -9456,10 +9456,10 @@ public class TelephonyManager { } /** - * Enable or disable AlternativeNetworkService. + * Enable or disable OpportunisticNetworkService. * * This method should be called to enable or disable - * AlternativeNetwork service on the device. + * OpportunisticNetwork service on the device. * * <p> * Requires Permission: @@ -9470,25 +9470,25 @@ public class TelephonyManager { * @hide */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - public boolean setAlternativeNetworkState(boolean enable) { + public boolean setOpportunisticNetworkState(boolean enable) { String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; boolean ret = false; try { - IAns iAlternativeNetworkService = getIAns(); - if (iAlternativeNetworkService != null) { - ret = iAlternativeNetworkService.setEnable(enable, pkgForDebug); + IOns iOpportunisticNetworkService = getIOns(); + if (iOpportunisticNetworkService != null) { + ret = iOpportunisticNetworkService.setEnable(enable, pkgForDebug); } } catch (RemoteException ex) { - Rlog.e(TAG, "enableAlternativeNetwork RemoteException", ex); + Rlog.e(TAG, "enableOpportunisticNetwork RemoteException", ex); } return ret; } /** - * is AlternativeNetworkService enabled + * is OpportunisticNetworkService enabled * - * This method should be called to determine if the AlternativeNetworkService is + * This method should be called to determine if the OpportunisticNetworkService is * enabled * * <p> @@ -9497,17 +9497,17 @@ public class TelephonyManager { * @hide */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - public boolean isAlternativeNetworkEnabled() { + public boolean isOpportunisticNetworkEnabled() { String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; boolean isEnabled = false; try { - IAns iAlternativeNetworkService = getIAns(); - if (iAlternativeNetworkService != null) { - isEnabled = iAlternativeNetworkService.isEnabled(pkgForDebug); + IOns iOpportunisticNetworkService = getIOns(); + if (iOpportunisticNetworkService != null) { + isEnabled = iOpportunisticNetworkService.isEnabled(pkgForDebug); } } catch (RemoteException ex) { - Rlog.e(TAG, "enableAlternativeNetwork RemoteException", ex); + Rlog.e(TAG, "enableOpportunisticNetwork RemoteException", ex); } return isEnabled; @@ -9862,9 +9862,9 @@ public class TelephonyManager { public boolean setPreferredOpportunisticDataSubscription(int subId) { String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; try { - IAns iAlternativeNetworkService = getIAns(); - if (iAlternativeNetworkService != null) { - return iAlternativeNetworkService.setPreferredData(subId, pkgForDebug); + IOns iOpportunisticNetworkService = getIOns(); + if (iOpportunisticNetworkService != null) { + return iOpportunisticNetworkService.setPreferredData(subId, pkgForDebug); } } catch (RemoteException ex) { Rlog.e(TAG, "setPreferredData RemoteException", ex); @@ -9886,9 +9886,9 @@ public class TelephonyManager { String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; try { - IAns iAlternativeNetworkService = getIAns(); - if (iAlternativeNetworkService != null) { - subId = iAlternativeNetworkService.getPreferredData(pkgForDebug); + IOns iOpportunisticNetworkService = getIOns(); + if (iOpportunisticNetworkService != null) { + subId = iOpportunisticNetworkService.getPreferredData(pkgForDebug); } } catch (RemoteException ex) { Rlog.e(TAG, "getPreferredData RemoteException", ex); @@ -9899,8 +9899,8 @@ public class TelephonyManager { /** * Update availability of a list of networks in the current location. * - * This api should be called to inform AlternativeNetwork Service about the availability - * of a network at the current location. This information will be used by AlternativeNetwork + * This api should be called to inform OpportunisticNetwork Service about the availability + * of a network at the current location. This information will be used by OpportunisticNetwork * service to decide to attach to the network opportunistically. If an empty list is passed, * it is assumed that no network is available. * Requires that the calling app has carrier privileges on both primary and @@ -9915,9 +9915,9 @@ public class TelephonyManager { String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; boolean ret = false; try { - IAns iAlternativeNetworkService = getIAns(); - if (iAlternativeNetworkService != null) { - ret = iAlternativeNetworkService.updateAvailableNetworks(availableNetworks, + IOns iOpportunisticNetworkService = getIOns(); + if (iOpportunisticNetworkService != null) { + ret = iOpportunisticNetworkService.updateAvailableNetworks(availableNetworks, pkgForDebug); } } catch (RemoteException ex) { diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index a94b163ffd75..a5f56bbebd75 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -232,18 +232,21 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu private final String mCountryIso; private final String mMnc; private final int mEmergencyServiceCategoryBitmask; + private final List<String> mEmergencyUrns; private final int mEmergencyNumberSourceBitmask; private final int mEmergencyCallRouting; /** @hide */ public EmergencyNumber(@NonNull String number, @NonNull String countryIso, @NonNull String mnc, @EmergencyServiceCategories int emergencyServiceCategories, + @NonNull List<String> emergencyUrns, @EmergencyNumberSources int emergencyNumberSources, @EmergencyCallRouting int emergencyCallRouting) { this.mNumber = number; this.mCountryIso = countryIso; this.mMnc = mnc; this.mEmergencyServiceCategoryBitmask = emergencyServiceCategories; + this.mEmergencyUrns = emergencyUrns; this.mEmergencyNumberSourceBitmask = emergencyNumberSources; this.mEmergencyCallRouting = emergencyCallRouting; } @@ -254,6 +257,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu mCountryIso = source.readString(); mMnc = source.readString(); mEmergencyServiceCategoryBitmask = source.readInt(); + mEmergencyUrns = source.createStringArrayList(); mEmergencyNumberSourceBitmask = source.readInt(); mEmergencyCallRouting = source.readInt(); } @@ -265,6 +269,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu dest.writeString(mCountryIso); dest.writeString(mMnc); dest.writeInt(mEmergencyServiceCategoryBitmask); + dest.writeStringList(mEmergencyUrns); dest.writeInt(mEmergencyNumberSourceBitmask); dest.writeInt(mEmergencyCallRouting); } @@ -345,6 +350,22 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu } /** + * Returns the list of emergency Uniform Resources Names (URN) of the emergency number. + * + * For example, {@code urn:service:sos} is the generic URN for contacting emergency services + * of all type. + * + * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General; + * RFC 5031 + * + * @return list of emergency Uniform Resources Names (URN) or an empty list if the emergency + * number does not have a specified emergency Uniform Resource Name. + */ + public @NonNull List<String> getEmergencyUrns() { + return mEmergencyUrns; + } + + /** * Checks if the emergency service category is unspecified for the emergency number * {@link #EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED}. * @@ -434,6 +455,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu return "EmergencyNumber:" + "Number-" + mNumber + "|CountryIso-" + mCountryIso + "|Mnc-" + mMnc + "|ServiceCategories-" + Integer.toBinaryString(mEmergencyServiceCategoryBitmask) + + "|Urns-" + mEmergencyUrns + "|Sources-" + Integer.toBinaryString(mEmergencyNumberSourceBitmask) + "|Routing-" + Integer.toBinaryString(mEmergencyCallRouting); } @@ -448,6 +470,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu && mCountryIso.equals(other.mCountryIso) && mMnc.equals(other.mMnc) && mEmergencyServiceCategoryBitmask == other.mEmergencyServiceCategoryBitmask + && mEmergencyUrns.equals(other.mEmergencyUrns) && mEmergencyNumberSourceBitmask == other.mEmergencyNumberSourceBitmask && mEmergencyCallRouting == other.mEmergencyCallRouting; } @@ -455,7 +478,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu @Override public int hashCode() { return Objects.hash(mNumber, mCountryIso, mMnc, mEmergencyServiceCategoryBitmask, - mEmergencyNumberSourceBitmask, mEmergencyCallRouting); + mEmergencyUrns, mEmergencyNumberSourceBitmask, mEmergencyCallRouting); } /** @@ -584,6 +607,9 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu != second.getEmergencyServiceCategoryBitmask()) { return false; } + if (first.getEmergencyUrns().equals(second.getEmergencyUrns())) { + return false; + } if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) { return false; } @@ -605,6 +631,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu if (areSameEmergencyNumbers(first, second)) { return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(), first.getEmergencyServiceCategoryBitmask(), + first.getEmergencyUrns(), first.getEmergencyNumberSourceBitmask() | second.getEmergencyNumberSourceBitmask(), first.getEmergencyCallRouting()); diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 6326cc688914..cc9befedcdf7 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -616,14 +616,13 @@ public class EuiccManager { /** * Update the nickname for the given subscription. * - * <p>Requires that the calling app has the - * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for - * internal system use only. + * <p>Requires that the calling app has carrier privileges according to the metadata of the + * profile to be updated, or the + * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. * * @param subscriptionId the ID of the subscription to update. * @param nickname the new nickname to apply. * @param callbackIntent a PendingIntent to launch when the operation completes. - * @hide */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void updateSubscriptionNickname( @@ -634,7 +633,7 @@ public class EuiccManager { } try { getIEuiccController().updateSubscriptionNickname( - subscriptionId, nickname, callbackIntent); + subscriptionId, nickname, mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 9c8d078a579b..525a96a4dae9 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -33,6 +33,8 @@ import com.android.internal.telephony.PhoneConstants; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * Parcelable object to handle IMS call profile. @@ -323,6 +325,15 @@ public final class ImsCallProfile implements Parcelable { EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; /** + * The emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} returns + * {@link #SERVICE_TYPE_EMERGENCY}. + * + * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General; + * 3gpp 22.101, Section 10 - Emergency Calls. + */ + private List<String> mEmergencyUrns = new ArrayList<>(); + + /** * The emergency call routing, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -524,6 +535,7 @@ public final class ImsCallProfile implements Parcelable { + ", restrictCause=" + mRestrictCause + ", mediaProfile=" + mMediaProfile.toString() + ", emergencyServiceCategories=" + mEmergencyCallRouting + + ", emergencyUrns=" + mEmergencyUrns + ", emergencyCallRouting=" + mEmergencyCallRouting + " }"; } @@ -540,6 +552,7 @@ public final class ImsCallProfile implements Parcelable { out.writeBundle(filteredExtras); out.writeParcelable(mMediaProfile, 0); out.writeInt(mEmergencyServiceCategories); + out.writeStringList(mEmergencyUrns); out.writeInt(mEmergencyCallRouting); } @@ -549,6 +562,7 @@ public final class ImsCallProfile implements Parcelable { mCallExtras = in.readBundle(); mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader()); mEmergencyServiceCategories = in.readInt(); + mEmergencyUrns = in.createStringArrayList(); mEmergencyCallRouting = in.readInt(); } @@ -760,20 +774,21 @@ public final class ImsCallProfile implements Parcelable { } /** - * Set the emergency service categories and emergency call routing. The set value is valid + * Set the emergency number information. The set value is valid * only if {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY} * * Reference: 3gpp 23.167, Section 6 - Functional description; + * 3gpp 24.503, Section 5.1.6.8.1 - General; * 3gpp 22.101, Section 10 - Emergency Calls. * * @hide */ public void setEmergencyCallInfo(EmergencyNumber num) { setEmergencyServiceCategories(num.getEmergencyServiceCategoryBitmask()); + setEmergencyUrns(num.getEmergencyUrns()); setEmergencyCallRouting(num.getEmergencyCallRouting()); } - /** * Set the emergency service categories. The set value is valid only if * {@link #getServiceType} returns {@link #SERVICE_TYPE_EMERGENCY} @@ -800,6 +815,18 @@ public final class ImsCallProfile implements Parcelable { } /** + * Set the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} + * returns {@link #SERVICE_TYPE_EMERGENCY}. + * + * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General; + * 3gpp 22.101, Section 10 - Emergency Calls. + */ + @VisibleForTesting + public void setEmergencyUrns(List<String> emergencyUrns) { + mEmergencyUrns = emergencyUrns; + } + + /** * Set the emergency call routing, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * @@ -841,6 +868,17 @@ public final class ImsCallProfile implements Parcelable { } /** + * Get the emergency Uniform Resource Names (URN), only valid if {@link #getServiceType} + * returns {@link #SERVICE_TYPE_EMERGENCY}. + * + * Reference: 3gpp 24.503, Section 5.1.6.8.1 - General; + * 3gpp 22.101, Section 10 - Emergency Calls. + */ + public List<String> getEmergencyUrns() { + return mEmergencyUrns; + } + + /** * Get the emergency call routing, only valid if {@link #getServiceType} returns * {@link #SERVICE_TYPE_EMERGENCY} * diff --git a/telephony/java/com/android/internal/telephony/IAns.aidl b/telephony/java/com/android/internal/telephony/IOns.aidl index 98bcd415a1ca..d6779f1fb334 100755 --- a/telephony/java/com/android/internal/telephony/IAns.aidl +++ b/telephony/java/com/android/internal/telephony/IOns.aidl @@ -18,13 +18,13 @@ package com.android.internal.telephony; import android.telephony.AvailableNetworkInfo; -interface IAns { +interface IOns { /** - * Enable or disable Alternative Network service. + * Enable or disable Opportunistic Network service. * * This method should be called to enable or disable - * AlternativeNetwork service on the device. + * OpportunisticNetwork service on the device. * * <p> * Requires Permission: @@ -38,9 +38,9 @@ interface IAns { boolean setEnable(boolean enable, String callingPackage); /** - * is Alternative Network service enabled + * is Opportunistic Network service enabled * - * This method should be called to determine if the Alternative Network service is enabled + * This method should be called to determine if the Opportunistic Network service is enabled * * <p> * Requires Permission: @@ -84,7 +84,7 @@ interface IAns { * Update availability of a list of networks in the current location. * * This api should be called if the caller is aware of the availability of a network - * at the current location. This information will be used by AlternativeNetwork service + * at the current location. This information will be used by OpportunisticNetwork service * to decide to attach to the network. If an empty list is passed, * it is assumed that no network is available. * Requires that the calling app has carrier privileges on both primary and diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java index 9874f809c0b4..a508b068065f 100644 --- a/telephony/java/com/android/internal/telephony/SmsApplication.java +++ b/telephony/java/com/android/internal/telephony/SmsApplication.java @@ -18,6 +18,8 @@ package com.android.internal.telephony; import android.Manifest.permission; import android.app.AppOpsManager; +import android.app.role.RoleManager; +import android.app.role.RoleManagerCallback; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -29,13 +31,12 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.res.Resources; import android.net.Uri; +import android.os.AsyncTask; import android.os.Binder; import android.os.Debug; import android.os.Process; import android.os.UserHandle; -import android.provider.Settings; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.Rlog; @@ -50,6 +51,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Class for managing the primary application that we will deliver SMS/MMS messages to @@ -67,6 +72,7 @@ public final class SmsApplication { private static final String SCHEME_SMSTO = "smsto"; private static final String SCHEME_MMS = "mms"; private static final String SCHEME_MMSTO = "mmsto"; + private static final boolean DEBUG = false; private static final boolean DEBUG_MULTIUSER = false; private static final int[] DEFAULT_APP_EXCLUSIVE_APPOPS = { @@ -240,7 +246,11 @@ public final class SmsApplication { // Get the list of apps registered for SMS Intent intent = new Intent(Intents.SMS_DELIVER_ACTION); - List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0, + if (DEBUG) { + intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); + } + List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>(); @@ -266,7 +276,8 @@ public final class SmsApplication { // Update any existing entries with mms receiver class intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION); intent.setDataAndType(null, "application/vnd.wap.mms-message"); - List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0, + List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); for (ResolveInfo resolveInfo : mmsReceivers) { final ActivityInfo activityInfo = resolveInfo.activityInfo; @@ -286,7 +297,8 @@ public final class SmsApplication { // Update any existing entries with respond via message intent class. intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, Uri.fromParts(SCHEME_SMSTO, "", null)); - List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, 0, + List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); for (ResolveInfo resolveInfo : respondServices) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; @@ -306,7 +318,8 @@ public final class SmsApplication { // Update any existing entries with supports send to. intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(SCHEME_SMSTO, "", null)); - List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, 0, + List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); for (ResolveInfo resolveInfo : sendToActivities) { final ActivityInfo activityInfo = resolveInfo.activityInfo; @@ -323,7 +336,9 @@ public final class SmsApplication { // Update any existing entries with the default sms changed handler. intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED); List<ResolveInfo> smsAppChangedReceivers = - packageManager.queryBroadcastReceiversAsUser(intent, 0, userId); + packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" + smsAppChangedReceivers); @@ -348,7 +363,9 @@ public final class SmsApplication { // Update any existing entries with the external provider changed handler. intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE); List<ResolveInfo> providerChangedReceivers = - packageManager.queryBroadcastReceiversAsUser(intent, 0, userId); + packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" + providerChangedReceivers); @@ -373,7 +390,9 @@ public final class SmsApplication { // Update any existing entries with the sim full handler. intent = new Intent(Intents.SIM_FULL_ACTION); List<ResolveInfo> simFullReceivers = - packageManager.queryBroadcastReceiversAsUser(intent, 0, userId); + packageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers=" + simFullReceivers); @@ -406,7 +425,8 @@ public final class SmsApplication { if (smsApplicationData != null) { if (!smsApplicationData.isComplete()) { Log.w(LOG_TAG, "Package " + packageName - + " lacks required manifest declarations to be a default sms app"); + + " lacks required manifest declarations to be a default sms app: " + + smsApplicationData); receivers.remove(packageName); } } @@ -418,7 +438,7 @@ public final class SmsApplication { * Checks to see if we have a valid installed SMS application for the specified package name * @return Data for the specified package name or null if there isn't one */ - private static SmsApplicationData getApplicationForPackage( + public static SmsApplicationData getApplicationForPackage( Collection<SmsApplicationData> applications, String packageName) { if (packageName == null) { return null; @@ -456,8 +476,7 @@ public final class SmsApplication { Log.i(LOG_TAG, "getApplication userId=" + userId); } // Determine which application receives the broadcast - String defaultApplication = Settings.Secure.getStringForUser(context.getContentResolver(), - Settings.Secure.SMS_DEFAULT_APPLICATION, userId); + String defaultApplication = getDefaultSmsPackage(context, userId); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication); } @@ -469,27 +488,6 @@ public final class SmsApplication { if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplication appData=" + applicationData); } - // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do - // this if the caller asked us to. - if (updateIfNeeded && applicationData == null) { - // Try to find the default SMS package for this device - Resources r = context.getResources(); - String defaultPackage = - r.getString(com.android.internal.R.string.default_sms_application); - applicationData = getApplicationForPackage(applications, defaultPackage); - - if (applicationData == null) { - // Are there any applications? - if (applications.size() != 0) { - applicationData = (SmsApplicationData)applications.toArray()[0]; - } - } - - // If we found a new default app, update the setting - if (applicationData != null) { - setDefaultApplicationInternal(applicationData.mPackageName, context, userId); - } - } // If we found a package, make sure AppOps permissions are set up correctly if (applicationData != null) { @@ -513,7 +511,7 @@ public final class SmsApplication { // current SMS app will already be the preferred activity - but checking whether or // not this is true is just as expensive as reconfiguring the preferred activity so // we just reconfigure every time. - updateDefaultSmsApp(context, userId, applicationData); + defaultSmsAppChanged(context); } } if (DEBUG_MULTIUSER) { @@ -522,16 +520,17 @@ public final class SmsApplication { return applicationData; } - private static void updateDefaultSmsApp(Context context, int userId, - SmsApplicationData applicationData) { + private static String getDefaultSmsPackage(Context context, int userId) { + return context.getSystemService(RoleManager.class).getDefaultSmsPackage(userId); + } + + /** + * Grants various permissions and appops on sms app change + */ + private static void defaultSmsAppChanged(Context context) { PackageManager packageManager = context.getPackageManager(); AppOpsManager appOps = context.getSystemService(AppOpsManager.class); - // Configure this as the preferred activity for SENDTO sms/mms intents - configurePreferredActivity(packageManager, new ComponentName( - applicationData.mPackageName, applicationData.mSendToClass), - userId); - // Assign permission to special system apps assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, PHONE_PACKAGE_NAME); @@ -603,8 +602,7 @@ public final class SmsApplication { final UserHandle userHandle = UserHandle.of(userId); // Get old package name - String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(), - Settings.Secure.SMS_DEFAULT_APPLICATION, userId); + String oldPackageName = getDefaultSmsPackage(context, userId); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName + @@ -636,16 +634,29 @@ public final class SmsApplication { } } - // Update the secure setting. - Settings.Secure.putStringForUser(context.getContentResolver(), - Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName, - userId); - - // Allow relevant appops for the newly configured default SMS app. - setExclusiveAppops(applicationData.mPackageName, appOps, applicationData.mUid, - AppOpsManager.MODE_ALLOWED); + // Update the setting. + CompletableFuture<Void> res = new CompletableFuture<>(); + context.getSystemService(RoleManager.class).addRoleHolderAsUser( + RoleManager.ROLE_SMS, applicationData.mPackageName, UserHandle.of(userId), + AsyncTask.THREAD_POOL_EXECUTOR, new RoleManagerCallback() { + @Override + public void onSuccess() { + res.complete(null); + } + + @Override + public void onFailure() { + res.completeExceptionally(new RuntimeException()); + } + }); + try { + res.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Log.e(LOG_TAG, "Exception while adding sms role holder " + applicationData, e); + return; + } - updateDefaultSmsApp(context, userId, applicationData); + defaultSmsAppChanged(context); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData); diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index 870a689f85b1..dd40d560250d 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -39,7 +39,7 @@ interface IEuiccController { oneway void switchToSubscription(int subscriptionId, String callingPackage, in PendingIntent callbackIntent); oneway void updateSubscriptionNickname(int subscriptionId, String nickname, - in PendingIntent callbackIntent); + String callingPackage, in PendingIntent callbackIntent); oneway void eraseSubscriptions(in PendingIntent callbackIntent); oneway void retainSubscriptionsForFactoryReset(in PendingIntent callbackIntent); }
\ No newline at end of file diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 37158e5fe0b9..e1d6e01d6d06 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -25,7 +25,8 @@ java_sdk_library { "android.test.mock", ], + srcs_lib: "framework", + srcs_lib_whitelist_dirs: ["core/java"], srcs_lib_whitelist_pkgs: ["android"], - metalava_enabled: false, compile_dex: true, } diff --git a/tests/PackageWatchdog/Android.mk b/tests/PackageWatchdog/Android.mk new file mode 100644 index 000000000000..b53f27d28740 --- /dev/null +++ b/tests/PackageWatchdog/Android.mk @@ -0,0 +1,34 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +# PackageWatchdogTest +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_PACKAGE_NAME := PackageWatchdogTest +LOCAL_MODULE_TAGS := tests +LOCAL_STATIC_JAVA_LIBRARIES := \ + junit \ + frameworks-base-testutils \ + android-support-test \ + services + +LOCAL_JAVA_LIBRARIES := \ + android.test.runner + +LOCAL_PRIVATE_PLATFORM_APIS := true +LOCAL_COMPATIBILITY_SUITE := device-tests + +include $(BUILD_PACKAGE) diff --git a/tests/PackageWatchdog/AndroidManifest.xml b/tests/PackageWatchdog/AndroidManifest.xml new file mode 100644 index 000000000000..fa89528403c3 --- /dev/null +++ b/tests/PackageWatchdog/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tests.packagewatchdog" > + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.tests.packagewatchdog" + android:label="PackageWatchdog Test"/> +</manifest> diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java new file mode 100644 index 000000000000..ec07037b3a8f --- /dev/null +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -0,0 +1,286 @@ +/* + * 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; + +import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.os.test.TestLooper; +import android.support.test.InstrumentationRegistry; + +import com.android.server.PackageWatchdog.PackageHealthObserver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on +// behavior of observers receiving crash notifications or not to determine if it's registered +/** + * Test PackageWatchdog. + */ +public class PackageWatchdogTest { + private static final String APP_A = "com.package.a"; + private static final String APP_B = "com.package.b"; + private static final String OBSERVER_NAME_1 = "observer1"; + private static final String OBSERVER_NAME_2 = "observer2"; + private static final String OBSERVER_NAME_3 = "observer3"; + private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); + private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); + private TestLooper mTestLooper; + + @Before + public void setUp() throws Exception { + mTestLooper = new TestLooper(); + mTestLooper.startAutoDispatch(); + } + + @After + public void tearDown() throws Exception { + new File(InstrumentationRegistry.getContext().getFilesDir(), + "package-watchdog.xml").delete(); + } + + /** + * Test registration, unregistration, package expiry and duration reduction + */ + @Test + public void testRegistration() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); + TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); + + // Start observing for observer1 which will be unregistered + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + // Start observing for observer2 which will expire + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); + // Start observing for observer3 which will have expiry duration reduced + watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION); + + // Verify packages observed at start + // 1 + assertEquals(1, watchdog.getPackages(observer1).size()); + assertTrue(watchdog.getPackages(observer1).contains(APP_A)); + // 2 + assertEquals(2, watchdog.getPackages(observer2).size()); + assertTrue(watchdog.getPackages(observer2).contains(APP_A)); + assertTrue(watchdog.getPackages(observer2).contains(APP_B)); + // 3 + assertEquals(1, watchdog.getPackages(observer3).size()); + assertTrue(watchdog.getPackages(observer3).contains(APP_A)); + + // Then unregister observer1 + watchdog.unregisterHealthObserver(observer1); + + // Verify observer2 and observer3 left + // 1 + assertNull(watchdog.getPackages(observer1)); + // 2 + assertEquals(2, watchdog.getPackages(observer2).size()); + assertTrue(watchdog.getPackages(observer2).contains(APP_A)); + assertTrue(watchdog.getPackages(observer2).contains(APP_B)); + // 3 + assertEquals(1, watchdog.getPackages(observer3).size()); + assertTrue(watchdog.getPackages(observer3).contains(APP_A)); + + // Then advance time a little and run messages in Handlers so observer2 expires + Thread.sleep(SHORT_DURATION); + mTestLooper.dispatchAll(); + + // Verify observer3 left with reduced expiry duration + // 1 + assertNull(watchdog.getPackages(observer1)); + // 2 + assertNull(watchdog.getPackages(observer2)); + // 3 + assertEquals(1, watchdog.getPackages(observer3).size()); + assertTrue(watchdog.getPackages(observer3).contains(APP_A)); + + // Then advance time some more and run messages in Handlers so observer3 expires + Thread.sleep(LONG_DURATION); + mTestLooper.dispatchAll(); + + // Verify observer3 expired + // 1 + assertNull(watchdog.getPackages(observer1)); + // 2 + assertNull(watchdog.getPackages(observer2)); + // 3 + assertNull(watchdog.getPackages(observer3)); + } + + /** + * Test package observers are persisted and loaded on startup + */ + @Test + public void testPersistence() throws Exception { + PackageWatchdog watchdog1 = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); + + watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); + + // Verify 2 observers are registered and saved internally + // 1 + assertEquals(1, watchdog1.getPackages(observer1).size()); + assertTrue(watchdog1.getPackages(observer1).contains(APP_A)); + // 2 + assertEquals(2, watchdog1.getPackages(observer2).size()); + assertTrue(watchdog1.getPackages(observer2).contains(APP_A)); + assertTrue(watchdog1.getPackages(observer2).contains(APP_B)); + + + // Then advance time and run IO Handler so file is saved + mTestLooper.dispatchAll(); + + // Then start a new watchdog + PackageWatchdog watchdog2 = createWatchdog(); + + // Verify the new watchdog loads observers on startup but nothing registered + assertEquals(0, watchdog2.getPackages(observer1).size()); + assertEquals(0, watchdog2.getPackages(observer2).size()); + // Verify random observer not saved returns null + assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3))); + + // Then regiser observer1 + watchdog2.registerHealthObserver(observer1); + watchdog2.registerHealthObserver(observer2); + + // Verify 2 observers are registered after reload + // 1 + assertEquals(1, watchdog1.getPackages(observer1).size()); + assertTrue(watchdog1.getPackages(observer1).contains(APP_A)); + // 2 + assertEquals(2, watchdog1.getPackages(observer2).size()); + assertTrue(watchdog1.getPackages(observer2).contains(APP_A)); + assertTrue(watchdog1.getPackages(observer2).contains(APP_B)); + } + + /** + * Test package failure under threshold does not notify observers + */ + @Test + public void testNoPackageFailureBeforeThreshold() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); + + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + + // Then fail APP_A below the threshold + for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) { + watchdog.onPackageFailure(new String[]{APP_A}); + } + + // Verify that observers are not notified + assertEquals(0, observer1.mFailedPackages.size()); + assertEquals(0, observer2.mFailedPackages.size()); + } + + /** + * Test package failure and notifies all observer since none handles the failure + */ + @Test + public void testPackageFailureNotifyAll() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); + + // Start observing for observer1 and observer2 without handling failures + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A, APP_B), SHORT_DURATION); + + // Then fail APP_A and APP_B above the threshold + for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { + watchdog.onPackageFailure(new String[]{APP_A, APP_B}); + } + + // Verify all observers are notifed of all package failures + List<String> observer1Packages = observer1.mFailedPackages; + List<String> observer2Packages = observer2.mFailedPackages; + assertEquals(2, observer1Packages.size()); + assertEquals(1, observer2Packages.size()); + assertEquals(APP_A, observer1Packages.get(0)); + assertEquals(APP_B, observer1Packages.get(1)); + assertEquals(APP_A, observer2Packages.get(0)); + } + + /** + * Test package failure and notifies only one observer because it handles the failure + */ + @Test + public void testPackageFailureNotifyOne() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, true /* shouldHandle */); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, true /* shouldHandle */); + + // Start observing for observer1 and observer2 with failure handling + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + + // Then fail APP_A above the threshold + for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) { + watchdog.onPackageFailure(new String[]{APP_A}); + } + + // Verify only one observer is notifed + assertEquals(1, observer1.mFailedPackages.size()); + assertEquals(APP_A, observer1.mFailedPackages.get(0)); + assertEquals(0, observer2.mFailedPackages.size()); + } + + private PackageWatchdog createWatchdog() { + return new PackageWatchdog(InstrumentationRegistry.getContext(), + mTestLooper.getLooper()); + } + + private static class TestObserver implements PackageHealthObserver { + private final String mName; + private boolean mShouldHandle; + final List<String> mFailedPackages = new ArrayList<>(); + + TestObserver(String name) { + mName = name; + } + + TestObserver(String name, boolean shouldHandle) { + mName = name; + mShouldHandle = shouldHandle; + } + + public boolean onHealthCheckFailed(String packageName) { + mFailedPackages.add(packageName); + return mShouldHandle; + } + + public String getName() { + return mName; + } + } +} diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 9d1edbf1eaf0..f6f35fdadcd1 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -18,6 +18,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-target-minus-junit4 \ platform-test-annotations \ services.core \ + services.ipmemorystore \ services.net LOCAL_JAVA_LIBRARIES := \ diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java new file mode 100644 index 000000000000..eae9710215ca --- /dev/null +++ b/tests/net/java/android/net/IpMemoryStoreTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.content.Context; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpMemoryStoreTest { + @Mock + Context mMockContext; + @Mock + IIpMemoryStore mMockService; + IpMemoryStore mStore; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mStore = new IpMemoryStore(mMockContext, mMockService); + } + + @Test + public void testNetworkAttributes() { + // TODO : implement this + } + + @Test + public void testPrivateData() { + // TODO : implement this + } + + @Test + public void testFindL2Key() { + // TODO : implement this + } + + @Test + public void testIsSameNetwork() { + // TODO : implement this + } + +} diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java new file mode 100644 index 000000000000..a9f9758bb1f8 --- /dev/null +++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ipmemorystore; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collections; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ParcelableTests { + @Test + public void testNetworkAttributesParceling() throws Exception { + final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); + NetworkAttributes in = builder.build(); + assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + // groupHint stays null this time around + builder.setDnsAddresses(Collections.emptyList()); + builder.setMtu(18); + in = builder.build(); + assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9")); + builder.setGroupHint("groupHint"); + builder.setDnsAddresses(Arrays.asList( + InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"), + InetAddress.getByName("6.7.8.9"))); + builder.setMtu(1_000_000); + in = builder.build(); + assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + builder.setMtu(null); + in = builder.build(); + assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + } + + @Test + public void testPrivateDataParceling() throws Exception { + final Blob in = new Blob(); + in.data = new byte[] {89, 111, 108, 111}; + final Blob out = parcelingRoundTrip(in); + // Object.equals on byte[] tests the references + assertEquals(in.data.length, out.data.length); + assertTrue(Arrays.equals(in.data, out.data)); + } + + @Test + public void testSameL3NetworkResponseParceling() throws Exception { + final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable(); + parcelable.l2Key1 = "key 1"; + parcelable.l2Key2 = "key 2"; + parcelable.confidence = 0.43f; + + final SameL3NetworkResponse in = new SameL3NetworkResponse(parcelable); + assertEquals("key 1", in.l2Key1); + assertEquals("key 2", in.l2Key2); + assertEquals(0.43f, in.confidence, 0.01f /* delta */); + + final SameL3NetworkResponse out = + new SameL3NetworkResponse(parcelingRoundTrip(in.toParcelable())); + + assertEquals(in, out); + assertEquals(in.l2Key1, out.l2Key1); + assertEquals(in.l2Key2, out.l2Key2); + assertEquals(in.confidence, out.confidence, 0.01f /* delta */); + } + + private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception { + final Parcel p = Parcel.obtain(); + in.writeToParcel(p, /* flags */ 0); + p.setDataPosition(0); + final byte[] marshalledData = p.marshall(); + p.recycle(); + + final Parcel q = Parcel.obtain(); + q.unmarshall(marshalledData, 0, marshalledData.length); + q.setDataPosition(0); + + final Parcelable.Creator<T> creator = (Parcelable.Creator<T>) + in.getClass().getField("CREATOR").get(null); // static object, so null receiver + final T unmarshalled = (T) creator.createFromParcel(q); + q.recycle(); + return unmarshalled; + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 71529fdffd5f..bf3964416e11 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -26,6 +26,8 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; +import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; @@ -69,17 +71,19 @@ import static org.junit.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; - import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -89,7 +93,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; -import android.net.CaptivePortal; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.PacketKeepalive; @@ -97,6 +100,8 @@ import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.ConnectivityManager.TooManyRequestsException; import android.net.ConnectivityThread; import android.net.INetd; +import android.net.INetworkMonitor; +import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; @@ -114,12 +119,14 @@ import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; import android.net.NetworkRequest; import android.net.NetworkSpecifier; +import android.net.NetworkStack; import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.StringNetworkSpecifier; import android.net.UidRange; -import android.net.captiveportal.CaptivePortalProbeResult; import android.net.metrics.IpConnectivityLog; +import android.net.shared.NetworkMonitorUtils; +import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; import android.os.ConditionVariable; import android.os.Handler; @@ -148,12 +155,9 @@ import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.connectivity.ConnectivityConstants; import com.android.server.connectivity.DefaultNetworkMetrics; -import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; -import com.android.server.connectivity.NetworkAgentInfo; -import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; @@ -168,6 +172,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.mockito.stubbing.Answer; import java.net.Inet4Address; import java.net.InetAddress; @@ -230,6 +235,7 @@ public class ConnectivityServiceTest { @Mock INetworkStatsService mStatsService; @Mock INetworkPolicyManager mNpm; @Mock INetd mMockNetd; + @Mock NetworkStack mNetworkStack; private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class); @@ -299,6 +305,7 @@ public class ConnectivityServiceTest { public Object getSystemService(String name) { if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class); + if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack; return super.getSystemService(name); } @@ -386,7 +393,7 @@ public class ConnectivityServiceTest { } private class MockNetworkAgent { - private final WrappedNetworkMonitor mWrappedNetworkMonitor; + private final INetworkMonitor mNetworkMonitor; private final NetworkInfo mNetworkInfo; private final NetworkCapabilities mNetworkCapabilities; private final HandlerThread mHandlerThread; @@ -402,6 +409,26 @@ public class ConnectivityServiceTest { // mNetworkStatusReceived. private String mRedirectUrl; + private INetworkMonitorCallbacks mNmCallbacks; + private int mNmValidationResult = NETWORK_TEST_RESULT_INVALID; + private String mNmValidationRedirectUrl = null; + private boolean mNmProvNotificationRequested = false; + + void setNetworkValid() { + mNmValidationResult = NETWORK_TEST_RESULT_VALID; + mNmValidationRedirectUrl = null; + } + + void setNetworkInvalid() { + mNmValidationResult = NETWORK_TEST_RESULT_INVALID; + mNmValidationRedirectUrl = null; + } + + void setNetworkPortal(String redirectUrl) { + setNetworkInvalid(); + mNmValidationRedirectUrl = redirectUrl; + } + MockNetworkAgent(int transport) { this(transport, new LinkProperties()); } @@ -434,6 +461,29 @@ public class ConnectivityServiceTest { } mHandlerThread = new HandlerThread("Mock-" + typeName); mHandlerThread.start(); + + mNetworkMonitor = mock(INetworkMonitor.class); + final Answer validateAnswer = inv -> { + new Thread(this::onValidationRequested).start(); + return null; + }; + + try { + doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(); + doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt()); + } catch (RemoteException e) { + fail(e.getMessage()); + } + + final ArgumentCaptor<Network> nmNetworkCaptor = + ArgumentCaptor.forClass(Network.class); + final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor = + ArgumentCaptor.forClass(INetworkMonitorCallbacks.class); + doNothing().when(mNetworkStack).makeNetworkMonitor( + nmNetworkCaptor.capture(), + any() /* name */, + nmCbCaptor.capture()); + mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext, "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities, linkProperties, mScore, new NetworkMisc()) { @@ -465,10 +515,40 @@ public class ConnectivityServiceTest { mPreventReconnectReceived.open(); } }; + + assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId); + mNmCallbacks = nmCbCaptor.getValue(); + + try { + mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor); + } catch (RemoteException e) { + fail(e.getMessage()); + } + // Waits for the NetworkAgent to be registered, which includes the creation of the // NetworkMonitor. waitForIdle(); - mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor(); + } + + private void onValidationRequested() { + try { + if (mNmProvNotificationRequested + && mNmValidationResult == NETWORK_TEST_RESULT_VALID) { + mNmCallbacks.hideProvisioningNotification(); + mNmProvNotificationRequested = false; + } + + mNmCallbacks.notifyNetworkTested( + mNmValidationResult, mNmValidationRedirectUrl); + + if (mNmValidationRedirectUrl != null) { + mNmCallbacks.showProvisioningNotification( + "test_provisioning_notif_action"); + mNmProvNotificationRequested = true; + } + } catch (RemoteException e) { + fail(e.getMessage()); + } } public void adjustScore(int change) { @@ -539,7 +619,7 @@ public class ConnectivityServiceTest { NetworkCallback callback = null; final ConditionVariable validatedCv = new ConditionVariable(); if (validated) { - mWrappedNetworkMonitor.gen204ProbeResult = 204; + setNetworkValid(); NetworkRequest request = new NetworkRequest.Builder() .addTransportType(mNetworkCapabilities.getTransportTypes()[0]) .clearCapabilities() @@ -564,15 +644,14 @@ public class ConnectivityServiceTest { if (validated) { // Wait for network to validate. waitFor(validatedCv); - mWrappedNetworkMonitor.gen204ProbeResult = 500; + setNetworkInvalid(); } if (callback != null) mCm.unregisterNetworkCallback(callback); } public void connectWithCaptivePortal(String redirectUrl) { - mWrappedNetworkMonitor.gen204ProbeResult = 200; - mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl; + setNetworkPortal(redirectUrl); connect(false); } @@ -603,10 +682,6 @@ public class ConnectivityServiceTest { return mDisconnected; } - public WrappedNetworkMonitor getWrappedNetworkMonitor() { - return mWrappedNetworkMonitor; - } - public void sendLinkProperties(LinkProperties lp) { mNetworkAgent.sendLinkProperties(lp); } @@ -880,28 +955,6 @@ public class ConnectivityServiceTest { } } - // NetworkMonitor implementation allowing overriding of Internet connectivity probe result. - private class WrappedNetworkMonitor extends NetworkMonitor { - public final Handler connectivityHandler; - // HTTP response code fed back to NetworkMonitor for Internet connectivity probe. - public int gen204ProbeResult = 500; - public String gen204ProbeRedirectUrl = null; - - public WrappedNetworkMonitor(Context context, Handler handler, - NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest, - IpConnectivityLog log) { - super(context, handler, networkAgentInfo, defaultRequest, log, - NetworkMonitor.Dependencies.DEFAULT); - connectivityHandler = handler; - } - - @Override - protected CaptivePortalProbeResult isCaptivePortal() { - if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); } - return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null); - } - } - private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker { public volatile boolean configRestrictsAvoidBadWifi; public volatile int configMeteredMultipathPreference; @@ -923,7 +976,6 @@ public class ConnectivityServiceTest { private class WrappedConnectivityService extends ConnectivityService { public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker; - private WrappedNetworkMonitor mLastCreatedNetworkMonitor; private MockableSystemProperties mSystemProperties; public WrappedConnectivityService(Context context, INetworkManagementService netManager, @@ -971,15 +1023,6 @@ public class ConnectivityServiceTest { } } - @Override - public NetworkMonitor createNetworkMonitor(Context context, Handler handler, - NetworkAgentInfo nai, NetworkRequest defaultRequest) { - final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor( - context, handler, nai, defaultRequest, mock(IpConnectivityLog.class)); - mLastCreatedNetworkMonitor = monitor; - return monitor; - } - public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) { return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; } @@ -1017,10 +1060,6 @@ public class ConnectivityServiceTest { protected void registerNetdEventCallback() { } - public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() { - return mLastCreatedNetworkMonitor; - } - public void mockVpn(int uid) { synchronized (mVpns) { int userId = UserHandle.getUserId(uid); @@ -2439,7 +2478,7 @@ public class ConnectivityServiceTest { // Make captive portal disappear then revalidate. // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL. - mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; + mWiFiNetworkAgent.setNetworkValid(); mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); @@ -2448,13 +2487,13 @@ public class ConnectivityServiceTest { // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. - mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500; + mWiFiNetworkAgent.setNetworkInvalid(); mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); } @Test - public void testCaptivePortalApp() { + public void testCaptivePortalApp() throws RemoteException { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); @@ -2477,21 +2516,19 @@ public class ConnectivityServiceTest { mServiceContext.expectNoStartActivityIntent(fastTimeoutMs); // Turn into a captive portal. - mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302; + mWiFiNetworkAgent.setNetworkPortal("http://example.com"); mCm.reportNetworkConnectivity(wifiNetwork, false); captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - // Check that startCaptivePortalApp sends the expected intent. + // Check that startCaptivePortalApp sends the expected command to NetworkMonitor. mCm.startCaptivePortalApp(wifiNetwork); - Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS); - assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction()); - assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK)); - - // Have the app report that the captive portal is dismissed, and check that we revalidate. - mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; - CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); - c.reportCaptivePortalDismissed(); + verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1)) + .launchCaptivePortalApp(); + + // Report that the captive portal is dismissed, and check that callbacks are fired + mWiFiNetworkAgent.setNetworkValid(); + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); @@ -2524,20 +2561,6 @@ public class ConnectivityServiceTest { waitFor(avoidCv); assertNoCallbacks(captivePortalCallback, validatedCallback); - - // Now test ignore mode. - setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE); - - // Bring up a network with a captive portal. - // Since we're ignoring captive portals, the network will validate. - mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); - String secondRedirectUrl = "http://example.com/secondPath"; - mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); - - // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); - // But there should be no CaptivePortal callback. - captivePortalCallback.assertNoCallback(); } private NetworkRequest.Builder newWifiRequestBuilder() { @@ -3169,7 +3192,7 @@ public class ConnectivityServiceTest { Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi. - mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599; + mWiFiNetworkAgent.setNetworkInvalid(); mCm.reportNetworkConnectivity(wifiNetwork, false); defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); @@ -3213,7 +3236,7 @@ public class ConnectivityServiceTest { wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi and expect the dialog to appear. - mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599; + mWiFiNetworkAgent.setNetworkInvalid(); mCm.reportNetworkConnectivity(wifiNetwork, false); defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); @@ -4002,11 +4025,9 @@ public class ConnectivityServiceTest { final String TLS_SERVER6 = "2001:db8:53::53"; final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) }; final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 }; - final Handler h = mCellNetworkAgent.getWrappedNetworkMonitor().connectivityHandler; - h.sendMessage(h.obtainMessage( - NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, - mCellNetworkAgent.getNetwork().netId, - new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS))); + mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved( + new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel()); + waitForIdle(); verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork( anyInt(), mStringArrayCaptor.capture(), any(), any(), @@ -4294,6 +4315,12 @@ public class ConnectivityServiceTest { ranges.add(new UidRange(uid, uid)); mMockVpn.setNetworkAgent(vpnNetworkAgent); mMockVpn.setUids(ranges); + // VPN networks do not satisfy the default request and are automatically validated + // by NetworkMonitor + assertFalse(NetworkMonitorUtils.isValidationRequired( + mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities)); + vpnNetworkAgent.setNetworkValid(); + vpnNetworkAgent.connect(false); mMockVpn.connect(); diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java index 01b468af9447..38322e925a24 100644 --- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java @@ -17,7 +17,6 @@ package com.android.server.connectivity; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; -import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_MODE; @@ -29,13 +28,13 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; -import android.content.ContentResolver; import android.content.Context; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.RouteInfo; +import android.net.shared.PrivateDnsConfig; import android.os.INetworkManagementService; import android.provider.Settings; import android.support.test.filters.SmallTest; @@ -43,18 +42,16 @@ import android.support.test.runner.AndroidJUnit4; import android.test.mock.MockContentResolver; import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.connectivity.DnsManager.PrivateDnsConfig; -import com.android.server.connectivity.MockableSystemProperties; -import java.net.InetAddress; -import java.util.Arrays; - -import org.junit.runner.RunWith; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.net.InetAddress; +import java.util.Arrays; + /** * Tests for {@link DnsManager}. * @@ -133,7 +130,7 @@ public class DnsManagerTest { PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "strictmode.com"); mDnsManager.updatePrivateDns(new Network(TEST_NETID), - new DnsManager.PrivateDnsConfig("strictmode.com", new InetAddress[] { + new PrivateDnsConfig("strictmode.com", new InetAddress[] { InetAddress.parseNumericAddress("6.6.6.6"), InetAddress.parseNumericAddress("2001:db8:66:66::1") })); diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 354cf2f2239e..4c52d818269d 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -23,10 +23,10 @@ import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.reset; import android.app.PendingIntent; import android.content.Context; @@ -36,18 +36,18 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMisc; -import android.support.test.runner.AndroidJUnit4; +import android.net.NetworkStack; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import android.text.format.DateUtils; import com.android.internal.R; import com.android.server.ConnectivityService; -import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; -import org.junit.runner.RunWith; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -70,13 +70,16 @@ public class LingerMonitorTest { @Mock NetworkMisc mMisc; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; + @Mock NetworkStack mNetworkStack; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mCtx.getResources()).thenReturn(mResources); when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity"); - when(mConnService.createNetworkMonitor(any(), any(), any(), any())).thenReturn(null); + when(mCtx.getSystemServiceName(NetworkStack.class)) + .thenReturn(Context.NETWORK_STACK_SERVICE); + when(mCtx.getSystemService(Context.NETWORK_STACK_SERVICE)).thenReturn(mNetworkStack); mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT); } @@ -349,7 +352,7 @@ public class LingerMonitorTest { caps.addCapability(0); caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, - caps, 50, mCtx, null, mMisc, null, mConnService); + caps, 50, mCtx, null, mMisc, mConnService); nai.everValidated = true; return nai; } diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java new file mode 100644 index 000000000000..859a54d29321 --- /dev/null +++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net.ipmemorystore; + +import android.content.Context; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link IpMemoryStoreServiceTest}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class IpMemoryStoreServiceTest { + @Mock + Context mMockContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNetworkAttributes() { + final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext); + // TODO : implement this + } + + @Test + public void testPrivateData() { + final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext); + // TODO : implement this + } + + @Test + public void testFindL2Key() { + final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext); + // TODO : implement this + } + + @Test + public void testIsSameNetwork() { + final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext); + // TODO : implement this + } +} diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index c42a8889e373..0580df60f32a 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -58,6 +58,7 @@ cc_defaults { "libprotobuf-cpp-lite", "libz", ], + stl: "libc++_static", group_static_libs: true, } diff --git a/tools/bit/command.h b/tools/bit/command.h index fb44900b0806..dd7103e10fe7 100644 --- a/tools/bit/command.h +++ b/tools/bit/command.h @@ -25,7 +25,7 @@ using namespace std; struct Command { - Command(const string& prog); + explicit Command(const string& prog); ~Command(); void AddArg(const string& arg); diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index 9968bda6458f..4491a8567441 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -67,7 +67,7 @@ cpp_type_name(java_type_t type) case JAVA_TYPE_STRING: return "char const*"; case JAVA_TYPE_BYTE_ARRAY: - return "char const*"; + return "const BytesField&"; default: return "UNKNOWN"; } @@ -270,10 +270,6 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, chainField.name.c_str(), chainField.name.c_str()); } } - } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { - fprintf(out, ", %s arg%d, size_t arg%d_length", - cpp_type_name(*arg), argIndex, argIndex); - } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { fprintf(out, ", const std::map<int, int32_t>& arg%d_1, " "const std::map<int, int64_t>& arg%d_2, " @@ -355,7 +351,8 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, " event.end();\n\n"); } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { fprintf(out, - " event.AppendCharArray(arg%d, arg%d_length);\n", + " event.AppendCharArray(arg%d.arg, " + "arg%d.arg_length);\n", argIndex, argIndex); } else { if (*arg == JAVA_TYPE_STRING) { @@ -397,10 +394,6 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, chainField.name.c_str(), chainField.name.c_str()); } } - } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { - fprintf(out, ", %s arg%d, size_t arg%d_length", - cpp_type_name(*arg), argIndex, argIndex); - } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { fprintf(out, ", const std::map<int, int32_t>& arg%d_1, " @@ -434,8 +427,6 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, chainField.name.c_str(), chainField.name.c_str()); } } - } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { - fprintf(out, ", arg%d, arg%d_length", argIndex, argIndex); } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { fprintf(out, ", arg%d_1, arg%d_2, arg%d_3, arg%d_4", argIndex, argIndex, argIndex, argIndex); @@ -494,7 +485,14 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, " arg%d = \"\";\n", argIndex); fprintf(out, " }\n"); } - fprintf(out, " event << arg%d;\n", argIndex); + if (*arg == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, + " event.AppendCharArray(arg%d.arg, " + "arg%d.arg_length);", + argIndex, argIndex); + } else { + fprintf(out, " event << arg%d;\n", argIndex); + } if (argIndex == 2) { fprintf(out, " event.end();\n\n"); fprintf(out, " event.end();\n\n"); @@ -577,7 +575,9 @@ void build_non_chained_decl_map(const Atoms& atoms, static void write_cpp_usage( FILE* out, const string& method_name, const string& atom_code_name, const AtomDecl& atom, const AtomDecl &attributionDecl) { - fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str()); + fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), + atom_code_name.c_str()); + for (vector<AtomField>::const_iterator field = atom.fields.begin(); field != atom.fields.end(); field++) { if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { @@ -601,11 +601,6 @@ static void write_cpp_usage( field->name.c_str(), field->name.c_str(), field->name.c_str()); - } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) { - fprintf(out, ", %s %s, size_t %s_length", - cpp_type_name(field->javaType), field->name.c_str(), - field->name.c_str()); - } else { fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); } @@ -639,9 +634,6 @@ static void write_cpp_method_header( "const std::map<int, char const*>& arg%d_3, " "const std::map<int, float>& arg%d_4", argIndex, argIndex, argIndex, argIndex); - } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { - fprintf(out, ", %s arg%d, size_t arg%d_length", - cpp_type_name(*arg), argIndex, argIndex); } else { fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); } @@ -708,6 +700,15 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, "};\n"); fprintf(out, "\n"); + fprintf(out, "struct BytesField {\n"); + fprintf(out, + " BytesField(char const* array, size_t len) : arg(array), " + "arg_length(len) {}\n"); + fprintf(out, " char const* arg;\n"); + fprintf(out, " size_t arg_length;\n"); + fprintf(out, "};\n"); + fprintf(out, "\n"); + fprintf(out, "struct StateAtomFieldOptions {\n"); fprintf(out, " std::vector<int> primaryFields;\n"); fprintf(out, " int exclusiveField;\n"); @@ -1183,6 +1184,11 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp fprintf(out, " str%d = NULL;\n", argIndex); fprintf(out, " }\n"); + fprintf(out, + " android::util::BytesField bytesField%d(str%d, " + "str%d_length);", + argIndex, argIndex, argIndex); + } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { hadStringOrChain = true; for (auto chainField : attributionDecl.fields) { @@ -1240,7 +1246,8 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp // stats_write call argIndex = 1; - fprintf(out, "\n int ret = android::util::%s(code", cpp_method_name.c_str()); + fprintf(out, "\n int ret = android::util::%s(code", + cpp_method_name.c_str()); for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { @@ -1255,16 +1262,12 @@ write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp } } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { fprintf(out, ", int32_t_map, int64_t_map, string_map, float_map"); + } else if (*arg == JAVA_TYPE_BYTE_ARRAY) { + fprintf(out, ", bytesField%d", argIndex); } else { - const char* argName = (*arg == JAVA_TYPE_STRING || - *arg == JAVA_TYPE_BYTE_ARRAY) - ? "str" - : "arg"; + const char* argName = + (*arg == JAVA_TYPE_STRING) ? "str" : "arg"; fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex); - - if (*arg == JAVA_TYPE_BYTE_ARRAY) { - fprintf(out, ", %s%d_length", argName, argIndex); - } } argIndex++; } diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h index f14bbfd55b5f..bddd9819e8f5 100644 --- a/tools/streaming_proto/Errors.h +++ b/tools/streaming_proto/Errors.h @@ -11,7 +11,7 @@ using namespace std; struct Error { Error(); - explicit Error(const Error& that); + Error(const Error& that); Error(const string& filename, int lineno, const char* message); string filename; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index b265c40e628d..517bf3b47a7a 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -189,6 +189,7 @@ public class WifiManager { */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5; + /** @hide */ @IntDef(prefix = { "STATUS_NETWORK_SUGGESTIONS_" }, value = { STATUS_NETWORK_SUGGESTIONS_SUCCESS, STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL, @@ -234,6 +235,12 @@ public class WifiManager { @SystemApi public static final int WIFI_CREDENTIAL_FORGOT = 1; + /** @hide */ + public static final int PASSPOINT_HOME_NETWORK = 0; + + /** @hide */ + public static final int PASSPOINT_ROAMING_NETWORK = 1; + /** * Broadcast intent action indicating that a Passpoint provider icon has been received. * @@ -1207,26 +1214,30 @@ public class WifiManager { * match the ScanResult. * * @param scanResults a list of scanResult that represents the BSSID - * @return List that consists of {@link WifiConfiguration} and corresponding scanResults. + * @return List that consists of {@link WifiConfiguration} and corresponding scanResults per + * network type({@link #PASSPOINT_HOME_NETWORK} and {@link #PASSPOINT_ROAMING_NETWORK}). * @throws UnsupportedOperationException if Passpoint is not enabled on the device. * @hide */ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) - public List<Pair<WifiConfiguration, List<ScanResult>>> getAllMatchingWifiConfigs( + public List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> getAllMatchingWifiConfigs( @NonNull List<ScanResult> scanResults) { - List<Pair<WifiConfiguration, List<ScanResult>>> configs = new ArrayList<>(); + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>(); try { - Map<String, List<ScanResult>> results = mService.getAllMatchingFqdnsForScanResults( - scanResults); + Map<String, Map<Integer, List<ScanResult>>> results = + mService.getAllMatchingFqdnsForScanResults( + scanResults); if (results.isEmpty()) { return configs; } List<WifiConfiguration> wifiConfigurations = - mService.getWifiConfigsForPasspointProfiles(new ArrayList<>(results.keySet())); + mService.getWifiConfigsForPasspointProfiles( + new ArrayList<>(results.keySet())); for (WifiConfiguration configuration : wifiConfigurations) { - List<ScanResult> scanResultList = results.get(configuration.FQDN); - if (scanResultList != null) { - configs.add(Pair.create(configuration, scanResultList)); + Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get( + configuration.FQDN); + if (scanResultsPerNetworkType != null) { + configs.add(Pair.create(configuration, scanResultsPerNetworkType)); } } } catch (RemoteException e) { @@ -1642,7 +1653,7 @@ public class WifiManager { * suggestion back using this API.</li> * * @param networkSuggestions List of network suggestions provided by the app. - * @return Status code corresponding to the values in {@link NetworkSuggestionsStatusCode}. + * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values. * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app. * @throws {@link SecurityException} if the caller is missing required permissions. */ @@ -1663,8 +1674,7 @@ public class WifiManager { * * @param networkSuggestions List of network suggestions to be removed. Pass an empty list * to remove all the previous suggestions provided by the app. - * @return Status code corresponding to the values in - * {@link NetworkSuggestionsStatusCode}. + * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values. * Any matching suggestions are removed from the device and will not be considered for any * further connection attempts. */ diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index 4e29dd1132f6..2c96c057e37d 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -89,7 +89,7 @@ public class BaseWifiService extends IWifiManager.Stub { } @Override - public Map<String, List<ScanResult>> getAllMatchingFqdnsForScanResults( + public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults( List<ScanResult> scanResults) { throw new UnsupportedOperationException(); } |