Merge "Move global grayscale to ColorDisplayService"
diff --git a/Android.bp b/Android.bp
index f6d49e9..ea6a631 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,6 +104,11 @@
"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 @@
"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 @@
"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",
@@ -731,6 +738,7 @@
"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",
@@ -739,11 +747,13 @@
"android.hardware.vibrator-V1.0-java",
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
+ "android.hardware.vibrator-V1.3-java",
"android.hardware.wifi-V1.0-java-constants",
"android.hardware.radio-V1.0-java",
"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",
],
@@ -871,11 +881,16 @@
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 7e0c33c..0ff1743 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26076,6 +26076,27 @@
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 +27142,21 @@
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 +27391,36 @@
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 +29748,6 @@
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 +34437,8 @@
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 +34480,7 @@
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 +34526,7 @@
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 +34534,10 @@
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 +42729,7 @@
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 +42766,9 @@
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 +44924,7 @@
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 +44978,7 @@
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 +49191,7 @@
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 +49343,9 @@
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 +52396,8 @@
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 bf367cb..b190e9f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -86,11 +86,13 @@
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 @@
}
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 @@
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 @@
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();
@@ -537,6 +560,13 @@
method public void onVrStateChanged(boolean);
}
+ public final class WallpaperColors implements android.os.Parcelable {
+ ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int);
+ method public int getColorHints();
+ field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1
+ field public static final int HINT_SUPPORTS_DARK_THEME = 2; // 0x2
+ }
+
public final class WallpaperInfo implements android.os.Parcelable {
method public boolean supportsAmbientMode();
}
@@ -841,6 +871,73 @@
}
+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 {
@@ -849,6 +946,87 @@
}
+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 {
@@ -1035,7 +1213,9 @@
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";
@@ -1121,6 +1301,7 @@
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;
@@ -2951,7 +3132,7 @@
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);
}
@@ -3211,6 +3392,22 @@
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);
@@ -5143,6 +5340,24 @@
}
+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 {
@@ -5253,6 +5468,7 @@
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);
@@ -5270,6 +5486,19 @@
}
+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 {
@@ -6380,6 +6609,7 @@
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 {
@@ -6389,6 +6619,11 @@
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;
@@ -6850,6 +7085,7 @@
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();
@@ -6864,6 +7100,7 @@
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 ae3c1e0..575875d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -89,18 +89,26 @@
}
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 @@
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 @@
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 4918747..4f88127 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -39,6 +39,17 @@
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 @@
}
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 @@
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 @@
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 560ccb6..ad9830b 100755
--- a/cmds/idmap2/static-checks.sh
+++ b/cmds/idmap2/static-checks.sh
@@ -70,7 +70,15 @@
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 6788f7d..d160b73 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 @@
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 @@
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 @@
}
@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 @@
@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 @@
} finally {
cbThread.getLooper().quit();
try {
- mController.unregisterCallbackListener(this);
+ mController.unregisterCallbackListener(mControllerCallbackLink);
} catch (Exception e) {
// ignoring
}
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index e0e1b72..ed22df5 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -490,7 +490,6 @@
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 fdb71bb..c89848e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -121,6 +121,7 @@
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 @@
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 8faf08a..7767f04 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -271,6 +271,13 @@
@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 @@
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 @@
}
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 @@
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 @@
}
}
+ @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 @@
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 9329fbc..2240302 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -19,5 +19,7 @@
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 78fe002..e155fe2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -36,26 +36,33 @@
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 @@
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 @@
"foreground", // MODE_FOREGROUND
};
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "UID_STATE_" }, value = {
@@ -173,9 +224,16 @@
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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* Metrics about an op when its uid is cached.
* @hide
*/
+ @TestApi
@SystemApi
public static final int UID_STATE_CACHED = 5;
@@ -237,7 +300,7 @@
@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 @@
}
}
+ /** @hide */
+ public static final String KEY_HISTORICAL_OPS = "historical_ops";
+
/**
* Retrieve the op switch that controls the given operation.
* @hide
@@ -1731,7 +1797,7 @@
* 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 @@
* 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 @@
@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 @@
/**
* @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 @@
/**
* @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 @@
/**
* @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 @@
/**
* Return this entry's current mode, such as {@link #MODE_ALLOWED}.
*/
- public int getMode() {
+ public @Mode int getMode() {
return mMode;
}
@@ -2086,41 +2152,774 @@
};
}
+ /** @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 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 @NonNull HistoricalOps spliceFromBeginning(double splicePoint) {
+ return splice(splicePoint, true);
+ }
+
+ /**
+ * 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
+ */
+ 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;
+ }
+
+ /**
+ * 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.
+ *
+ * @hide
+ */
+ 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;
+ }
+
+ /**
+ * @return Rounded value up at the 0.5 boundary.
+ *
+ * @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 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 historical package ops at the given index.
+ *
+ * @see #getPackageCount()
+ */
+ public @NonNull HistoricalPackageOps getPackageOpsAt(int index) {
+ if (mHistoricalPackageOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalPackageOps.valueAt(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 int mUid;
private final @NonNull String mPackageName;
- private final @NonNull List<HistoricalOpEntry> mEntries;
+ private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
- /**
- * @hide
- */
- public HistoricalPackageOps(int uid, @NonNull String packageName) {
- mUid = uid;
+ /** @hide */
+ public HistoricalPackageOps(@NonNull String packageName) {
mPackageName = packageName;
- mEntries = new ArrayList<>();
}
- HistoricalPackageOps(@NonNull Parcel parcel) {
- mUid = parcel.readInt();
+ 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();
- mEntries = parcel.createTypedArrayList(HistoricalOpEntry.CREATOR);
+ mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR);
}
- /**
- * @hide
- */
- public void addEntry(@NonNull HistoricalOpEntry entry) {
- mEntries.add(entry);
+ 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);
}
/**
@@ -2133,36 +2932,33 @@
}
/**
- * Gets the UID which the data represents.
+ * Gets number historical app ops.
*
- * @return The UID which the data represents.
+ * @return The number historical app ops.
+ *
+ * @see #getOpAt(int)
*/
- public int getUid() {
- return mUid;
+ public int getOpCount() {
+ if (mHistoricalOps == null) {
+ return 0;
+ }
+ return mHistoricalOps.size();
}
/**
- * Gets number historical app op entries.
- *
- * @return The number historical app op entries.
- *
- * @see #getEntryAt(int)
- */
- public int getEntryCount() {
- return mEntries.size();
- }
-
- /**
- * Gets the historical at a given index.
+ * 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 @@
*
* @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 @@
@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 @@
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 @@
*
* @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 @@
* @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 @@
* @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 @@
* @see #getBackgroundAccessCount()
*/
public long getAccessCount(@UidState int uidState) {
+ if (mAccessCount == null) {
+ return 0;
+ }
return mAccessCount[uidState];
}
@@ -2311,6 +3265,9 @@
* @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 @@
* @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 @@
* @see #getBackgroundRejectCount()
*/
public long getRejectCount(@UidState int uidState) {
+ if (mRejectCount == null) {
+ return 0;
+ }
return mRejectCount[uidState];
}
@@ -2352,6 +3315,9 @@
* @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 @@
* @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 @@
* @see #getBackgroundAccessDuration()
*/
public long getAccessDuration(@UidState int uidState) {
+ if (mAccessDuration == null) {
+ return 0;
+ }
return mAccessDuration[uidState];
}
@@ -2397,34 +3369,29 @@
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;
- }
- if (!Arrays.equals(mRejectCount, otherInstance.mRejectCount)) {
- return false;
- }
- return Arrays.equals(mAccessDuration, otherInstance.mAccessDuration);
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalOp(this);
}
- @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[] getOrCreateAccessCount() {
+ if (mAccessCount == null) {
+ mAccessCount = new long[_NUM_UID_STATE];
+ }
+ return mAccessCount;
+ }
+
+ private @NonNull long[] getOrCreateRejectCount() {
+ if (mRejectCount == null) {
+ mRejectCount = new long[_NUM_UID_STATE];
+ }
+ return mRejectCount;
+ }
+
+ private @NonNull long[] getOrCreateAccessDuration() {
+ if (mAccessDuration == null) {
+ mAccessDuration = new long[_NUM_UID_STATE];
+ }
+ return mAccessDuration;
}
/**
@@ -2444,17 +3411,62 @@
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 @@
* @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 @@
* @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 @@
}
/**
- * 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 @@
* 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 @@
@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 @@
* @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 @@
@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 @@
/** @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 @@
*/
@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 @@
*/
@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 @@
}
/**
+ * 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 @@
}
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 b5295d1..07dbb6b 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -124,6 +124,10 @@
/** 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 9ddf4bd..d827f6c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -21,8 +21,11 @@
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.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.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.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 @@
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 @@
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 @@
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/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index ace814a..38a98d3c8 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -56,6 +56,7 @@
* eg. A launcher may set its text color to black if this flag is specified.
* @hide
*/
+ @SystemApi
public static final int HINT_SUPPORTS_DARK_TEXT = 1 << 0;
/**
@@ -64,6 +65,7 @@
* eg. A launcher may set its drawer color to black if this flag is specified.
* @hide
*/
+ @SystemApi
public static final int HINT_SUPPORTS_DARK_THEME = 1 << 1;
/**
@@ -234,7 +236,7 @@
* @see WallpaperColors#fromDrawable(Drawable)
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor,
@Nullable Color tertiaryColor, int colorHints) {
@@ -349,7 +351,7 @@
* @return True if dark text is supported.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public int getColorHints() {
return mColorHints;
}
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 2990b57..e0a15a5 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -19,6 +19,7 @@
import static android.app.ActivityThread.isSystem;
import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
import static android.app.WindowConfigurationProto.APP_BOUNDS;
+import static android.app.WindowConfigurationProto.BOUNDS;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.view.Surface.rotationToString;
@@ -585,6 +586,9 @@
}
protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
+ if (mBounds != null) {
+ mBounds.writeToProto(protoOutputStream, BOUNDS);
+ }
protoOutputStream.end(token);
}
@@ -606,6 +610,10 @@
mAppBounds = new Rect();
mAppBounds.readFromProto(proto, APP_BOUNDS);
break;
+ case (int) BOUNDS:
+ mBounds = new Rect();
+ mBounds.readFromProto(proto, BOUNDS);
+ break;
case (int) WINDOWING_MODE:
mWindowingMode = proto.readInt(WINDOWING_MODE);
break;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7da67d9..9247486 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6815,8 +6815,12 @@
}
/**
- * 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 cc4d4b1a..7d03f00 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1699,7 +1699,8 @@
@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 0000000..a66413b
--- /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 0000000..3f71518
--- /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 0000000..f67b2c8
--- /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 0000000..f18520f
--- /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 0000000..e626d69
--- /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 0000000..a8917f7
--- /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 0000000..b4d8977
--- /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 0000000..69f5d55
--- /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 0000000..24f5ad8
--- /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 0000000..1952583
--- /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 0000000..f5ce7c3
--- /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 0000000..16f3e6b
--- /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 0000000..5767bf4
--- /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 0000000..87ccb66
--- /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 0000000..f8578d4
--- /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 0000000..e829526
--- /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 0000000..1d7308e
--- /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 0000000..2ddbd08
--- /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 0000000..e4e2bc2
--- /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 0000000..99c1c44
--- /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 0000000..ebed2da
--- /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 0000000..18317e1
--- /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 0000000..bf69eea
--- /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 0000000..0b8fb47
--- /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/core/java/android/app/prediction/IPredictionCallback.aidl b/core/java/android/app/prediction/IPredictionCallback.aidl
new file mode 100644
index 0000000..f6f241e
--- /dev/null
+++ b/core/java/android/app/prediction/IPredictionCallback.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.app.prediction;
+
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+oneway interface IPredictionCallback {
+
+ 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 0000000..114a1ff
--- /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/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 2d630a6..d73e73f 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -67,6 +67,56 @@
private static final String LOG_TAG = RoleManager.class.getSimpleName();
/**
+ * The name of the proxy calling role.
+ * <p>
+ * A proxy calling app implements the {@link android.telecom.CallRedirectionService} API and
+ * provides a means to re-write the phone number for an outgoing call to place the call through
+ * a proxy calling service.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @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.
+ * <p>
+ * A call screening and caller id app implements the
+ * {@link android.telecom.CallScreeningService} API.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @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. A call companion app implements the
+ * {@link android.telecom.InCallService} API.
+ * <p>
+ * Multiple apps app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
+
+ /**
+ * The name of the car mode dialer app role.
+ * <p>
+ * Similar to the {@link #ROLE_DIALER} role, this role determines which app is responsible for
+ * showing the user interface for ongoing calls on the device. This app filling this role is
+ * only used when the device is in car mode (see
+ * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information). An app
+ * filling this role must implement the {@link android.telecom.InCallService} API.
+ * <p>
+ * A single app may fill this role at any one time.
+ * @hide
+ */
+ public static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP";
+
+ /**
* The name of the dialer role.
*/
public static final String ROLE_DIALER = "android.app.role.DIALER";
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index a2b7d58..8ee9e53 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -36,6 +36,11 @@
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 eb7be6f..cefc700 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -337,6 +337,15 @@
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 @@
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 @@
/**
* 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 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 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/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2978058..576466f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1866,7 +1866,15 @@
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 2b266b7..c984651 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1621,7 +1621,7 @@
}
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 @@
}
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 @@
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 @@
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 @@
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 @@
} else if (tagName.equals(TAG_USES_SDK)) {
if (SDK_VERSION > 0) {
- sa = res.obtainAttributes(parser,
- com.android.internal.R.styleable.AndroidManifestUsesSdk);
+ 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);
+ }
- int minVers = 1;
- String minCode = null;
- int targetVers = 0;
- String targetCode = null;
+ @Override public int getMinSdkVersion() {
+ return getAttributeAsInt(
+ R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+ }
- 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;
- }
+ @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;
}
- 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;
- }
+ pkg.applicationInfo.minSdkVersion = versions.first;
+ pkg.applicationInfo.targetSdkVersion = versions.second;
sa.recycle();
-
- final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode,
- SDK_VERSION, SDK_CODENAMES, outError);
- if (minSdkVersion < 0) {
- 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 = minSdkVersion;
- pkg.applicationInfo.targetSdkVersion = targetSdkVersion;
}
-
XmlUtils.skipCurrentTag(parser);
-
} else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestSupportsScreens);
@@ -2675,6 +2714,67 @@
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/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index a8c3b88..6e519c1 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -475,6 +475,14 @@
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 b238d77..f652f85 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -73,6 +73,10 @@
* @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 @@
}
/**
+ * 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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 436b4a1..abc00fe 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2051,6 +2051,16 @@
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 e7d441d..da5d96e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -167,6 +167,8 @@
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 0000000..6f88dec
--- /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 0000000..41f969a
--- /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 0000000..0bc2575
--- /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 be0dc07..2df8ab7 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 @@
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 0000000..b35f097
--- /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/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 3a79206..4a466f3 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -55,6 +55,8 @@
private String mIfaceName;
private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
private ArrayList<InetAddress> mDnses = new ArrayList<>();
+ // PCSCF addresses are addresses of SIP proxies that only exist for the IMS core service.
+ private ArrayList<InetAddress> mPcscfs = new ArrayList<InetAddress>();
private ArrayList<InetAddress> mValidatedPrivateDnses = new ArrayList<>();
private boolean mUsePrivateDns;
private String mPrivateDnsServerName;
@@ -179,6 +181,7 @@
mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
mUsePrivateDns = source.mUsePrivateDns;
mPrivateDnsServerName = source.mPrivateDnsServerName;
+ mPcscfs.addAll(source.mPcscfs);
mDomains = source.mDomains;
mRoutes.addAll(source.mRoutes);
mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
@@ -525,6 +528,60 @@
}
/**
+ * Adds the given {@link InetAddress} to the list of PCSCF servers, if not present.
+ *
+ * @param pcscfServer The {@link InetAddress} to add to the list of PCSCF servers.
+ * @return true if the PCSCF server was added, false otherwise.
+ * @hide
+ */
+ public boolean addPcscfServer(InetAddress pcscfServer) {
+ if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) {
+ mPcscfs.add(pcscfServer);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes the given {@link InetAddress} from the list of PCSCF servers.
+ *
+ * @param pcscf Server The {@link InetAddress} to remove from the list of PCSCF servers.
+ * @return true if the PCSCF server was removed, false otherwise.
+ * @hide
+ */
+ public boolean removePcscfServer(InetAddress pcscfServer) {
+ if (pcscfServer != null) {
+ return mPcscfs.remove(pcscfServer);
+ }
+ return false;
+ }
+
+ /**
+ * Replaces the PCSCF servers in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link InetAddress} objects.
+ *
+ * @param addresses The {@link Collection} of PCSCF servers to set in this object.
+ * @hide
+ */
+ public void setPcscfServers(Collection<InetAddress> pcscfServers) {
+ mPcscfs.clear();
+ for (InetAddress pcscfServer: pcscfServers) {
+ addPcscfServer(pcscfServer);
+ }
+ }
+
+ /**
+ * Returns all the {@link InetAddress} for PCSCF servers on this link.
+ *
+ * @return An unmodifiable {@link List} of {@link InetAddress} for PCSCF servers on
+ * this link.
+ * @hide
+ */
+ public List<InetAddress> getPcscfServers() {
+ return Collections.unmodifiableList(mPcscfs);
+ }
+
+ /**
* Sets the DNS domain search path used on this link.
*
* @param domains A {@link String} listing in priority order the comma separated
@@ -767,6 +824,7 @@
mDnses.clear();
mUsePrivateDns = false;
mPrivateDnsServerName = null;
+ mPcscfs.clear();
mDomains = null;
mRoutes.clear();
mHttpProxy = null;
@@ -813,6 +871,12 @@
resultJoiner.add(mPrivateDnsServerName);
}
+ if (!mPcscfs.isEmpty()) {
+ resultJoiner.add("PcscfAddresses: [");
+ resultJoiner.add(TextUtils.join(",", mPcscfs));
+ resultJoiner.add("]");
+ }
+
if (!mValidatedPrivateDnses.isEmpty()) {
final StringJoiner validatedPrivateDnsesJoiner =
new StringJoiner(",", "ValidatedPrivateDnsAddresses: [", "]");
@@ -965,6 +1029,36 @@
}
/**
+ * Returns true if this link has an IPv4 PCSCF server.
+ *
+ * @return {@code true} if there is an IPv4 PCSCF server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv4PcscfServer() {
+ for (InetAddress ia : mPcscfs) {
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link has an IPv6 PCSCF server.
+ *
+ * @return {@code true} if there is an IPv6 PCSCF server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv6PcscfServer() {
+ for (InetAddress ia : mPcscfs) {
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns true if this link is provisioned for global IPv4 connectivity.
* This requires an IP address, default route, and DNS server.
*
@@ -1117,6 +1211,19 @@
}
/**
+ * Compares this {@code LinkProperties} PCSCF addresses against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalPcscfs(LinkProperties target) {
+ Collection<InetAddress> targetPcscfs = target.getPcscfServers();
+ return (mPcscfs.size() == targetPcscfs.size()) ?
+ mPcscfs.containsAll(targetPcscfs) : false;
+ }
+
+ /**
* Compares this {@code LinkProperties} Routes against the target
*
* @param target LinkProperties to compare.
@@ -1218,6 +1325,7 @@
&& isIdenticalDnses(target)
&& isIdenticalPrivateDns(target)
&& isIdenticalValidatedPrivateDnses(target)
+ && isIdenticalPcscfs(target)
&& isIdenticalRoutes(target)
&& isIdenticalHttpProxy(target)
&& isIdenticalStackedLinks(target)
@@ -1334,6 +1442,7 @@
+ mMtu * 51
+ ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode())
+ (mUsePrivateDns ? 57 : 0)
+ + mPcscfs.size() * 67
+ ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode());
}
@@ -1357,6 +1466,10 @@
}
dest.writeBoolean(mUsePrivateDns);
dest.writeString(mPrivateDnsServerName);
+ dest.writeInt(mPcscfs.size());
+ for (InetAddress d : mPcscfs) {
+ dest.writeByteArray(d.getAddress());
+ }
dest.writeString(mDomains);
dest.writeInt(mMtu);
dest.writeString(mTcpBufferSizes);
@@ -1406,6 +1519,12 @@
}
netProp.setUsePrivateDns(in.readBoolean());
netProp.setPrivateDnsServerName(in.readString());
+ addressCount = in.readInt();
+ for (int i = 0; i < addressCount; i++) {
+ try {
+ netProp.addPcscfServer(InetAddress.getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) { }
+ }
netProp.setDomains(in.readString());
netProp.setMtu(in.readInt());
netProp.setTcpBufferSizes(in.readString());
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index d4a0ec63..2eac6de 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -48,14 +48,16 @@
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 @@
});
}
+ /**
+ * 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 @@
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 @@
"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 @@
}
// 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 0000000..b52fce6
--- /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 0000000..9dbef11
--- /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 0000000..4926feb
--- /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 0000000..dea0cc4
--- /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 0000000..57f59a1
--- /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 0000000..294bd3b
--- /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 0000000..5d07504
--- /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 0000000..d7e5b27
--- /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 0000000..0894d72
--- /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 0000000..0cb37e9
--- /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 0000000..7196699
--- /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 0000000..5b016ec
--- /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 0000000..fb36ef4
--- /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 0000000..1343d24
--- /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 0000000..4e696ae
--- /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 cc6bb12..b9cdcc0 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -899,6 +899,33 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 c3e0489..0942d97 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1326,7 +1326,7 @@
* @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 de1128df..4918ad1 100644
--- a/core/java/android/os/PowerSaveState.java
+++ b/core/java/android/os/PowerSaveState.java
@@ -27,7 +27,7 @@
/**
* 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 9801406..68d6d85 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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 b5aeba0..226d1d0 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -74,10 +74,13 @@
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 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 @@
*/
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 @@
}
/**
- * 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 16f9a43..4649776 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -86,7 +86,7 @@
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 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 @@
ACCELEROMETER_ROTATION,
SHOW_BATTERY_PERCENT,
NOTIFICATION_VIBRATION_INTENSITY,
+ RING_VIBRATION_INTENSITY,
HAPTIC_FEEDBACK_INTENSITY,
DISPLAY_COLOR_MODE,
ALARM_ALERT,
@@ -4397,6 +4414,7 @@
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 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 @@
/** @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 @@
/** @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 @@
* 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 @@
* 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 @@
/**
* 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 @@
* 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 @@
/**
* 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 @@
"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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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;
- }
- }
- }
}
/**
@@ -11272,14 +11218,14 @@
* 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
*/
@@ -13835,6 +13781,7 @@
* enabled (boolean)
* requires_targeting_p (boolean)
* max_squeeze_remeasure_attempts (int)
+ * edit_choices_before_sending (boolean)
* </pre>
* @see com.android.systemui.statusbar.policy.SmartReplyConstants
* @hide
@@ -14019,6 +13966,53 @@
*/
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 0000000..b77405a
--- /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 0000000..3a6d166
--- /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 26bf361..e5e028d 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.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 final void setContentCaptureWhitelist(@Nullable List<String> packages,
@Nullable List<ComponentName> activities) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -176,7 +177,7 @@
*/
public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
boolean enabled) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -187,7 +188,7 @@
*/
public final void setPackageContentCaptureEnabled(@NonNull String packageName,
boolean enabled) {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
}
/**
@@ -196,7 +197,7 @@
*/
@NonNull
public final Set<ComponentName> getContentCaptureDisabledActivities() {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
return null;
}
@@ -206,7 +207,7 @@
*/
@NonNull
public final Set<String> getContentCaptureDisabledPackages() {
- //TODO(b/111276913): implement
+ //TODO(b/122595322): implement
return null;
}
@@ -255,6 +256,16 @@
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 @@
@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 @@
}
/**
- * 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 @@
} 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 0000000..0da8039
--- /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 0000000..1926478
--- /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 433483f7..7a58681 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 @@
/**
* @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/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 8b97e0e..0edcb3d 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -22,7 +22,9 @@
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 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 @@
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 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 a86abe5..9d3552f 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -18,6 +18,9 @@
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 dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import java.lang.annotation.Retention;
import java.util.Objects;
/**
@@ -462,7 +466,7 @@
/**
* 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 @@
};
/**
+ * 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 @@
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 @@
@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 @@
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 @@
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 @@
}
/**
+ * 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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
}
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 @@
}
/**
+ * 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/View.java b/core/java/android/view/View.java
index cb2c40e..cd0e579 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8199,6 +8199,19 @@
* <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 @@
} 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 95a346f..c630177 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -794,7 +794,7 @@
* @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 e962e7c..ff45efd 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.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 @@
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 @@
/** @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 @@
*/
@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 @@
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 344b9973..6890beaf 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -46,8 +46,7 @@
/**
* 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 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 @@
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 @@
}
/**
+ * 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 @@
* @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 @@
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 01776f8..be9c00f 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -32,7 +32,28 @@
* @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 8be887b..a29aaf0 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -157,7 +157,8 @@
*
* @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 @@
}
mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
- applicationToken, activityComponent));
+ applicationToken, activityComponent, flags));
}
@Override
@@ -181,7 +182,8 @@
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 @@
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 @@
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 86b89adb..b7a486a 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -24,6 +24,7 @@
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.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 @@
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 @@
}
}
- 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 @@
@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 @@
@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 @@
@Override
public void setLocaleList(LocaleList localeList) {
- // TODO(b/111276913): implement or move to superclass
+ mNode.mLocaleList = localeList;
}
@Override
@@ -402,5 +1001,66 @@
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 b233b75..57ed7f9 100644
--- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java
+++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
@@ -41,6 +41,17 @@
*/
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 @@
/**
* {@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 @@
/**
* {@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 4c6862c..5dc8b19 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -67,12 +67,6 @@
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/Magnifier.java b/core/java/android/widget/Magnifier.java
index 20febc2..afe46701 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -837,8 +837,8 @@
mContentWidth = width;
mContentHeight = height;
- mOffsetX = (int) (0.1f * width);
- mOffsetY = (int) (0.1f * height);
+ mOffsetX = (int) (1.05f * elevation);
+ mOffsetY = (int) (1.05f * elevation);
// Setup the surface we will use for drawing the content and shadow.
mSurfaceWidth = mContentWidth + 2 * mOffsetX;
mSurfaceHeight = mContentHeight + 2 * mOffsetY;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0ea8579..51b8734 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3520,7 +3520,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
- Preconditions.checkArgumentPositive(textSelectHandle,
+ Preconditions.checkArgument(textSelectHandle != 0,
"The text select handle should be a valid drawable resource id.");
setTextSelectHandle(mContext.getDrawable(textSelectHandle));
}
@@ -3577,7 +3577,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
- Preconditions.checkArgumentPositive(textSelectHandleLeft,
+ Preconditions.checkArgument(textSelectHandleLeft != 0,
"The text select left handle should be a valid drawable resource id.");
setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
}
@@ -3634,7 +3634,7 @@
*/
@android.view.RemotableViewMethod
public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
- Preconditions.checkArgumentPositive(textSelectHandleRight,
+ Preconditions.checkArgument(textSelectHandleRight != 0,
"The text select right handle should be a valid drawable resource id.");
setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
}
@@ -3667,9 +3667,7 @@
* @see #setTextCursorDrawable(int)
* @attr ref android.R.styleable#TextView_textCursorDrawable
*/
- public void setTextCursorDrawable(@NonNull Drawable textCursorDrawable) {
- Preconditions.checkNotNull(textCursorDrawable,
- "The cursor drawable should not be null.");
+ public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
mCursorDrawable = textCursorDrawable;
mCursorDrawableRes = 0;
if (mEditor != null) {
@@ -3687,8 +3685,6 @@
* @attr ref android.R.styleable#TextView_textCursorDrawable
*/
public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
- Preconditions.checkArgumentPositive(textCursorDrawable,
- "The cursor drawable should be a valid drawable resource id.");
setTextCursorDrawable(mContext.getDrawable(textCursorDrawable));
}
@@ -8061,6 +8057,26 @@
}
}
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) {
@@ -10831,25 +10847,6 @@
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 e59bee4..c4ab91f 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 @@
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 0000000..f24d12e
--- /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 100c6ee..adf7692 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 @@
* 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 @@
// 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 53b56f2..6a28059 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -153,13 +153,11 @@
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 9087dd2..197e873 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -97,13 +97,11 @@
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/jni/Android.bp b/core/jni/Android.bp
index dc6a73a..088e13f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -41,7 +41,7 @@
"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 @@
"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 687b105..1092222 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -218,6 +218,7 @@
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 @@
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_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index ecf8119..50cff5c 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -334,7 +334,7 @@
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 @@
}
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 @@
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 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 @@
{ "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 0000000..50b2288
--- /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/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto
index 2d15552..6cc1a40 100644
--- a/core/proto/android/app/window_configuration.proto
+++ b/core/proto/android/app/window_configuration.proto
@@ -29,4 +29,5 @@
optional .android.graphics.RectProto app_bounds = 1;
optional int32 windowing_mode = 2;
optional int32 activity_type = 3;
+ optional .android.graphics.RectProto bounds = 4;
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a00fde9..cc5aa20 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -1018,7 +1018,9 @@
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 449a7b3..8b85a28 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 1c98c66..1feb59a 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 01fbf80..010accf 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 041fb7e..74943c7 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -43,7 +43,8 @@
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 1750dac..0f83a29 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 @@
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 @@
@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 @@
});
}
+ @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 @@
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 @@
// 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 f863356..4e405ca 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.isStatic;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings.Global;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -550,7 +551,12 @@
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 0000000..59f3a4c
--- /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 0000000..995946b
--- /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/MultiDexLegacyTestAppTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
index f2bd353..2dc30ea 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
@@ -18,7 +18,7 @@
## 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 @@
## 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 4e6ec14..7812c58 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 9e41a92..b3044dc 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/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 04bf804..21531ab 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -99,6 +99,46 @@
<font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
</family>
+ <family name="arbutus-slab">
+ <font weight="400" style="normal">ArbutusSlab-Regular.ttf</font>
+ </family>
+
+ <family name="arvo">
+ <font weight="400" style="normal">Arvo-Regular.ttf</font>
+ <font weight="400" style="italic">Arvo-Italic.ttf</font>
+ <font weight="700" style="normal">Arvo-Bold.ttf</font>
+ <font weight="700" style="italic">Arvo-BoldItalic.ttf</font>
+ </family>
+ <alias name="arvo-bold" to="arvo" weight="700" />
+
+ <family name="lato">
+ <font weight="400" style="normal">Lato-Regular.ttf</font>
+ <font weight="400" style="italic">Lato-Italic.ttf</font>
+ <font weight="700" style="normal">Lato-Bold.ttf</font>
+ <font weight="700" style="italic">Lato-BoldItalic.ttf</font>
+ </family>
+ <alias name="lato-bold" to="lato" weight="700" />
+
+ <family name="rubik">
+ <font weight="400" style="normal">Rubik-Regular.ttf</font>
+ <font weight="400" style="italic">Rubik-Italic.ttf</font>
+ <font weight="500" style="normal">Rubik-Medium.ttf</font>
+ <font weight="500" style="italic">Rubik-MediumItalic.ttf</font>
+ <font weight="700" style="normal">Rubik-Bold.ttf</font>
+ <font weight="700" style="italic">Rubik-BoldItalic.ttf</font>
+ </family>
+ <alias name="rubik-medium" to="rubik" weight="500" />
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600" />
+
<!-- fallback fonts -->
<family lang="und-Arab" variant="elegant">
<font weight="400" style="normal">NotoNaskhArabic-Regular.ttf</font>
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index 76eab4a..fcebad3 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -831,6 +831,7 @@
* content://media/<table_name>/<row_index> (or)
* file://sdcard/test.mp4
* http://test.com/test.mp4
+ * https://test.com/test.mp4
*
* Here <table_name> shall be "video" or "audio" or "images"
* <row_index> the index of the content in given table
@@ -843,7 +844,7 @@
scheme.equals(ContentResolver.SCHEME_FILE)) {
path = uri.getPath();
- } else if (scheme.equals("http")) {
+ } else if (scheme.equals("http") || scheme.equals("https")) {
path = uri.toString();
} else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 9b86b77..25f6775 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1075,6 +1075,11 @@
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 b9860ad..635d0ec 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 @@
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 6298013..40f2b0c 100644
--- a/libs/hwui/HardwareBitmapUploader.h
+++ b/libs/hwui/HardwareBitmapUploader.h
@@ -23,6 +23,7 @@
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 019950f..b2351fc 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -53,16 +53,16 @@
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 56eedff..8cd97ed 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -93,7 +93,9 @@
, 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 524dfb0..174a140 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 @@
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 f9de8a5..8cdcc46 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 0a90f85..b4f19c9 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -39,7 +39,7 @@
virtual ~WeakLooperCallback() { }
public:
- WeakLooperCallback(const wp<LooperCallback>& callback) :
+ explicit WeakLooperCallback(const wp<LooperCallback>& callback) :
mCallback(callback) {
}
@@ -89,10 +89,6 @@
mLocked.animationPending = false;
- mLocked.displayWidth = -1;
- mLocked.displayHeight = -1;
- mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
-
mLocked.presentation = PRESENTATION_POINTER;
mLocked.presentationChanged = false;
@@ -110,15 +106,6 @@
mLocked.lastFrameUpdatedTime = 0;
mLocked.buttonState = 0;
-
- mPolicy->loadPointerIcon(&mLocked.pointerIcon);
-
- loadResources();
-
- if (mLocked.pointerIcon.isValid()) {
- mLocked.pointerIconChanged = true;
- updatePointerLocked();
- }
}
PointerController::~PointerController() {
@@ -144,23 +131,15 @@
bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
float* outMaxX, float* outMaxY) const {
- if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+
+ if (!mLocked.viewport.isValid()) {
return false;
}
- *outMinX = 0;
- *outMinY = 0;
- switch (mLocked.displayOrientation) {
- case DISPLAY_ORIENTATION_90:
- case DISPLAY_ORIENTATION_270:
- *outMaxX = mLocked.displayHeight - 1;
- *outMaxY = mLocked.displayWidth - 1;
- break;
- default:
- *outMaxX = mLocked.displayWidth - 1;
- *outMaxY = mLocked.displayHeight - 1;
- break;
- }
+ *outMinX = mLocked.viewport.logicalLeft;
+ *outMinY = mLocked.viewport.logicalTop;
+ *outMaxX = mLocked.viewport.logicalRight - 1;
+ *outMaxY = mLocked.viewport.logicalBottom - 1;
return true;
}
@@ -231,6 +210,12 @@
*outY = mLocked.pointerY;
}
+int32_t PointerController::getDisplayId() const {
+ AutoMutex _l(mLock);
+
+ return mLocked.viewport.displayId;
+}
+
void PointerController::fade(Transition transition) {
AutoMutex _l(mLock);
@@ -355,48 +340,56 @@
void PointerController::reloadPointerResources() {
AutoMutex _l(mLock);
- loadResources();
-
- if (mLocked.presentation == PRESENTATION_POINTER) {
- mLocked.additionalMouseResources.clear();
- mLocked.animationResources.clear();
- mPolicy->loadPointerIcon(&mLocked.pointerIcon);
- mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
- &mLocked.animationResources);
- }
-
- mLocked.presentationChanged = true;
+ loadResourcesLocked();
updatePointerLocked();
}
-void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
- AutoMutex _l(mLock);
+/**
+ * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation,
+ * so here we are getting the dimensions in the original, unrotated orientation (orientation 0).
+ */
+static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) {
+ width = viewport.deviceWidth;
+ height = viewport.deviceHeight;
- // Adjust to use the display's unrotated coordinate frame.
- if (orientation == DISPLAY_ORIENTATION_90
- || orientation == DISPLAY_ORIENTATION_270) {
- int32_t temp = height;
- height = width;
- width = temp;
+ if (viewport.orientation == DISPLAY_ORIENTATION_90
+ || viewport.orientation == DISPLAY_ORIENTATION_270) {
+ std::swap(width, height);
+ }
+}
+
+void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
+ AutoMutex _l(mLock);
+ if (viewport == mLocked.viewport) {
+ return;
}
- if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
- mLocked.displayWidth = width;
- mLocked.displayHeight = height;
+ const DisplayViewport oldViewport = mLocked.viewport;
+ mLocked.viewport = viewport;
+
+ int32_t oldDisplayWidth, oldDisplayHeight;
+ getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight);
+ int32_t newDisplayWidth, newDisplayHeight;
+ getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight);
+
+ // Reset cursor position to center if size or display changed.
+ if (oldViewport.displayId != viewport.displayId
+ || oldDisplayWidth != newDisplayWidth
+ || oldDisplayHeight != newDisplayHeight) {
float minX, minY, maxX, maxY;
if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
mLocked.pointerX = (minX + maxX) * 0.5f;
mLocked.pointerY = (minY + maxY) * 0.5f;
+ // Reload icon resources for density may be changed.
+ loadResourcesLocked();
} else {
mLocked.pointerX = 0;
mLocked.pointerY = 0;
}
fadeOutAndReleaseAllSpotsLocked();
- }
-
- if (mLocked.displayOrientation != orientation) {
+ } else if (oldViewport.orientation != viewport.orientation) {
// Apply offsets to convert from the pixel top-left corner position to the pixel center.
// This creates an invariant frame of reference that we can easily rotate when
// taking into account that the pointer may be located at fractional pixel offsets.
@@ -405,37 +398,37 @@
float temp;
// Undo the previous rotation.
- switch (mLocked.displayOrientation) {
+ switch (oldViewport.orientation) {
case DISPLAY_ORIENTATION_90:
temp = x;
- x = mLocked.displayWidth - y;
+ x = oldViewport.deviceHeight - y;
y = temp;
break;
case DISPLAY_ORIENTATION_180:
- x = mLocked.displayWidth - x;
- y = mLocked.displayHeight - y;
+ x = oldViewport.deviceWidth - x;
+ y = oldViewport.deviceHeight - y;
break;
case DISPLAY_ORIENTATION_270:
temp = x;
x = y;
- y = mLocked.displayHeight - temp;
+ y = oldViewport.deviceWidth - temp;
break;
}
// Perform the new rotation.
- switch (orientation) {
+ switch (viewport.orientation) {
case DISPLAY_ORIENTATION_90:
temp = x;
x = y;
- y = mLocked.displayWidth - temp;
+ y = viewport.deviceHeight - temp;
break;
case DISPLAY_ORIENTATION_180:
- x = mLocked.displayWidth - x;
- y = mLocked.displayHeight - y;
+ x = viewport.deviceWidth - x;
+ y = viewport.deviceHeight - y;
break;
case DISPLAY_ORIENTATION_270:
temp = x;
- x = mLocked.displayHeight - y;
+ x = viewport.deviceWidth - y;
y = temp;
break;
}
@@ -444,7 +437,6 @@
// and save the results.
mLocked.pointerX = x - 0.5f;
mLocked.pointerY = y - 0.5f;
- mLocked.displayOrientation = orientation;
}
updatePointerLocked();
@@ -614,11 +606,16 @@
mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
}
-void PointerController::updatePointerLocked() {
+void PointerController::updatePointerLocked() REQUIRES(mLock) {
+ if (!mLocked.viewport.isValid()) {
+ return;
+ }
+
mSpriteController->openTransaction();
mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+ mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
if (mLocked.pointerAlpha > 0) {
mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
@@ -729,8 +726,18 @@
}
}
-void PointerController::loadResources() {
+void PointerController::loadResourcesLocked() REQUIRES(mLock) {
mPolicy->loadPointerResources(&mResources);
+
+ if (mLocked.presentation == PRESENTATION_POINTER) {
+ mLocked.additionalMouseResources.clear();
+ mLocked.animationResources.clear();
+ mPolicy->loadPointerIcon(&mLocked.pointerIcon);
+ mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
+ &mLocked.animationResources);
+ }
+
+ mLocked.pointerIconChanged = true;
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 7f4e5a5..a32cc42 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -23,6 +23,7 @@
#include <vector>
#include <ui/DisplayInfo.h>
+#include <input/DisplayViewport.h>
#include <input/Input.h>
#include <PointerControllerInterface.h>
#include <utils/BitSet.h>
@@ -96,6 +97,7 @@
virtual int32_t getButtonState() const;
virtual void setPosition(float x, float y);
virtual void getPosition(float* outX, float* outY) const;
+ virtual int32_t getDisplayId() const;
virtual void fade(Transition transition);
virtual void unfade(Transition transition);
@@ -106,7 +108,7 @@
void updatePointerIcon(int32_t iconId);
void setCustomPointerIcon(const SpriteIcon& icon);
- void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
+ void setDisplayViewport(const DisplayViewport& viewport);
void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void reloadPointerResources();
@@ -156,9 +158,7 @@
size_t animationFrameIndex;
nsecs_t lastFrameUpdatedTime;
- int32_t displayWidth;
- int32_t displayHeight;
- int32_t displayOrientation;
+ DisplayViewport viewport;
InactivityTimeout inactivityTimeout;
@@ -182,7 +182,7 @@
Vector<Spot*> spots;
Vector<sp<Sprite> > recycledSprites;
- } mLocked;
+ } mLocked GUARDED_BY(mLock);
bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
void setPositionLocked(float x, float y);
@@ -207,7 +207,7 @@
void fadeOutAndReleaseSpotLocked(Spot* spot);
void fadeOutAndReleaseAllSpotsLocked();
- void loadResources();
+ void loadResourcesLocked();
};
} // namespace android
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index eb2bc98..c1868d3 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -144,13 +144,16 @@
}
}
- // Resize sprites if needed.
+ // Resize and/or reparent sprites if needed.
SurfaceComposerClient::Transaction t;
bool needApplyTransaction = false;
for (size_t i = 0; i < numSprites; i++) {
SpriteUpdate& update = updates.editItemAt(i);
+ if (update.state.surfaceControl == nullptr) {
+ continue;
+ }
- if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+ if (update.state.wantSurfaceVisible()) {
int32_t desiredWidth = update.state.icon.bitmap.width();
int32_t desiredHeight = update.state.icon.bitmap.height();
if (update.state.surfaceWidth < desiredWidth
@@ -170,6 +173,12 @@
}
}
}
+
+ // If surface is a new one, we have to set right layer stack.
+ if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) {
+ t.setLayerStack(update.state.surfaceControl, update.state.displayId);
+ needApplyTransaction = true;
+ }
}
if (needApplyTransaction) {
t.apply();
@@ -236,7 +245,7 @@
if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
|| (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
| DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
- | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+ | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) {
needApplyTransaction = true;
if (wantSurfaceVisibleAndDrawn
@@ -445,6 +454,15 @@
}
}
+void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.displayId != displayId) {
+ mLocked.state.displayId = displayId;
+ invalidateLocked(DIRTY_DISPLAY_ID);
+ }
+}
+
void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
bool wasDirty = mLocked.state.dirty;
mLocked.state.dirty |= dirty;
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 31e43e9..5b216f5 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -125,6 +125,9 @@
/* Sets the sprite transformation matrix. */
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+
+ /* Sets the id of the display where the sprite should be shown. */
+ virtual void setDisplayId(int32_t displayId) = 0;
};
/*
@@ -170,6 +173,7 @@
DIRTY_LAYER = 1 << 4,
DIRTY_VISIBILITY = 1 << 5,
DIRTY_HOTSPOT = 1 << 6,
+ DIRTY_DISPLAY_ID = 1 << 7,
};
/* Describes the state of a sprite.
@@ -180,7 +184,7 @@
struct SpriteState {
inline SpriteState() :
dirty(0), visible(false),
- positionX(0), positionY(0), layer(0), alpha(1.0f),
+ positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT),
surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
}
@@ -193,6 +197,7 @@
int32_t layer;
float alpha;
SpriteTransformationMatrix transformationMatrix;
+ int32_t displayId;
sp<SurfaceControl> surfaceControl;
int32_t surfaceWidth;
@@ -225,6 +230,7 @@
virtual void setLayer(int32_t layer);
virtual void setAlpha(float alpha);
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+ virtual void setDisplayId(int32_t displayId);
inline const SpriteState& getStateLocked() const {
return mLocked.state;
diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h
index c84de4c..0b7f6e46 100644
--- a/libs/protoutil/include/android/util/EncodedBuffer.h
+++ b/libs/protoutil/include/android/util/EncodedBuffer.h
@@ -38,13 +38,13 @@
{
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 @@
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 bdc84da..e795b81 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -89,7 +89,6 @@
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 @@
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 @@
// --- 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 d96597b..59c6a0a 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -43,6 +43,7 @@
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 @@
@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 @@
* @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 @@
*/
@Deprecated
public void clearTestProviderEnabled(String provider) {
- setTestProviderEnabled(provider, true);
+ setTestProviderEnabled(provider, false);
}
/**
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index b531325..866634e 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -16,9 +16,6 @@
package com.android.internal.location;
-import java.io.UnsupportedEncodingException;
-import java.util.concurrent.TimeUnit;
-
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -26,20 +23,23 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.location.LocationManager;
import android.location.INetInitiatedListener;
+import android.location.LocationManager;
+import android.os.RemoteException;
import android.os.SystemClock;
-import android.telephony.TelephonyManager;
+import android.os.UserHandle;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
-import android.os.RemoteException;
-import android.os.UserHandle;
+import android.telephony.TelephonyManager;
import android.util.Log;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.telephony.GsmAlphabet;
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.TimeUnit;
+
/**
* A GPS Network-initiated Handler class used by LocationManager.
*
@@ -92,9 +92,6 @@
public static final int GPS_ENC_SUPL_UCS2 = 3;
public static final int GPS_ENC_UNKNOWN = -1;
- // Limit on SUPL NI emergency mode time extension after emergency sessions ends
- private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300; // 5 minute maximum
-
private final Context mContext;
private final TelephonyManager mTelephonyManager;
private final PhoneStateListener mPhoneStateListener;
@@ -252,19 +249,9 @@
}
public void setEmergencyExtensionSeconds(int emergencyExtensionSeconds) {
- if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
- Log.w(TAG, "emergencyExtensionSeconds " + emergencyExtensionSeconds
- + " too high, reset to " + MAX_EMERGENCY_MODE_EXTENSION_SECONDS);
- emergencyExtensionSeconds = MAX_EMERGENCY_MODE_EXTENSION_SECONDS;
- } else if (emergencyExtensionSeconds < 0) {
- Log.w(TAG, "emergencyExtensionSeconds " + emergencyExtensionSeconds
- + " is negative, reset to zero.");
- emergencyExtensionSeconds = 0;
- }
mEmergencyExtensionMillis = TimeUnit.SECONDS.toMillis(emergencyExtensionSeconds);
}
-
// Handles NI events from HAL
public void handleNiNotification(GpsNiNotification notif) {
if (DEBUG) Log.d(TAG, "in handleNiNotification () :"
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index b09335c..35f2877 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -18,5 +18,7 @@
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/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index f482f64..df96994 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -1334,11 +1334,11 @@
return addTask(new Task(CALL_COMPLETED_SET_WAKE_LOCK, false) {
@Override
void process() {
- boolean washeld = false;
+ boolean wasHeld = false;
if (mWakeLock != null) {
if (mWakeLock.isHeld()) {
- washeld = true;
+ wasHeld = true;
mWakeLock.release();
}
}
@@ -1346,7 +1346,7 @@
mWakeLock = wakeLock;
if (mWakeLock != null) {
mWakeLock.setReferenceCounted(false);
- if (washeld) {
+ if (wasHeld) {
mWakeLock.acquire();
}
}
diff --git a/media/java/android/media/Session2Command.java b/media/java/android/media/Session2Command.java
index d2a5aae..8b285f2 100644
--- a/media/java/android/media/Session2Command.java
+++ b/media/java/android/media/Session2Command.java
@@ -36,8 +36,6 @@
* 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 @@
}
@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 @@
* @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 @@
/**
* 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 122dfb1..4668ec4 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/java/android/media/Session2CommandGroup.java
@@ -130,7 +130,7 @@
@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 4634c69..e1fff38 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -171,7 +171,7 @@
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 0000000..8ee8c7d
--- /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 0000000..a143c9b
--- /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 bfc05fa..1524ad9 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 @@
// 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 626338d..9b86bfc 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -17,7 +17,7 @@
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 @@
* @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 a843881..2ba09fd 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -17,10 +17,9 @@
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 @@
* @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 @@
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 fac8897..5c02e7c 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 51148e2..7ac3ef2 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.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 @@
* @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 ef2df15..a1b8170 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -21,12 +21,12 @@
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 @@
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 @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 @@
};
}
- 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 @@
}
@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 @@
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 df8cc35..e07cf15 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -24,7 +24,6 @@
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 @@
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 @@
}
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 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 @@
/**
* @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 @@
}
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
}
@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 @@
@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 @@
@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 @@
@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 ef5cf00..56ea484 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -98,7 +98,7 @@
* @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 @@
}
/**
+ * 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 0000000..c489e5b
--- /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 0000000..7547bff
--- /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/jni/Android.bp b/media/jni/Android.bp
index fda0fc4..d6b6339 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -102,7 +102,6 @@
"libhidlbase",
"libhidlmemory",
- "libpowermanager", // Used by JWakeLock. Will be replace with public SDK API.
"libmediametrics", // Used by MediaMetrics. Will be replaced with stable C API.
"libbinder", // Used by JWakeLock and MediaMetrics.
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 7e6a8ab..9b4e730 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -86,7 +86,8 @@
// ----------------------------------------------------------------------------
struct fields_t {
- jfieldID context;
+ jfieldID context; // passed from Java to native, used for creating JWakeLock
+ jfieldID nativeContext; // mNativeContext in MediaPlayer2.java
jfieldID surface_texture;
jmethodID post_event;
@@ -225,21 +226,21 @@
static sp<MediaPlayer2> getMediaPlayer(JNIEnv* env, jobject thiz)
{
Mutex::Autolock l(sLock);
- MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.context);
+ MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.nativeContext);
return sp<MediaPlayer2>(p);
}
static sp<MediaPlayer2> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer2>& player)
{
Mutex::Autolock l(sLock);
- sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.context);
+ sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.nativeContext);
if (player.get()) {
player->incStrong((void*)setMediaPlayer);
}
if (old != 0) {
old->decStrong((void*)setMediaPlayer);
}
- env->SetLongField(thiz, fields.context, (jlong)player.get());
+ env->SetLongField(thiz, fields.nativeContext, (jlong)player.get());
return old;
}
@@ -955,11 +956,16 @@
return;
}
- fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
+ fields.context = env->GetFieldID(clazz, "mContext", "Landroid/content/Context;");
if (fields.context == NULL) {
return;
}
+ fields.nativeContext = env->GetFieldID(clazz, "mNativeContext", "J");
+ if (fields.nativeContext == NULL) {
+ return;
+ }
+
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;JIII[B)V");
if (fields.post_event == NULL) {
@@ -1013,7 +1019,8 @@
jint sessionId, jobject weak_this)
{
ALOGV("native_setup");
- sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId);
+ jobject context = env->GetObjectField(thiz, fields.context);
+ sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId, context);
if (mp == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 8c43683..44f8725 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -18,5 +18,7 @@
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 5cfb09b..fdcfc44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -83,6 +83,10 @@
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 0936256..88decc8 100644
--- a/native/webview/plat_support/Android.bp
+++ b/native/webview/plat_support/Android.bp
@@ -24,7 +24,6 @@
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 6b7d8d0..0000000
--- 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 eab1340..0000000
--- 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 0000000..e0f4ded
--- /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 0000000..b992788
--- /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 0000000..0993c9a
--- /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 8e3785e..4c638811 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.mk
+++ b/packages/CarrierDefaultApp/tests/unit/Android.mk
@@ -21,7 +21,7 @@
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 3a06a09..7a26d95 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 4688848..2f7d599 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -21,10 +21,10 @@
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 8516d94..0b0f1ec 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 74bc147..4fabf10 100644
--- a/packages/NetworkStack/src/android/net/util/SharedLog.java
+++ b/packages/NetworkStack/src/android/net/util/SharedLog.java
@@ -69,6 +69,10 @@
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
similarity index 78%
rename from services/net/java/android/net/util/Stopwatch.java
rename to packages/NetworkStack/src/android/net/util/Stopwatch.java
index cb15ee5..c316699 100644
--- a/services/net/java/android/net/util/Stopwatch.java
+++ b/packages/NetworkStack/src/android/net/util/Stopwatch.java
@@ -38,9 +38,9 @@
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 @@
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 @@
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 @@
}
}
+ /**
+ * 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 7fea1e0..057012d 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -25,18 +25,31 @@
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 @@
* <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 @@
}
@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
similarity index 84%
rename from services/core/java/com/android/server/connectivity/NetworkMonitor.java
rename to packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 2a00025..94ea1b9 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_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.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.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.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.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 @@
// 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 @@
}
}
- // 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 @@
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 @@
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 @@
* 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 @@
// 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 @@
@Nullable
private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
+ private NetworkCapabilities mNetworkCapabilities;
+ private LinkProperties mLinkProperties;
+
@VisibleForTesting
protected boolean mIsCaptivePortalCheckEnabled;
@@ -304,7 +276,7 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 @@
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 @@
}
private boolean isValidationRequired() {
- return isValidationRequired(
- mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities);
+ return NetworkMonitorUtils.isValidationRequired(
+ mDefaultRequest.networkCapabilities, mNetworkCapabilities);
}
- private void notifyNetworkTestResultInvalid(Object obj) {
- mConnectivityServiceHandler.sendMessage(obtainMessage(
- EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj));
+ 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 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 @@
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 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 @@
@Override
public void exit() {
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null);
- mConnectivityServiceHandler.sendMessage(message);
+ hideProvisioningNotification();
}
}
@@ -751,9 +829,7 @@
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 @@
}
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 @@
+ 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 @@
// 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 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 @@
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 @@
// 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 @@
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;
+ 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.");
}
- 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;
- }
+ 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 @@
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 @@
}
@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 @@
}
/**
+ * 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 @@
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
similarity index 68%
rename from tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
rename to packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
index 6e07b26..d31fa77 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.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.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 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 @@
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 @@
private static final int RETURN_CODE_DNS_SUCCESS = 0;
private static final int RETURN_CODE_DNS_TIMEOUT = 255;
+ 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);
+
+ private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+
+ private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
- mAgent.linkProperties = new LinkProperties();
- mAgent.networkCapabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
- mAgent.networkInfo = mNetworkInfo;
-
- mNotMeteredAgent.linkProperties = new LinkProperties();
- mNotMeteredAgent.networkCapabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- mNotMeteredAgent.networkInfo = mNetworkInfo;
-
- when(mAgent.network()).thenReturn(mNetwork);
- when(mDependencies.getNetwork(any())).thenReturn(mNetwork);
+ 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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
}
@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 @@
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 @@
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 @@
}
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 @@
}
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 419273e..bcf37ff 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 @@
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 bff2c84..fa95bf2 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 d041556..0c6d57d 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/drawable/bubble_expanded_header_bg.xml b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
new file mode 100644
index 0000000..26bf981
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
@@ -0,0 +1,32 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="?android:attr/colorBackgroundFloating"/>
+ <corners
+ android:topLeftRadius="@dimen/corner_size"
+ android:topRightRadius="@dimen/corner_size"/>
+ </shape>
+ </item>
+ <item android:gravity="bottom">
+ <shape>
+ <size android:height="1dp"/>
+ <solid android:color="?android:attr/textColorSecondary" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index e52aa14..b9b1bb1 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -22,7 +22,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:textColor="?android:attr/textColorPrimary"
android:gravity="center_vertical|start"
android:paddingStart="@dimen/battery_level_padding_start"
diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml
index b2307e7..1aeb52c 100644
--- a/packages/SystemUI/res/layout/bubble_expanded_view.xml
+++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml
@@ -20,11 +20,23 @@
android:layout_width="match_parent"
android:id="@+id/bubble_expanded_view">
- <!-- TODO: header -->
-
<View
android:id="@+id/pointer_view"
android:layout_width="@dimen/bubble_pointer_width"
android:layout_height="@dimen/bubble_pointer_height"
/>
+
+ <TextView
+ android:id="@+id/bubble_content_header"
+ android:background="@drawable/bubble_expanded_header_bg"
+ android:textAppearance="@*android:style/TextAppearance.Material.Title"
+ android:textSize="18sp"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bubble_expanded_header_height"
+ android:gravity="start|center_vertical"
+ android:singleLine="true"
+ android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding"
+ android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding"
+ />
+
</com.android.systemui.bubbles.BubbleExpandedViewContainer>
diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
index 307b538..5bcc1b3 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 22b8d2f..4b65b6a 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" >
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 cfa372b..0000000
--- 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 34c208a..02062bb 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/config.xml b/packages/SystemUI/res/values/config.xml
index 889db66..7cc5524 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -449,6 +449,11 @@
better (narrower) line-break for a double-line smart reply button. -->
<integer name="config_smart_replies_in_notifications_max_squeeze_remeasure_attempts">3</integer>
+ <!-- Smart replies in notifications: Whether by default tapping on a choice should let the user
+ edit the input before it is sent to the app. Developers can override this via
+ RemoteInput.Builder.setEditChoicesBeforeSending. -->
+ <bool name="config_smart_replies_in_notifications_edit_choices_before_sending">false</bool>
+
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
Blank sends the user to the Chooser first.
This name is in the ComponentName flattened format (package/class) -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 06df0e7..10e5f74 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -997,4 +997,8 @@
<dimen name="bubble_pointer_width">6dp</dimen>
<!-- Extra padding around the dismiss target for bubbles -->
<dimen name="bubble_dismiss_slop">16dp</dimen>
+ <!-- Height of the header within the expanded view. -->
+ <dimen name="bubble_expanded_header_height">48dp</dimen>
+ <!-- Left and right padding applied to the header. -->
+ <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index dac20b5..633f868 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 8a5a69b..22a0b36 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -125,7 +125,7 @@
<style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
@@ -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 f3bdbae..078947c 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 @@
*/
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 c0a1d89..e6acfbe 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 @@
/**
* 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 @@
* @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 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 2aba3fa..af32f48 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 @@
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 27d624a..17cc1d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -14,13 +14,13 @@
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 @@
* 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 @@
@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 @@
* @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 @@
}
}
- /**
- * 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 6864ea1..8e273ef 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.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.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;
@@ -276,9 +274,6 @@
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);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 9d81064..ec6ecc6 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.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 @@
@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 @@
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 f324a05b..ce9c637 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.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 @@
/**
*/
@Binds
- public abstract IconLogger provideIconLogger(IconLoggerImpl loggerImpl);
-
- /**
- */
- @Binds
public abstract CastController provideCastController(CastControllerImpl controllerImpl);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 9f363f6..7e5b426 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,12 +25,20 @@
private int mUid;
private String mPackageName;
private long mTimeStarted;
+ private String mState;
public AppOpItem(int code, int uid, String packageName, long timeStarted) {
this.mCode = code;
this.mUid = uid;
this.mPackageName = packageName;
this.mTimeStarted = timeStarted;
+ mState = new StringBuilder()
+ .append("AppOpItem(")
+ .append("Op code=").append(code).append(", ")
+ .append("UID=").append(uid).append(", ")
+ .append("Package name=").append(packageName)
+ .append(")")
+ .toString();
}
public int getCode() {
@@ -48,4 +56,9 @@
public long getTimeStarted() {
return mTimeStarted;
}
+
+ @Override
+ public String toString() {
+ return mState;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index e3bcb37..c013df3 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -29,7 +29,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -47,7 +50,7 @@
@Singleton
public class AppOpsControllerImpl implements AppOpsController,
AppOpsManager.OnOpActiveChangedListener,
- AppOpsManager.OnOpNotedListener {
+ AppOpsManager.OnOpNotedListener, Dumpable {
private static final long NOTED_OP_TIME_DELAY_MS = 5000;
private static final String TAG = "AppOpsControllerImpl";
@@ -271,6 +274,22 @@
}
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("AppOpsController state:");
+ pw.println(" Active Items:");
+ for (int i = 0; i < mActiveItems.size(); i++) {
+ final AppOpItem item = mActiveItems.get(i);
+ pw.print(" "); pw.println(item.toString());
+ }
+ pw.println(" Noted Items:");
+ for (int i = 0; i < mNotedItems.size(); i++) {
+ final AppOpItem item = mNotedItems.get(i);
+ pw.print(" "); pw.println(item.toString());
+ }
+
+ }
+
protected final class H extends Handler {
H(Looper looper) {
super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index 3167b9e..016b8fe 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.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 @@
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 @@
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 @@
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 @@
@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 @@
}
@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 @@
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 @@
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");
+ private void handleBiometricAuthenticated(boolean authenticated) {
+ if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
- mCurrentDialog.announceForAccessibility(
- mContext.getResources()
- .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
- if (mCurrentDialog.requiresConfirmation()) {
- mConfirmShowing = true;
- mCurrentDialog.showConfirmationButton(true /* show */);
+ 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 @@
if (DEBUG) Log.d(TAG, "Dialog already dismissed");
return;
}
- mTryAgainShowing = false;
mCurrentDialog.showErrorMessage(error);
}
@@ -279,8 +275,6 @@
}
mReceiver = null;
mDialogShowing = false;
- mConfirmShowing = false;
- mTryAgainShowing = false;
mCurrentDialog.startDismiss();
}
@@ -294,7 +288,6 @@
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling negative button", e);
}
- mTryAgainShowing = false;
handleHideDialog(false /* userCanceled */);
}
@@ -308,25 +301,16 @@
} 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 @@
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 9934bfd..b8c69c80 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 @@
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;
@@ -78,12 +81,19 @@
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;
+
private Bundle mBundle;
- private final LinearLayout mDialog;
+
private int mLastState;
private boolean mAnimatingAway;
private boolean mWasForceRemoved;
@@ -91,15 +101,13 @@
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();
private final Runnable mShowAnimationRunnable = new Runnable() {
@Override
@@ -124,7 +132,7 @@
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 +166,6 @@
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 +188,19 @@
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 +217,9 @@
}, getDelayAfterAuthenticatedDurationMs());
});
- tryAgain.setOnClickListener((View v) -> {
+ mTryAgainButton.setOnClickListener((View v) -> {
showTryAgainButton(false /* show */);
+ handleClearMessage(false /* requireTryAgain */);
mCallback.onTryAgainPressed();
});
@@ -215,15 +227,17 @@
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 +258,34 @@
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 +314,7 @@
? (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 +325,7 @@
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 +342,9 @@
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 +421,42 @@
return mLayout;
}
- // Clears the temporary message and shows the help message.
- private void handleClearMessage() {
- updateState(STATE_AUTHENTICATING);
- mErrorText.setText(getHintStringResourceId());
- mErrorText.setTextColor(mTextColor);
+ // Clears the temporary message and shows the help message. If requireTryAgain is true,
+ // we will start the authenticating state again.
+ private void handleClearMessage(boolean requireTryAgain) {
+ if (!requireTryAgain) {
+ updateState(STATE_AUTHENTICATING);
+ mErrorText.setText(getHintStringResourceId());
+ mErrorText.setTextColor(mTextColor);
+ mErrorText.setVisibility(View.VISIBLE);
+ } else {
+ updateState(STATE_IDLE);
+ mErrorText.setVisibility(View.INVISIBLE);
+ }
}
// 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 +475,16 @@
}
public void showTryAgainButton(boolean show) {
- final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
if (show) {
- tryAgain.setVisibility(View.VISIBLE);
+ mTryAgainButton.setVisibility(View.VISIBLE);
} else {
- tryAgain.setVisibility(View.GONE);
+ mTryAgainButton.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 de3f947..359cb04 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,243 @@
*/
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
+ 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 {
+ super.showTryAgainButton(show);
+ }
+ }
+
@Override
protected int getHintStringResourceId() {
return R.string.face_dialog_looking_for_face;
@@ -56,7 +296,9 @@
@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 +320,19 @@
}
@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 +349,14 @@
}
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 1a6cee2..d63836b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
@@ -49,7 +49,7 @@
@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 +68,15 @@
}
@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/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7577609..9f3ff78 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -23,15 +23,20 @@
import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
+import android.util.Log;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -68,6 +73,8 @@
private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
+ private static final String ENABLE_BUBBLE_ACTIVITY_VIEW = "experiment_bubble_activity_view";
+ private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
private final Context mContext;
private final NotificationEntryManager mNotificationEntryManager;
@@ -189,6 +196,9 @@
// It's new
BubbleView bubble = new BubbleView(mContext);
bubble.setNotif(notif);
+ if (shouldUseActivityView(mContext)) {
+ bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+ }
mBubbles.put(bubble.getKey(), bubble);
boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
@@ -216,6 +226,21 @@
}
}
+ @Nullable
+ private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
+ Notification notification = notif.notification.getNotification();
+ if (canLaunchInActivityView(notification.getAppOverlayIntent())) {
+ return notification.getAppOverlayIntent();
+ } else if (shouldUseContentIntent(mContext)
+ && canLaunchInActivityView(notification.contentIntent)) {
+ Log.d(TAG, "[addBubble " + notif.key
+ + "]: No appOverlayIntent, using contentIntent.");
+ return notification.contentIntent;
+ }
+ Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView.");
+ return null;
+ }
+
/**
* Removes the bubble associated with the {@param uri}.
*/
@@ -223,6 +248,7 @@
BubbleView bv = mBubbles.get(key);
if (mStackView != null && bv != null) {
mStackView.removeBubble(bv);
+ bv.destroyActivityView(mStackView);
bv.getEntry().setBubbleDismissed(true);
}
@@ -282,9 +308,10 @@
}
}
}
- for (BubbleView view : viewsToRemove) {
- mBubbles.remove(view.getKey());
- mStackView.removeBubble(view);
+ for (BubbleView bubbleView : viewsToRemove) {
+ mBubbles.remove(bubbleView.getKey());
+ mStackView.removeBubble(bubbleView);
+ bubbleView.destroyActivityView(mStackView);
}
if (mStackView != null) {
mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
@@ -306,6 +333,17 @@
return mTempRect;
}
+ private boolean canLaunchInActivityView(PendingIntent intent) {
+ if (intent == null) {
+ return false;
+ }
+ ActivityInfo info =
+ intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
+ return info != null
+ && ActivityInfo.isResizeableMode(info.resizeMode)
+ && (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
+ }
+
@VisibleForTesting
BubbleStackView getStackView() {
return mStackView;
@@ -378,4 +416,14 @@
return Settings.Secure.getInt(context.getContentResolver(),
ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
}
+
+ private static boolean shouldUseActivityView(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ ENABLE_BUBBLE_ACTIVITY_VIEW, 0) != 0;
+ }
+
+ private static boolean shouldUseContentIntent(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
index e28d96b..badefe1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -24,6 +24,7 @@
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
@@ -35,6 +36,8 @@
// The triangle pointing to the expanded view
private View mPointerView;
+ // The view displayed between the pointer and the expanded view
+ private TextView mHeaderView;
// The view that is being displayed for the expanded state
private View mExpandedView;
@@ -68,6 +71,7 @@
TriangleShape.create(width, height, true /* pointUp */));
triangleDrawable.setTint(Color.WHITE); // TODO: dark mode
mPointerView.setBackground(triangleDrawable);
+ mHeaderView = findViewById(R.id.bubble_content_header);
}
/**
@@ -80,9 +84,19 @@
}
/**
+ * Set the text displayed within the header.
+ */
+ public void setHeaderText(CharSequence text) {
+ mHeaderView.setText(text);
+ }
+
+ /**
* Set the view to display for the expanded state. Passing null will clear the view.
*/
public void setExpandedView(View view) {
+ if (mExpandedView == view) {
+ return;
+ }
if (mExpandedView != null) {
removeView(mExpandedView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index d69e7b3..3280a33 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -21,10 +21,13 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.app.ActivityView;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.RectF;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -50,6 +53,7 @@
*/
public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
+ private static final String TAG = "BubbleStackView";
private Point mDisplaySize;
private FrameLayout mBubbleContainer;
@@ -59,6 +63,7 @@
private int mBubblePadding;
private boolean mIsExpanded;
+ private int mExpandedBubbleHeight;
private BubbleView mExpandedBubble;
private Point mCollapsedPosition;
private BubbleTouchHandler mTouchHandler;
@@ -106,6 +111,7 @@
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mDisplaySize = new Point();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getSize(mDisplaySize);
@@ -389,27 +395,63 @@
}
private void updateExpandedBubble() {
- if (mExpandedBubble != null) {
+ if (mExpandedBubble == null) {
+ return;
+ }
+
+ if (mExpandedBubble.hasAppOverlayIntent()) {
+ ActivityView expandedView = mExpandedBubble.getActivityView();
+ expandedView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
+
+ final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
+ mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
+ mExpandedViewContainer.setExpandedView(expandedView);
+ expandedView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ Log.d(TAG, "onActivityViewReady("
+ + mExpandedBubble.getEntry().key + "): " + view);
+ view.startActivity(intent);
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ NotificationEntry entry = mExpandedBubble.getEntry();
+ Log.d(TAG, "onActivityViewDestroyed(key="
+ + ((entry != null) ? entry.key : "(none)") + "): " + view);
+ }
+ });
+ } else {
ExpandableNotificationRow row = mExpandedBubble.getRowView();
- if (!row.equals(mExpandedViewContainer.getChildAt(0))) {
+ if (!row.equals(mExpandedViewContainer.getExpandedView())) {
// Different expanded view than what we have
mExpandedViewContainer.setExpandedView(null);
}
- int pointerPosition = mExpandedBubble.getPosition().x
- + (mExpandedBubble.getWidth() / 2);
- mExpandedViewContainer.setPointerPosition(pointerPosition);
mExpandedViewContainer.setExpandedView(row);
+ mExpandedViewContainer.setHeaderText(null);
}
+ int pointerPosition = mExpandedBubble.getPosition().x
+ + (mExpandedBubble.getWidth() / 2);
+ mExpandedViewContainer.setPointerPosition(pointerPosition);
}
private void applyCurrentState() {
+ Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
if (!mIsExpanded) {
mExpandedViewContainer.setExpandedView(null);
} else {
mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
- ExpandableNotificationRow row = mExpandedBubble.getRowView();
- applyRowState(row);
+ View expandedView = mExpandedViewContainer.getExpandedView();
+ if (expandedView instanceof ActivityView) {
+ if (expandedView.isAttachedToWindow()) {
+ ((ActivityView) expandedView).onLocationChanged();
+ }
+ } else {
+ applyRowState(mExpandedBubble.getRowView());
+ }
}
int bubbsCount = mBubbleContainer.getChildCount();
for (int i = 0; i < bubbsCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 88030ee..96b2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -33,7 +33,7 @@
* Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
* dismissing, and flings.
*/
-public class BubbleTouchHandler implements View.OnTouchListener {
+class BubbleTouchHandler implements View.OnTouchListener {
private BubbleController mController = Dependency.get(BubbleController.class);
private PipDismissViewController mDismissViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 3307992..c1bbb93 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -16,7 +16,9 @@
package com.android.systemui.bubbles;
+import android.app.ActivityView;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
@@ -25,7 +27,9 @@
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -37,7 +41,7 @@
/**
* A floating object on the screen that has a collapsed and expanded state.
*/
-public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
+class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
private static final String TAG = "BubbleView";
private Context mContext;
@@ -46,6 +50,8 @@
private NotificationEntry mEntry;
private int mBubbleSize;
private int mIconSize;
+ private PendingIntent mAppOverlayIntent;
+ private ActivityView mActivityView;
public BubbleView(Context context) {
this(context, null);
@@ -117,12 +123,51 @@
}
/**
- * @return the view to display when the bubble is expanded.
+ * @return the view to display notification content when the bubble is expanded.
*/
public ExpandableNotificationRow getRowView() {
return mEntry.getRow();
}
+ /**
+ * @return a view used to display app overlay content when expanded.
+ */
+ public ActivityView getActivityView() {
+ if (mActivityView == null) {
+ mActivityView = new ActivityView(mContext);
+ Log.d(TAG, "[getActivityView] created: " + mActivityView);
+ }
+ return mActivityView;
+ }
+
+ /**
+ * Removes and releases an ActivityView if one was previously created for this bubble.
+ */
+ public void destroyActivityView(ViewGroup tmpParent) {
+ if (mActivityView == null) {
+ return;
+ }
+ // HACK: Only release if initialized. There's no way to know if the ActivityView has
+ // been initialized. Calling release() if it hasn't been initialized will crash.
+
+ if (!mActivityView.isAttachedToWindow()) {
+ // HACK: release() will crash if the view is not attached.
+
+ mActivityView.setVisibility(View.GONE);
+ tmpParent.addView(mActivityView, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+ try {
+ mActivityView.release();
+ } catch (IllegalStateException ex) {
+ Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex);
+ }
+
+ ((ViewGroup) mActivityView.getParent()).removeView(mActivityView);
+ mActivityView = null;
+ }
+
@Override
public void setPosition(int x, int y) {
setTranslationX(x);
@@ -162,4 +207,20 @@
lp.height = mBubbleSize;
v.setLayoutParams(lp);
}
+
+ /**
+ * @return whether an ActivityView should be used to display the content of this Bubble
+ */
+ public boolean hasAppOverlayIntent() {
+ return mAppOverlayIntent != null;
+ }
+
+ public PendingIntent getAppOverlayIntent() {
+ return mAppOverlayIntent;
+
+ }
+
+ public void setAppOverlayIntent(PendingIntent intent) {
+ mAppOverlayIntent = intent;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 323cf1f..f14495b 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 @@
*/
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 01ee5ca..4388200 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 @@
@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)
}
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index e030e40..e63f88a 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 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 40c6039..28285e14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -699,7 +699,7 @@
}
public void updateEverything() {
- post(() -> setClickable(false));
+ post(() -> setClickable(!mExpanded));
}
public void setQSPanel(final QSPanel qsPanel) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 81757d0..c474faf 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 @@
private float mBackButtonAlpha;
private MotionEvent mStatusBarGestureDownEvent;
private float mWindowCornerRadius;
+ private boolean mSupportsRoundedCornersOnWindows;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@@ -244,6 +245,18 @@
}
}
+ 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 @@
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 6a01563..904478e 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 @@
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 @@
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 @@
}
@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 @@
}
}
- @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 @@
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 @@
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 6d2c001..0000000
--- 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/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
deleted file mode 100644
index 0652227..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
+++ /dev/null
@@ -1,34 +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;
-
-import com.android.internal.statusbar.StatusBarIcon;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them
- * in a linear layout
- */
-public class StatusBarIconContainer {
- private final List<StatusBarIcon> mIcons = new ArrayList<>();
-
- public StatusBarIconContainer(List<StatusBarIcon> icons) {
- mIcons.addAll(icons);
- }
-}
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 e698e64..45db002 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 @@
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.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 @@
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 @@
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 NotificationEntryManager(Context context) {
mContext = context;
mNotificationData = new NotificationData();
- mDeferredNotificationViewUpdateHandler = new Handler();
}
/** Adds a {@link NotificationEntryListener}. */
@@ -156,7 +150,6 @@
NotificationListContainer listContainer,
HeadsUpManager headsUpManager) {
mPresenter = presenter;
- mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
mNotificationData.setHeadsUpManager(headsUpManager);
mListContainer = listContainer;
@@ -180,14 +173,6 @@
return mNotificationData;
}
- protected Context getContext() {
- return mContext;
- }
-
- protected NotificationPresenter getPresenter() {
- return mPresenter;
- }
-
@Override
public void onReorderingAllowed() {
updateNotifications();
@@ -229,15 +214,6 @@
}
}
- 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 @@
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationAdded(entry);
}
- maybeScheduleUpdateNotificationViews(entry);
} else {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryReinflated(entry);
@@ -463,8 +438,6 @@
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 9e0dd84..95bd1ce 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.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.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 @@
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 @@
/** 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/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 280dda0..577e8d6 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 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/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index d873b0c..75adf50 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 @@
handled = true;
}
handled |= super.onTouchEvent(event);
- return mDozing ? handled : true;
+ return !mDozing || mPulsing || handled;
}
private boolean handleQsTouch(MotionEvent event) {
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 e25c829..853d7ab 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 @@
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 @@
@VisibleForTesting
protected void onHideWallpaperTimeout() {
- if (mState != ScrimState.AOD) {
+ if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
return;
}
@@ -364,7 +367,7 @@
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 @@
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 @@
// 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 @@
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 @@
}
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 fb3c4aa..72519ba3 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 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 6d78f72..9abd86d 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @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 db7589d..f846036 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.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 @@
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 @@
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 @@
return;
}
- mIconLogger.onIconHidden(slotName);
-
int slotIndex = getSlotIndex(slotName);
List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder();
for (StatusBarIconHolder holder : iconsToRemove) {
@@ -297,7 +292,6 @@
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 @@
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 0f8970f..bb23608 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 @@
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 53e461d..8b25c34 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 @@
@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 @@
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 @@
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/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index de7ef3b..4299af7 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 @@
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 710e1df..0000000
--- 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 ba6369e..0000000
--- 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/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 6193159..0c63e29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -18,6 +18,7 @@
import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
+import android.app.RemoteInput;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -42,14 +43,18 @@
private static final String KEY_REQUIRES_TARGETING_P = "requires_targeting_p";
private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS =
"max_squeeze_remeasure_attempts";
+ private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
+ "edit_choices_before_sending";
private final boolean mDefaultEnabled;
private final boolean mDefaultRequiresP;
private final int mDefaultMaxSqueezeRemeasureAttempts;
+ private final boolean mDefaultEditChoicesBeforeSending;
private boolean mEnabled;
private boolean mRequiresTargetingP;
private int mMaxSqueezeRemeasureAttempts;
+ private boolean mEditChoicesBeforeSending;
private final Context mContext;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -66,6 +71,8 @@
R.bool.config_smart_replies_in_notifications_requires_targeting_p);
mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger(
R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
+ mDefaultEditChoicesBeforeSending = resources.getBoolean(
+ R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -90,6 +97,8 @@
mRequiresTargetingP = mParser.getBoolean(KEY_REQUIRES_TARGETING_P, mDefaultRequiresP);
mMaxSqueezeRemeasureAttempts = mParser.getInt(
KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts);
+ mEditChoicesBeforeSending = mParser.getBoolean(
+ KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
}
}
@@ -113,4 +122,24 @@
public int getMaxSqueezeRemeasureAttempts() {
return mMaxSqueezeRemeasureAttempts;
}
+
+ /**
+ * Returns whether by tapping on a choice should let the user edit the input before it
+ * is sent to the app.
+ *
+ * @param remoteInputEditChoicesBeforeSending The value from
+ * {@link RemoteInput#getEditChoicesBeforeSending()}
+ */
+ public boolean getEffectiveEditChoicesBeforeSending(
+ @RemoteInput.EditChoicesBeforeSending int remoteInputEditChoicesBeforeSending) {
+ switch (remoteInputEditChoicesBeforeSending) {
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED:
+ return false;
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED:
+ return true;
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO:
+ default:
+ return mEditChoicesBeforeSending;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 68b172d..a76cf16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -242,9 +242,8 @@
b.setText(choice);
OnDismissAction action = () -> {
- // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags.
- if (smartReplies.remoteInput.getEditChoicesBeforeSending()
- == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) {
+ if (mConstants.getEffectiveEditChoicesBeforeSending(
+ smartReplies.remoteInput.getEditChoicesBeforeSending())) {
entry.remoteInputText = choice;
mRemoteInputManager.activateRemoteInput(b,
new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
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 0000000..52cabe2
--- /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 0000000..d457dac
--- /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 0000000..25b0b0a
--- /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 0000000..637e48e
--- /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 0000000..ad0b98b
--- /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 1844df5..fbc1c20 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 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 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 @@
// 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 @@
@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 @@
}
@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 @@
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 @@
@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 @@
// 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 @@
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 @@
@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 @@
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 @@
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 521d5d1..53ad0b5 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 @@
@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 @@
assertTrue("mFixedSizeAllowed should be true", allowed);
mEventCountdown.countDown();
}
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode, long duration) {
+ mAmbientEventCountdown.countDown();
+ }
};
}
};
@@ -132,4 +134,23 @@
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/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 146c5d6..8eb42c4 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.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 @@
},
visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager);
mScrimController.setHasBackdrop(false);
+ mScrimController.setWallpaperSupportsAmbientMode(false);
}
@Test
@@ -474,6 +475,26 @@
}
@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 @@
}
@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 @@
@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 @@
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 5c34730..0000000
--- 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/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 2266b47..37a56a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -20,6 +20,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import android.app.RemoteInput;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
@@ -51,6 +52,8 @@
resources.addOverride(R.bool.config_smart_replies_in_notifications_enabled, true);
resources.addOverride(
R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts, 7);
+ resources.addOverride(
+ R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
}
@@ -104,6 +107,51 @@
assertEquals(5, mConstants.getMaxSqueezeRemeasureAttempts());
}
+ @Test
+ public void testGetEffectiveEditChoicesBeforeSendingWithNoConfig() {
+ overrideSetting("enabled=true");
+ triggerConstantsOnChange();
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+ assertTrue(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+ }
+
+ @Test
+ public void testGetEffectiveEditChoicesBeforeSendingWithEnabledConfig() {
+ overrideSetting("enabled=true,edit_choices_before_sending=true");
+ triggerConstantsOnChange();
+ assertTrue(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+ assertTrue(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+ }
+
+ @Test
+ public void testGetEffectiveEditChoicesBeforeSendingWithDisabledConfig() {
+ overrideSetting("enabled=true,edit_choices_before_sending=false");
+ triggerConstantsOnChange();
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO));
+ assertTrue(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED));
+ assertFalse(
+ mConstants.getEffectiveEditChoicesBeforeSending(
+ RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
+ }
+
private void overrideSetting(String flags) {
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
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 0000000..c44a366
--- /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 0000000..55b0aae
--- /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/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml b/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
index 165aab2..a6aa64c 100644
--- a/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
+++ b/packages/overlays/FontArbutusSourceOverlay/res/values/config.xml
@@ -19,7 +19,7 @@
<!-- Name of a font family to use for body text. -->
<string name="config_bodyFontFamily" translatable="false">source-sans-pro</string>
<!-- Name of a font family to use for medium body text. -->
- <string name="config_bodyFontFamilyMedium" translatable="false">source-sans-pro-medium</string>
+ <string name="config_bodyFontFamilyMedium" translatable="false">source-sans-pro-semi-bold</string>
<!-- Name of a font family to use for headlines. If empty, falls back to platform default -->
<string name="config_headlineFontFamily" translatable="false">arbutus-slab</string>
<!-- Name of the font family used for system surfaces where the font should use medium weight -->
diff --git a/packages/overlays/FontArvoLatoOverlay/res/values/config.xml b/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
index 229c578..4e70d72 100644
--- a/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
+++ b/packages/overlays/FontArvoLatoOverlay/res/values/config.xml
@@ -19,10 +19,10 @@
<!-- Name of a font family to use for body text. -->
<string name="config_bodyFontFamily" translatable="false">lato</string>
<!-- Name of a font family to use for medium body text. -->
- <string name="config_bodyFontFamilyMedium" translatable="false">lato-medium</string>
+ <string name="config_bodyFontFamilyMedium" translatable="false">lato-bold</string>
<!-- Name of a font family to use for headlines. If empty, falls back to platform default -->
<string name="config_headlineFontFamily" translatable="false">arvo</string>
<!-- Name of the font family used for system surfaces where the font should use medium weight -->
- <string name="config_headlineFontFamilyMedium" translatable="false">arvo-medium</string>
+ <string name="config_headlineFontFamilyMedium" translatable="false">arvo-bold</string>
</resources>
diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
index a40cf1c..d969c69 100644
--- a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
+++ b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
@@ -16,6 +16,9 @@
#define LOG_TAG "PacProcessor"
+#include <stdlib.h>
+#include <string>
+
#include <utils/Log.h>
#include <utils/Mutex.h>
#include "android_runtime/AndroidRuntime.h"
@@ -23,24 +26,24 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
-#include "proxy_resolver_v8.h"
+#include "proxy_resolver_v8_wrapper.h"
namespace android {
-net::ProxyResolverV8* proxyResolver = NULL;
+ProxyResolverV8Handle* proxyResolver = NULL;
bool pacSet = false;
-String16 jstringToString16(JNIEnv* env, jstring jstr) {
+std::u16string jstringToString16(JNIEnv* env, jstring jstr) {
const jchar* str = env->GetStringCritical(jstr, 0);
- String16 str16(reinterpret_cast<const char16_t*>(str),
+ std::u16string str16(reinterpret_cast<const char16_t*>(str),
env->GetStringLength(jstr));
env->ReleaseStringCritical(jstr, str);
return str16;
}
-jstring string16ToJstring(JNIEnv* env, String16 string) {
- const char16_t* str = string.string();
- size_t len = string.size();
+jstring string16ToJstring(JNIEnv* env, std::u16string string) {
+ const char16_t* str = string.data();
+ size_t len = string.length();
return env->NewString(reinterpret_cast<const jchar*>(str), len);
}
@@ -48,7 +51,7 @@
static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JNIEnv* /* env */,
jobject) {
if (proxyResolver == NULL) {
- proxyResolver = new net::ProxyResolverV8(net::ProxyResolverJSBindings::CreateDefault());
+ proxyResolver = ProxyResolverV8Handle_new();
pacSet = false;
return JNI_FALSE;
}
@@ -58,7 +61,7 @@
static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(JNIEnv* /* env */,
jobject) {
if (proxyResolver != NULL) {
- delete proxyResolver;
+ ProxyResolverV8Handle_delete(proxyResolver);
proxyResolver = NULL;
return JNI_FALSE;
}
@@ -67,14 +70,14 @@
static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JNIEnv* env, jobject,
jstring script) {
- String16 script16 = jstringToString16(env, script);
+ std::u16string script16 = jstringToString16(env, script);
if (proxyResolver == NULL) {
ALOGE("V8 Parser not started when setting PAC script");
return JNI_TRUE;
}
- if (proxyResolver->SetPacScript(script16) != OK) {
+ if (ProxyResolverV8Handle_SetPacScript(proxyResolver, script16.data()) != OK) {
ALOGE("Unable to set PAC script");
return JNI_TRUE;
}
@@ -85,9 +88,8 @@
static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(JNIEnv* env, jobject,
jstring url, jstring host) {
- String16 url16 = jstringToString16(env, url);
- String16 host16 = jstringToString16(env, host);
- String16 ret;
+ std::u16string url16 = jstringToString16(env, url);
+ std::u16string host16 = jstringToString16(env, host);
if (proxyResolver == NULL) {
ALOGE("V8 Parser not initialized when running PAC script");
@@ -99,12 +101,14 @@
return NULL;
}
- if (proxyResolver->GetProxyForURL(url16, host16, &ret) != OK) {
- String8 ret8(ret);
- ALOGE("Error Running PAC: %s", ret8.string());
+ std::unique_ptr<char16_t, decltype(&free)> result = std::unique_ptr<char16_t, decltype(&free)>(
+ ProxyResolverV8Handle_GetProxyForURL(proxyResolver, url16.data(), host16.data()), &free);
+ if (result.get() == NULL) {
+ ALOGE("Error Running PAC");
return NULL;
}
+ std::u16string ret(result.get());
jstring jret = string16ToJstring(env, ret);
return jret;
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 665773c..aff5e13 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -5325,7 +5325,7 @@
// 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 @@
// 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 01734f4..31385ed 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -17,13 +17,16 @@
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 0000000..a7be587
--- /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 0000000..833eaa0
--- /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 0000000..ed7cc67
--- /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 0000000..24d592c
--- /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 0000000..45ea86f
--- /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/art-profile b/services/art-profile
index af9d7a9..7e4884b 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -6970,6 +6970,7 @@
PLcom/android/server/am/ActivityManagerService$LocalService;->getMemoryStateForProcesses()Ljava/util/List;
PLcom/android/server/am/ActivityManagerService$LocalService;->getTopVisibleActivities()Ljava/util/List;
PLcom/android/server/am/ActivityManagerService$LocalService;->getUidProcessState(I)I
+PLcom/android/server/am/ActivityManagerService$LocalService;->isAppForeground(I)Z
PLcom/android/server/am/ActivityManagerService$LocalService;->isCallerRecents(I)Z
PLcom/android/server/am/ActivityManagerService$LocalService;->isRuntimeRestarted()Z
PLcom/android/server/am/ActivityManagerService$LocalService;->isSystemReady()Z
@@ -7125,7 +7126,6 @@
PLcom/android/server/am/ActivityManagerService;->installEncryptionUnawareProviders(I)V
PLcom/android/server/am/ActivityManagerService;->installSystemProviders()V
PLcom/android/server/am/ActivityManagerService;->isAllowedWhileBooting(Landroid/content/pm/ApplicationInfo;)Z
-PLcom/android/server/am/ActivityManagerService;->isAppForeground(I)Z
PLcom/android/server/am/ActivityManagerService;->isAssistDataAllowedOnCurrentActivity()Z
PLcom/android/server/am/ActivityManagerService;->isGetTasksAllowed(Ljava/lang/String;II)Z
PLcom/android/server/am/ActivityManagerService;->isInLockTaskMode()Z
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index d7a2365..6b0adfb 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -850,19 +850,29 @@
mFullBackupQueue = readFullBackupSchedule();
}
- // Register for broadcasts about package install, etc., so we can
- // update the provider list.
+ // Register for broadcasts about package changes.
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver,
+ UserHandle.of(mUserId),
+ filter,
+ /* broadcastPermission */ null,
+ /* scheduler */ null);
+
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiver(mBroadcastReceiver, sdFilter);
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver,
+ UserHandle.of(mUserId),
+ sdFilter,
+ /* broadcastPermission */ null,
+ /* scheduler */ null);
}
private ArrayList<FullBackupEntry> readFullBackupSchedule() {
@@ -1127,17 +1137,23 @@
}
}
- // ----- Track installation/removal of packages -----
+ /**
+ * A {@link BroadcastReceiver} tracking changes to packages and sd cards in order to update our
+ * internal bookkeeping.
+ */
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
- if (MORE_DEBUG) Slog.d(TAG, "Received broadcast " + intent);
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Received broadcast " + intent);
+ }
String action = intent.getAction();
boolean replacing = false;
boolean added = false;
boolean changed = false;
Bundle extras = intent.getExtras();
- String[] pkgList = null;
+ String[] packageList = null;
+
if (Intent.ACTION_PACKAGE_ADDED.equals(action)
|| Intent.ACTION_PACKAGE_REMOVED.equals(action)
|| Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
@@ -1145,69 +1161,70 @@
if (uri == null) {
return;
}
- final String pkgName = uri.getSchemeSpecificPart();
- if (pkgName != null) {
- pkgList = new String[]{pkgName};
- }
- changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
- // At package-changed we only care about looking at new transport states
+ String packageName = uri.getSchemeSpecificPart();
+ if (packageName != null) {
+ packageList = new String[]{packageName};
+ }
+
+ changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
if (changed) {
- final String[] components =
+ // Look at new transport states for package changed events.
+ String[] components =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (MORE_DEBUG) {
- Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+ Slog.i(TAG, "Package " + packageName + " changed");
for (int i = 0; i < components.length; i++) {
Slog.i(TAG, " * " + components[i]);
}
}
mBackupHandler.post(
- () -> mTransportManager.onPackageChanged(pkgName, components));
- return; // nothing more to do in the PACKAGE_CHANGED case
+ () -> mTransportManager.onPackageChanged(packageName, components));
+ return;
}
added = Intent.ACTION_PACKAGE_ADDED.equals(action);
replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
added = true;
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
added = false;
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
}
- if (pkgList == null || pkgList.length == 0) {
+ if (packageList == null || packageList.length == 0) {
return;
}
- final int uid = extras.getInt(Intent.EXTRA_UID);
+ int uid = extras.getInt(Intent.EXTRA_UID);
if (added) {
synchronized (mBackupParticipants) {
if (replacing) {
- // This is the package-replaced case; we just remove the entry
- // under the old uid and fall through to re-add. If an app
- // just added key/value backup participation, this picks it up
- // as a known participant.
- removePackageParticipantsLocked(pkgList, uid);
+ // Remove the entry under the old uid and fall through to re-add. If an app
+ // just opted into key/value backup, add it as a known participant.
+ removePackageParticipantsLocked(packageList, uid);
}
- addPackageParticipantsLocked(pkgList);
+ addPackageParticipantsLocked(packageList);
}
- // If they're full-backup candidates, add them there instead
- final long now = System.currentTimeMillis();
- for (final String packageName : pkgList) {
+
+ long now = System.currentTimeMillis();
+ for (String packageName : packageList) {
try {
- PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
+ PackageInfo app =
+ mPackageManager.getPackageInfoAsUser(
+ packageName, /* flags */ 0, mUserId);
if (AppBackupUtils.appGetsFullBackup(app)
&& AppBackupUtils.appIsEligibleForBackup(
app.applicationInfo, mPackageManager)) {
enqueueFullBackup(packageName, now);
scheduleNextFullBackupJob(0);
} else {
- // The app might have just transitioned out of full-data into
- // doing key/value backups, or might have just disabled backups
- // entirely. Make sure it is no longer in the full-data queue.
+ // The app might have just transitioned out of full-data into doing
+ // key/value backups, or might have just disabled backups entirely. Make
+ // sure it is no longer in the full-data queue.
synchronized (mQueueLock) {
dequeueFullBackupLocked(packageName);
}
@@ -1216,32 +1233,28 @@
mBackupHandler.post(
() -> mTransportManager.onPackageAdded(packageName));
-
} catch (NameNotFoundException e) {
- // doesn't really exist; ignore it
if (DEBUG) {
Slog.w(TAG, "Can't resolve new app " + packageName);
}
}
}
- // Whenever a package is added or updated we need to update
- // the package metadata bookkeeping.
+ // Whenever a package is added or updated we need to update the package metadata
+ // bookkeeping.
dataChangedImpl(PACKAGE_MANAGER_SENTINEL);
} else {
- if (replacing) {
- // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
- } else {
- // Outright removal. In the full-data case, the app will be dropped
- // from the queue when its (now obsolete) name comes up again for
- // backup.
+ if (!replacing) {
+ // Outright removal. In the full-data case, the app will be dropped from the
+ // queue when its (now obsolete) name comes up again for backup.
synchronized (mBackupParticipants) {
- removePackageParticipantsLocked(pkgList, uid);
+ removePackageParticipantsLocked(packageList, uid);
}
}
- for (final String pkgName : pkgList) {
+
+ for (String packageName : packageList) {
mBackupHandler.post(
- () -> mTransportManager.onPackageRemoved(pkgName));
+ () -> mTransportManager.onPackageRemoved(packageName));
}
}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 4b24ef9..dc0f602 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.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 @@
/**
* 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 @@
}
@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 0000000..fc09d2e
--- /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 0000000..58dbea4
--- /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 0000000..e34f1ea
--- /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 0000000..385bc6c
--- /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 0000000..bf48d76
--- /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 7bbc543..a333381 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -279,7 +279,7 @@
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 66ceae4..d0666b9 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_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.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.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.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.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.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.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.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.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 @@
*/
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 @@
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 @@
// 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 @@
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 @@
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 @@
}
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 @@
}
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 @@
}
}
+ 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 @@
// 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 @@
// 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 @@
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 @@
return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
}
+ @Override
+ public NetworkRequest getDefaultRequest() {
+ return mDefaultRequest;
+ }
+
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
@@ -3247,7 +3328,9 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
// 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 @@
}
if (capabilitiesChanged) {
+ try {
+ nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
@@ -5739,7 +5870,15 @@
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 @@
@Override
public String getCaptivePortalServerUrl() {
enforceConnectivityInternalPermission();
- return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext);
+ return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext);
}
@Override
@@ -6113,12 +6252,6 @@
}
@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/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 2346cfc..869d564 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.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.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.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.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 @@
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 @@
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 @@
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 @@
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);
-
- // listen for background/foreground changes
- ActivityManager.OnUidImportanceListener uidImportanceListener =
- (uid, importance) -> mLocationHandler.post(
- () -> onUidImportanceChanged(uid, importance));
- mActivityManager.addOnUidImportanceListener(uidImportanceListener,
- FOREGROUND_IMPORTANCE_CUTOFF);
-
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- updateUserProfiles(mCurrentUserId);
-
- updateBackgroundThrottlingWhitelistLocked();
- updateLastLocationMaxAgeLocked();
-
- // prepare providers
- loadProvidersLocked();
- updateProvidersSettingsLocked();
- for (LocationProvider provider : mProviders) {
- applyRequirementsLocked(provider.getName());
- }
+ initializeLocked();
}
+ }
- // listen for settings changes
+ @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);
+
+ mLocationFudger = new LocationFudger(mContext, mHandler);
+ mBlacklist = new LocationBlacklist(mContext, mHandler);
+ mBlacklist.init();
+ mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
+
+ // prepare providers
+ initializeProvidersLocked();
+
+ // 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();
+ }
+ });
+
+ 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.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ onProviderAllowedChangedLocked(true);
}
}
}, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
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) {
- for (LocationProvider provider : mProviders) {
- applyRequirementsLocked(provider.getName());
- }
+ onBackgroundThrottleIntervalChangedLocked();
}
}
}, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS),
- true,
- new ContentObserver(mLocationHandler) {
- @Override
- public void onChange(boolean selfChange) {
- synchronized (mLock) {
- updateLastLocationMaxAgeLocked();
- }
- }
- }
- );
- 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 @@
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) {
+ @GuardedBy("mLock")
+ private void onAppOpChangedLocked() {
+ for (Receiver receiver : mReceivers.values()) {
+ receiver.updateMonitoring(true);
+ }
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
+ }
+ }
+
+ @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);
+ }
+ }
+
+ // perform removal outside of mReceivers loop
+ if (deadReceivers != null) {
+ for (Receiver receiver : deadReceivers) {
+ removeUpdatesLocked(receiver);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void onUidImportanceChangedLocked(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);
-
- if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
- affectedProviders.add(provider);
- }
- }
- }
- }
- for (String provider : affectedProviders) {
- applyRequirementsLocked(provider);
- }
-
- for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) {
- Identity callerIdentity = entry.getValue();
- if (callerIdentity.mUid == uid) {
+ 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)"));
- }
- if (foreground || isThrottlingExemptLocked(entry.getValue())) {
- mGnssMeasurementsProvider.addListener(
- IGnssMeasurementsListener.Stub.asInterface(entry.getKey()),
- callerIdentity.mUid, callerIdentity.mPackageName);
- } else {
- mGnssMeasurementsProvider.removeListener(
- IGnssMeasurementsListener.Stub.asInterface(entry.getKey()));
- }
- }
- }
-
- 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 "
+ Log.d(TAG, "request 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()));
+ record.updateForeground(foreground);
+
+ if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+ affectedProviders.add(provider);
}
}
}
+ }
+ for (String provider : affectedProviders) {
+ applyRequirementsLocked(provider);
+ }
- // TODO(b/120449926): The GNSS status listeners should be handled similar to the above.
+ 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()));
+ }
+ }
+ }
+
+ 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 @@
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 @@
+ "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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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) {
- mName = name;
- // TODO: initialize settings enabled?
+ this(name, false);
}
- @Override
- public void onAttachProvider(AbstractLocationProvider provider, boolean initiallyEnabled) {
- checkState(mProvider == null);
+ private LocationProvider(String name, boolean isManagedBySettings) {
+ mName = name;
+ mIsManagedBySettings = isManagedBySettings;
- // 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;
+ mProvider = null;
+ mUseable = false;
+ mAllowed = !mIsManagedBySettings;
+ mEnabled = false;
mProperties = null;
}
+ @GuardedBy("mLock")
+ public void attachLocked(AbstractLocationProvider provider) {
+ checkNotNull(provider);
+ checkState(mProvider == null);
+ mProvider = provider;
+
+ 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;
- mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
- UserHandle.ALL);
+ // 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);
+ }
+ }
+
+ 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);
+ }
+
+ // binder transactions coming from below LMS (ie location providers) need to be moved onto
+ // a different thread to avoid potential deadlock as code reenters the location providers
+ private void runInternal(Runnable runnable) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+ }
+
+ private class MockLocationProvider extends LocationProvider {
+
+ 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);
}
}
}
- private void runOnHandler(Runnable runnable) {
- if (Looper.myLooper() == mLocationHandler.getLooper()) {
- runnable.run();
- } else {
- mLocationHandler.post(runnable);
+ @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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
-
- /**
- * Returns the model name of the GNSS hardware.
- */
@Override
@Nullable
public String getGnssHardwareModelName() {
@@ -1306,32 +1573,24 @@
}
}
- /**
- * 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 @@
}
}
- /**
- * 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 @@
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 @@
}
}
- /**
- * 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 @@
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 @@
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);
- }
- } else {
- Slog.w(TAG, "reportLocationBatch() called without user permission, locations blocked");
- }
- }
-
- private void addProviderLocked(LocationProvider provider) {
- mProviders.add(provider);
- mProvidersByName.put(provider.getName(), provider);
- }
-
- private void removeProviderLocked(LocationProvider provider) {
- mProviders.remove(provider);
- mProvidersByName.remove(provider.getName());
- }
-
- /**
- * Returns "true" if access to the specified location provider is allowed by the 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);
+ if (mGnssBatchingProvider != null) {
+ mGnssBatchingInProgress = false;
+ return mGnssBatchingProvider.stop();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void addProviderLocked(LocationProvider provider) {
+ Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
+
+ mProviders.add(provider);
+
+ provider.onAllowedChangedLocked(); // allowed state may change while provider was inactive
+ provider.onUseableChangedLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void removeProviderLocked(LocationProvider provider) {
+ if (mProviders.remove(provider)) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ provider.onUseableChangedLocked();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private LocationProvider getLocationProviderLocked(String providerName) {
+ for (LocationProvider provider : mProviders) {
+ if (providerName.equals(provider.getName())) {
+ return provider;
}
}
- long identity = Binder.clearCallingIdentity();
- try {
- // Use system settings
- ContentResolver cr = mContext.getContentResolver();
- String allowedProviders = Settings.Secure.getStringForUser(
- cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId);
- return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ return null;
}
-
- /**
- * 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;
- }
- return isAllowedByUserSettingsLockedForUser(provider, userId);
- }
-
- /**
- * 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 @@
}
}
- /**
- * 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 @@
}
}
- /**
- * 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 @@
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 @@
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 @@
}
}
- /**
- * 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 @@
*/
@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 @@
@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 @@
*/
@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;
- }
- providers = getProviders(null, 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);
}
- if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + null);
+ if (!providers.isEmpty()) {
+ if (providers.contains(GPS_PROVIDER)) {
+ return GPS_PROVIDER;
+ } else if (providers.contains(NETWORK_PROVIDER)) {
+ return NETWORK_PROVIDER;
+ } else {
+ return providers.get(0);
+ }
+ }
+
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 @@
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 @@
// 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 @@
}
if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest);
- p.setRequest(providerRequest, worksource);
+ provider.setRequestLocked(providerRequest, worksource);
}
/**
@@ -1995,34 +2119,11 @@
@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 @@
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 @@
}
}
+ @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 @@
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 @@
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) {
- Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid,
- packageName, workSource, hideFromAppOps);
- requestLocationUpdatesLocked(sanitizedRequest, recevier, uid, packageName);
+ 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);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
+ 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();
+
+ 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);
+ }
}
}
+ @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 @@
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 @@
oldRecord.disposeLocked(false);
}
- if (provider.isEnabled()) {
+ if (provider.isUseableLocked()) {
applyRequirementsLocked(name);
} else {
// Notify the listener that updates are currently disabled
@@ -2308,14 +2409,23 @@
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 @@
}
}
+ @GuardedBy("mLock")
private void removeUpdatesLocked(Receiver receiver) {
if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
@@ -2356,51 +2467,53 @@
}
}
- 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
+ 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);
+ 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 @@
// 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 @@
} 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 @@
}
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 @@
if (request == null) request = DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
- checkPendingIntent(intent);
- 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);
-
- 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;
+ if (intent == null) {
+ throw new IllegalArgumentException("invalid pending intent: " + null);
}
- long identity = Binder.clearCallingIdentity();
- try {
- mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
- uid, packageName);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ checkPackageName(packageName);
+ 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);
+ }
+
+ // 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 @@
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 @@
}
@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);
-
- // 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;
+ checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
+ providerName);
- p.sendExtraCommand(command, extras);
+ // 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");
+ }
+
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ if (provider != null) {
+ provider.sendExtraCommandLocked(command, extras);
+ }
+
return true;
}
}
@@ -2707,44 +2809,29 @@
}
}
- /**
- * @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);
+ LocationProvider provider = getLocationProviderLocked(NETWORK_PROVIDER);
+ if (provider == null) {
+ return null;
+ }
+ return provider.getPackageLocked();
}
-
- if (p == null) {
- return null;
- }
- if (p.mProvider instanceof LocationProviderProxy) {
- return ((LocationProviderProxy) p.mProvider).getConnectedPackageName();
- }
- return null;
}
@Override
@@ -2780,241 +2867,96 @@
}
}
- /**
- * Returns the current location enabled/disabled status for a user
- *
- * @param userId the id of the user
- * @return true if location is enabled
- */
+ private boolean isLocationEnabled() {
+ return isLocationEnabledForUser(mCurrentUserId);
+ }
+
@Override
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 String allowedProviders = Settings.Secure.getStringForUser(
+ 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,
- userId);
- if (allowedProviders == null) {
- return false;
+ 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);
}
- 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;
}
+ return enabled;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- /**
- * 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");
-
- // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
- checkInteractAcrossUsersPermission(userId);
-
- 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(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- locationProvidersAllowed.toString(),
- userId);
- }
- } 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;
+ if (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);
+ synchronized (mLock) {
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ return provider != null && provider.isUseableForUserLocked(userId);
}
}
- /**
- * 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");
- }
- }
- }
-
- /**
- * 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
// 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 @@
}
}
- /**
- * 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 @@
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 @@
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);
}
+
+ MockLocationProvider mockProviderManager = new MockLocationProvider(name);
+ addProviderLocked(mockProviderManager);
+ mockProviderManager.attachLocked(new MockProvider(mockProviderManager, properties));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- 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");
- }
-
- 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 @@
}
@Override
- public void setTestProviderLocation(String provider, Location loc, String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
- return;
- }
-
- synchronized (mLock) {
- MockProvider mockProvider = mMockProviders.get(provider);
- if (mockProvider == null) {
- throw new IllegalArgumentException("Provider \"" + provider + "\" 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.
- EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
- provider + "!=" + loc.getProvider());
- }
-
- // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required
- long identity = Binder.clearCallingIdentity();
- try {
- mockProvider.setLocation(mock);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void setTestProviderEnabled(String provider, 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);
- }
- }
- }
-
- @Override
- public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime,
+ 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");
}
- mockProvider.setStatus(status, extras, updateTime);
+
+ 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(),
+ providerName + "!=" + location.getProvider());
+ }
+
+ ((MockLocationProvider) testProvider).setLocationLocked(location);
}
}
- private void log(String log) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.d(TAG, log);
+ @Override
+ public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
+ if (!canCallerAccessMockLocation(opPackageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ LocationProvider testProvider = getLocationProviderLocked(providerName);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
+ }
+
+ ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
+ }
+ }
+
+ @Override
+ public void setTestProviderStatus(String providerName, int status, Bundle extras,
+ long updateTime, String opPackageName) {
+ if (!canCallerAccessMockLocation(opPackageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ LocationProvider testProvider = getLocationProviderLocked(providerName);
+ if (testProvider == null || !testProvider.isMock()) {
+ throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
+ }
+
+ ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime);
}
}
@@ -3494,6 +3350,7 @@
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 @@
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 @@
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 f27d373..8adc416 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.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.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 @@
// 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 @@
// 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 @@
// 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 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 @@
* 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 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,24 +237,23 @@
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);
+ 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);
- }
+ }
+ // Save interested observers and notify them outside the lock
+ if (!observersToNotify.isEmpty()) {
+ packagesToReport.put(packages[pIndex], observersToNotify);
}
}
}
@@ -223,8 +261,11 @@
// 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 @@
// 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 @@
}
}
Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
+
return shortestDurationMs;
}
@@ -409,6 +453,8 @@
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/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 9f353a8..ea6d435 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -141,6 +141,7 @@
private boolean mLowPowerMode;
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
+ private int mRingIntensity;
native static boolean vibratorExists();
native static void vibratorInit();
@@ -149,6 +150,8 @@
native static boolean vibratorSupportsAmplitudeControl();
native static void vibratorSetAmplitude(int amplitude);
native static long vibratorPerformEffect(long effect, long strength);
+ static native boolean vibratorSupportsExternalControl();
+ static native void vibratorSetExternalControl(boolean enabled);
private final IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
@@ -426,6 +429,10 @@
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) {
@@ -532,12 +539,6 @@
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;
}
@@ -575,6 +576,13 @@
}
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 {
@@ -745,7 +753,9 @@
}
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;
@@ -767,7 +777,9 @@
}
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();
@@ -930,6 +942,9 @@
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
@@ -1278,6 +1293,7 @@
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 ed39d83..8ca4193 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1654,6 +1654,10 @@
}
}
+ 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 4f21ee8..bb239ac 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_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.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 @@
}
/**
+ * 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 @@
*/
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 @@
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
similarity index 94%
rename from services/core/java/com/android/server/AppOpsService.java
rename to services/core/java/com/android/server/appop/AppOpsService.java
index 3cdf09e..7ede6dc 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.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.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.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.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 @@
@VisibleForTesting
final SparseArray<UidState> mUidStates = new SparseArray<>();
+ private final HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
long mLastRealtime;
/*
@@ -654,6 +658,7 @@
public void systemReady() {
mConstants.startMonitoring(mContext.getContentResolver());
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
synchronized (this) {
boolean changed = false;
@@ -1012,113 +1017,99 @@
}
@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;
-
- 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;
- }
- 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<>();
- }
- historicalPackageOpsList.add(historicalPackageOps);
- }
- }
+ 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);
}
-
- 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;
+ 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 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);
+ }
+ }
+ }
+ }
+ }
+ final Bundle payload = new Bundle();
+ payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, ops);
+ callback.sendResult(payload);
}
-
- 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");
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
- 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);
-
- // 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 @@
+ 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 @@
+ 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 @@
+ 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 @@
+ 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 0000000..8d7811f
--- /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/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
new file mode 100644
index 0000000..4901d3a
--- /dev/null
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAppOpsTestCases",
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 905f826..9d6628c 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 @@
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 36ca4dc..fc1e326 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 @@
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 @@
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 @@
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 @@
boolean getFaceEnabledForApps() {
return mFaceEnabledForApps;
}
+
+ boolean getFaceAlwaysRequireConfirmation() {
+ return mFaceAlwaysRequireConfirmation;
+ }
}
private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
@@ -395,7 +414,7 @@
// 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 +431,20 @@
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);
@@ -579,8 +601,10 @@
}
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 +626,7 @@
modality |= pair.getKey();
}
- if (!mContinuing) {
+ if (!continuing) {
mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId);
mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
@@ -706,7 +730,8 @@
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 +750,9 @@
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 +776,10 @@
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/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 72f73f6..f4d8d4b 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -156,7 +156,7 @@
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/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
index 24865bc..6fa98b8 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 @@
* @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 b8f057d..d8bb635 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 @@
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.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.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.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 @@
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 262184b..54c89aa 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.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 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 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 @@
// 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 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 @@
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 @@
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/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index ea01a0b..6f5a196 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 @@
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 @@
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 @@
"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 de6cdd4..e777ce8 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.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 @@
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 @@
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 11faa56..2da698b 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 @@
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 7860122..528e0a4 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -27,10 +27,13 @@
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 +54,11 @@
// 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;
@@ -434,10 +442,14 @@
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;
}
@@ -1033,4 +1045,68 @@
pw.println("mActiveSource: " + mActiveSource);
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 7358eaa..048a0e4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -20,10 +20,12 @@
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.HdmiDeviceInfo;
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;
@@ -32,11 +34,13 @@
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";
@@ -60,16 +64,16 @@
// 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 +81,17 @@
// 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
@@ -143,43 +152,17 @@
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
@@ -323,7 +306,7 @@
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 +316,23 @@
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 +347,8 @@
@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 +358,8 @@
@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 +391,7 @@
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 +407,16 @@
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 +432,17 @@
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 +470,13 @@
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;
+ // Init arc whenever System Audio Mode is on
+ // Since some TV like LG don't request ARC on with System Audio Mode on request
+ if (newSystemAudioMode
+ && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
+ && !isArcEnabled() && isDirectConnectToTv()) {
+ addAndStartAction(new ArcInitiationActionFromAvr(this));
}
- 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;
}
protected void switchToAudioInput() {
@@ -534,7 +520,7 @@
return;
}
- if (setSystemAudioMode(false)) {
+ if (checkSupportAndSetSystemAudioMode(false)) {
// send <Set System Audio Mode> [“Off”]
mService.sendCecCommand(
HdmiCecMessageBuilder.buildSetSystemAudioMode(
@@ -557,6 +543,7 @@
* <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 +552,37 @@
}
}
+ /**
+ * 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;
}
@@ -589,13 +607,134 @@
mAutoDeviceOff = autoDeviceOff;
}
- private void routeToPort(int portId) {
- // TODO(AMYJOJO): route to specific input of the port
- mLocalActivePath = portId;
+ @Override
+ protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
+ int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ // Wake up if the new Active Source is the current device or under it
+ // or if System Audio Control is enabled.
+ if ((isSystemAudioActivated() || port >= 0) && mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+
+ 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);
+ }
}
- @VisibleForTesting
- protected int getLocalActivePath() {
- return mLocalActivePath;
+ 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;
+ }
+ // TODO(amyjojo): handle if switching to the current input
+ 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));
+ 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);
+ }
+
+ // 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
+ 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);
+ }
+ }
+
+ // Handle the system audio(ARC) part of the logic on receiving routing change or information.
+ private void handleRoutingChangeAndInformationForSystemAudio() {
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ // TODO(b/115637145): handle system aduio without ARC
+ routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
+ }
+
+ // Handle the routing control part of the logic on receiving routing change or information.
+ private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ if (getLocalActivePort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
+ routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
+ if (mService.playback() != null) {
+ mService.playback().setAndBroadcastActiveSource(
+ message, mService.getPhysicalAddress());
+ } else {
+ setAndBroadcastActiveSource(message, mService.getPhysicalAddress());
+ }
+ return;
+ }
+
+ int routingInformationPath =
+ getActivePathOnSwitchFromActivePortId(getLocalActivePort());
+ // 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(getLocalActivePort());
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d45b00b..379cc16 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -26,6 +26,7 @@
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 +36,10 @@
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 {
+final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
private static final String TAG = "HdmiCecLocalDevicePlayback";
private static final boolean WAKE_ON_HOTPLUG =
@@ -62,6 +61,11 @@
// 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 +104,6 @@
}
@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);
@@ -189,7 +174,7 @@
}
@ServiceThreadOnly
- void setActiveSource(boolean on) {
+ void setIsActiveSource(boolean on) {
assertRunOnServiceThread();
mIsActiveSource = on;
if (on) {
@@ -227,21 +212,6 @@
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();
@@ -254,10 +224,21 @@
protected boolean handleSetStreamPath(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- maySetActiveSource(physicalAddress);
- maySendActiveSource(message.getSource());
- wakeUpIfActiveSource();
- return true; // Broadcast message.
+ // If current device is the target path, set to Active Source.
+ // If the path is under the current device, should switch
+ int port = getLocalPortFromPhysicalAddress(physicalAddress);
+ if (port == 0) {
+ setIsActiveSource(true);
+ maySendActiveSource(message.getSource());
+ wakeUpIfActiveSource();
+ } else if (port > 0) {
+ // Wake up the device if the power is in standby mode for routing
+ if (mService.isPowerStandbyOrTransient()) {
+ mService.wakeUp();
+ }
+ routeToPort(port);
+ }
+ return true;
}
// Samsung model we tested sends <Routing Change> and <Request Active Source>
@@ -282,10 +263,11 @@
}
private void maySetActiveSource(int physicalAddress) {
- setActiveSource(physicalAddress == mService.getPhysicalAddress());
+ setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
}
- private void wakeUpIfActiveSource() {
+ @Override
+ protected void wakeUpIfActiveSource() {
if (!mIsActiveSource) {
return;
}
@@ -296,7 +278,8 @@
}
}
- private void maySendActiveSource(int dest) {
+ @Override
+ protected void maySendActiveSource(int dest) {
if (mIsActiveSource) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
mAddress, mService.getPhysicalAddress()));
@@ -306,14 +289,6 @@
}
}
- @Override
- @ServiceThreadOnly
- protected boolean handleRequestActiveSource(HdmiCecMessage message) {
- assertRunOnServiceThread();
- maySendActiveSource(message.getSource());
- return true; // Broadcast message.
- }
-
@ServiceThreadOnly
protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
@@ -361,16 +336,6 @@
@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 +344,20 @@
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 0000000..8d55299
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -0,0 +1,254 @@
+/*
+ * 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.RemoteException;
+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
+ private 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);
+
+ // Local active port number used for Routing Control.
+ // This records the default active port or the previous valid active 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 local active path
+ @GuardedBy("mLock")
+ @LocalActivePort
+ private 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
+ private void invokeCallback(IHdmiControlCallback callback, int result) {
+ assertRunOnServiceThread();
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Invoking callback failed:" + e);
+ }
+ }
+
+ @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);
+ }
+ 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 the parent class
+ // since we 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) {
+ // If the device has both playback and audio system logical addresses,
+ // playback will claim active source. Otherwise audio system will.
+ HdmiCecLocalDevice deviceToBeActiveSource = mService.playback();
+ if (deviceToBeActiveSource == null) {
+ deviceToBeActiveSource = mService.audioSystem();
+ }
+ if (this == deviceToBeActiveSource) {
+ ActiveSource activeSource = ActiveSource.of(mAddress, physicalAddress);
+ setIsActiveSource(true);
+ setActiveSource(activeSource);
+ wakeUpIfActiveSource();
+ maySendActiveSource(message.getSource());
+ }
+ }
+
+ @ServiceThreadOnly
+ void setIsActiveSource(boolean on) {
+ assertRunOnServiceThread();
+ mIsActiveSource = on;
+ }
+
+ @ServiceThreadOnly
+ // Check if current device is the Active Source
+ boolean isActiveSource() {
+ assertRunOnServiceThread();
+ return mIsActiveSource;
+ }
+
+ 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()));
+ }
+ }
+
+ @VisibleForTesting
+ protected void setLocalActivePort(@LocalActivePort int portId) {
+ synchronized (mLock) {
+ mLocalActivePort = portId;
+ }
+ }
+
+ // To get the local active port to switch to
+ // when receivng routing change or information.
+ @LocalActivePort
+ protected int getLocalActivePort() {
+ synchronized (mLock) {
+ return mLocalActivePort;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 941c321..3f949ba 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 @@
}
/**
+ * 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 e3a4084..903045d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -140,6 +140,10 @@
static final int STANDBY_SCREEN_OFF = 0;
static final int STANDBY_SHUTDOWN = 1;
+ private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
+ SystemProperties.getBoolean(
+ Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
+
/**
* Interface to report send result.
*/
@@ -413,7 +417,7 @@
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);
@@ -639,6 +643,10 @@
// 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);
@@ -1003,6 +1011,10 @@
if (connected && !isTvDevice()) {
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);
@@ -1651,6 +1663,9 @@
}
HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
if (device == null) {
+ device = audioSystem();
+ }
+ if (device == null) {
Slog.w(TAG, "Local device not available");
return;
}
@@ -1830,9 +1845,13 @@
@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 +2114,15 @@
return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
+ boolean isPlaybackDevice() {
+ return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
+ }
+
boolean isTvDeviceEnabled() {
return isTvDevice() && tv() != null;
}
- private HdmiCecLocalDevicePlayback playback() {
+ protected HdmiCecLocalDevicePlayback playback() {
return (HdmiCecLocalDevicePlayback)
mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 5c66316..46611dd 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 @@
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");
@@ -84,8 +84,12 @@
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.setIsActiveSource(true);
+ source.setActiveSource(getSourceAddress(), getSourcePath());
+ // Set local active port to HOME when One Touch Play.
+ source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
}
private void queryDevicePowerStatus() {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
index 2fdcb51..a6e6965 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;
/**
@@ -91,7 +92,7 @@
mSendRequestActiveSourceRetryCount++;
sendRequestActiveSource();
} else {
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
}
@@ -106,7 +107,7 @@
mSendSetSystemAudioModeRetryCount++;
sendSetSystemAudioMode(on, dest);
} else {
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
}
@@ -115,7 +116,7 @@
private void handleActiveSourceTimeout() {
HdmiLogger.debug("Cannot get active source.");
- audioSystem().setSystemAudioMode(false);
+ audioSystem().checkSupportAndSetSystemAudioMode(false);
finish();
}
@@ -123,12 +124,12 @@
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 d96b6cb..8810d1d 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 @@
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 @@
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,
@@ -1951,6 +1926,11 @@
}
// Native callback.
+ private int getPointerDisplayId() {
+ return mWindowManagerCallbacks.getPointerDisplayId();
+ }
+
+ // Native callback.
private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
if (!mSystemReady) {
return null;
@@ -2017,6 +1997,8 @@
KeyEvent event, int policyFlags);
public int getPointerLayer();
+
+ public int getPointerDisplayId();
}
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 3264790..d4b8eb2 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 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 @@
@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 d45869e..5fa3f52 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.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 @@
// 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 @@
+ 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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
@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 @@
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 void showInputMethodPickerFromClient(
IInputMethodClient client, int auxiliarySubtypeMode) {
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
if(!canShowInputMethodPickerLocked(client)) {
@@ -3103,7 +3200,7 @@
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 @@
@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 @@
}
}
synchronized (mMethodMap) {
- if (!calledFromValidUserLocked()) {
+ if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
return;
}
if (!mSystemReady) {
@@ -4102,7 +4199,7 @@
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 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 @@
}
}
+ 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 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 @@
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 @@
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 1137bf9..88d1a9c 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.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 @@
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 98ed3ea..f304ceb 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 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 4c7c420..b3f1018 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.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 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 @@
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/GnssConfiguration.java b/services/core/java/com/android/server/location/GnssConfiguration.java
new file mode 100644
index 0000000..29465ad
--- /dev/null
+++ b/services/core/java/com/android/server/location/GnssConfiguration.java
@@ -0,0 +1,365 @@
+/*
+ * 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.location;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.os.SystemProperties;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+/**
+ * A utility class to hold GNSS configuration properties.
+ *
+ * The trigger to load/reload the configuration parameters should be managed by the class
+ * that owns an instance of this class.
+ *
+ * Instances of this class are not thread-safe and should either be used from a single thread
+ * or with external synchronization when used by multiple threads.
+ */
+class GnssConfiguration {
+ private static final String TAG = "GnssConfiguration";
+
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ //TODO(b/33112647): Create gps_debug.conf with commented career parameters.
+ private static final String DEBUG_PROPERTIES_FILE = "/etc/gps_debug.conf";
+
+ // config.xml properties
+ private static final String CONFIG_SUPL_HOST = "SUPL_HOST";
+ private static final String CONFIG_SUPL_PORT = "SUPL_PORT";
+ private static final String CONFIG_C2K_HOST = "C2K_HOST";
+ private static final String CONFIG_C2K_PORT = "C2K_PORT";
+ private static final String CONFIG_SUPL_VER = "SUPL_VER";
+ private static final String CONFIG_SUPL_MODE = "SUPL_MODE";
+ private static final String CONFIG_SUPL_ES = "SUPL_ES";
+ private static final String CONFIG_LPP_PROFILE = "LPP_PROFILE";
+ private static final String CONFIG_A_GLONASS_POS_PROTOCOL_SELECT =
+ "A_GLONASS_POS_PROTOCOL_SELECT";
+ private static final String CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL =
+ "USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL";
+ private static final String CONFIG_GPS_LOCK = "GPS_LOCK";
+ private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC";
+
+ // Limit on NI emergency mode time extension after emergency sessions ends
+ private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300; // 5 minute maximum
+
+ // Persist property for LPP_PROFILE
+ static final String LPP_PROFILE = "persist.sys.gps.lpp";
+
+ // Represents an HAL interface version. Instances of this class are created in the JNI layer
+ // and returned through native methods.
+ private static class HalInterfaceVersion {
+ final int mMajor;
+ final int mMinor;
+
+ HalInterfaceVersion(int major, int minor) {
+ mMajor = major;
+ mMinor = minor;
+ }
+ }
+
+ /**
+ * Properties loaded from PROPERTIES_FILE.
+ */
+ private Properties mProperties;
+
+ private int mEsExtensionSec = 0;
+
+ private final Context mContext;
+
+ GnssConfiguration(Context context) {
+ mContext = context;
+ mProperties = new Properties();
+ }
+
+ /**
+ * Returns the full set of properties loaded.
+ */
+ Properties getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Returns the value of config parameter ES_EXTENSION_SEC. The value is range checked
+ * and constrained to min/max limits.
+ */
+ int getEsExtensionSec() {
+ return mEsExtensionSec;
+ }
+
+ /**
+ * Returns the value of config parameter SUPL_HOST or {@code null} if no value is
+ * provided.
+ */
+ String getSuplHost() {
+ return mProperties.getProperty(CONFIG_SUPL_HOST);
+ }
+
+ /**
+ * Returns the value of config parameter SUPL_PORT or {@code defaultPort} if no value is
+ * provided or if there is an error parsing the configured value.
+ */
+ int getSuplPort(int defaultPort) {
+ return getIntConfig(CONFIG_SUPL_PORT, defaultPort);
+ }
+
+ /**
+ * Returns the value of config parameter C2K_HOST or {@code null} if no value is
+ * provided.
+ */
+ String getC2KHost() {
+ return mProperties.getProperty(CONFIG_C2K_HOST);
+ }
+
+ /**
+ * Returns the value of config parameter C2K_PORT or {@code defaultPort} if no value is
+ * provided or if there is an error parsing the configured value.
+ */
+ int getC2KPort(int defaultPort) {
+ return getIntConfig(CONFIG_C2K_PORT, defaultPort);
+ }
+
+ /**
+ * Returns the value of config parameter SUPL_MODE or {@code defaultMode} if no value is
+ * provided or if there is an error parsing the configured value.
+ */
+ int getSuplMode(int defaultMode) {
+ return getIntConfig(CONFIG_SUPL_MODE, defaultMode);
+ }
+
+ /**
+ * Returns the value of config parameter SUPL_ES or {@code defaultSuplEs} if no value is
+ * provided or if there is an error parsing the configured value.
+ */
+ int getSuplEs(int defaulSuplEs) {
+ return getIntConfig(CONFIG_SUPL_ES, defaulSuplEs);
+ }
+
+ /**
+ * Returns the value of config parameter LPP_PROFILE or {@code null} if no value is
+ * provided.
+ */
+ String getLppProfile() {
+ return mProperties.getProperty(CONFIG_LPP_PROFILE);
+ }
+
+ /**
+ * Updates the GNSS HAL satellite blacklist.
+ */
+ void setSatelliteBlacklist(int[] constellations, int[] svids) {
+ native_set_satellite_blacklist(constellations, svids);
+ }
+
+ interface SetCarrierProperty {
+ boolean set(int value);
+ }
+
+ /**
+ * Loads the GNSS properties from carrier config file followed by the properties from
+ * gps debug config file.
+ */
+ void reloadGpsProperties() {
+ if (DEBUG) Log.d(TAG, "Reset GPS properties, previous size = " + mProperties.size());
+ loadPropertiesFromCarrierConfig();
+
+ String lpp_prof = SystemProperties.get(LPP_PROFILE);
+ if (!TextUtils.isEmpty(lpp_prof)) {
+ // override default value of this if lpp_prof is not empty
+ mProperties.setProperty(CONFIG_LPP_PROFILE, lpp_prof);
+ }
+ /*
+ * Overlay carrier properties from a debug configuration file.
+ */
+ loadPropertiesFromGpsDebugConfig(mProperties);
+
+ mEsExtensionSec = getRangeCheckedConfigEsExtensionSec();
+
+ final HalInterfaceVersion gnssConfigurationIfaceVersion =
+ native_get_gnss_configuration_version();
+ if (gnssConfigurationIfaceVersion != null) {
+ // Set to a range checked value.
+ if (isConfigEsExtensionSecSupported(gnssConfigurationIfaceVersion)
+ && !native_set_es_extension_sec(mEsExtensionSec)) {
+ Log.e(TAG, "Unable to set " + CONFIG_ES_EXTENSION_SEC + ": " + mEsExtensionSec);
+ }
+
+ Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
+ {
+ put(CONFIG_SUPL_VER, GnssConfiguration::native_set_supl_version);
+ put(CONFIG_SUPL_MODE, GnssConfiguration::native_set_supl_mode);
+
+ if (isConfigSuplEsSupported(gnssConfigurationIfaceVersion)) {
+ put(CONFIG_SUPL_ES, GnssConfiguration::native_set_supl_es);
+ }
+
+ put(CONFIG_LPP_PROFILE, GnssConfiguration::native_set_lpp_profile);
+ put(CONFIG_A_GLONASS_POS_PROTOCOL_SELECT,
+ GnssConfiguration::native_set_gnss_pos_protocol_select);
+ put(CONFIG_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL,
+ GnssConfiguration::native_set_emergency_supl_pdn);
+
+ if (isConfigGpsLockSupported(gnssConfigurationIfaceVersion)) {
+ put(CONFIG_GPS_LOCK, GnssConfiguration::native_set_gps_lock);
+ }
+ }
+ };
+
+ for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
+ String propertyName = entry.getKey();
+ String propertyValueString = mProperties.getProperty(propertyName);
+ if (propertyValueString != null) {
+ try {
+ int propertyValueInt = Integer.decode(propertyValueString);
+ boolean result = entry.getValue().set(propertyValueInt);
+ if (!result) {
+ Log.e(TAG, "Unable to set " + propertyName);
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Unable to parse propertyName: " + propertyValueString);
+ }
+ }
+ }
+ } else if (DEBUG) {
+ Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not"
+ + " supported");
+ }
+ }
+
+ /**
+ * Loads GNSS properties from carrier config file.
+ */
+ void loadPropertiesFromCarrierConfig() {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager == null) {
+ return;
+ }
+ PersistableBundle configs = configManager.getConfigForSubId(
+ SubscriptionManager.getDefaultDataSubscriptionId());
+ if (configs == null) {
+ if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config.");
+ configs = CarrierConfigManager.getDefaultConfig();
+ }
+ for (String configKey : configs.keySet()) {
+ if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
+ String key = configKey
+ .substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
+ .toUpperCase();
+ Object value = configs.get(configKey);
+ if (value instanceof String) {
+ // All GPS properties are of String type; convert so.
+ if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
+ mProperties.setProperty(key, (String) value);
+ }
+ }
+ }
+ }
+
+ private void loadPropertiesFromGpsDebugConfig(Properties properties) {
+ try {
+ File file = new File(DEBUG_PROPERTIES_FILE);
+ FileInputStream stream = null;
+ try {
+ stream = new FileInputStream(file);
+ properties.load(stream);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ } catch (IOException e) {
+ if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE);
+ }
+ }
+
+ private int getRangeCheckedConfigEsExtensionSec() {
+ int emergencyExtensionSeconds = getIntConfig(CONFIG_ES_EXTENSION_SEC, 0);
+ if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
+ Log.w(TAG, CONFIG_ES_EXTENSION_SEC + ": " + emergencyExtensionSeconds
+ + " too high, reset to " + MAX_EMERGENCY_MODE_EXTENSION_SECONDS);
+ emergencyExtensionSeconds = MAX_EMERGENCY_MODE_EXTENSION_SECONDS;
+ } else if (emergencyExtensionSeconds < 0) {
+ Log.w(TAG, CONFIG_ES_EXTENSION_SEC + ": " + emergencyExtensionSeconds
+ + " is negative, reset to zero.");
+ emergencyExtensionSeconds = 0;
+ }
+ return emergencyExtensionSeconds;
+ }
+
+ private int getIntConfig(String configParameter, int defaultValue) {
+ String valueString = mProperties.getProperty(configParameter);
+ if (TextUtils.isEmpty(valueString)) {
+ return defaultValue;
+ }
+ try {
+ return Integer.parseInt(valueString);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Unable to parse config parameter " + configParameter + " value: "
+ + valueString + ". Using default value: " + defaultValue);
+ return defaultValue;
+ }
+ }
+
+ private static boolean isConfigEsExtensionSecSupported(
+ HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+ // ES_EXTENSION_SEC is introduced in @2.0::IGnssConfiguration.hal
+ return gnssConfiguartionIfaceVersion.mMajor >= 2;
+ }
+
+ private static boolean isConfigSuplEsSupported(
+ HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+ // SUPL_ES is deprecated in @2.0::IGnssConfiguration.hal
+ return gnssConfiguartionIfaceVersion.mMajor < 2;
+ }
+
+ private static boolean isConfigGpsLockSupported(
+ HalInterfaceVersion gnssConfiguartionIfaceVersion) {
+ // GPS_LOCK is deprecated in @2.0::IGnssConfiguration.hal
+ return gnssConfiguartionIfaceVersion.mMajor < 2;
+ }
+
+ private static native HalInterfaceVersion native_get_gnss_configuration_version();
+
+ private static native boolean native_set_supl_version(int version);
+
+ private static native boolean native_set_supl_mode(int mode);
+
+ private static native boolean native_set_supl_es(int es);
+
+ private static native boolean native_set_lpp_profile(int lppProfile);
+
+ private static native boolean native_set_gnss_pos_protocol_select(int gnssPosProtocolSelect);
+
+ private static native boolean native_set_gps_lock(int gpsLock);
+
+ private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn);
+
+ private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds);
+
+ private static native boolean native_set_es_extension_sec(int emergencyExtensionSeconds);
+}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 05d77ab..269767a 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -74,12 +74,7 @@
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
-import libcore.io.IoUtils;
-
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -87,11 +82,7 @@
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
/**
* A GNSS implementation of LocationProvider used by LocationManager.
@@ -147,7 +138,6 @@
private static final int LOCATION_HAS_SPEED_ACCURACY = 64;
private static final int LOCATION_HAS_BEARING_ACCURACY = 128;
-
// IMPORTANT - the GPS_DELETE_* symbols here must match GnssAidingData enum in IGnss.hal
private static final int GPS_DELETE_EPHEMERIS = 0x0001;
private static final int GPS_DELETE_ALMANAC = 0x0002;
@@ -200,9 +190,6 @@
private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1;
private static final int AGPS_RIL_REQUEST_SETID_MSISDN = 2;
- //TODO(b/33112647): Create gps_debug.conf with commented career parameters.
- private static final String DEBUG_PROPERTIES_FILE = "/etc/gps_debug.conf";
-
// ref. location info
private static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1;
private static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2;
@@ -375,7 +362,7 @@
* Properties loaded from PROPERTIES_FILE.
* It must be accessed only inside {@link #mHandler}.
*/
- private Properties mProperties;
+ private GnssConfiguration mGnssConfiguration;
private String mSuplServerHost;
private int mSuplServerPort = TCP_MIN_PORT;
@@ -411,10 +398,6 @@
private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP";
private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT";
- // Persist property for LPP_PROFILE
- private final static String LPP_PROFILE = "persist.sys.gps.lpp";
-
-
private final PowerManager mPowerManager;
private final AlarmManager mAlarmManager;
private final PendingIntent mWakeupIntent;
@@ -502,7 +485,7 @@
*/
@Override
public void onUpdateSatelliteBlacklist(int[] constellations, int[] svids) {
- mHandler.post(() -> native_set_satellite_blacklist(constellations, svids));
+ mHandler.post(() -> mGnssConfiguration.setSatelliteBlacklist(constellations, svids));
}
private void subscriptionOrCarrierConfigChanged(Context context) {
@@ -525,17 +508,17 @@
}
if (isKeepLppProfile) {
// load current properties for the carrier
- loadPropertiesFromCarrierConfig(context, mProperties);
- String lpp_profile = mProperties.getProperty("LPP_PROFILE");
+ mGnssConfiguration.loadPropertiesFromCarrierConfig();
+ String lpp_profile = mGnssConfiguration.getLppProfile();
// set the persist property LPP_PROFILE for the value
if (lpp_profile != null) {
- SystemProperties.set(LPP_PROFILE, lpp_profile);
+ SystemProperties.set(GnssConfiguration.LPP_PROFILE, lpp_profile);
}
} else {
// reset the persist property
- SystemProperties.set(LPP_PROFILE, "");
+ SystemProperties.set(GnssConfiguration.LPP_PROFILE, "");
}
- reloadGpsProperties(context, mProperties);
+ reloadGpsProperties();
mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
}
} else {
@@ -564,138 +547,19 @@
return native_is_supported();
}
- interface SetCarrierProperty {
- boolean set(int value);
- }
-
- private void reloadGpsProperties(Context context, Properties properties) {
- if (DEBUG) Log.d(TAG, "Reset GPS properties, previous size = " + properties.size());
- loadPropertiesFromCarrierConfig(context, properties);
-
- String lpp_prof = SystemProperties.get(LPP_PROFILE);
- if (!TextUtils.isEmpty(lpp_prof)) {
- // override default value of this if lpp_prof is not empty
- properties.setProperty("LPP_PROFILE", lpp_prof);
- }
- /*
- * Overlay carrier properties from a debug configuration file.
- */
- loadPropertiesFromGpsDebugConfig(properties);
+ private void reloadGpsProperties() {
+ mGnssConfiguration.reloadGpsProperties();
+ setSuplHostPort();
// TODO: we should get rid of C2K specific setting.
- setSuplHostPort(properties.getProperty("SUPL_HOST"),
- properties.getProperty("SUPL_PORT"));
- mC2KServerHost = properties.getProperty("C2K_HOST");
- String portString = properties.getProperty("C2K_PORT");
- if (mC2KServerHost != null && portString != null) {
- try {
- mC2KServerPort = Integer.parseInt(portString);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse C2K_PORT: " + portString);
- }
- }
- if (native_is_gnss_configuration_supported()) {
- Map<String, SetCarrierProperty> map = new HashMap<String, SetCarrierProperty>() {
- {
- put("SUPL_VER", GnssLocationProvider::native_set_supl_version);
- put("SUPL_MODE", GnssLocationProvider::native_set_supl_mode);
- put("SUPL_ES", GnssLocationProvider::native_set_supl_es);
- put("LPP_PROFILE", GnssLocationProvider::native_set_lpp_profile);
- put("A_GLONASS_POS_PROTOCOL_SELECT",
- GnssLocationProvider::native_set_gnss_pos_protocol_select);
- put("USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL",
- GnssLocationProvider::native_set_emergency_supl_pdn);
- put("GPS_LOCK", GnssLocationProvider::native_set_gps_lock);
- }
- };
-
- for (Entry<String, SetCarrierProperty> entry : map.entrySet()) {
- String propertyName = entry.getKey();
- String propertyValueString = properties.getProperty(propertyName);
- if (propertyValueString != null) {
- try {
- int propertyValueInt = Integer.decode(propertyValueString);
- boolean result = entry.getValue().set(propertyValueInt);
- if (!result) {
- Log.e(TAG, "Unable to set " + propertyName);
- }
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse propertyName: " + propertyValueString);
- }
- }
- }
- } else if (DEBUG) {
- Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not"
- + " supported");
- }
-
- // SUPL_ES configuration.
- String suplESProperty = mProperties.getProperty("SUPL_ES");
- if (suplESProperty != null) {
- try {
- mSuplEsEnabled = (Integer.parseInt(suplESProperty) == 1);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse SUPL_ES: " + suplESProperty);
- }
- }
-
- String emergencyExtensionSecondsString
- = properties.getProperty("ES_EXTENSION_SEC", "0");
- try {
- int emergencyExtensionSeconds =
- Integer.parseInt(emergencyExtensionSecondsString);
- mNIHandler.setEmergencyExtensionSeconds(emergencyExtensionSeconds);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse ES_EXTENSION_SEC: "
- + emergencyExtensionSecondsString);
- }
- }
-
- private void loadPropertiesFromCarrierConfig(Context context, Properties properties) {
- CarrierConfigManager configManager = (CarrierConfigManager)
- mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configManager == null) {
- return;
- }
- PersistableBundle configs = configManager.getConfigForSubId(
- SubscriptionManager.getDefaultDataSubscriptionId());
- if (configs == null) {
- if (DEBUG) Log.d(TAG, "SIM not ready, use default carrier config.");
- configs = CarrierConfigManager.getDefaultConfig();
- }
- for (String configKey : configs.keySet()) {
- if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
- String key = configKey
- .substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
- .toUpperCase();
- Object value = configs.get(configKey);
- if (value instanceof String) {
- // All GPS properties are of String type; convert so.
- if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
- properties.setProperty(key, (String) value);
- }
- }
- }
- }
-
- private void loadPropertiesFromGpsDebugConfig(Properties properties) {
- try {
- File file = new File(DEBUG_PROPERTIES_FILE);
- FileInputStream stream = null;
- try {
- stream = new FileInputStream(file);
- properties.load(stream);
- } finally {
- IoUtils.closeQuietly(stream);
- }
-
- } catch (IOException e) {
- if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE);
- }
+ mC2KServerHost = mGnssConfiguration.getC2KHost();
+ mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT);
+ mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec());
+ mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1;
}
public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
Looper looper) {
- super(locationProviderManager, true);
+ super(locationProviderManager);
mContext = context;
@@ -733,7 +597,7 @@
// relative long time, so the ctor() is kept to create objects needed by this instance,
// while IO initialization and registration is delegated to our internal handler
// this approach is just fine because events are posted to our handler anyway
- mProperties = new Properties();
+ mGnssConfiguration = new GnssConfiguration(mContext);
sendMessage(INITIALIZE_HANDLER, 0, null);
// Create a GPS net-initiated handler.
@@ -788,6 +652,7 @@
}, UserHandle.ALL, intentFilter, null, mHandler);
setProperties(PROPERTIES);
+ setEnabled(true);
}
/**
@@ -918,7 +783,8 @@
mDownloadXtraWakeLock.acquire(DOWNLOAD_XTRA_DATA_TIMEOUT_MS);
Log.i(TAG, "WakeLock acquired by handleDownloadXtraData()");
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
- GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mProperties);
+ GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(
+ mGnssConfiguration.getProperties());
byte[] data = xtraDownloader.downloadXtraData();
if (data != null) {
if (DEBUG) Log.d(TAG, "calling native_inject_xtra_data");
@@ -963,17 +829,9 @@
}
}
- private void setSuplHostPort(String hostString, String portString) {
- if (hostString != null) {
- mSuplServerHost = hostString;
- }
- if (portString != null) {
- try {
- mSuplServerPort = Integer.parseInt(portString);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
- }
- }
+ private void setSuplHostPort() {
+ mSuplServerHost = mGnssConfiguration.getSuplHost();
+ mSuplServerPort = mGnssConfiguration.getSuplPort(TCP_MIN_PORT);
if (mSuplServerHost != null
&& mSuplServerPort > TCP_MIN_PORT
&& mSuplServerPort <= TCP_MAX_PORT) {
@@ -986,23 +844,17 @@
* Checks what SUPL mode to use, according to the AGPS mode as well as the
* allowed mode from properties.
*
- * @param properties GPS properties
* @param agpsEnabled whether AGPS is enabled by settings value
* @param singleShot whether "singleshot" is needed
* @return SUPL mode (MSA vs MSB vs STANDALONE)
*/
- private int getSuplMode(Properties properties, boolean agpsEnabled, boolean singleShot) {
+ private int getSuplMode(boolean agpsEnabled, boolean singleShot) {
if (agpsEnabled) {
- String modeString = properties.getProperty("SUPL_MODE");
- int suplMode = 0;
- if (!TextUtils.isEmpty(modeString)) {
- try {
- suplMode = Integer.parseInt(modeString);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse SUPL_MODE: " + modeString);
- return GPS_POSITION_MODE_STANDALONE;
- }
+ int suplMode = mGnssConfiguration.getSuplMode(0);
+ if (suplMode == 0) {
+ return GPS_POSITION_MODE_STANDALONE;
}
+
// MS-Based is the preferred mode for Assisted-GPS position computation, so we favor
// such mode when it is available
if (hasCapability(GPS_CAPABILITY_MSB) && (suplMode & AGPS_SUPL_MODE_MSB) != 0) {
@@ -1307,7 +1159,7 @@
boolean agpsEnabled =
(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0);
- mPositionMode = getSuplMode(mProperties, agpsEnabled, singleShot);
+ mPositionMode = getSuplMode(agpsEnabled, singleShot);
if (DEBUG) {
String mode;
@@ -1670,7 +1522,7 @@
// re-calls native_init() and other setup.
handleEnable();
// resend configuration into the restarted HAL service.
- reloadGpsProperties(mContext, mProperties);
+ reloadGpsProperties();
}
});
}
@@ -2072,7 +1924,7 @@
// load default GPS configuration
// (this configuration might change in the future based on SIM changes)
- reloadGpsProperties(mContext, mProperties);
+ reloadGpsProperties();
// TODO: When this object "finishes" we should unregister by invoking
// SubscriptionManager.getInstance(mContext).unregister
@@ -2227,8 +2079,6 @@
private static native boolean native_is_supported();
- private static native boolean native_is_gnss_configuration_supported();
-
private static native void native_init_once();
private native boolean native_init();
@@ -2284,21 +2134,4 @@
int lac, int cid);
private native void native_agps_set_id(int type, String setid);
-
- // GNSS Configuration
- private static native boolean native_set_supl_version(int version);
-
- private static native boolean native_set_supl_mode(int mode);
-
- private static native boolean native_set_supl_es(int es);
-
- private static native boolean native_set_lpp_profile(int lppProfile);
-
- private static native boolean native_set_gnss_pos_protocol_select(int gnssPosProtocolSelect);
-
- private static native boolean native_set_gps_lock(int gpsLock);
-
- private static native boolean native_set_emergency_supl_pdn(int emergencySuplPdn);
-
- private static native boolean native_set_satellite_blacklist(int[] constellations, int[] svIds);
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index dfcef70..a6da8c5 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 @@
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/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index bfbebf7..86bc9f3 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 MockProvider(
LocationProviderManager locationProviderManager, ProviderProperties properties) {
- super(locationProviderManager, true);
+ super(locationProviderManager);
mEnabled = true;
mLocation = null;
@@ -52,6 +52,7 @@
mExtras = null;
setProperties(properties);
+ setEnabled(true);
}
/** Sets the enabled state of this mock provider. */
@@ -63,8 +64,11 @@
/** 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 70d64b0..30260b2 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 @@
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 b70c64e..d611a17 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 @@
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.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.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 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 @@
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 @@
// 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
@@ -116,11 +116,11 @@
private boolean mIsActive = false;
private boolean mDestroyed = false;
- private long mDuration;
+ private long mDuration = -1;
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 @@
* @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 @@
}
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 ISessionCallback getCallback() {
+ public SessionCallbackLink getCallback() {
return mSessionCb.mCb;
}
@@ -468,7 +468,7 @@
+ ", 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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
@Override
- public void setQueue(ParceledListSlice queue) {
+ public void setQueue(List<QueueItem> queue) {
synchronized (mLock) {
mQueue = queue;
}
@@ -920,9 +943,9 @@
}
class SessionCb {
- private final ISessionCallback mCb;
+ private final SessionCallbackLink mCb;
- public SessionCb(ISessionCallback cb) {
+ SessionCb(SessionCallbackLink cb) {
mCb = cb;
}
@@ -930,224 +953,224 @@
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 @@
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 @@
}
@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 @@
@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 @@
@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 @@
}
@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 @@
}
@Override
- public ParceledListSlice getQueue() {
+ public List<QueueItem> getQueue() {
synchronized (mLock) {
return mQueue;
}
@@ -1432,12 +1455,12 @@
}
}
- 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 052d579..ce0e72b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,20 +40,23 @@
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
+import android.media.MediaController2;
+import android.media.Session2CommandGroup;
import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
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;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
@@ -73,6 +76,7 @@
import android.view.KeyEvent;
import android.view.ViewConfiguration;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -97,17 +101,24 @@
private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
private final SessionManagerImpl mSessionManagerImpl;
-
- // Keeps the full user id for each user.
- private final SparseIntArray mFullUserIds = new SparseIntArray();
- private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
- private final ArrayList<SessionsListenerRecord> mSessionsListeners
- = new ArrayList<SessionsListenerRecord>();
- private final Object mLock = new Object();
private final MessageHandler mHandler = new MessageHandler();
private final PowerManager.WakeLock mMediaEventWakeLock;
private final int mLongPressTimeout;
private final INotificationManager mNotificationManager;
+ private final Object mLock = new Object();
+ // Keeps the full user id for each user.
+ @GuardedBy("mLock")
+ private final SparseIntArray mFullUserIds = new SparseIntArray();
+ @GuardedBy("mLock")
+ private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>();
+ @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 SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
@@ -295,10 +306,13 @@
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) {
@@ -308,6 +322,7 @@
user.destroySessionsForUserLocked(userId);
}
}
+ mSession2TokensPerUser.remove(userId);
updateUser();
}
}
@@ -353,6 +368,9 @@
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.
@@ -362,6 +380,9 @@
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);
}
@@ -415,7 +436,7 @@
}
try {
- session.getCallback().asBinder().unlinkToDeath(session, 0);
+ session.getCallback().getBinder().unlinkToDeath(session, 0);
} catch (Exception e) {
// ignore exceptions while destroying a session.
}
@@ -501,7 +522,7 @@
}
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);
}
@@ -515,7 +536,7 @@
* 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);
@@ -525,7 +546,7 @@
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);
}
@@ -722,6 +743,16 @@
pw.println(indent + "Restored MediaButtonReceiverComponentType: "
+ mRestoredMediaButtonReceiverComponentType);
mPriorityStack.dump(pw, indent);
+ 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);
+ }
+ }
}
@Override
@@ -876,7 +907,7 @@
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();
@@ -904,7 +935,17 @@
if (DEBUG) {
Log.d(TAG, "Session2 is created " + sessionToken);
}
- // TODO: Keep the session.
+ if (uid != sessionToken.getUid()) {
+ throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+ + " but actually=" + sessionToken.getUid());
+ }
+ Controller2Callback callback = new Controller2Callback(sessionToken);
+ // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
+ // it's closed.
+ // TODO: Keep controller as well for better readability
+ // because the GC behavior isn't straightforward.
+ MediaController2 controller = new MediaController2(getContext(), sessionToken,
+ new HandlerExecutor(mHandler), callback);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -932,6 +973,34 @@
}
@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();
@@ -1930,4 +1999,28 @@
obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
}
}
+
+ private class Controller2Callback extends MediaController2.ControllerCallback {
+ private final Session2Token mToken;
+
+ Controller2Callback(Session2Token token) {
+ mToken = token;
+ }
+
+ @Override
+ public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mToken.getUid());
+ mSession2TokensPerUser.get(userId).add(mToken);
+ }
+ }
+
+ @Override
+ public void onDisconnected(MediaController2 controller) {
+ synchronized (mLock) {
+ 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 0000000..e241591
--- /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 0000000..faa4714
--- /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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7ca39df..d0b20e8 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 @@
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 @@
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 @@
}
}
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 5cb6c34..516927f 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 @@
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 @@
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 @@
}
mPrepared = prepared;
-
+ mStagedSessionReady = isReady;
+ mStagedSessionFailed = isFailed;
+ mStagedSessionApplied = isApplied;
+ mStagedSessionErrorCode = stagedSessionErrorCode;
if (sealed) {
synchronized (mLock) {
try {
@@ -2023,8 +2031,9 @@
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 @@
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 @@
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 3301962..2dc5437 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 @@
}
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 2329356..c297c62 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 @@
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 @@
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 @@
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();
+ void preRebootVerification(@NonNull PackageInstallerSession session) {
+ boolean success = true;
+ if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
- 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);
+ 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 @@
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 dd04652..aaa1874 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 @@
&& 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 e212b04..437ef73 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/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index 244ccb6..5cbe74c 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 565bb706..a027873 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.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 02689a9..7d03d82 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 @@
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 @@
}
}
+
+ 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 6400c88..1d74350 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.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 @@
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 @@
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 @@
}
}
- /**
- * 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 @@
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
similarity index 98%
rename from services/core/java/com/android/server/power/BatterySaverPolicy.java
rename to services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index cedb548..48a041e 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.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 @@
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 @@
// 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 a60f16d..b7f28da 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.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 @@
}
/**
- * 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 8213205..79b44eb 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.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/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index fc21adb..7c1e619 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 @@
}
@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 @@
}
}
- @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 2cab63a..ef77140 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 @@
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 65d5b10..7236d79 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.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 @@
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 @@
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 @@
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 410f864..fcc8284 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.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 @@
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 @@
}
}
+ 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 6f8f85f..f8f0d1c 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 @@
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 @@
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 0f286ce..d8644df 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 @@
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 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 42121ca..e5bf21a 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.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 @@
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/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 639ed02..f9c9d33 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -1,5 +1,6 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -9,7 +10,6 @@
import android.os.Debug;
import android.os.IBinder;
import android.util.Slog;
-import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.WindowManager;
@@ -204,6 +204,37 @@
+ WindowManagerService.TYPE_LAYER_OFFSET;
}
+ /** Callback to get pointer display id. */
+ @Override
+ public int getPointerDisplayId() {
+ synchronized (mService.mGlobalLock) {
+ // If desktop mode is not enabled, show on the default display.
+ if (!mService.mForceDesktopModeOnExternalDisplays) {
+ return DEFAULT_DISPLAY;
+ }
+
+ // Look for the topmost freeform display.
+ int firstExternalDisplayId = DEFAULT_DISPLAY;
+ for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
+ final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
+ // Heuristic solution here. Currently when "Freeform windows" developer option is
+ // enabled we automatically put secondary displays in freeform mode and emulating
+ // "desktop mode". It also makes sense to show the pointer on the same display.
+ if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return displayContent.getDisplayId();
+ }
+
+ if (firstExternalDisplayId == DEFAULT_DISPLAY
+ && displayContent.getDisplayId() != DEFAULT_DISPLAY) {
+ firstExternalDisplayId = displayContent.getDisplayId();
+ }
+ }
+
+ // Look for the topmost non-default display
+ return firstExternalDisplayId;
+ }
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index b4fe837..c8c5e8f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -107,6 +107,7 @@
"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",
@@ -119,6 +120,7 @@
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
"android.hardware.vibrator@1.2",
+ "android.hardware.vibrator@1.3",
"android.hardware.vr@1.0",
"android.frameworks.schedulerservice@1.0",
"android.frameworks.sensorservice@1.0",
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index defcfd9..63dca62 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -22,6 +22,7 @@
#include <android/hardware/vibrator/1.1/types.h>
#include <android/hardware/vibrator/1.2/IVibrator.h>
#include <android/hardware/vibrator/1.2/types.h>
+#include <android/hardware/vibrator/1.3/IVibrator.h>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
@@ -42,6 +43,7 @@
namespace V1_0 = android::hardware::vibrator::V1_0;
namespace V1_1 = android::hardware::vibrator::V1_1;
namespace V1_2 = android::hardware::vibrator::V1_2;
+namespace V1_3 = android::hardware::vibrator::V1_3;
namespace android {
@@ -136,6 +138,19 @@
}
}
+static jboolean vibratorSupportsExternalControl(JNIEnv*, jobject) {
+ return halCall(&V1_3::IVibrator::supportsExternalControl).withDefault(false);
+}
+
+static void vibratorSetExternalControl(JNIEnv*, jobject, jboolean enabled) {
+ Status status = halCall(&V1_3::IVibrator::setExternalControl, static_cast<uint32_t>(enabled))
+ .withDefault(Status::UNKNOWN_ERROR);
+ if (status != Status::OK) {
+ ALOGE("Failed to set vibrator external control (%" PRIu32 ").",
+ static_cast<uint32_t>(status));
+ }
+}
+
static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
Status status;
uint32_t lengthMs;
@@ -187,7 +202,9 @@
{ "vibratorOff", "()V", (void*)vibratorOff },
{ "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
{ "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
- { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
+ { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect},
+ { "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
+ { "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 43d2dcf..6f105ec 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@
jmethodID getLongPressTimeout;
jmethodID getPointerLayer;
jmethodID getPointerIcon;
+ jmethodID getPointerDisplayId;
jmethodID getKeyboardLayoutOverlay;
jmethodID getDeviceAlias;
jmethodID getTouchCalibrationForInputDevice;
@@ -174,15 +175,6 @@
loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon);
}
-static void updatePointerControllerFromViewport(
- sp<PointerController> controller, const DisplayViewport* const viewport) {
- if (controller != nullptr && viewport != nullptr) {
- const int32_t width = viewport->logicalRight - viewport->logicalLeft;
- const int32_t height = viewport->logicalBottom - viewport->logicalTop;
- controller->setDisplayViewport(width, height, viewport->orientation);
- }
-}
-
enum {
WM_ACTION_PASS_TO_USER = 1,
};
@@ -310,14 +302,19 @@
// Input devices to be disabled
SortedVector<int32_t> disabledInputDevices;
+
+ // Associated Pointer controller display.
+ int32_t pointerDisplayId;
} mLocked GUARDED_BY(mLock);
std::atomic<bool> mInteractive;
- void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
+ void updateInactivityTimeoutLocked();
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
-
+ const DisplayViewport* findDisplayViewportLocked(int32_t displayId);
+ int32_t getPointerDisplayId();
+ void updatePointerDisplayLocked();
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
static inline JNIEnv* jniEnv() {
@@ -342,6 +339,7 @@
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
mLocked.pointerCapture = false;
+ mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
}
mInteractive = true;
@@ -391,9 +389,10 @@
return false;
}
-static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) {
- for (const DisplayViewport& v : viewports) {
- if (v.type == ViewportType::VIEWPORT_INTERNAL) {
+const DisplayViewport* NativeInputManager::findDisplayViewportLocked(int32_t displayId)
+ REQUIRES(mLock) {
+ for (const DisplayViewport& v : mLocked.viewports) {
+ if (v.displayId == displayId) {
return &v;
}
}
@@ -420,20 +419,14 @@
}
}
- const DisplayViewport* newInternalViewport = findInternalViewport(viewports);
- {
+ // Get the preferred pointer controller displayId.
+ int32_t pointerDisplayId = getPointerDisplayId();
+
+ { // acquire lock
AutoMutex _l(mLock);
- const DisplayViewport* oldInternalViewport = findInternalViewport(mLocked.viewports);
- // Internal viewport has changed if there wasn't one earlier, and there is one now, or,
- // if they are different.
- const bool internalViewportChanged = (newInternalViewport != nullptr) &&
- (oldInternalViewport == nullptr || (*oldInternalViewport != *newInternalViewport));
- if (internalViewportChanged) {
- sp<PointerController> controller = mLocked.pointerController.promote();
- updatePointerControllerFromViewport(controller, newInternalViewport);
- }
mLocked.viewports = viewports;
- }
+ mLocked.pointerDisplayId = pointerDisplayId;
+ } // release lock
mInputManager->getReader()->requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -556,15 +549,42 @@
controller = new PointerController(this, mLooper, mLocked.spriteController);
mLocked.pointerController = controller;
-
- const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports);
- updatePointerControllerFromViewport(controller, internalViewport);
-
- updateInactivityTimeoutLocked(controller);
+ updateInactivityTimeoutLocked();
}
+
+ updatePointerDisplayLocked();
+
return controller;
}
+int32_t NativeInputManager::getPointerDisplayId() {
+ JNIEnv* env = jniEnv();
+ jint pointerDisplayId = env->CallIntMethod(mServiceObj,
+ gServiceClassInfo.getPointerDisplayId);
+ if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
+ pointerDisplayId = ADISPLAY_ID_DEFAULT;
+ }
+
+ return pointerDisplayId;
+}
+
+void NativeInputManager::updatePointerDisplayLocked() REQUIRES(mLock) {
+ ATRACE_CALL();
+
+ sp<PointerController> controller = mLocked.pointerController.promote();
+ if (controller != nullptr) {
+ const DisplayViewport* viewport = findDisplayViewportLocked(mLocked.pointerDisplayId);
+ if (viewport == nullptr) {
+ ALOGW("Can't find pointer display viewport, fallback to default display.");
+ viewport = findDisplayViewportLocked(ADISPLAY_ID_DEFAULT);
+ }
+
+ if (viewport != nullptr) {
+ controller->setDisplayViewport(*viewport);
+ }
+ }
+}
+
void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) {
if (mLocked.spriteController == nullptr) {
JNIEnv* env = jniEnv();
@@ -821,16 +841,16 @@
if (mLocked.systemUiVisibility != visibility) {
mLocked.systemUiVisibility = visibility;
-
- sp<PointerController> controller = mLocked.pointerController.promote();
- if (controller != nullptr) {
- updateInactivityTimeoutLocked(controller);
- }
+ updateInactivityTimeoutLocked();
}
}
-void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller)
- REQUIRES(mLock) {
+void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) {
+ sp<PointerController> controller = mLocked.pointerController.promote();
+ if (controller == nullptr) {
+ return;
+ }
+
bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
controller->setInactivityTimeout(lightsOut
? PointerController::INACTIVITY_TIMEOUT_SHORT
@@ -1480,27 +1500,6 @@
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);
@@ -1688,8 +1687,6 @@
(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",
@@ -1824,6 +1821,9 @@
GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
"getPointerIcon", "()Landroid/view/PointerIcon;");
+ GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
+ "getPointerDisplayId", "()I");
+
GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
"getKeyboardLayoutOverlay",
"(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 6d8fc1c..b290bc5 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -135,6 +135,7 @@
using IGnss_V2_0 = android::hardware::gnss::V2_0::IGnss;
using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
+using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
using IGnssMeasurement_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurement;
using IGnssMeasurement_V2_0 = android::hardware::gnss::V2_0::IGnssMeasurement;
@@ -179,6 +180,7 @@
sp<IGnssDebug> gnssDebugIface = nullptr;
sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
+sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
sp<IGnssNi> gnssNiIface = nullptr;
sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -313,6 +315,15 @@
}
}
+static jobject createHalInterfaceVersionJavaObject(JNIEnv* env, jint major, jint minor) {
+ jclass versionClass =
+ env->FindClass("com/android/server/location/GnssConfiguration$HalInterfaceVersion");
+ jmethodID versionCtor = env->GetMethodID(versionClass, "<init>", "(II)V");
+ jobject version = env->NewObject(versionClass, versionCtor, major, minor);
+ env->DeleteLocalRef(versionClass);
+ return version;
+}
+
struct ScopedJniString {
ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
@@ -1421,10 +1432,19 @@
gnssNiIface = gnssNi;
}
- if (gnssHal_V1_1 != nullptr) {
+ if (gnssHal_V2_0 != nullptr) {
+ auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
+ if (!gnssConfiguration.isOk()) {
+ ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
+ } else {
+ gnssConfigurationIface_V2_0 = gnssConfiguration;
+ gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_0;
+ gnssConfigurationIface = gnssConfigurationIface_V2_0;
+ }
+ } else if (gnssHal_V1_1 != nullptr) {
auto gnssConfiguration = gnssHal_V1_1->getExtensionGnssConfiguration_1_1();
if (!gnssConfiguration.isOk()) {
- ALOGD("Unable to get a handle to GnssConfiguration");
+ ALOGD("Unable to get a handle to GnssConfiguration_V1_1");
} else {
gnssConfigurationIface_V1_1 = gnssConfiguration;
gnssConfigurationIface = gnssConfigurationIface_V1_1;
@@ -1463,9 +1483,23 @@
return (agnssRilIface != nullptr) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean android_location_gpsLocationProvider_is_gnss_configuration_supported(
- JNIEnv* /* env */, jclass /* jclazz */) {
- return (gnssConfigurationIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
+ JNIEnv* env, jclass /* jclazz */) {
+ jint major, minor;
+ if (gnssConfigurationIface_V2_0 != nullptr) {
+ major = 2;
+ minor = 0;
+ } else if (gnssConfigurationIface_V1_1 != nullptr) {
+ major = 1;
+ minor = 1;
+ } else if (gnssConfigurationIface != nullptr) {
+ major = 1;
+ minor = 0;
+ } else {
+ return nullptr;
+ }
+
+ return createHalInterfaceVersionJavaObject(env, major, minor);
}
static jboolean android_location_GnssLocationProvider_init(JNIEnv* env, jobject obj) {
@@ -2278,9 +2312,9 @@
return boolToJbool(result.isOk());
}
-static jboolean android_location_GnssLocationProvider_set_emergency_supl_pdn(JNIEnv*,
- jobject,
- jint emergencySuplPdn) {
+static jboolean android_location_GnssConfiguration_set_emergency_supl_pdn(JNIEnv*,
+ jobject,
+ jint emergencySuplPdn) {
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2294,7 +2328,7 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_supl_version(JNIEnv*,
+static jboolean android_location_GnssConfiguration_set_supl_version(JNIEnv*,
jobject,
jint version) {
if (gnssConfigurationIface == nullptr) {
@@ -2309,9 +2343,14 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_supl_es(JNIEnv*,
- jobject,
- jint suplEs) {
+static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
+ jobject,
+ jint suplEs) {
+ if (gnssConfigurationIface_V2_0 != nullptr) {
+ ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0.");
+ return JNI_FALSE;
+ }
+
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2325,9 +2364,9 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_supl_mode(JNIEnv*,
- jobject,
- jint mode) {
+static jboolean android_location_GnssConfiguration_set_supl_mode(JNIEnv*,
+ jobject,
+ jint mode) {
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2341,9 +2380,14 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_gps_lock(JNIEnv*,
- jobject,
- jint gpsLock) {
+static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
+ jobject,
+ jint gpsLock) {
+ if (gnssConfigurationIface_V2_0 != nullptr) {
+ ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
+ return JNI_FALSE;
+ }
+
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2357,7 +2401,7 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_lpp_profile(JNIEnv*,
+static jboolean android_location_GnssConfiguration_set_lpp_profile(JNIEnv*,
jobject,
jint lppProfile) {
if (gnssConfigurationIface == nullptr) {
@@ -2374,9 +2418,9 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_gnss_pos_protocol_select(JNIEnv*,
- jobject,
- jint gnssPosProtocol) {
+static jboolean android_location_GnssConfiguration_set_gnss_pos_protocol_select(JNIEnv*,
+ jobject,
+ jint gnssPosProtocol) {
if (gnssConfigurationIface == nullptr) {
ALOGE("no GNSS configuration interface available");
return JNI_FALSE;
@@ -2390,7 +2434,7 @@
}
}
-static jboolean android_location_GnssLocationProvider_set_satellite_blacklist(
+static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
if (gnssConfigurationIface_V1_1 == nullptr) {
ALOGI("No GNSS Satellite Blacklist interface available");
@@ -2431,6 +2475,27 @@
}
}
+static jboolean android_location_GnssConfiguration_set_es_extension_sec(
+ JNIEnv*, jobject, jint emergencyExtensionSeconds) {
+ if (gnssConfigurationIface == nullptr) {
+ ALOGE("no GNSS configuration interface available");
+ return JNI_FALSE;
+ }
+
+ if (gnssConfigurationIface_V2_0 == nullptr) {
+ ALOGI("Config parameter ES_EXTENSION_SEC is not supported in IGnssConfiguration.hal"
+ " versions earlier than 2.0.");
+ return JNI_FALSE;
+ }
+
+ auto result = gnssConfigurationIface_V2_0->setEsExtensionSec(emergencyExtensionSeconds);
+ if (result.isOk()) {
+ return result;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) {
if (gnssBatchingIface == nullptr) {
return 0; // batching not supported, size = 0
@@ -2498,17 +2563,14 @@
android_location_GnssLocationProvider_class_init_native)},
{"native_is_supported", "()Z", reinterpret_cast<void *>(
android_location_GnssLocationProvider_is_supported)},
- {"native_is_gnss_configuration_supported", "()Z",
- reinterpret_cast<void *>(
- android_location_gpsLocationProvider_is_gnss_configuration_supported)},
{"native_init_once", "()V", reinterpret_cast<void *>(
android_location_GnssLocationProvider_init_once)},
{"native_init", "()Z", reinterpret_cast<void *>(android_location_GnssLocationProvider_init)},
{"native_cleanup", "()V", reinterpret_cast<void *>(
android_location_GnssLocationProvider_cleanup)},
{"native_set_position_mode",
- "(IIIIIZ)Z",
- reinterpret_cast<void*>(android_location_GnssLocationProvider_set_position_mode)},
+ "(IIIIIZ)Z",
+ reinterpret_cast<void*>(android_location_GnssLocationProvider_set_position_mode)},
{"native_start", "()Z", reinterpret_cast<void*>(android_location_GnssLocationProvider_start)},
{"native_stop", "()Z", reinterpret_cast<void*>(android_location_GnssLocationProvider_stop)},
{"native_delete_aiding_data",
@@ -2545,31 +2607,6 @@
{"native_get_internal_state",
"()Ljava/lang/String;",
reinterpret_cast<void *>(android_location_GnssLocationProvider_get_internal_state)},
- {"native_set_supl_es",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_es)},
- {"native_set_supl_version",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_version)},
- {"native_set_supl_mode",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_supl_mode)},
- {"native_set_lpp_profile",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_lpp_profile)},
- {"native_set_gnss_pos_protocol_select",
- "(I)Z",
- reinterpret_cast<void *>(
- android_location_GnssLocationProvider_set_gnss_pos_protocol_select)},
- {"native_set_gps_lock",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_gps_lock)},
- {"native_set_emergency_supl_pdn",
- "(I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_emergency_supl_pdn)},
- {"native_set_satellite_blacklist",
- "([I[I)Z",
- reinterpret_cast<void *>(android_location_GnssLocationProvider_set_satellite_blacklist)},
};
static const JNINativeMethod sMethodsBatching[] = {
@@ -2663,6 +2700,42 @@
reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_agps_data_conn_failed)},
};
+static const JNINativeMethod sConfigurationMethods[] = {
+ /* name, signature, funcPtr */
+ {"native_get_gnss_configuration_version",
+ "()Lcom/android/server/location/GnssConfiguration$HalInterfaceVersion;",
+ reinterpret_cast<void *>(
+ android_location_GnssConfiguration_get_gnss_configuration_version)},
+ {"native_set_supl_es",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_es)},
+ {"native_set_supl_version",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_version)},
+ {"native_set_supl_mode",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_supl_mode)},
+ {"native_set_lpp_profile",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_lpp_profile)},
+ {"native_set_gnss_pos_protocol_select",
+ "(I)Z",
+ reinterpret_cast<void *>(
+ android_location_GnssConfiguration_set_gnss_pos_protocol_select)},
+ {"native_set_gps_lock",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_gps_lock)},
+ {"native_set_emergency_supl_pdn",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_emergency_supl_pdn)},
+ {"native_set_satellite_blacklist",
+ "([I[I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_satellite_blacklist)},
+ {"native_set_es_extension_sec",
+ "(I)Z",
+ reinterpret_cast<void *>(android_location_GnssConfiguration_set_es_extension_sec)},
+};
+
int register_android_server_location_GnssLocationProvider(JNIEnv* env) {
jniRegisterNativeMethods(
env,
@@ -2689,6 +2762,11 @@
"com/android/server/location/GnssNetworkConnectivityHandler",
sNetworkConnectivityMethods,
NELEM(sNetworkConnectivityMethods));
+ jniRegisterNativeMethods(
+ env,
+ "com/android/server/location/GnssConfiguration",
+ sConfigurationMethods,
+ NELEM(sConfigurationMethods));
return jniRegisterNativeMethods(
env,
"com/android/server/location/GnssLocationProvider",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7f6895a..76ae5cc 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.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.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 @@
}
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 @@
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 @@
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.LOCK_NOW)
+ .setAdmin(adminComponent)
.setInt(flags)
.write();
}
@@ -6101,7 +6103,9 @@
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 @@
}
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 @@
@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 @@
// 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 d0ec0ee..699bec2 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 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.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 @@
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 @@
// 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 @@
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 @@
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 0000000..013cf56
--- /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 0000000..c9759bf
--- /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 4326c39..e960fd1 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.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;
@@ -260,6 +262,10 @@
"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 +792,11 @@
traceBeginAndSlog("StartRollbackManagerService");
mSystemServiceManager.startService(RollbackManagerService.class);
traceEnd();
+
+ // Service to capture bugreports.
+ traceBeginAndSlog("StartBugreportManagerService");
+ mSystemServiceManager.startService(BugreportManagerService.class);
+ traceEnd();
}
/**
@@ -1020,6 +1031,18 @@
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 +1176,16 @@
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 +1200,15 @@
}
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 +1828,6 @@
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();
@@ -1985,6 +2024,20 @@
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 +2232,7 @@
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 ae697b7..3b4d6a7 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -11,10 +11,10 @@
]
}
-// 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 0000000..463cf2a
--- /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 0000000..41e0bad
--- /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 8b7b59d..2cdb2b0 100644
--- a/services/net/java/android/net/util/SharedLog.java
+++ b/services/net/java/android/net/util/SharedLog.java
@@ -70,6 +70,10 @@
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/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 5e84ab0..7dac795 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -71,6 +71,7 @@
import android.annotation.Nullable;
import android.app.Application;
+import android.app.ApplicationPackageManager;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
@@ -134,6 +135,8 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.shadows.ShadowQueuedWork;
@@ -157,6 +160,7 @@
@Config(
shadows = {
FrameworkShadowLooper.class,
+ KeyValueBackupTaskTest.ShadowApplicationPackageManager.class,
ShadowBackupDataInput.class,
ShadowBackupDataOutput.class,
ShadowEventLog.class,
@@ -244,6 +248,7 @@
@After
public void tearDown() throws Exception {
ShadowBackupDataInput.reset();
+ ShadowApplicationPackageManager.reset();
}
@Test
@@ -2435,7 +2440,8 @@
mPackageManager.setApplicationEnabledSetting(
packageData.packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
PackageInfo packageInfo = getPackageInfo(packageData);
- mShadowPackageManager.addPackage(packageInfo);
+ mShadowPackageManager.installPackage(packageInfo);
+ ShadowApplicationPackageManager.setPackageInfo(packageInfo);
mContext.sendBroadcast(getPackageAddedIntent(packageData));
// Run the backup looper because on the receiver we post MSG_SCHEDULE_BACKUP_PACKAGE
mShadowBackupLooper.runToEndOfTasks();
@@ -2848,4 +2854,29 @@
throw mException;
}
}
+
+ /**
+ * Extends {@link org.robolectric.shadows.ShadowApplicationPackageManager} to return the correct
+ * package in user-specific invocations.
+ */
+ @Implements(value = ApplicationPackageManager.class)
+ public static class ShadowApplicationPackageManager
+ extends org.robolectric.shadows.ShadowApplicationPackageManager {
+ private static PackageInfo sPackageInfo;
+
+ static void setPackageInfo(PackageInfo packageInfo) {
+ sPackageInfo = packageInfo;
+ }
+
+ @Override
+ protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) {
+ return sPackageInfo;
+ }
+
+ /** Clear {@link #sPackageInfo}. */
+ @Resetter
+ public static void reset() {
+ sPackageInfo = null;
+ }
+ }
}
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 6a10ff4..2cc338c 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.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 05cb48b..8109b1c 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.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 9626990..cbdc6c3 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 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
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsActiveWatcherTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index 7e0dfcf..65c5781 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
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsNotedWatcherTest.java
rename to 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
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/appops/AppOpsServiceTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 4ae9bea..36f84d0 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.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
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java
rename to services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index aac96a1..eb0c627 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 34edd9f..757a046 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.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 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 @@
private @Mock
Resources mResources;
- private @Mock
- IInputMethodManager mIInputMethodManager;
+ @Mock
+ private OverlayPackagesProvider.Injector mInjector;
private @Mock
Context mTestContext;
private Resources mRealResources;
@@ -81,6 +79,7 @@
InstrumentationRegistry.getTargetContext().getCacheDir());
setSystemInputMethods();
+ setIsPerProfileModeEnabled(false);
setRequiredAppsManagedDevice();
setVendorRequiredAppsManagedDevice();
setDisallowedAppsManagedDevice();
@@ -95,7 +94,7 @@
setVendorDisallowedAppsManagedUser();
mRealResources = InstrumentationRegistry.getTargetContext().getResources();
- mHelper = new OverlayPackagesProvider(mTestContext, mIInputMethodManager);
+ mHelper = new OverlayPackagesProvider(mTestContext, mInjector);
}
@Test
@@ -165,6 +164,15 @@
}
@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 @@
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/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index bdede33..c442ae8 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 @@
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;
@@ -52,6 +54,7 @@
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 +67,86 @@
@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
+ void wakeUp() {}
- @Override
- boolean isControlEnabled() {
- return true;
- }
- };
+ @Override
+ boolean isControlEnabled() {
+ return true;
+ }
+ };
mMyLooper = mTestLooper.getLooper();
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+ mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
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 +177,12 @@
@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 +198,9 @@
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 +214,11 @@
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 +232,11 @@
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 +252,17 @@
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 +281,15 @@
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();
@@ -292,7 +300,7 @@
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 +317,9 @@
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 +328,9 @@
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 +339,9 @@
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 +350,9 @@
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 +362,12 @@
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 +381,7 @@
@Test
public void terminateSystemAudioMode_systemAudioModeOn() throws Exception {
- mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+ mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
mMusicMute = false;
HdmiCecMessage expectedMessage =
@@ -458,7 +466,7 @@
assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class))
- .isNotEmpty();
+ .isNotEmpty();
}
@Test
@@ -473,7 +481,7 @@
assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class))
- .isNotEmpty();
+ .isNotEmpty();
}
@Test
@@ -521,12 +529,96 @@
assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
}
- @Test
- public void handleSetStreamPath_underCurrentDevice() {
- assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(0);
+ public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
HdmiCecMessage message =
- HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
- assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetStreamPath(message)).isTrue();
- assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(1);
+ 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 handleSystemAudioModeRequest_fromNonTV_tVSupport() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+ ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+ mAvrPhysicalAddress, true);
+ HdmiCecMessage expectedMessage =
+ 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);
+ }
+
+ @Test
+ public void handleRoutingChange_currentActivePortIsHome() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, mAvrPhysicalAddress);
+
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+ ActiveSource expectedActiveSource = ActiveSource.of(ADDR_AUDIO_SYSTEM, mAvrPhysicalAddress);
+ int expectedLocalActivePort = Constants.CEC_SWITCH_HOME;
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource())
+ .isEqualTo(expectedActiveSource);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePort())
+ .isEqualTo(expectedLocalActivePort);
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleRoutingInformation_currentActivePortIsHDMI1() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x2000);
+ mHdmiCecLocalDeviceAudioSystem.setLocalActivePort(Constants.CEC_SWITCH_HDMI1);
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_AUDIO_SYSTEM, 0x2100);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingInformation(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+ }
+
+ @Test
+ public void handleRoutingChange_homeIsActive_playbackSendActiveSource() {
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x2000);
+
+ HdmiCecMessage expectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x2000);
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRoutingChange(message)).isTrue();
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(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 0000000..76f638c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.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);
+ }
+
+ @Test
+ public void handleSetStreamPath_underCurrentDevice() {
+ assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0);
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
+ assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+ 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 c7809d3..ef974f2 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 @@
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 @@
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/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 561c61f..0dff03f 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.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
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
rename to services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index acf511e..73eefcf 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 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 @@
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 @@
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 @@
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 b6f1817..8f9d2ba 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 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 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 @@
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 bd29d2a..5589ca1 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.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 @@
.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 f1c6eab..bb3ab35 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.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 @@
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 @@
.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 c343fe7..8c6ac23 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 @@
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 @@
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/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 38efc74..3d7cbb5 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -657,9 +657,8 @@
return;
}
- if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
- model.setStopped();
- }
+ model.setStopped();
+
try {
callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
} catch (DeadObjectException e) {
@@ -802,9 +801,7 @@
return;
}
- if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
- modelData.setStopped();
- }
+ modelData.setStopped();
try {
modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2820836..dcaa499 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -239,6 +239,30 @@
"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 @@
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 @@
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 @@
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 @@
mExtras,
mIntentExtras,
mCreationTimeMillis,
- mCallIdentification);
+ mCallIdentification,
+ mCallDirection);
}
/** {@hide} */
@@ -902,7 +937,8 @@
Bundle extras,
Bundle intentExtras,
long creationTimeMillis,
- CallIdentification callIdentification) {
+ CallIdentification callIdentification,
+ int callDirection) {
mTelecomCallId = telecomCallId;
mHandle = handle;
mHandlePresentation = handlePresentation;
@@ -920,6 +956,7 @@
mIntentExtras = intentExtras;
mCreationTimeMillis = creationTimeMillis;
mCallIdentification = callIdentification;
+ mCallDirection = callDirection;
}
/** {@hide} */
@@ -941,7 +978,8 @@
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 97af06c..87834fd 100644
--- a/telecomm/java/android/telecom/CallIdentification.java
+++ b/telecomm/java/android/telecom/CallIdentification.java
@@ -250,8 +250,8 @@
mDetails = details;
mPhoto = photo;
mNuisanceConfidence = nuisanceConfidence;
- mCallScreeningAppName = callScreeningPackageName;
- mCallScreeningPackageName = callScreeningAppName;
+ mCallScreeningAppName = callScreeningAppName;
+ mCallScreeningPackageName = callScreeningPackageName;
}
private String mName;
@@ -430,4 +430,22 @@
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 be96b3c..826ad82 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -21,6 +21,7 @@
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 @@
/**
* 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 @@
* 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 @@
}
/**
- * 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 1aeeca7..f5f0af7 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -40,11 +40,30 @@
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 @@
* }
* </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 @@
* 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 @@
* 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 911786e..f7dec83 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -24,6 +24,7 @@
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 @@
private final Bundle mExtras;
private final long mCreationTimeMillis;
private final CallIdentification mCallIdentification;
+ private final int mCallDirection;
public ParcelableCall(
String id,
@@ -92,7 +94,8 @@
Bundle intentExtras,
Bundle extras,
long creationTimeMillis,
- CallIdentification callIdentification) {
+ CallIdentification callIdentification,
+ int callDirection) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -120,6 +123,7 @@
mExtras = extras;
mCreationTimeMillis = creationTimeMillis;
mCallIdentification = callIdentification;
+ mCallDirection = callDirection;
}
/** The unique ID of the call. */
@@ -318,6 +322,13 @@
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 @@
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 @@
intentExtras,
extras,
creationTimeMillis,
- callIdentification);
+ callIdentification,
+ callDirection);
}
@Override
@@ -429,6 +442,7 @@
destination.writeParcelable(mRttCall, 0);
destination.writeLong(mCreationTimeMillis);
destination.writeParcelable(mCallIdentification, 0);
+ destination.writeInt(mCallDirection);
}
@Override
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 9fc1b6f..bf9bf9a 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -170,7 +170,8 @@
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 a1e8b19..51d5ab1 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -174,6 +174,16 @@
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 @@
@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 @@
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 @@
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 @@
this.mIsMetered = isMetered;
this.mIsGroupDisabled = isGroupDisabled;
this.mCarrierId = carrierid;
+ this.mProfileClass = profileClass;
}
@@ -466,6 +478,15 @@
}
/**
+ * @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 @@
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 @@
dest.writeBoolean(mIsMetered);
dest.writeBoolean(mIsGroupDisabled);
dest.writeInt(mCarrierId);
+ dest.writeInt(mProfileClass);
}
@Override
@@ -662,7 +685,8 @@
+ " 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 @@
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 @@
&& 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 2c712a1..34f7abd 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -22,6 +22,7 @@
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.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 @@
* @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 @@
@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/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index a94b163..a5f56bb 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -232,18 +232,21 @@
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 @@
mCountryIso = source.readString();
mMnc = source.readString();
mEmergencyServiceCategoryBitmask = source.readInt();
+ mEmergencyUrns = source.createStringArrayList();
mEmergencyNumberSourceBitmask = source.readInt();
mEmergencyCallRouting = source.readInt();
}
@@ -265,6 +269,7 @@
dest.writeString(mCountryIso);
dest.writeString(mMnc);
dest.writeInt(mEmergencyServiceCategoryBitmask);
+ dest.writeStringList(mEmergencyUrns);
dest.writeInt(mEmergencyNumberSourceBitmask);
dest.writeInt(mEmergencyCallRouting);
}
@@ -345,6 +350,22 @@
}
/**
+ * 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 @@
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 @@
&& 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 @@
@Override
public int hashCode() {
return Objects.hash(mNumber, mCountryIso, mMnc, mEmergencyServiceCategoryBitmask,
- mEmergencyNumberSourceBitmask, mEmergencyCallRouting);
+ mEmergencyUrns, mEmergencyNumberSourceBitmask, mEmergencyCallRouting);
}
/**
@@ -584,6 +607,9 @@
!= second.getEmergencyServiceCategoryBitmask()) {
return false;
}
+ if (first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
+ return false;
+ }
if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) {
return false;
}
@@ -605,6 +631,7 @@
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 6326cc6..cc9befe 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -616,14 +616,13 @@
/**
* 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 @@
}
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 9c8d078..525a96a 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -33,6 +33,8 @@
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 @@
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 @@
+ ", restrictCause=" + mRestrictCause
+ ", mediaProfile=" + mMediaProfile.toString()
+ ", emergencyServiceCategories=" + mEmergencyCallRouting
+ + ", emergencyUrns=" + mEmergencyUrns
+ ", emergencyCallRouting=" + mEmergencyCallRouting + " }";
}
@@ -540,6 +552,7 @@
out.writeBundle(filteredExtras);
out.writeParcelable(mMediaProfile, 0);
out.writeInt(mEmergencyServiceCategories);
+ out.writeStringList(mEmergencyUrns);
out.writeInt(mEmergencyCallRouting);
}
@@ -549,6 +562,7 @@
mCallExtras = in.readBundle();
mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
mEmergencyServiceCategories = in.readInt();
+ mEmergencyUrns = in.createStringArrayList();
mEmergencyCallRouting = in.readInt();
}
@@ -760,20 +774,21 @@
}
/**
- * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 870a689..dd40d56 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 @@
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 37158e5..e1d6e01 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -25,7 +25,8 @@
"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 0000000..b53f27d
--- /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 0000000..fa89528
--- /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 0000000..ec07037
--- /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 9d1edbf..f6f35fd 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -18,6 +18,7 @@
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 0000000..eae9710
--- /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/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index 9695e9a..f82b380 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -53,6 +53,8 @@
private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
+ private static InetAddress PCSCFV6 = NetworkUtils.numericToInetAddress(
+ "2001:0db8:85a3:0000:0000:8a2e:0370:1");
private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
@@ -86,6 +88,9 @@
assertTrue(source.isIdenticalValidatedPrivateDnses(target));
assertTrue(target.isIdenticalValidatedPrivateDnses(source));
+ assertTrue(source.isIdenticalPcscfs(target));
+ assertTrue(target.isIdenticalPcscfs(source));
+
assertTrue(source.isIdenticalRoutes(target));
assertTrue(target.isIdenticalRoutes(source));
@@ -128,6 +133,8 @@
// set 2 dnses
source.addDnsServer(DNS1);
source.addDnsServer(DNS2);
+ // set 1 pcscf
+ source.addPcscfServer(PCSCFV6);
// set 2 gateways
source.addRoute(new RouteInfo(GATEWAY1));
source.addRoute(new RouteInfo(GATEWAY2));
@@ -141,6 +148,7 @@
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -154,6 +162,7 @@
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -167,6 +176,7 @@
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -179,6 +189,21 @@
// change dnses
target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.setMtu(MTU);
+ assertFalse(source.equals(target));
+
+ target.clear();
+ target.setInterfaceName(NAME);
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+ target.addDnsServer(DNS2);
+ // change pcscf
+ target.addPcscfServer(NetworkUtils.numericToInetAddress(
+ "2001::1"));
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
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 0000000..a9f9758
--- /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 71529fd..bf39644 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_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.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.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.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.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.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.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 @@
@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 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 @@
}
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 @@
// 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 @@
}
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 @@
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 @@
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 @@
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 @@
return mDisconnected;
}
- public WrappedNetworkMonitor getWrappedNetworkMonitor() {
- return mWrappedNetworkMonitor;
- }
-
public void sendLinkProperties(LinkProperties lp) {
mNetworkAgent.sendLinkProperties(lp);
}
@@ -880,28 +955,6 @@
}
}
- // 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 @@
private class WrappedConnectivityService extends ConnectivityService {
public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
- private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
private MockableSystemProperties mSystemProperties;
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
@@ -971,15 +1023,6 @@
}
}
- @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 @@
protected void registerNetdEventCallback() {
}
- public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
- return mLastCreatedNetworkMonitor;
- }
-
public void mockVpn(int uid) {
synchronized (mVpns) {
int userId = UserHandle.getUserId(uid);
@@ -2439,7 +2478,7 @@
// 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 @@
// 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 @@
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));
+ verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
+ .launchCaptivePortalApp();
- // 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();
+ // 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 @@
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 @@
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 @@
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 @@
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 @@
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 01b468a..38322e9 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.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.test.mock.MockContentResolver;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
-import com.android.server.connectivity.MockableSystemProperties;
+
+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;
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
/**
* Tests for {@link DnsManager}.
*
@@ -133,7 +130,7 @@
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 354cf2f..4c52d81 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.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.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 @@
@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 @@
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 0000000..859a54d
--- /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/bit/command.h b/tools/bit/command.h
index fb44900..dd7103e 100644
--- a/tools/bit/command.h
+++ b/tools/bit/command.h
@@ -25,7 +25,7 @@
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 9968bda..4491a85 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -67,7 +67,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"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 @@
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 @@
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 @@
// 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 @@
}
} 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 f14bbfd..bddd981 100644
--- a/tools/streaming_proto/Errors.h
+++ b/tools/streaming_proto/Errors.h
@@ -11,7 +11,7 @@
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 b265c40..517bf3b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -189,6 +189,7 @@
*/
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 @@
@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 @@
* 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 @@
* 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 @@
*
* @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/AbstractWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
similarity index 96%
rename from wifi/java/com/android/server/wifi/AbstractWifiService.java
rename to wifi/java/com/android/server/wifi/BaseWifiService.java
index f06346c..2c96c05 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -38,14 +38,13 @@
import android.os.Messenger;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.RemoteException;
import android.os.WorkSource;
import java.util.List;
import java.util.Map;
/**
- * Abstract class implementing IWifiManager with stub methods throwing runtime exceptions.
+ * Empty concrete class implementing IWifiManager with stub methods throwing runtime exceptions.
*
* This class is meant to be extended by real implementations of IWifiManager in order to facilitate
* cross-repo changes to WiFi internal APIs, including the introduction of new APIs, the removal of
@@ -56,10 +55,13 @@
* then given a short grace period to update themselves before the @Deprecated stub is removed for
* good. If the API scheduled for removal has a replacement or an overload (signature change),
* these should be introduced before the stub is removed to allow children to migrate.
+ *
+ * When a new API is added to IWifiManager.aidl, a stub should be added in BaseWifiService as
+ * well otherwise compilation will fail.
*/
-public abstract class AbstractWifiService extends IWifiManager.Stub {
+public class BaseWifiService extends IWifiManager.Stub {
- private static final String TAG = AbstractWifiService.class.getSimpleName();
+ private static final String TAG = BaseWifiService.class.getSimpleName();
@Override
public int getSupportedFeatures() {
@@ -87,7 +89,7 @@
}
@Override
- public Map<String, List<ScanResult>> getAllMatchingFqdnsForScanResults(
+ public Map<String, Map<Integer, List<ScanResult>>> getAllMatchingFqdnsForScanResults(
List<ScanResult> scanResults) {
throw new UnsupportedOperationException();
}