Merge "Implement test harness mode"
diff --git a/Android.bp b/Android.bp
index bc767ae..e63463c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -191,6 +191,10 @@
"core/java/android/hardware/input/IInputDevicesChangedListener.aidl",
"core/java/android/hardware/input/ITabletModeChangedListener.aidl",
"core/java/android/hardware/iris/IIrisService.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardware.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl",
+ "core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl",
"core/java/android/hardware/location/IGeofenceHardware.aidl",
"core/java/android/hardware/location/IGeofenceHardwareCallback.aidl",
"core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index ea61114..491f677 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10219,7 +10219,7 @@
field public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
field public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED";
field public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED";
- field public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
+ field @Deprecated public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
field public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
field public static final String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
field public static final String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
@@ -25913,6 +25913,23 @@
method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle);
}
+ public abstract class MediaSession2Service extends android.app.Service {
+ ctor public MediaSession2Service();
+ method public final void addSession(@NonNull android.media.MediaSession2);
+ method @NonNull public final java.util.List<android.media.MediaSession2> getSessions();
+ method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession();
+ method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2);
+ method public final void removeSession(@NonNull android.media.MediaSession2);
+ field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
+ }
+
+ public static class MediaSession2Service.MediaNotification {
+ ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification);
+ method @NonNull public android.app.Notification getNotification();
+ method public int getNotificationId();
+ }
+
public final class MediaSync {
ctor public MediaSync();
method @NonNull public android.view.Surface createInputSurface();
@@ -37894,18 +37911,18 @@
method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static String getDocumentId(android.net.Uri);
- method public static android.os.Bundle getDocumentMetadata(android.content.ContentInterface, android.net.Uri) throws java.io.FileNotFoundException;
+ method @Nullable public static android.os.Bundle getDocumentMetadata(@NonNull android.content.ContentInterface, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.os.Bundle getDocumentMetadata(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException;
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentInterface, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method @Deprecated public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public static String getRootId(android.net.Uri);
method public static String getSearchDocumentsQuery(android.net.Uri);
method public static String getTreeDocumentId(android.net.Uri);
- method public static boolean isChildDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
+ method public static boolean isChildDocument(@NonNull android.content.ContentInterface, @NonNull android.net.Uri, @NonNull android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static boolean isChildDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
method public static boolean isDocumentUri(android.content.Context, @Nullable android.net.Uri);
- method public static boolean isRootUri(android.content.Context, @Nullable android.net.Uri);
- method public static boolean isRootsUri(android.content.Context, @Nullable android.net.Uri);
+ method public static boolean isRootUri(@NonNull android.content.Context, @Nullable android.net.Uri);
+ method public static boolean isRootsUri(@NonNull android.content.Context, @Nullable android.net.Uri);
method public static boolean isTreeUri(android.net.Uri);
method public static android.net.Uri moveDocument(android.content.ContentInterface, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
method @Deprecated public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException;
@@ -37976,6 +37993,7 @@
field public static final String COLUMN_FLAGS = "flags";
field public static final String COLUMN_ICON = "icon";
field public static final String COLUMN_MIME_TYPES = "mime_types";
+ field public static final String COLUMN_QUERY_ARGS = "query_args";
field public static final String COLUMN_ROOT_ID = "root_id";
field public static final String COLUMN_SUMMARY = "summary";
field public static final String COLUMN_TITLE = "title";
@@ -37998,7 +38016,7 @@
method public void deleteDocument(String) throws java.io.FileNotFoundException;
method public void ejectRoot(String);
method public android.provider.DocumentsContract.Path findDocumentPath(@Nullable String, String) throws java.io.FileNotFoundException;
- method @Nullable public android.os.Bundle getDocumentMetadata(String) throws java.io.FileNotFoundException;
+ method @Nullable public android.os.Bundle getDocumentMetadata(@NonNull String) throws java.io.FileNotFoundException;
method public String[] getDocumentStreamTypes(String, String);
method public String getDocumentType(String) throws java.io.FileNotFoundException;
method public final String getType(android.net.Uri);
@@ -38023,7 +38041,7 @@
method public android.database.Cursor queryRecentDocuments(String, String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException;
method public abstract android.database.Cursor queryRoots(String[]) throws java.io.FileNotFoundException;
method public android.database.Cursor querySearchDocuments(String, String, String[]) throws java.io.FileNotFoundException;
- method public android.database.Cursor querySearchDocuments(String, String[], android.os.Bundle) throws java.io.FileNotFoundException;
+ method public android.database.Cursor querySearchDocuments(@NonNull String, @Nullable String[], @NonNull android.os.Bundle) throws java.io.FileNotFoundException;
method public void removeDocument(String, String) throws java.io.FileNotFoundException;
method public String renameDocument(String, String) throws java.io.FileNotFoundException;
method public final void revokeDocumentPermission(String);
@@ -43596,6 +43614,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
method public android.telecom.PhoneAccountHandle getSimCallManager();
method public String getSystemDialerPackage();
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getUserSelectedOutgoingPhoneAccount();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle);
@@ -49674,6 +49693,7 @@
}
public class Surface implements android.os.Parcelable {
+ ctor public Surface(android.view.SurfaceControl);
ctor public Surface(android.graphics.SurfaceTexture);
method public int describeContents();
method public boolean isValid();
@@ -49696,6 +49716,38 @@
ctor public Surface.OutOfResourcesException(String);
}
+ public final class SurfaceControl implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isValid();
+ method public void readFromParcel(android.os.Parcel);
+ method public void release();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
+ }
+
+ public static class SurfaceControl.Builder {
+ ctor public SurfaceControl.Builder();
+ method public android.view.SurfaceControl build();
+ method public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+ method public android.view.SurfaceControl.Builder setName(String);
+ method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
+ method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
+ }
+
+ public static class SurfaceControl.Transaction implements java.io.Closeable {
+ ctor public SurfaceControl.Transaction();
+ method public void apply();
+ method public void close();
+ method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
+ method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
+ method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+ method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
+ method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+ method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
+ }
+
public interface SurfaceHolder {
method public void addCallback(android.view.SurfaceHolder.Callback);
method public android.view.Surface getSurface();
@@ -49740,6 +49792,7 @@
ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int);
method public boolean gatherTransparentRegion(android.graphics.Region);
method public android.view.SurfaceHolder getHolder();
+ method public android.view.SurfaceControl getSurfaceControl();
method public void setSecure(boolean);
method public void setZOrderMediaOverlay(boolean);
method public void setZOrderOnTop(boolean);
diff --git a/api/system-current.txt b/api/system-current.txt
index aa2fa26..6695b18 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1322,6 +1322,7 @@
public class OverlayManager {
method public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(@Nullable String, int);
+ method public boolean setEnabled(@Nullable String, boolean, int);
method public boolean setEnabledExclusiveInCategory(@Nullable String, int);
}
@@ -3209,12 +3210,14 @@
method public int getQuality();
method public float getSmallestDisplacement();
method public android.os.WorkSource getWorkSource();
+ method public boolean isLocationSettingsIgnored();
method public boolean isLowPowerMode();
method public android.location.LocationRequest setExpireAt(long);
method public android.location.LocationRequest setExpireIn(long);
method public android.location.LocationRequest setFastestInterval(long);
method public void setHideFromAppOps(boolean);
method public android.location.LocationRequest setInterval(long);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean);
method public android.location.LocationRequest setLowPowerMode(boolean);
method public android.location.LocationRequest setNumUpdates(int);
method public android.location.LocationRequest setProvider(String);
@@ -3535,6 +3538,76 @@
method public void unregisterCallback(@NonNull android.media.session.ControllerCallbackLink);
}
+ public abstract static class MediaSession.Callback {
+ method public void onSetMediaButtonEventDelegate(@NonNull android.media.session.MediaSessionEngine.MediaButtonEventDelegate);
+ }
+
+ public final class MediaSessionEngine implements java.lang.AutoCloseable {
+ ctor public MediaSessionEngine(@NonNull android.content.Context, @NonNull android.media.session.SessionLink, @NonNull android.media.session.SessionCallbackLink, @NonNull android.media.session.MediaSessionEngine.CallbackStub, int);
+ method public void close();
+ method public String getCallingPackage();
+ method @NonNull public android.media.session.MediaController getController();
+ method @NonNull public android.media.session.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method @NonNull public android.media.session.MediaSession.Token getSessionToken();
+ method public boolean isActive();
+ method public static boolean isActiveState(int);
+ method public void sendSessionEvent(@NonNull String, @Nullable android.os.Bundle);
+ method public void setActive(boolean);
+ method public void setCallback(@Nullable android.media.session.MediaSession.Callback);
+ method public void setCallback(@Nullable android.media.session.MediaSession.Callback, @NonNull android.os.Handler);
+ method public void setExtras(@Nullable android.os.Bundle);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent);
+ method public void setMetadata(@Nullable android.media.MediaMetadata);
+ method public void setPlaybackState(@Nullable android.media.session.PlaybackState);
+ method public void setPlaybackToLocal(android.media.AudioAttributes);
+ method public void setPlaybackToRemote(@NonNull android.media.VolumeProvider);
+ method public void setQueue(@Nullable java.util.List<android.media.session.MediaSession.QueueItem>);
+ method public void setQueueTitle(@Nullable CharSequence);
+ method public void setRatingType(int);
+ method public void setSessionActivity(@Nullable android.app.PendingIntent);
+ }
+
+ public static final class MediaSessionEngine.CallbackStub {
+ ctor public MediaSessionEngine.CallbackStub();
+ method public void onAdjustVolume(String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void onCommand(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle, android.os.ResultReceiver);
+ method public void onCustomAction(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onFastForward(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onMediaButton(String, int, int, android.content.Intent, int, android.os.ResultReceiver);
+ method public void onMediaButtonFromController(String, int, int, android.media.session.ControllerCallbackLink, android.content.Intent);
+ method public void onNext(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPause(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPlay(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPlayFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPlayFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPlayFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void onPrepare(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onPrepareFromMediaId(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPrepareFromSearch(String, int, int, android.media.session.ControllerCallbackLink, String, android.os.Bundle);
+ method public void onPrepareFromUri(String, int, int, android.media.session.ControllerCallbackLink, android.net.Uri, android.os.Bundle);
+ method public void onPrevious(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onRate(String, int, int, android.media.session.ControllerCallbackLink, android.media.Rating);
+ method public void onRewind(String, int, int, android.media.session.ControllerCallbackLink);
+ method public void onSeekTo(String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void onSetVolumeTo(String, int, int, android.media.session.ControllerCallbackLink, int);
+ method public void onSkipToTrack(String, int, int, android.media.session.ControllerCallbackLink, long);
+ method public void onStop(String, int, int, android.media.session.ControllerCallbackLink);
+ }
+
+ public static interface MediaSessionEngine.MediaButtonEventDelegate {
+ method public boolean onMediaButtonIntent(android.content.Intent);
+ }
+
+ public static final class MediaSessionEngine.QueueItem {
+ ctor public MediaSessionEngine.QueueItem(android.media.MediaDescription, long);
+ ctor public MediaSessionEngine.QueueItem(android.os.Parcel);
+ method public android.media.MediaDescription getDescription();
+ method public long getQueueId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
public final class MediaSessionManager {
method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
@@ -3908,6 +3981,7 @@
}
public class ConnectivityManager {
+ method public boolean getAvoidBadWifi();
method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl();
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean);
@@ -4498,7 +4572,6 @@
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(int, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, android.net.wifi.WifiManager.ActionListener);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disableEphemeralNetwork(String);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void forget(int, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
@@ -5449,6 +5522,7 @@
field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
+ field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
}
public static interface DeviceConfig.OnPropertyChangedListener {
@@ -6090,12 +6164,15 @@
method public abstract int onSwitchToSubscription(int, @Nullable String, boolean);
method public abstract int onUpdateSubscriptionNickname(int, String, String);
field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
+ field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
field @Deprecated public static final String ACTION_RESOLVE_CONFIRMATION_CODE = "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
field public static final String ACTION_RESOLVE_DEACTIVATE_SIM = "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
field public static final String ACTION_RESOLVE_NO_PRIVILEGES = "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
field public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS = "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS";
+ field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
field public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
field public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
field public static final String EXTRA_RESOLUTION_ALLOW_POLICY_RULES = "android.service.euicc.extra.RESOLUTION_ALLOW_POLICY_RULES";
@@ -6673,6 +6750,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.WRITE_SECURE_SETTINGS}) public boolean setDefaultDialer(String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(android.telecom.PhoneAccountHandle);
field public static final String EXTRA_CALL_BACK_INTENT = "android.telecom.extra.CALL_BACK_INTENT";
field public static final String EXTRA_CLEAR_MISSED_CALLS_INTENT = "android.telecom.extra.CLEAR_MISSED_CALLS_INTENT";
field public static final String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE";
@@ -7838,9 +7916,12 @@
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
+ field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION";
field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+ field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2
field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1
field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3
@@ -7851,7 +7932,10 @@
field public static final int EUICC_OTA_SUCCEEDED = 3; // 0x3
field public static final String EXTRA_ACTIVATION_TYPE = "android.telephony.euicc.extra.ACTIVATION_TYPE";
field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS";
+ field public static final String EXTRA_ENABLE_SUBSCRIPTION = "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
field public static final String EXTRA_FORCE_PROVISION = "android.telephony.euicc.extra.FORCE_PROVISION";
+ field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+ field public static final String EXTRA_SUBSCRIPTION_NICKNAME = "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
}
@IntDef(prefix={"EUICC_OTA_"}, value={android.telephony.euicc.EuiccManager.EUICC_OTA_IN_PROGRESS, android.telephony.euicc.EuiccManager.EUICC_OTA_FAILED, android.telephony.euicc.EuiccManager.EUICC_OTA_SUCCEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_NOT_NEEDED, android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface EuiccManager.OtaStatus {
@@ -8906,6 +8990,22 @@
package android.util {
+ public class DocumentsStatsLog {
+ method public static void logActivityLaunch(int, boolean, int, int);
+ method public static void logFileOperation(int, int);
+ method public static void logFileOperationCanceled(int);
+ method public static void logFileOperationCopyMoveMode(int, int);
+ method public static void logFileOperationFailure(int, int);
+ method public static void logFilePick(int, long, int, boolean, int, int, int);
+ method public static void logInvalidScopedAccessRequest(int);
+ method public static void logPickerLaunchedFrom(String);
+ method public static void logRootVisited(int, int);
+ method public static void logSearchMode(int);
+ method public static void logSearchType(int);
+ method public static void logStartupMs(int);
+ method public static void logUserAction(int);
+ }
+
public class EventLog {
method public static void readEventsOnWrapping(int[], long, java.util.Collection<android.util.EventLog.Event>) throws java.io.IOException;
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 961a2c9..1942b2e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -203,6 +203,7 @@
SeStateChanged se_state_changed = 140;
SeOmapiReported se_omapi_reported = 141;
BroadcastDispatchLatencyReported broadcast_dispatch_latency_reported = 142;
+ AttentionManagerServiceResultReported attention_manager_service_result_reported = 143;
}
// Pulled events will start at field 10000.
@@ -4469,11 +4470,30 @@
}
/**
- * Logs the dispatch latencey of a broadcast during processing of BOOT_COMPLETED.
- * The dispatch latencey is the dispatchClockTime - enqueueClockTime.
+ * Logs the dispatch latency of a broadcast during processing of BOOT_COMPLETED.
+ * The dispatch latency is the dispatchClockTime - enqueueClockTime.
* Logged from:
* frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
*/
message BroadcastDispatchLatencyReported {
- optional int64 dispatch_latency_millis = 1;
+ optional int64 dispatch_latency_millis = 1;
+}
+
+/**
+ * Logs AttentionManagerService attention check result.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/attention/AttentionManagerService.java
+ */
+message AttentionManagerServiceResultReported {
+ // See core/java/android/service/attention/AttentionService.java
+ enum AttentionCheckResult {
+ UNKNOWN = 20;
+ ATTENTION_SUCCESS_ABSENT = 0;
+ ATTENTION_SUCCESS_PRESENT = 1;
+ ATTENTION_FAILURE_PREEMPTED = 2;
+ ATTENTION_FAILURE_TIMED_OUT = 3;
+ ATTENTION_FAILURE_UNKNOWN = 4;
+ }
+ optional AttentionCheckResult attention_check_result = 1 [default = UNKNOWN];
}
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 4174ad7..1b7fbfe 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -23,8 +23,10 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
import com.android.internal.os.BaseCommand;
import com.android.internal.telecom.ITelecomService;
@@ -45,6 +47,8 @@
private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
+ private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
+ "set-user-selected-outgoing-phone-account";
private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
@@ -70,6 +74,8 @@
+ "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
+ "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
+ + "usage: telecom set-user-selected-outgoing-phone-account <COMPONENT> <ID> "
+ + "<USER_SN>\n"
+ "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
+ "usage: telecom set-test-call-screening-app <PACKAGE>\n"
+ "usage: telecom set-test-auto-mode-app <PACKAGE>\n"
@@ -104,16 +110,18 @@
mTelecomService = ITelecomService.Stub.asInterface(
ServiceManager.getService(Context.TELECOM_SERVICE));
if (mTelecomService == null) {
+ Log.w(this, "onRun: Can't access telecom manager.");
showError("Error: Could not access the Telecom Manager. Is the system running?");
return;
}
mUserManager = IUserManager.Stub
.asInterface(ServiceManager.getService(Context.USER_SERVICE));
if (mUserManager == null) {
+ Log.w(this, "onRun: Can't access user manager.");
showError("Error: Could not access the User Manager. Is the system running?");
return;
}
-
+ Log.i(this, "onRun: parsing command.");
String command = nextArgRequired();
switch (command) {
case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
@@ -143,6 +151,9 @@
case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
runRegisterSimPhoneAccount();
break;
+ case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
+ runSetUserSelectedOutgoingPhoneAccount();
+ break;
case COMMAND_UNREGISTER_PHONE_ACCOUNT:
runUnregisterPhoneAccount();
break;
@@ -159,6 +170,7 @@
runWaitOnHandler();
break;
default:
+ Log.w(this, "onRun: unknown command: %s", command);
throw new IllegalArgumentException ("unknown command '" + command + "'");
}
}
@@ -227,6 +239,13 @@
mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
}
+ private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
+ Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
+ final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
+ mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
+ System.out.println("Success - " + handle + " set as default outgoing account.");
+ }
+
private void runUnregisterPhoneAccount() throws RemoteException {
final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
mTelecomService.unregisterPhoneAccount(handle);
@@ -256,7 +275,10 @@
}
- private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException{
+ private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
+ if (TextUtils.isEmpty(mArgs.peekNextArg())) {
+ return null;
+ }
final ComponentName component = parseComponentName(nextArgRequired());
final String accountId = nextArgRequired();
final String userSnInStr = nextArgRequired();
@@ -265,6 +287,7 @@
final int userSn = Integer.parseInt(userSnInStr);
userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
} catch (NumberFormatException ex) {
+ Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
}
return new PhoneAccountHandle(component, accountId, userHandle);
@@ -277,4 +300,4 @@
}
return cn;
}
-}
\ No newline at end of file
+}
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3ec0db4..c2e441b 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -2768,6 +2768,11 @@
HPLandroid/hardware/location/GeofenceHardwareService$1;->registerForMonitorStateChangeCallback(ILandroid/hardware/location/IGeofenceHardwareMonitorCallback;)Z
HPLandroid/hardware/location/GeofenceHardwareService$1;->removeGeofence(II)Z
HPLandroid/hardware/location/GeofenceHardwareService;->checkPermission(III)V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IActivityRecognitionHardwareClient;
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
+HPLandroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
HPLandroid/hardware/location/IContextHubCallback$Stub;->asBinder()Landroid/os/IBinder;
HPLandroid/hardware/location/IContextHubCallback$Stub;->onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
HPLandroid/hardware/location/IContextHubService$Stub$Proxy;->findNanoAppOnHub(ILandroid/hardware/location/NanoAppFilter;)[I
@@ -21777,6 +21782,7 @@
HSPLandroid/hardware/input/TouchCalibration$1;-><init>()V
HSPLandroid/hardware/input/TouchCalibration;-><init>()V
HSPLandroid/hardware/input/TouchCalibration;->getAffineTransform()[F
+HSPLandroid/hardware/location/ActivityRecognitionHardware;->isSupported()Z
HSPLandroid/hardware/location/ContextHubInfo$1;-><init>()V
HSPLandroid/hardware/location/ContextHubInfo;-><init>(Landroid/hardware/contexthub/V1_0/ContextHub;)V
HSPLandroid/hardware/location/ContextHubMessage$1;-><init>()V
@@ -21796,6 +21802,13 @@
HSPLandroid/hardware/location/GeofenceHardwareService;-><init>()V
HSPLandroid/hardware/location/GeofenceHardwareService;->onBind(Landroid/content/Intent;)Landroid/os/IBinder;
HSPLandroid/hardware/location/GeofenceHardwareService;->onCreate()V
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->disableActivityEvent(Ljava/lang/String;I)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->enableActivityEvent(Ljava/lang/String;IJ)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->flush()Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->getSupportedActivities()[Ljava/lang/String;
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->isActivitySupported(Ljava/lang/String;)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->registerSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z
+HSPLandroid/hardware/location/IActivityRecognitionHardware;->unregisterSink(Landroid/hardware/location/IActivityRecognitionHardwareSink;)Z
HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->asBinder()Landroid/os/IBinder;
HSPLandroid/hardware/location/IContextHubCallback$Stub$Proxy;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V
HSPLandroid/hardware/location/IContextHubCallback;->onMessageReceipt(IILandroid/hardware/location/ContextHubMessage;)V
@@ -55637,6 +55650,7 @@
Landroid/hardware/input/KeyboardLayout$1;
Landroid/hardware/input/TouchCalibration$1;
Landroid/hardware/input/TouchCalibration;
+Landroid/hardware/location/ActivityRecognitionHardware;
Landroid/hardware/location/ContextHubInfo$1;
Landroid/hardware/location/ContextHubInfo;
Landroid/hardware/location/ContextHubManager;
@@ -55652,6 +55666,11 @@
Landroid/hardware/location/GeofenceHardwareRequestParcelable$1;
Landroid/hardware/location/GeofenceHardwareService$1;
Landroid/hardware/location/GeofenceHardwareService;
+Landroid/hardware/location/IActivityRecognitionHardware$Stub;
+Landroid/hardware/location/IActivityRecognitionHardware;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;
+Landroid/hardware/location/IActivityRecognitionHardwareClient;
Landroid/hardware/location/IContextHubCallback$Stub$Proxy;
Landroid/hardware/location/IContextHubCallback;
Landroid/hardware/location/IContextHubClient$Stub;
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 3a4d904..fc47f67 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -462,6 +462,8 @@
Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/input/IInputManager;
Landroid/hardware/input/IInputManager$Stub;->TRANSACTION_injectInputEvent:I
Landroid/hardware/input/IInputManager;->injectInputEvent(Landroid/view/InputEvent;I)Z
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V
+Landroid/hardware/location/IActivityRecognitionHardwareClient;->onAvailabilityChanged(ZLandroid/hardware/location/IActivityRecognitionHardware;)V
Landroid/hardware/location/IContextHubService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IContextHubService;
Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/hardware/usb/IUsbManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/usb/IUsbManager;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index cd798ad..c8a2a9c 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -1405,7 +1405,10 @@
android.hardware.input.InputManager$InputDeviceListener
android.hardware.input.InputManager$InputDeviceListenerDelegate
android.hardware.input.InputManager$InputDevicesChangedListener
+android.hardware.location.ActivityRecognitionHardware
android.hardware.location.ContextHubManager
+android.hardware.location.IActivityRecognitionHardware
+android.hardware.location.IActivityRecognitionHardware$Stub
android.hardware.radio.RadioManager
android.hardware.soundtrigger.SoundTrigger
android.hardware.soundtrigger.SoundTrigger$ConfidenceLevel
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 35098a0..cebe6e12 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -37,6 +37,7 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -382,7 +383,8 @@
void init(int connectionId, IBinder windowToken);
boolean onGesture(int gestureId);
boolean onKeyEvent(KeyEvent event);
- void onMagnificationChanged(@NonNull Region region,
+ /** Magnification changed callbacks for different displays */
+ void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY);
void onSoftKeyboardShowModeChanged(int showMode);
void onPerformGestureResult(int sequence, boolean completedSuccessfully);
@@ -452,7 +454,9 @@
private WindowManager mWindowManager;
- private MagnificationController mMagnificationController;
+ /** List of magnification controllers, mapping from displayId -> MagnificationController. */
+ private final SparseArray<MagnificationController> mMagnificationControllers =
+ new SparseArray<>(0);
private SoftKeyboardController mSoftKeyboardController;
private AccessibilityButtonController mAccessibilityButtonController;
@@ -483,8 +487,10 @@
* client code.
*/
private void dispatchServiceConnected() {
- if (mMagnificationController != null) {
- mMagnificationController.onServiceConnected();
+ synchronized (mLock) {
+ for (int i = 0; i < mMagnificationControllers.size(); i++) {
+ mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
+ }
}
if (mSoftKeyboardController != null) {
mSoftKeyboardController.onServiceConnected();
@@ -652,11 +658,34 @@
*/
@NonNull
public final MagnificationController getMagnificationController() {
+ return getMagnificationController(Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Returns the magnification controller of specified logical display, which may be used to
+ * query and modify the state of display magnification.
+ * <p>
+ * <strong>Note:</strong> In order to control magnification, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canControlMagnification}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @return the magnification controller
+ *
+ * @hide
+ */
+ @NonNull
+ public final MagnificationController getMagnificationController(int displayId) {
synchronized (mLock) {
- if (mMagnificationController == null) {
- mMagnificationController = new MagnificationController(this, mLock);
+ MagnificationController controller = mMagnificationControllers.get(displayId);
+ if (controller == null) {
+ controller = new MagnificationController(this, mLock, displayId);
+ mMagnificationControllers.put(displayId, controller);
}
- return mMagnificationController;
+ return controller;
}
}
@@ -765,11 +794,14 @@
}
}
- private void onMagnificationChanged(@NonNull Region region, float scale,
+ private void onMagnificationChanged(int displayId, @NonNull Region region, float scale,
float centerX, float centerY) {
- if (mMagnificationController != null) {
- mMagnificationController.dispatchMagnificationChanged(
- region, scale, centerX, centerY);
+ MagnificationController controller;
+ synchronized (mLock) {
+ controller = mMagnificationControllers.get(displayId);
+ }
+ if (controller != null) {
+ controller.dispatchMagnificationChanged(region, scale, centerX, centerY);
}
}
@@ -794,6 +826,7 @@
*/
public static final class MagnificationController {
private final AccessibilityService mService;
+ private final int mDisplayId;
/**
* Map of listeners to their handlers. Lazily created when adding the
@@ -802,19 +835,19 @@
private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
private final Object mLock;
- MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock) {
+ MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock,
+ int displayId) {
mService = service;
mLock = lock;
+ mDisplayId = displayId;
}
/**
* Called when the service is connected.
*/
- void onServiceConnected() {
- synchronized (mLock) {
- if (mListeners != null && !mListeners.isEmpty()) {
- setMagnificationCallbackEnabled(true);
- }
+ void onServiceConnectedLocked() {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
}
}
@@ -891,7 +924,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- connection.setMagnificationCallbackEnabled(enabled);
+ connection.setMagnificationCallbackEnabled(mDisplayId, enabled);
} catch (RemoteException re) {
throw new RuntimeException(re);
}
@@ -952,7 +985,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationScale();
+ return connection.getMagnificationScale(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain scale", re);
re.rethrowFromSystemServer();
@@ -981,7 +1014,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationCenterX();
+ return connection.getMagnificationCenterX(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain center X", re);
re.rethrowFromSystemServer();
@@ -1010,7 +1043,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationCenterY();
+ return connection.getMagnificationCenterY(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain center Y", re);
re.rethrowFromSystemServer();
@@ -1044,7 +1077,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.getMagnificationRegion();
+ return connection.getMagnificationRegion(mDisplayId);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to obtain magnified region", re);
re.rethrowFromSystemServer();
@@ -1073,7 +1106,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.resetMagnification(animate);
+ return connection.resetMagnification(mDisplayId, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to reset", re);
re.rethrowFromSystemServer();
@@ -1101,7 +1134,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.setMagnificationScaleAndCenter(
+ return connection.setMagnificationScaleAndCenter(mDisplayId,
scale, Float.NaN, Float.NaN, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to set scale", re);
@@ -1133,7 +1166,7 @@
mService.mConnectionId);
if (connection != null) {
try {
- return connection.setMagnificationScaleAndCenter(
+ return connection.setMagnificationScaleAndCenter(mDisplayId,
Float.NaN, centerX, centerY, animate);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Failed to set center", re);
@@ -1624,9 +1657,10 @@
}
@Override
- public void onMagnificationChanged(@NonNull Region region,
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
- AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
+ AccessibilityService.this.onMagnificationChanged(displayId, region, scale,
+ centerX, centerY);
}
@Override
@@ -1729,13 +1763,15 @@
mCaller.sendMessage(message);
}
- public void onMagnificationChanged(@NonNull Region region,
+ /** Magnification changed callbacks for different displays */
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = region;
args.arg2 = scale;
args.arg3 = centerX;
args.arg4 = centerY;
+ args.argi1 = displayId;
final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
mCaller.sendMessage(message);
@@ -1865,7 +1901,10 @@
final float scale = (float) args.arg2;
final float centerX = (float) args.arg3;
final float centerY = (float) args.arg4;
- mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+ final int displayId = args.argi1;
+ args.recycle();
+ mCallback.onMagnificationChanged(displayId, region, scale,
+ centerX, centerY);
}
} return;
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 4e96b8f..1dae4fc 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -41,7 +41,7 @@
void onKeyEvent(in KeyEvent event, int sequence);
- void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
+ void onMagnificationChanged(int displayId, in Region region, float scale, float centerX, float centerY);
void onSoftKeyboardShowModeChanged(int showMode);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 276131f..8c38fe4 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -70,20 +70,20 @@
oneway void setOnKeyEventResult(boolean handled, int sequence);
- float getMagnificationScale();
+ float getMagnificationScale(int displayId);
- float getMagnificationCenterX();
+ float getMagnificationCenterX(int displayId);
- float getMagnificationCenterY();
+ float getMagnificationCenterY(int displayId);
- Region getMagnificationRegion();
+ Region getMagnificationRegion(int displayId);
- boolean resetMagnification(boolean animate);
+ boolean resetMagnification(int displayId, boolean animate);
- boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY,
boolean animate);
- void setMagnificationCallbackEnabled(boolean enabled);
+ void setMagnificationCallbackEnabled(int displayId, boolean enabled);
boolean setSoftKeyboardShowMode(int showMode);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 836627e..1063be4 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -74,6 +74,7 @@
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StrictMode;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -1049,33 +1050,56 @@
@Retention(RetentionPolicy.SOURCE)
@interface ContentCaptureNotificationType{}
-
- private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
- final ContentCaptureManager cm = getContentCaptureManager();
- if (cm == null) return;
-
+ private String getContentCaptureTypeAsString(@ContentCaptureNotificationType int type) {
switch (type) {
case CONTENT_CAPTURE_START:
- //TODO(b/111276913): decide whether the InteractionSessionId should be
- // saved / restored in the activity bundle - probably not
- int flags = 0;
- if ((getWindow().getAttributes().flags
- & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
- flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
- }
- cm.onActivityStarted(mToken, getComponentName(), flags);
- break;
+ return "START";
case CONTENT_CAPTURE_PAUSE:
- cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED);
- break;
+ return "PAUSE";
case CONTENT_CAPTURE_RESUME:
- cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED);
- break;
+ return "RESUME";
case CONTENT_CAPTURE_STOP:
- cm.onActivityStopped();
- break;
+ return "STOP";
default:
- Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+ return "UNKNOW-" + type;
+ }
+ }
+
+ private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "notifyContentCapture(" + getContentCaptureTypeAsString(type) + ") for "
+ + mComponent.toShortString());
+ }
+ try {
+ final ContentCaptureManager cm = getContentCaptureManager();
+ if (cm == null) return;
+
+ switch (type) {
+ case CONTENT_CAPTURE_START:
+ //TODO(b/111276913): decide whether the InteractionSessionId should be
+ // saved / restored in the activity bundle - probably not
+ int flags = 0;
+ if ((getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ flags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
+ }
+ cm.onActivityStarted(mToken, getComponentName(), flags);
+ break;
+ case CONTENT_CAPTURE_PAUSE:
+ cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_PAUSED);
+ break;
+ case CONTENT_CAPTURE_RESUME:
+ cm.flush(ContentCaptureSession.FLUSH_REASON_ACTIVITY_RESUMED);
+ break;
+ case CONTENT_CAPTURE_STOP:
+ cm.onActivityStopped();
+ break;
+ default:
+ Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7767f04..a3243a5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5939,6 +5939,11 @@
Binder.enableTracing();
}
+ // Initialize heap profiling.
+ if (isAppProfileable || Build.IS_DEBUGGABLE) {
+ nInitZygoteChildHeapProfiling();
+ }
+
// Allow renderer debugging features if we're debuggable.
boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
@@ -6965,4 +6970,5 @@
// ------------------ Regular JNI ------------------------
private native void nPurgePendingResources();
private native void nDumpGraphicsInfo(FileDescriptor fd);
+ private native void nInitZygoteChildHeapProfiling();
}
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index ab8f234..4d3711a 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -328,7 +328,7 @@
}
} else {
mTmpTransaction.reparent(mRootSurfaceControl,
- mSurfaceView.getSurfaceControl().getHandle()).apply();
+ mSurfaceView.getSurfaceControl()).apply();
}
if (mVirtualDisplay != null) {
@@ -390,7 +390,7 @@
.build();
try {
- wm.reparentDisplayContent(displayId, mRootSurfaceControl.getHandle());
+ wm.reparentDisplayContent(displayId, mRootSurfaceControl);
wm.dontOverrideDisplayInfo(displayId);
if (mSingleTaskInstance) {
mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 1622c06..853b45e 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1324,7 +1324,8 @@
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
* scanned by MediaScanner appear in the applications used to view media (for example,
- * Gallery app).
+ * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is
+ * ignored and the file is always scanned by MediaScanner.
* @param mimeType mimetype of the file.
* @param path absolute pathname to the file. The file should be world-readable, so that it can
* be managed by the Downloads App and any other app that is used to read it (for example,
@@ -1353,7 +1354,8 @@
* @param description the description that would appear for this file in Downloads App.
* @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files
* scanned by MediaScanner appear in the applications used to view media (for example,
- * Gallery app).
+ * Gallery app). Starting from {@link android.os.Build.VERSION_CODES#Q}, this argument is
+ * ignored and the file is always scanned by MediaScanner.
* @param mimeType mimetype of the file.
* @param path absolute pathname to the file. The file should be world-readable, so that it can
* be managed by the Downloads App and any other app that is used to read it (for example,
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 3f9627e..a021e3c 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1240,7 +1240,7 @@
}
@Override
- public void onMagnificationChanged(@NonNull Region region,
+ public void onMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
/* do nothing */
}
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index 5b87de4..5f1a94c 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -215,19 +215,12 @@
}
/**
- * Start VR Input method for the packageName in {@link ComponentName}.
- * This method notifies InputMethodManagerService to use VR IME instead of
- * regular phone IME.
- * @param componentName ComponentName of a VR InputMethod that should be set as selected
- * input by InputMethodManagerService.
+ * This method is not implemented.
+ *
+ * @param componentName not used
*/
@RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
public void setVrInputMethod(ComponentName componentName) {
- try {
- mService.setVrInputMethod(componentName);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
}
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8497656..3c487a1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3192,7 +3192,18 @@
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
+ *
+ * <p class="note">If the user has chosen a {@link android.telecom.CallRedirectionService} to
+ * handle redirection of outgoing calls, this intent will NOT be sent as an ordered broadcast.
+ * This means that attempts to re-write the outgoing call by other apps using this intent will
+ * be ignored.
+ * </p>
+ *
+ * @deprecated Apps that redirect outgoing calls should use the
+ * {@link android.telecom.CallRedirectionService} API. Apps that perform call screening
+ * should use the {@link android.telecom.CallScreeningService} API.
*/
+ @Deprecated
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NEW_OUTGOING_CALL =
"android.intent.action.NEW_OUTGOING_CALL";
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7a2220bf..8e72fa5 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -54,6 +54,7 @@
this(context, IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE)));
}
+
/**
* Request that an overlay package is enabled and any other overlay packages with the same
* target package and category are disabled.
@@ -75,6 +76,26 @@
}
/**
+ * Request that an overlay package is enabled.
+ *
+ * @param packageName the name of the overlay package to enable.
+ * @param enable {@code false} if the overlay should be turned off.
+ * @param userId The user for which to change the overlay.
+ * @return true if the system successfully registered the request, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean setEnabled(@Nullable final String packageName, final boolean enable,
+ int userId) {
+ try {
+ return mService.setEnabled(packageName, enable, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns information about all overlays for the given target package for
* the specified user. The returned list is ordered according to the
* overlay priority with the highest priority at the end of the list.
diff --git a/core/java/android/hardware/hdmi/HdmiUtils.java b/core/java/android/hardware/hdmi/HdmiUtils.java
index 3081738..8c94b78 100644
--- a/core/java/android/hardware/hdmi/HdmiUtils.java
+++ b/core/java/android/hardware/hdmi/HdmiUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,18 @@
package android.hardware.hdmi;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
- * Various utilities to handle HDMI CEC messages.
+ * Various utilities related to HDMI CEC.
*
* TODO(b/110094868): unhide for Q
* @hide
*/
-public class HdmiUtils {
-
+public final class HdmiUtils {
/**
* Return value of {@link #getLocalPortFromPhysicalAddress(int, int)}
*/
@@ -78,4 +82,164 @@
}
return port;
}
+
+ /**
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HDMI_RELATIVE_POSITION_UNKNOWN, HDMI_RELATIVE_POSITION_DIRECTLY_BELOW,
+ HDMI_RELATIVE_POSITION_BELOW, HDMI_RELATIVE_POSITION_SAME,
+ HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE, HDMI_RELATIVE_POSITION_ABOVE,
+ HDMI_RELATIVE_POSITION_SIBLING, HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH})
+ public @interface HdmiAddressRelativePosition {}
+ /**
+ * HDMI relative position is not determined.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_UNKNOWN = 0;
+ /**
+ * HDMI relative position: directly blow the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIRECTLY_BELOW = 1;
+ /**
+ * HDMI relative position: indirectly below the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_BELOW = 2;
+ /**
+ * HDMI relative position: the same device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_SAME = 3;
+ /**
+ * HDMI relative position: directly above the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE = 4;
+ /**
+ * HDMI relative position: indirectly above the device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_ABOVE = 5;
+ /**
+ * HDMI relative position: directly below a same device.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_SIBLING = 6;
+ /**
+ * HDMI relative position: different branch.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ public static final int HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH = 7;
+
+ private static final int NPOS = -1;
+
+ /**
+ * Check if the given physical address is valid.
+ *
+ * @param address physical address
+ * @return {@code true} if the given address is valid
+ */
+ public static boolean isValidPhysicalAddress(int address) {
+ if (address < 0 || address >= 0xFFFF) {
+ return false;
+ }
+ int mask = 0xF000;
+ boolean hasZero = false;
+ for (int i = 0; i < 4; i++) {
+ if ((address & mask) == 0) {
+ hasZero = true;
+ } else if (hasZero) {
+ // only 0s are valid after a 0.
+ // e.g. 0x1012 is not valid.
+ return false;
+ }
+ mask >>= 4;
+ }
+ return true;
+ }
+
+
+ /**
+ * Returns the relative position of two physical addresses.
+ */
+ @HdmiAddressRelativePosition
+ public static int getHdmiAddressRelativePosition(int src, int dest) {
+ if (src == 0xFFFF || dest == 0xFFFF) {
+ // address not assigned
+ return HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ try {
+ int firstDiffPos = physicalAddressFirstDifferentDigitPos(src, dest);
+ if (firstDiffPos == NPOS) {
+ return HDMI_RELATIVE_POSITION_SAME;
+ }
+ int mask = (0xF000 >> (firstDiffPos * 4));
+ int nextPos = firstDiffPos + 1;
+ if ((src & mask) == 0) {
+ // src is above dest
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE;
+ }
+ if (((0xF000 >> (nextPos * 4)) & dest) == 0) {
+ // next digit is 0
+ return HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE;
+ }
+ return HDMI_RELATIVE_POSITION_ABOVE;
+ }
+
+ if ((dest & mask) == 0) {
+ // src is below dest
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+ }
+ if (((0xF000 >> (nextPos * 4)) & src) == 0) {
+ // next digit is 0
+ return HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
+ }
+ return HDMI_RELATIVE_POSITION_BELOW;
+ }
+ if (nextPos == 4) {
+ // last digits are different
+ return HDMI_RELATIVE_POSITION_SIBLING;
+ }
+ if (((0xF000 >> (nextPos * 4)) & src) == 0 && ((0xF000 >> (nextPos * 4)) & dest) == 0) {
+ return HDMI_RELATIVE_POSITION_SIBLING;
+ }
+ return HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH;
+ } catch (IllegalArgumentException e) {
+ // invalid address
+ return HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ }
+
+ private static int physicalAddressFirstDifferentDigitPos(int address1, int address2)
+ throws IllegalArgumentException {
+ if (!isValidPhysicalAddress(address1)) {
+ throw new IllegalArgumentException(address1 + " is not a valid address.");
+ }
+ if (!isValidPhysicalAddress(address2)) {
+ throw new IllegalArgumentException(address2 + " is not a valid address.");
+ }
+ int mask = 0xF000;
+ for (int i = 0; i < 4; i++) {
+ if ((address1 & mask) != (address2 & mask)) {
+ return i;
+ }
+ mask = mask >> 4;
+ }
+ return NPOS;
+ }
}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl b/core/java/android/hardware/location/ActivityChangedEvent.aidl
similarity index 69%
rename from tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
rename to core/java/android/hardware/location/ActivityChangedEvent.aidl
index 62a8c48..21f2445 100644
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
+++ b/core/java/android/hardware/location/ActivityChangedEvent.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,13 +11,9 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package com.android.testing.alarmservice;
+package android.hardware.location;
-interface Alarm {
- int prepare();
- int setAlarmAndWait(long timeoutMills);
- int done();
-}
\ No newline at end of file
+parcelable ActivityChangedEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ActivityChangedEvent.java b/core/java/android/hardware/location/ActivityChangedEvent.java
new file mode 100644
index 0000000..16cfe6e
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityChangedEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.hardware.location;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ *
+ * @hide
+ */
+public class ActivityChangedEvent implements Parcelable {
+ private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+ public ActivityChangedEvent(ActivityRecognitionEvent[] activityRecognitionEvents) {
+ if (activityRecognitionEvents == null) {
+ throw new InvalidParameterException(
+ "Parameter 'activityRecognitionEvents' must not be null.");
+ }
+
+ mActivityRecognitionEvents = Arrays.asList(activityRecognitionEvents);
+ }
+
+ @NonNull
+ public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+ return mActivityRecognitionEvents;
+ }
+
+ public static final Creator<ActivityChangedEvent> CREATOR =
+ new Creator<ActivityChangedEvent>() {
+ @Override
+ public ActivityChangedEvent createFromParcel(Parcel source) {
+ int activityRecognitionEventsLength = source.readInt();
+ ActivityRecognitionEvent[] activityRecognitionEvents =
+ new ActivityRecognitionEvent[activityRecognitionEventsLength];
+ source.readTypedArray(activityRecognitionEvents, ActivityRecognitionEvent.CREATOR);
+
+ return new ActivityChangedEvent(activityRecognitionEvents);
+ }
+
+ @Override
+ public ActivityChangedEvent[] newArray(int size) {
+ return new ActivityChangedEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ ActivityRecognitionEvent[] activityRecognitionEventArray =
+ mActivityRecognitionEvents.toArray(new ActivityRecognitionEvent[0]);
+ parcel.writeInt(activityRecognitionEventArray.length);
+ parcel.writeTypedArray(activityRecognitionEventArray, flags);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+ for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+ builder.append("\n ");
+ builder.append(event.toString());
+ }
+ builder.append("\n]");
+
+ return builder.toString();
+ }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionEvent.java b/core/java/android/hardware/location/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..190030a
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionEvent.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ *
+ * @hide
+ */
+public class ActivityRecognitionEvent implements Parcelable {
+ private final String mActivity;
+ private final int mEventType;
+ private final long mTimestampNs;
+
+ public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+ mActivity = activity;
+ mEventType = eventType;
+ mTimestampNs = timestampNs;
+ }
+
+ public String getActivity() {
+ return mActivity;
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ public static final Creator<ActivityRecognitionEvent> CREATOR =
+ new Creator<ActivityRecognitionEvent>() {
+ @Override
+ public ActivityRecognitionEvent createFromParcel(Parcel source) {
+ String activity = source.readString();
+ int eventType = source.readInt();
+ long timestampNs = source.readLong();
+
+ return new ActivityRecognitionEvent(activity, eventType, timestampNs);
+ }
+
+ @Override
+ public ActivityRecognitionEvent[] newArray(int size) {
+ return new ActivityRecognitionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mActivity);
+ parcel.writeInt(mEventType);
+ parcel.writeLong(mTimestampNs);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Activity='%s', EventType=%s, TimestampNs=%s",
+ mActivity,
+ mEventType,
+ mTimestampNs);
+ }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
new file mode 100644
index 0000000..8acd1ff
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.hardware.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A class that implements an {@link IActivityRecognitionHardware} backed up by the Activity
+ * Recognition HAL.
+ *
+ * @hide
+ */
+public class ActivityRecognitionHardware extends IActivityRecognitionHardware.Stub {
+ private static final String TAG = "ActivityRecognitionHW";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+ private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+ + HARDWARE_PERMISSION + "' not granted to access ActivityRecognitionHardware";
+
+ private static final int INVALID_ACTIVITY_TYPE = -1;
+ private static final int NATIVE_SUCCESS_RESULT = 0;
+ private static final int EVENT_TYPE_DISABLED = 0;
+ private static final int EVENT_TYPE_ENABLED = 1;
+
+ /**
+ * Contains the number of supported Event Types.
+ *
+ * NOTE: increment this counter every time a new EVENT_TYPE_ is added to
+ * com.android.location.provider.ActivityRecognitionProvider
+ */
+ private static final int EVENT_TYPE_COUNT = 3;
+
+ private static ActivityRecognitionHardware sSingletonInstance;
+ private static final Object sSingletonInstanceLock = new Object();
+
+ private final Context mContext;
+ private final int mSupportedActivitiesCount;
+ private final String[] mSupportedActivities;
+ private final int[][] mSupportedActivitiesEnabledEvents;
+ private final SinkList mSinks = new SinkList();
+
+ private static class Event {
+ public int activity;
+ public int type;
+ public long timestamp;
+ }
+
+ private ActivityRecognitionHardware(Context context) {
+ nativeInitialize();
+
+ mContext = context;
+ mSupportedActivities = fetchSupportedActivities();
+ mSupportedActivitiesCount = mSupportedActivities.length;
+ mSupportedActivitiesEnabledEvents = new int[mSupportedActivitiesCount][EVENT_TYPE_COUNT];
+ }
+
+ public static ActivityRecognitionHardware getInstance(Context context) {
+ synchronized (sSingletonInstanceLock) {
+ if (sSingletonInstance == null) {
+ sSingletonInstance = new ActivityRecognitionHardware(context);
+ }
+
+ return sSingletonInstance;
+ }
+ }
+
+ public static boolean isSupported() {
+ return nativeIsSupported();
+ }
+
+ @Override
+ public String[] getSupportedActivities() {
+ checkPermissions();
+ return mSupportedActivities;
+ }
+
+ @Override
+ public boolean isActivitySupported(String activity) {
+ checkPermissions();
+ int activityType = getActivityType(activity);
+ return activityType != INVALID_ACTIVITY_TYPE;
+ }
+
+ @Override
+ public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+ checkPermissions();
+ return mSinks.register(sink);
+ }
+
+ @Override
+ public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+ checkPermissions();
+ return mSinks.unregister(sink);
+ }
+
+ @Override
+ public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
+ checkPermissions();
+
+ int activityType = getActivityType(activity);
+ if (activityType == INVALID_ACTIVITY_TYPE) {
+ return false;
+ }
+
+ int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs);
+ if (result == NATIVE_SUCCESS_RESULT) {
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_ENABLED;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean disableActivityEvent(String activity, int eventType) {
+ checkPermissions();
+
+ int activityType = getActivityType(activity);
+ if (activityType == INVALID_ACTIVITY_TYPE) {
+ return false;
+ }
+
+ int result = nativeDisableActivityEvent(activityType, eventType);
+ if (result == NATIVE_SUCCESS_RESULT) {
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean flush() {
+ checkPermissions();
+ int result = nativeFlush();
+ return result == NATIVE_SUCCESS_RESULT;
+ }
+
+ /**
+ * Called by the Activity-Recognition HAL.
+ */
+ private void onActivityChanged(Event[] events) {
+ if (events == null || events.length == 0) {
+ if (DEBUG) Log.d(TAG, "No events to broadcast for onActivityChanged.");
+ return;
+ }
+
+ int eventsLength = events.length;
+ ActivityRecognitionEvent activityRecognitionEventArray[] =
+ new ActivityRecognitionEvent[eventsLength];
+ for (int i = 0; i < eventsLength; ++i) {
+ Event event = events[i];
+ String activityName = getActivityName(event.activity);
+ activityRecognitionEventArray[i] =
+ new ActivityRecognitionEvent(activityName, event.type, event.timestamp);
+ }
+ ActivityChangedEvent activityChangedEvent =
+ new ActivityChangedEvent(activityRecognitionEventArray);
+
+ int size = mSinks.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
+ try {
+ sink.onActivityChanged(activityChangedEvent);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering activity changed event.", e);
+ }
+ }
+ mSinks.finishBroadcast();
+ }
+
+ private String getActivityName(int activityType) {
+ if (activityType < 0 || activityType >= mSupportedActivities.length) {
+ String message = String.format(
+ "Invalid ActivityType: %d, SupportedActivities: %d",
+ activityType,
+ mSupportedActivities.length);
+ Log.e(TAG, message);
+ return null;
+ }
+
+ return mSupportedActivities[activityType];
+ }
+
+ private int getActivityType(String activity) {
+ if (TextUtils.isEmpty(activity)) {
+ return INVALID_ACTIVITY_TYPE;
+ }
+
+ int supportedActivitiesLength = mSupportedActivities.length;
+ for (int i = 0; i < supportedActivitiesLength; ++i) {
+ if (activity.equals(mSupportedActivities[i])) {
+ return i;
+ }
+ }
+
+ return INVALID_ACTIVITY_TYPE;
+ }
+
+ private void checkPermissions() {
+ mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ }
+
+ private String[] fetchSupportedActivities() {
+ String[] supportedActivities = nativeGetSupportedActivities();
+ if (supportedActivities != null) {
+ return supportedActivities;
+ }
+
+ return new String[0];
+ }
+
+ private class SinkList extends RemoteCallbackList<IActivityRecognitionHardwareSink> {
+ @Override
+ public void onCallbackDied(IActivityRecognitionHardwareSink callback) {
+ int callbackCount = mSinks.getRegisteredCallbackCount();
+ if (DEBUG) Log.d(TAG, "RegisteredCallbackCount: " + callbackCount);
+ if (callbackCount != 0) {
+ return;
+ }
+ // currently there is only one client for this, so if all its sinks have died, we clean
+ // up after them, this ensures that the AR HAL is not out of sink
+ for (int activity = 0; activity < mSupportedActivitiesCount; ++activity) {
+ for (int event = 0; event < EVENT_TYPE_COUNT; ++event) {
+ disableActivityEventIfEnabled(activity, event);
+ }
+ }
+ }
+
+ private void disableActivityEventIfEnabled(int activityType, int eventType) {
+ if (mSupportedActivitiesEnabledEvents[activityType][eventType] != EVENT_TYPE_ENABLED) {
+ return;
+ }
+
+ int result = nativeDisableActivityEvent(activityType, eventType);
+ mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
+ String message = String.format(
+ "DisableActivityEvent: activityType=%d, eventType=%d, result=%d",
+ activityType,
+ eventType,
+ result);
+ Log.e(TAG, message);
+ }
+ }
+
+ // native bindings
+ static { nativeClassInit(); }
+
+ private static native void nativeClassInit();
+ private static native boolean nativeIsSupported();
+
+ private native void nativeInitialize();
+ private native void nativeRelease();
+ private native String[] nativeGetSupportedActivities();
+ private native int nativeEnableActivityEvent(
+ int activityType,
+ int eventType,
+ long reportLatenceNs);
+ private native int nativeDisableActivityEvent(int activityType, int eventType);
+ private native int nativeFlush();
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardware.aidl b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
new file mode 100644
index 0000000..bc6b183
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/license/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardwareSink;
+
+/**
+ * Activity Recognition Hardware provider interface.
+ * This interface can be used to implement hardware based activity recognition.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardware {
+ /**
+ * Gets an array of supported activities by hardware.
+ */
+ String[] getSupportedActivities();
+
+ /**
+ * Returns true if the given activity is supported, false otherwise.
+ */
+ boolean isActivitySupported(in String activityType);
+
+ /**
+ * Registers a sink with Hardware Activity-Recognition.
+ */
+ boolean registerSink(in IActivityRecognitionHardwareSink sink);
+
+ /**
+ * Unregisters a sink with Hardware Activity-Recognition.
+ */
+ boolean unregisterSink(in IActivityRecognitionHardwareSink sink);
+
+ /**
+ * Enables tracking of a given activity/event type, if the activity is supported.
+ */
+ boolean enableActivityEvent(in String activityType, int eventType, long reportLatencyNs);
+
+ /**
+ * Disables tracking of a given activity/eventy type.
+ */
+ boolean disableActivityEvent(in String activityType, int eventType);
+
+ /**
+ * Requests hardware for all the activity events detected up to the given point in time.
+ */
+ boolean flush();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl
new file mode 100644
index 0000000..3fe645c
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareClient.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/license/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware client interface.
+ * This interface can be used to receive interfaces to implementations of
+ * {@link IActivityRecognitionHardware}.
+ *
+ * @hide
+ */
+oneway interface IActivityRecognitionHardwareClient {
+ /**
+ * Hardware Activity-Recognition availability event.
+ *
+ * @param isSupported whether the platform has hardware support for the feature
+ * @param instance the available instance to provide access to the feature
+ */
+ void onAvailabilityChanged(in boolean isSupported, in IActivityRecognitionHardware instance);
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
new file mode 100644
index 0000000..21c8e87
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/license/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.hardware.location.ActivityChangedEvent;
+
+/**
+ * Activity Recognition Hardware provider Sink interface.
+ * This interface can be used to implement sinks to receive activity notifications.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardwareSink {
+ /**
+ * Activity changed event.
+ */
+ void onActivityChanged(in ActivityChangedEvent event);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
new file mode 100644
index 0000000..12e3117
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/license/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware watcher. This interface can be used to receive interfaces to
+ * implementations of {@link IActivityRecognitionHardware}.
+ *
+ * @deprecated use {@link IActivityRecognitionHardwareClient} instead.
+
+ * @hide
+ */
+interface IActivityRecognitionHardwareWatcher {
+ /**
+ * Hardware Activity-Recognition availability event.
+ */
+ void onInstanceChanged(in IActivityRecognitionHardware instance);
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index cee3a40..c809cca 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3699,6 +3699,19 @@
}
/**
+ * Determine whether the device is configured to avoid bad wifi.
+ * @hide
+ */
+ @SystemApi
+ public boolean getAvoidBadWifi() {
+ try {
+ return mService.getAvoidBadWifi();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* It is acceptable to briefly use multipath data to provide seamless connectivity for
* time-sensitive user-facing operations when the system default network is temporarily
* unresponsive. The amount of data should be limited (less than one megabyte for every call to
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 3d34574..131925e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -165,6 +165,7 @@
void setAvoidUnvalidated(in Network network);
void startCaptivePortalApp(in Network network);
+ boolean getAvoidBadWifi();
int getMultipathPreference(in Network Network);
NetworkRequest getDefaultRequest();
diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
index 57f59a1..fb4ca3b 100644
--- a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
+++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
@@ -25,6 +25,6 @@
* Network attributes were fetched for the specified L2 key. While the L2 key will never
* be null, the attributes may be if no data is stored about this L2 key.
*/
- void onL2KeyResponse(in StatusParcelable status, in String l2Key,
+ void onNetworkAttributesRetrieved(in StatusParcelable status, in String l2Key,
in NetworkAttributesParcelable attributes);
}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
index b932d21..5397b57 100644
--- a/core/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -37,27 +37,57 @@
public class NetworkAttributes {
private static final boolean DBG = true;
+ // Weight cutoff for grouping. To group, a similarity score is computed with the following
+ // algorithm : if both fields are non-null and equals() then add their assigned weight, else if
+ // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT),
+ // otherwise add nothing.
+ // As a guideline, this should be something like 60~75% of the total weights in this class. The
+ // design states "in essence a reader should imagine that if two important columns don't match,
+ // or one important and several unimportant columns don't match then the two records are
+ // considered a different group".
+ private static final float TOTAL_WEIGHT_CUTOFF = 520.0f;
+ // The portion of the weight that is earned when scoring group-sameness by having both columns
+ // being null. This is because some networks rightfully don't have some attributes (e.g. a
+ // V6-only network won't have an assigned V4 address) and both being null should count for
+ // something, but attributes may also be null just because data is unavailable.
+ private static final float NULL_MATCH_WEIGHT = 0.25f;
+
// The v4 address that was assigned to this device the last time it joined this network.
// This typically comes from DHCP but could be something else like static configuration.
// This does not apply to IPv6.
// TODO : add a list of v6 prefixes for the v6 case.
@Nullable
public final Inet4Address assignedV4Address;
+ private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
// Optionally supplied by the client if it has an opinion on L3 network. For example, this
// could be a hash of the SSID + security type on WiFi.
@Nullable
public final String groupHint;
+ private static final float WEIGHT_GROUPHINT = 300.0f;
// The list of DNS server addresses.
@Nullable
public final List<InetAddress> dnsAddresses;
+ private static final float WEIGHT_DNSADDRESSES = 200.0f;
// The mtu on this network.
@Nullable
public final Integer mtu;
+ private static final float WEIGHT_MTU = 50.0f;
- NetworkAttributes(
+ // The sum of all weights in this class. Tests ensure that this stays equal to the total of
+ // all weights.
+ /** @hide */
+ @VisibleForTesting
+ public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
+ + WEIGHT_GROUPHINT
+ + WEIGHT_DNSADDRESSES
+ + WEIGHT_MTU;
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkAttributes(
@Nullable final Inet4Address assignedV4Address,
@Nullable final String groupHint,
@Nullable final List<InetAddress> dnsAddresses,
@@ -126,6 +156,34 @@
return parcelable;
}
+ private float samenessContribution(final float weight,
+ @Nullable final Object o1, @Nullable final Object o2) {
+ if (null == o1) {
+ return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
+ }
+ return Objects.equals(o1, o2) ? weight : 0f;
+ }
+
+ /** @hide */
+ public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
+ final float samenessScore =
+ samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
+ + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
+ + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
+ + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
+ // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
+ // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
+ // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
+ // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
+ // between 0.5 and 1.0.
+ if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
+ return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
+ } else {
+ return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
+ + 0.5f;
+ }
+ }
+
/** @hide */
public static class Builder {
@Nullable
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
index d040dcc..291aca8 100644
--- a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -91,7 +91,8 @@
return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
}
- SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ /** @hide */
+ public SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
final float confidence) {
this.l2Key1 = l2Key1;
this.l2Key2 = l2Key2;
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index ca39051..51c3c4c 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -72,6 +72,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -831,6 +832,16 @@
return false;
}
+ /** {@hide} */
+ public static boolean contains(Collection<File> dirs, File file) {
+ for (File dir : dirs) {
+ if (contains(dir, file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Test if a file lives under the given directory, either as a direct child
* or a distant grandchild.
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index a4d5c6f..92fcbb6 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -20,8 +20,10 @@
import android.annotation.Nullable;
/**
- * Handy class for starting a new thread that has a looper. The looper can then be
- * used to create handler classes. Note that start() must still be called.
+ * A {@link Thread} that has a {@link Looper}.
+ * The {@link Looper} can then be used to create {@link Handler}s.
+ * <p>
+ * Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
*/
public class HandlerThread extends Thread {
int mPriority;
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 2299ab2..76fe560 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -140,6 +140,21 @@
return mUids[index];
}
+ /**
+ * Return the UID to which this WorkSource should be attributed to, i.e, the UID that
+ * initiated the work and not the UID performing it. If the WorkSource has no UIDs, returns -1
+ * instead.
+ *
+ * @hide
+ */
+ public int getAttributionUid() {
+ if (isEmpty()) {
+ return -1;
+ }
+
+ return mNum > 0 ? mUids[0] : mChains.get(0).getAttributionUid();
+ }
+
/** @hide */
@TestApi
public String getName(int index) {
@@ -912,17 +927,18 @@
/**
* Return the UID to which this WorkChain should be attributed to, i.e, the UID that
- * initiated the work and not the UID performing it.
+ * initiated the work and not the UID performing it. Returns -1 if the chain is empty.
*/
public int getAttributionUid() {
- return mUids[0];
+ return mSize > 0 ? mUids[0] : -1;
}
/**
* Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
+ * Returns null if the chain is empty.
*/
public String getAttributionTag() {
- return mTags[0];
+ return mTags.length > 0 ? mTags[0] : null;
}
// TODO: The following three trivial getters are purely for testing and will be removed
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index df1a713..714a061 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -35,6 +35,7 @@
import java.io.CharArrayWriter;
import java.io.File;
+import java.util.Locale;
/**
* Information about a shared/external storage volume for a specific user.
@@ -263,6 +264,11 @@
return mFsUuid;
}
+ /** {@hide} */
+ public @Nullable String getNormalizedUuid() {
+ return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null;
+ }
+
/**
* Parse and return volume UUID as FAT volume ID, or return -1 if unable to
* parse or UUID is unknown.
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 8c3aa17..5d310e1 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -42,6 +42,7 @@
import java.io.CharArrayWriter;
import java.io.File;
import java.util.Comparator;
+import java.util.Locale;
import java.util.Objects;
/**
@@ -254,6 +255,10 @@
return fsUuid;
}
+ public @Nullable String getNormalizedFsUuid() {
+ return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+ }
+
@UnsupportedAppUsage
public int getMountUserId() {
return mountUserId;
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index a7e6601..cd3efb4 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -94,6 +94,15 @@
@SystemApi
public static final String NAMESPACE_NETD_NATIVE = "netd_native";
+ /**
+ * Namespace for features related to the ExtServices Notification Assistant.
+ * These features are applied immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a323ed1..5f1c560 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -16,7 +16,6 @@
package android.provider;
-import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkCollectionNotEmpty;
@@ -50,6 +49,8 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import dalvik.system.VMRuntime;
import java.io.File;
@@ -640,6 +641,28 @@
public static final String COLUMN_MIME_TYPES = "mime_types";
/**
+ * Query arguments supported by this root. This column is optional
+ * and related to {@link #COLUMN_FLAGS} and {@link #FLAG_SUPPORTS_SEARCH}.
+ * If the flags include {@link #FLAG_SUPPORTS_SEARCH}, and the column is
+ * {@code null}, the root is assumed to support {@link #QUERY_ARG_DISPLAY_NAME}
+ * search of {@link Document#COLUMN_DISPLAY_NAME}. Multiple query arguments
+ * can be separated by a newline. For example, a root supporting
+ * {@link #QUERY_ARG_MIME_TYPES} and {@link #QUERY_ARG_DISPLAY_NAME} might
+ * return "android:query-arg-mime-types\nandroid:query-arg-display-name".
+ * <p>
+ * Type: STRING
+ * @see #COLUMN_FLAGS
+ * @see #FLAG_SUPPORTS_SEARCH
+ * @see #QUERY_ARG_DISPLAY_NAME
+ * @see #QUERY_ARG_FILE_SIZE_OVER
+ * @see #QUERY_ARG_LAST_MODIFIED_AFTER
+ * @see #QUERY_ARG_MIME_TYPES
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
+ */
+ public static final String COLUMN_QUERY_ARGS = "query_args";
+
+ /**
* MIME type for a root.
*/
public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
@@ -680,6 +703,8 @@
* String)
* @see DocumentsProvider#querySearchDocuments(String, String,
* String[])
+ * @see DocumentsProvider#querySearchDocuments(String, String[],
+ * Bundle)
*/
public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
@@ -1109,11 +1134,13 @@
}
/**
- * Test if the given URI represents roots backed by {@link DocumentsProvider}.
+ * Test if the given URI represents all roots of the authority
+ * backed by {@link DocumentsProvider}.
*
* @see #buildRootsUri(String)
*/
- public static boolean isRootsUri(Context context, @Nullable Uri uri) {
+ public static boolean isRootsUri(@NonNull Context context, @Nullable Uri uri) {
+ Preconditions.checkNotNull(context, "context can not be null");
return isRootUri(context, uri, 1 /* pathSize */);
}
@@ -1122,7 +1149,8 @@
*
* @see #buildRootUri(String, String)
*/
- public static boolean isRootUri(Context context, @Nullable Uri uri) {
+ public static boolean isRootUri(@NonNull Context context, @Nullable Uri uri) {
+ Preconditions.checkNotNull(context, "context can not be null");
return isRootUri(context, uri, 2 /* pathSize */);
}
@@ -1215,6 +1243,7 @@
* {@hide}
*/
public static String getSearchDocumentsQuery(@NonNull Bundle bundle) {
+ Preconditions.checkNotNull(bundle, "bundle can not be null");
return bundle.getString(QUERY_ARG_DISPLAY_NAME, "" /* defaultValue */);
}
@@ -1315,8 +1344,12 @@
* @return if given document is a descendant of the given parent.
* @see Root#FLAG_SUPPORTS_IS_CHILD
*/
- public static boolean isChildDocument(ContentInterface content, Uri parentDocumentUri,
- Uri childDocumentUri) throws FileNotFoundException {
+ public static boolean isChildDocument(@NonNull ContentInterface content,
+ @NonNull Uri parentDocumentUri, @NonNull Uri childDocumentUri)
+ throws FileNotFoundException {
+ Preconditions.checkNotNull(content, "content can not be null");
+ Preconditions.checkNotNull(parentDocumentUri, "parentDocumentUri can not be null");
+ Preconditions.checkNotNull(childDocumentUri, "childDocumentUri can not be null");
try {
final Bundle in = new Bundle();
in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
@@ -1325,7 +1358,7 @@
final Bundle out = content.call(parentDocumentUri.getAuthority(),
METHOD_IS_CHILD_DOCUMENT, null, in);
if (out == null) {
- throw new RemoteException("Failed to get a reponse from isChildDocument query.");
+ throw new RemoteException("Failed to get a response from isChildDocument query.");
}
if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
throw new RemoteException("Response did not include result field..");
@@ -1559,8 +1592,10 @@
* @param documentUri a Document URI
* @return a Bundle of Bundles.
*/
- public static Bundle getDocumentMetadata(ContentInterface content, Uri documentUri)
- throws FileNotFoundException {
+ public static @Nullable Bundle getDocumentMetadata(@NonNull ContentInterface content,
+ @NonNull Uri documentUri) throws FileNotFoundException {
+ Preconditions.checkNotNull(content, "content can not be null");
+ Preconditions.checkNotNull(documentUri, "documentUri can not be null");
try {
final Bundle in = new Bundle();
in.putParcelable(EXTRA_URI, documentUri);
@@ -1595,8 +1630,6 @@
*/
public static Path findDocumentPath(ContentInterface content, Uri treeUri)
throws FileNotFoundException {
- checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri.");
-
try {
final Bundle in = new Bundle();
in.putParcelable(DocumentsContract.EXTRA_URI, treeUri);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 70c84f8..9b9e2de 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -37,6 +37,7 @@
import android.Manifest;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AuthenticationRequiredException;
import android.content.ClipDescription;
@@ -63,6 +64,8 @@
import android.provider.DocumentsContract.Root;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
@@ -687,14 +690,17 @@
* extras {@link Bundle} when any QUERY_ARG_* value was honored
* during the preparation of the results.
*
+ * @see Root#COLUMN_QUERY_ARGS
* @see ContentResolver#EXTRA_HONORED_ARGS
* @see DocumentsContract#EXTRA_LOADING
* @see DocumentsContract#EXTRA_INFO
* @see DocumentsContract#EXTRA_ERROR
*/
@SuppressWarnings("unused")
- public Cursor querySearchDocuments(String rootId, String[] projection, Bundle queryArgs)
- throws FileNotFoundException {
+ public Cursor querySearchDocuments(@NonNull String rootId, @Nullable String[] projection,
+ @NonNull Bundle queryArgs) throws FileNotFoundException {
+ Preconditions.checkNotNull(rootId, "rootId can not be null");
+ Preconditions.checkNotNull(queryArgs, "queryArgs can not be null");
return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
projection);
}
@@ -732,7 +738,7 @@
* @return a Bundle of Bundles.
* @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri)
*/
- public @Nullable Bundle getDocumentMetadata(String documentId)
+ public @Nullable Bundle getDocumentMetadata(@NonNull String documentId)
throws FileNotFoundException {
throw new UnsupportedOperationException("Metadata not supported");
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 3a49986..487198b 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -70,6 +70,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -1224,7 +1226,7 @@
if (sv.isPrimary()) {
return VOLUME_EXTERNAL;
} else {
- return checkArgumentVolumeName(sv.getUuid());
+ return checkArgumentVolumeName(sv.getNormalizedUuid());
}
}
throw new IllegalStateException("Unknown volume at " + path);
@@ -2919,7 +2921,7 @@
if (vi.isPrimary()) {
volumeNames.add(VOLUME_EXTERNAL);
} else {
- volumeNames.add(vi.getFsUuid());
+ volumeNames.add(vi.getNormalizedFsUuid());
}
}
}
@@ -2953,8 +2955,7 @@
// When not one of the well-known values above, it must be a hex UUID
for (int i = 0; i < volumeName.length(); i++) {
final char c = volumeName.charAt(i);
- if (('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
- || ('0' <= c && c <= '9') || (c == '-')) {
+ if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) {
continue;
} else {
throw new IllegalArgumentException("Invalid volume name: " + volumeName);
@@ -2963,23 +2964,26 @@
return volumeName;
}
- /** {@hide} */
+ /**
+ * Return path where the given volume is mounted. Not valid for
+ * {@link #VOLUME_INTERNAL}.
+ *
+ * @hide
+ */
public static @NonNull File getVolumePath(@NonNull String volumeName)
throws FileNotFoundException {
if (TextUtils.isEmpty(volumeName)) {
throw new IllegalArgumentException();
}
- if (VOLUME_INTERNAL.equals(volumeName)) {
- return Environment.getDataDirectory();
- } else if (VOLUME_EXTERNAL.equals(volumeName)) {
+ if (VOLUME_EXTERNAL.equals(volumeName)) {
return Environment.getExternalStorageDirectory();
}
final StorageManager sm = AppGlobals.getInitialApplication()
.getSystemService(StorageManager.class);
for (VolumeInfo vi : sm.getVolumes()) {
- if (Objects.equals(vi.getFsUuid(), volumeName)) {
+ if (Objects.equals(vi.getNormalizedFsUuid(), volumeName)) {
final File path = vi.getPathForUser(UserHandle.myUserId());
if (path != null) {
return path;
@@ -2992,6 +2996,33 @@
}
/**
+ * Return paths that should be scanned for the given volume.
+ *
+ * @hide
+ */
+ public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
+ throws FileNotFoundException {
+ if (TextUtils.isEmpty(volumeName)) {
+ throw new IllegalArgumentException();
+ }
+
+ final ArrayList<File> res = new ArrayList<>();
+ if (VOLUME_INTERNAL.equals(volumeName)) {
+ res.add(new File(Environment.getRootDirectory(), "media"));
+ res.add(new File(Environment.getOemDirectory(), "media"));
+ res.add(new File(Environment.getProductDirectory(), "media"));
+ } else {
+ res.add(getVolumePath(volumeName));
+ final UserManager um = AppGlobals.getInitialApplication()
+ .getSystemService(UserManager.class);
+ if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) {
+ res.add(Environment.getDataPreloadsMediaDirectory());
+ }
+ }
+ return res;
+ }
+
+ /**
* Uri for querying the state of the media scanner.
*/
public static Uri getMediaScannerUri() {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b2c2d0e..707778f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11244,6 +11244,8 @@
* service_max_inactivity (long)
* service_bg_start_timeout (long)
* process_start_async (boolean)
+ * use_mem_aware_service_restarts (boolean)
+ * service_restart_delay_duration (long)
* </pre>
*
* <p>
@@ -13928,11 +13930,12 @@
* The following keys are supported:
*
* <pre>
- * enabled (boolean)
- * requires_targeting_p (boolean)
- * max_squeeze_remeasure_attempts (int)
- * edit_choices_before_sending (boolean)
- * show_in_heads_up (boolean)
+ * enabled (boolean)
+ * requires_targeting_p (boolean)
+ * max_squeeze_remeasure_attempts (int)
+ * edit_choices_before_sending (boolean)
+ * show_in_heads_up (boolean)
+ * min_num_system_generated_replies (int)
* </pre>
* @see com.android.systemui.statusbar.policy.SmartReplyConstants
* @hide
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 4dc10cd..ffb524d 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -103,10 +103,23 @@
*/
public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
"android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
+
/** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
"android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
+ /** @see android.telephony.euicc.EuiccManager#ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+ /** @see android.telephony.euicc.EuiccManager#ACTION_DELETE_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+ /** @see android.telephony.euicc.EuiccManager#ACTION_RENAME_SUBSCRIPTION_PRIVILEGED */
+ public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+ "android.service.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
// LUI resolution actions. These are called by the platform to resolve errors in situations that
// require user interaction.
// TODO(b/33075886): Define extras for any input parameters to these dialogs once they are
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index f7acfc5..b0269e3 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -110,13 +110,5 @@
* @param standy True if the device is entering standby, false if it's exiting standby.
*/
void setStandbyEnabled(boolean standby);
-
- /**
- * Start VR Input method for the given packageName in {@param componentName}.
- * This method notifies InputMethodManagerService to use VR IME instead of
- * regular phone IME.
- */
- void setVrInputMethod(in ComponentName componentName);
-
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0d7223d..b197c8a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -784,8 +784,11 @@
// only internal implementations like ImageWallpaper
DisplayInfo displayInfo = new DisplayInfo();
mDisplay.getDisplayInfo(displayInfo);
- mLayout.width = Math.max(displayInfo.logicalWidth, myWidth);
- mLayout.height = Math.max(displayInfo.logicalHeight, myHeight);
+ final float layoutScale = Math.max(
+ (float) displayInfo.logicalHeight / (float) myHeight,
+ (float) displayInfo.logicalWidth / (float) myWidth);
+ mLayout.height = (int) (myHeight * layoutScale);
+ mLayout.width = (int) (myWidth * layoutScale);
mWindowFlags |= WindowManager.LayoutParams.FLAG_SCALED;
}
diff --git a/core/java/android/util/DocumentsStatsLog.java b/core/java/android/util/DocumentsStatsLog.java
new file mode 100644
index 0000000..f483944
--- /dev/null
+++ b/core/java/android/util/DocumentsStatsLog.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.SystemApi;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+
+/**
+ * DocumentsStatsLog provides APIs to send DocumentsUI related events to statsd.
+ * @hide
+ */
+@SystemApi
+public class DocumentsStatsLog {
+
+ private DocumentsStatsLog() {}
+
+ /**
+ * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
+ *
+ * @param action action that launches DocumentsUI.
+ * @param hasInitialUri is DocumentsUI launched with
+ * {@link DocumentsContract#EXTRA_INITIAL_URI}.
+ * @param mimeType the requested mime type.
+ * @param rootUri the resolved rootUri, or {@code null} if the provider doesn't
+ * support {@link DocumentsProvider#findDocumentPath(String, String)}
+ */
+ public static void logActivityLaunch(
+ int action, boolean hasInitialUri, int mimeType, int rootUri) {
+ StatsLog.write(StatsLog.DOCS_UI_LAUNCH_REPORTED, action, hasInitialUri, mimeType, rootUri);
+ }
+
+ /**
+ * Logs root visited event.
+ *
+ * @param scope whether it's in FILES or PICKER mode.
+ * @param root the root that user visited
+ */
+ public static void logRootVisited(int scope, int root) {
+ StatsLog.write(StatsLog.DOCS_UI_ROOT_VISITED, scope, root);
+ }
+
+ /**
+ * Logs file operation stats. Call this when a file operation has completed.
+ *
+ * @param provider whether it's system or external provider
+ * @param fileOp the file operation
+ */
+ public static void logFileOperation(int provider, int fileOp) {
+ StatsLog.write(StatsLog.DOCS_UI_PROVIDER_FILE_OP, provider, fileOp);
+ }
+
+ /**
+ * Logs file operation stats. Call this when a copy/move operation has completed with a specific
+ * mode.
+ *
+ * @param fileOp copy or move file operation
+ * @param mode the mode for copy and move operation
+ */
+ public static void logFileOperationCopyMoveMode(int fileOp, int mode) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_COPY_MOVE_MODE_REPORTED, fileOp, mode);
+ }
+
+ /**
+ * Logs file sub operation stats. Call this when a file operation has failed.
+ *
+ * @param authority the authority of the source document
+ * @param subOp the sub-file operation
+ */
+ public static void logFileOperationFailure(int authority, int subOp) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_FAILURE, authority, subOp);
+ }
+
+ /**
+ * Logs the cancellation of a file operation. Call this when a job is canceled
+ *
+ * @param fileOp the file operation.
+ */
+ public static void logFileOperationCanceled(int fileOp) {
+ StatsLog.write(StatsLog.DOCS_UI_FILE_OP_CANCELED, fileOp);
+ }
+
+ /**
+ * Logs startup time in milliseconds.
+ *
+ * @param startupMs
+ */
+ public static void logStartupMs(int startupMs) {
+ StatsLog.write(StatsLog.DOCS_UI_STARTUP_MS, startupMs);
+ }
+
+ /**
+ * Logs the action that was started by user.
+ *
+ * @param userAction
+ */
+ public static void logUserAction(int userAction) {
+ StatsLog.write(StatsLog.DOCS_UI_USER_ACTION_REPORTED, userAction);
+ }
+
+ /**
+ * Logs the invalid type when invalid scoped access is requested.
+ *
+ * @param type the type of invalid scoped access request.
+ */
+ public static void logInvalidScopedAccessRequest(int type) {
+ StatsLog.write(StatsLog.DOCS_UI_INVALID_SCOPED_ACCESS_REQUEST, type);
+ }
+
+ /**
+ * Logs the package name that launches docsui picker mode.
+ *
+ * @param packageName
+ */
+ public static void logPickerLaunchedFrom(String packageName) {
+ StatsLog.write(StatsLog.DOCS_UI_PICKER_LAUNCHED_FROM_REPORTED, packageName);
+ }
+
+ /**
+ * Logs the search type.
+ *
+ * @param searchType
+ */
+ public static void logSearchType(int searchType) {
+ StatsLog.write(StatsLog.DOCS_UI_SEARCH_TYPE_REPORTED, searchType);
+ }
+
+ /**
+ * Logs the search mode.
+ *
+ * @param searchMode
+ */
+ public static void logSearchMode(int searchMode) {
+ StatsLog.write(StatsLog.DOCS_UI_SEARCH_MODE_REPORTED, searchMode);
+ }
+
+ /**
+ * Logs the pick result information.
+ *
+ * @param actionCount total user action count during pick process.
+ * @param duration total time spent on pick process.
+ * @param fileCount number of picked files.
+ * @param isSearching are the picked files found by search.
+ * @param root the root where the picked files located.
+ * @param mimeType the mime type of the picked file. Only for single-select case.
+ * @param repeatedlyPickTimes number of times that the file has been picked before. Only for
+ * single-select case.
+ */
+ public static void logFilePick(int actionCount, long duration, int fileCount,
+ boolean isSearching, int root, int mimeType, int repeatedlyPickTimes) {
+ StatsLog.write(StatsLog.DOCS_UI_PICK_RESULT_REPORTED, actionCount, duration, fileCount,
+ isSearching, root, mimeType, repeatedlyPickTimes);
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 330d72f..42ac880 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -51,6 +51,7 @@
import android.view.AppTransitionAnimationSpec;
import android.view.WindowContentFrameStats;
import android.view.WindowManager;
+import android.view.SurfaceControl;
/**
* System private interface to the window manager.
@@ -555,8 +556,8 @@
* display content info to any SurfaceControl, as this would be a security issue.
*
* @param displayId The id of the display.
- * @param surfaceControlHandle The SurfaceControl handle that the top level layers for the
+ * @param surfaceControlHandle The SurfaceControl that the top level layers for the
* display should be re-parented to.
*/
- void reparentDisplayContent(int displayId, in IBinder surfaceControlHandle);
+ void reparentDisplayContent(int displayId, in SurfaceControl sc);
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index f3cb376..7fcb2af 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -185,6 +185,18 @@
}
/**
+ * Create a Surface assosciated with a given {@link SurfaceControl}. Buffers submitted to this
+ * surface will be displayed by the system compositor according to the parameters
+ * specified by the control. Multiple surfaces may be constructed from one SurfaceControl,
+ * but only one can be connected (e.g. have an active EGL context) at a time.
+ *
+ * @param from The SurfaceControl to assosciate this Surface with
+ */
+ public Surface(SurfaceControl from) {
+ copyFrom(from);
+ }
+
+ /**
* Create Surface from a {@link SurfaceTexture}.
*
* Images drawn to the Surface will be made available to the {@link
@@ -494,7 +506,6 @@
* in to it.
*
* @param other {@link SurfaceControl} to copy from.
- *
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5e98236..863b717 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -27,6 +27,10 @@
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.NAME;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
@@ -58,10 +62,16 @@
import java.io.Closeable;
/**
- * SurfaceControl
- * @hide
+ * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is
+ * a combination of a buffer source, and metadata about how to display the buffers.
+ * By constructing a {@link Surface} from this SurfaceControl you can submit buffers to be
+ * composited. Using {@link SurfaceControl.Transaction} you can manipulate various
+ * properties of how the buffer will be displayed on-screen. SurfaceControl's are
+ * arranged into a scene-graph like hierarchy, and as such any SurfaceControl may have
+ * a parent. Geometric properties like transform, crop, and Z-ordering will be inherited
+ * from the parent, as if the child were content in the parents buffer stream.
*/
-public class SurfaceControl implements Parcelable {
+public final class SurfaceControl implements Parcelable {
private static final String TAG = "SurfaceControl";
private static native long nativeCreate(SurfaceSession session, String name,
@@ -103,6 +113,8 @@
float dtdy, float dsdy);
private static native void nativeSetColorTransform(long transactionObj, long nativeObject,
float[] matrix, float[] translation);
+ private static native void nativeSetGeometry(long transactionObj, long nativeObject,
+ Rect sourceCrop, Rect dest, long orientation);
private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
private static native void nativeSetFlags(long transactionObj, long nativeObject,
int flags, int mask);
@@ -156,7 +168,7 @@
private static native void nativeReparentChildren(long transactionObj, long nativeObject,
IBinder handle);
private static native void nativeReparent(long transactionObj, long nativeObject,
- IBinder parentHandle);
+ long newParentNativeObject);
private static native void nativeSeverChildren(long transactionObj, long nativeObject);
private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
int scalingMode);
@@ -331,8 +343,7 @@
*/
public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
- /* Display power modes * /
-
+ // Display power modes.
/**
* Display power mode off: used while blanking the screen.
* Use only with {@link SurfaceControl#setDisplayPowerMode}.
@@ -403,7 +414,6 @@
/**
* Builder class for {@link SurfaceControl} objects.
- * @hide
*/
public static class Builder {
private SurfaceSession mSession;
@@ -427,8 +437,14 @@
}
/**
- * Construct a new {@link SurfaceControl} with the set parameters.
- * @hide
+ * Begin building a SurfaceControl.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Construct a new {@link SurfaceControl} with the set parameters. The builder
+ * remains valid.
*/
public SurfaceControl build() {
if (mWidth < 0 || mHeight < 0) {
@@ -447,7 +463,6 @@
* Set a debugging-name for the SurfaceControl.
*
* @param name A name to identify the Surface in debugging.
- * @hide
*/
public Builder setName(String name) {
mName = name;
@@ -459,9 +474,9 @@
*
* @param width The buffer width in pixels.
* @param height The buffer height in pixels.
- * @hide
*/
- public Builder setBufferSize(int width, int height) {
+ public Builder setBufferSize(@IntRange(from = 0) int width,
+ @IntRange(from = 0) int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException(
"width and height must be positive");
@@ -474,8 +489,8 @@
/**
* Set the pixel format of the controlled surface's buffers, using constants from
* {@link android.graphics.PixelFormat}.
- * @hide
*/
+ @NonNull
public Builder setFormat(@PixelFormat.Format int format) {
mFormat = format;
return this;
@@ -490,6 +505,7 @@
* @param protectedContent Whether to require a protected sink.
* @hide
*/
+ @NonNull
public Builder setProtected(boolean protectedContent) {
if (protectedContent) {
mFlags |= PROTECTED_APP;
@@ -506,6 +522,7 @@
* not a complete prevention of readback as {@link #setProtected}.
* @hide
*/
+ @NonNull
public Builder setSecure(boolean secure) {
if (secure) {
mFlags |= SECURE;
@@ -537,8 +554,8 @@
* If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
* were set automatically.
* @param opaque Whether the Surface is OPAQUE.
- * @hide
*/
+ @NonNull
public Builder setOpaque(boolean opaque) {
if (opaque) {
mFlags |= OPAQUE;
@@ -556,9 +573,9 @@
* of the parent.
*
* @param parent The parent control.
- * @hide
*/
- public Builder setParent(SurfaceControl parent) {
+ @NonNull
+ public Builder setParent(@Nullable SurfaceControl parent) {
mParent = parent;
return this;
}
@@ -673,9 +690,6 @@
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, int windowType, int ownerUid)
throws OutOfResourcesException, IllegalArgumentException {
- if (session == null) {
- throw new IllegalArgumentException("session must not be null");
- }
if (name == null) {
throw new IllegalArgumentException("name must not be null");
}
@@ -729,9 +743,6 @@
mCloseGuard.open("release");
}
- /**
- * @hide
- */
public void readFromParcel(Parcel in) {
if (in == null) {
throw new IllegalArgumentException("source must not be null");
@@ -748,17 +759,11 @@
assignNativeObject(object);
}
- /**
- * @hide
- */
@Override
public int describeContents() {
return 0;
}
- /**
- * @hide
- */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
@@ -791,9 +796,6 @@
proto.end(token);
}
- /**
- * @hide
- */
public static final Creator<SurfaceControl> CREATOR
= new Creator<SurfaceControl>() {
public SurfaceControl createFromParcel(Parcel in) {
@@ -823,10 +825,12 @@
}
/**
- * Release the local reference to the server-side surface.
- * Always call release() when you're done with a Surface.
- * This will make the surface invalid.
- * @hide
+ * Release the local reference to the server-side surface. The surface
+ * may continue to exist on-screen as long as its parent continues
+ * to exist. To explicitly remove a surface from the screen use
+ * {@link Transaction#reparent} with a null-parent.
+ *
+ * Always call release() when you're done with a SurfaceControl.
*/
public void release() {
if (mNativeObject != 0) {
@@ -866,7 +870,10 @@
}
/**
- * @hide
+ * Check whether this instance points to a valid layer with the system-compositor. For
+ * example this may be false if construction failed, or the layer was released.
+ *
+ * @return Whether this SurfaceControl is valid.
*/
public boolean isValid() {
return mNativeObject != 0;
@@ -962,9 +969,9 @@
/**
* @hide
*/
- public void reparent(IBinder newParentHandle) {
+ public void reparent(SurfaceControl newParent) {
synchronized(SurfaceControl.class) {
- sGlobalTransaction.reparent(this, newParentHandle);
+ sGlobalTransaction.reparent(this, newParent);
}
}
@@ -1270,9 +1277,6 @@
}
}
- /**
- * @hide
- */
@Override
public String toString() {
return "Surface(name=" + mName + ")/@0x" +
@@ -1286,6 +1290,7 @@
/**
* Describes the properties of a physical display known to surface flinger.
+ * @hide
*/
public static final class PhysicalDisplayInfo {
/**
@@ -1777,9 +1782,12 @@
}
/**
- * @hide
+ * An atomic set of changes to a set of SurfaceControl.
*/
public static class Transaction implements Closeable {
+ /**
+ * @hide
+ */
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
Transaction.class.getClassLoader(),
nativeGetNativeTransactionFinalizer(), 512);
@@ -1789,9 +1797,12 @@
Runnable mFreeNativeResources;
/**
- * @hide
+ * Open a new transaction object. The transaction may be filed with commands to
+ * manipulate {@link SurfaceControl} instances, and then applied atomically with
+ * {@link #apply}. Eventually the user should invoke {@link #close}, when the object
+ * is no longer required. Note however that re-using a transaction after a call to apply
+ * is allowed as a convenience.
*/
- @UnsupportedAppUsage
public Transaction() {
mNativeObject = nativeCreateTransaction();
mFreeNativeResources
@@ -1801,9 +1812,7 @@
/**
* Apply the transaction, clearing it's state, and making it usable
* as a new transaction.
- * @hide
*/
- @UnsupportedAppUsage
public void apply() {
apply(false);
}
@@ -1811,7 +1820,6 @@
/**
* Close the transaction, if the transaction was not already applied this will cancel the
* transaction.
- * @hide
*/
@Override
public void close() {
@@ -1841,6 +1849,27 @@
}
/**
+ * Toggle the visibility of a given Layer and it's sub-tree.
+ *
+ * @param sc The SurfaceControl for which to set the visibility
+ * @param visible The new visibility
+ * @return This transaction object.
+ */
+ @NonNull
+ public Transaction setVisibility(@NonNull SurfaceControl sc, boolean visible) {
+ sc.checkNotReleased();
+ if (visible) {
+ return show(sc);
+ } else {
+ return hide(sc);
+ }
+ }
+
+ /**
+ * Request that a given surface and it's sub-tree be shown.
+ *
+ * @param sc The surface to show.
+ * @return This transaction.
* @hide
*/
@UnsupportedAppUsage
@@ -1851,6 +1880,10 @@
}
/**
+ * Request that a given surface and it's sub-tree be hidden.
+ *
+ * @param sc The surface to hidden.
+ * @return This transaction.
* @hide
*/
@UnsupportedAppUsage
@@ -1871,10 +1904,17 @@
}
/**
- * @hide
+ * Set the default buffer size for the SurfaceControl, if there is an
+ * {@link Surface} assosciated with the control, then
+ * this will be the default size for buffers dequeued from it.
+ * @param sc The surface to set the buffer size for.
+ * @param w The default width
+ * @param h The default height
+ * @return This Transaction
*/
- @UnsupportedAppUsage
- public Transaction setBufferSize(SurfaceControl sc, int w, int h) {
+ @NonNull
+ public Transaction setBufferSize(@NonNull SurfaceControl sc,
+ @IntRange(from = 0) int w, @IntRange(from = 0) int h) {
sc.checkNotReleased();
mResizedSurfaces.put(sc, new Point(w, h));
nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
@@ -1882,10 +1922,17 @@
}
/**
- * @hide
+ * Set the Z-order for a given SurfaceControl, relative to it's siblings.
+ * If two siblings share the same Z order the ordering is undefined. Surfaces
+ * with a negative Z will be placed below the parent surface.
+ *
+ * @param sc The SurfaceControl to set the Z order on
+ * @param z The Z-order
+ * @return This Transaction.
*/
- @UnsupportedAppUsage
- public Transaction setLayer(SurfaceControl sc, int z) {
+ @NonNull
+ public Transaction setLayer(@NonNull SurfaceControl sc,
+ @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
sc.checkNotReleased();
nativeSetLayer(mNativeObject, sc.mNativeObject, z);
return this;
@@ -1912,10 +1959,15 @@
}
/**
- * @hide
+ * Set the alpha for a given surface. If the alpha is non-zero the SurfaceControl
+ * will be blended with the Surfaces under it according to the specified ratio.
+ *
+ * @param sc The given SurfaceControl.
+ * @param alpha The alpha to set.
*/
- @UnsupportedAppUsage
- public Transaction setAlpha(SurfaceControl sc, float alpha) {
+ @NonNull
+ public Transaction setAlpha(@NonNull SurfaceControl sc,
+ @FloatRange(from = 0.0, to = 1.0) float alpha) {
sc.checkNotReleased();
nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
return this;
@@ -1947,6 +1999,25 @@
}
/**
+ * Specify how the buffer assosciated with this Surface is mapped in to the
+ * parent coordinate space. The source frame will be scaled to fit the destination
+ * frame, after being rotated according to the orientation parameter.
+ *
+ * @param sc The SurfaceControl to specify the geometry of
+ * @param sourceCrop The source rectangle in buffer space. Or null for the entire buffer.
+ * @param destFrame The destination rectangle in parent space. Or null for the source frame.
+ * @param orientation The buffer rotation
+ * @return This transaction object.
+ */
+ @NonNull
+ public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
+ @Nullable Rect destFrame, @Surface.Rotation int orientation) {
+ sc.checkNotReleased();
+ nativeSetGeometry(mNativeObject, sc.mNativeObject, sourceCrop, destFrame, orientation);
+ return this;
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
@@ -2023,20 +2094,20 @@
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
sc.checkNotReleased();
nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle,
long frameNumber) {
if (frameNumber < 0) {
@@ -2047,10 +2118,10 @@
return this;
}
- @UnsupportedAppUsage
/**
* @hide
*/
+ @UnsupportedAppUsage
public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
long frameNumber) {
if (frameNumber < 0) {
@@ -2071,13 +2142,25 @@
return this;
}
- /** Re-parents a specific child layer to a new parent
- * @hide
+ /**
+ * Re-parents a given layer to a new parent. Children inherit transform (position, scaling)
+ * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the
+ * parent Surface.
+ *
+ * @param sc The SurfaceControl to reparent
+ * @param newParent The new parent for the given control.
+ * @return This Transaction
*/
- public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+ @NonNull
+ public Transaction reparent(@NonNull SurfaceControl sc,
+ @Nullable SurfaceControl newParent) {
sc.checkNotReleased();
- nativeReparent(mNativeObject, sc.mNativeObject,
- newParentHandle);
+ long otherObject = 0;
+ if (newParent != null) {
+ newParent.checkNotReleased();
+ otherObject = newParent.mNativeObject;
+ }
+ nativeReparent(mNativeObject, sc.mNativeObject, otherObject);
return this;
}
@@ -2245,9 +2328,12 @@
/**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
- * @hide
+ *
+ * @param other The transaction to merge in to this one.
+ * @return This transaction.
*/
- public Transaction merge(Transaction other) {
+ @NonNull
+ public Transaction merge(@NonNull Transaction other) {
mResizedSurfaces.putAll(other.mResizedSurfaces);
other.mResizedSurfaces.clear();
nativeMergeTransaction(mNativeObject, other.mNativeObject);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 61fb00d..45e6c50 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -120,10 +120,11 @@
final Rect mScreenRect = new Rect();
SurfaceSession mSurfaceSession;
- SurfaceControlWithBackground mSurfaceControl;
+ SurfaceControl mSurfaceControl;
// In the case of format changes we switch out the surface in-place
// we need to preserve the old one until the new one has drawn.
SurfaceControl mDeferredDestroySurfaceControl;
+ SurfaceControl mBackgroundControl;
final Rect mTmpRect = new Rect();
final Configuration mConfiguration = new Configuration();
@@ -487,6 +488,29 @@
}
}
+ private void updateBackgroundVisibilityInTransaction() {
+ if (mBackgroundControl == null) {
+ return;
+ }
+ if ((mSurfaceFlags & PixelFormat.OPAQUE) == 0) {
+ mBackgroundControl.show();
+ mBackgroundControl.setLayer(Integer.MIN_VALUE);
+ } else {
+ mBackgroundControl.hide();
+ }
+ }
+
+ private void releaseSurfaces() {
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ if (mBackgroundControl != null) {
+ mBackgroundControl.destroy();
+ mBackgroundControl = null;
+ }
+ }
+
/** @hide */
protected void updateSurface() {
if (!mHaveFrame) {
@@ -553,14 +577,21 @@
updateOpaqueFlag();
final String name = "SurfaceView - " + viewRoot.getTitle().toString();
- mSurfaceControl = new SurfaceControlWithBackground(
- name,
- (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
- new SurfaceControl.Builder(mSurfaceSession)
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
- .setFormat(mFormat)
- .setParent(viewRoot.getSurfaceControl())
- .setFlags(mSurfaceFlags));
+ mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName(name)
+ .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
+ .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+ .setFormat(mFormat)
+ .setParent(viewRoot.getSurfaceControl())
+ .setFlags(mSurfaceFlags)
+ .build();
+ mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName("Background for -" + name)
+ .setOpaque(true)
+ .setColorLayer(true)
+ .setParent(mSurfaceControl)
+ .build();
+
} else if (mSurfaceControl == null) {
return;
}
@@ -577,11 +608,13 @@
SurfaceControl.openTransaction();
try {
mSurfaceControl.setLayer(mSubLayer);
+
if (mViewVisibility) {
mSurfaceControl.show();
} else {
mSurfaceControl.hide();
}
+ updateBackgroundVisibilityInTransaction();
// While creating the surface, we will set it's initial
// geometry. Outside of that though, we should generally
@@ -724,8 +757,7 @@
if (mSurfaceControl != null && !mSurfaceCreated) {
mSurface.release();
- mSurfaceControl.destroy();
- mSurfaceControl = null;
+ releaseSurfaces();
}
}
} catch (Exception ex) {
@@ -823,7 +855,6 @@
final ViewRootImpl viewRoot = getViewRootImpl();
applySurfaceTransforms(mSurfaceControl, position, frameNumber);
- applySurfaceTransforms(mSurfaceControl.mBackgroundControl, position, frameNumber);
applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface,
frameNumber);
@@ -950,7 +981,19 @@
* @hide
*/
public void setResizeBackgroundColor(int bgColor) {
- mSurfaceControl.setBackgroundColor(bgColor);
+ if (mBackgroundControl == null) {
+ return;
+ }
+
+ final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
+ Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
+
+ SurfaceControl.openTransaction();
+ try {
+ mBackgroundControl.setColor(colorComponents);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
}
@UnsupportedAppUsage
@@ -1128,154 +1171,12 @@
};
/**
- * @hide
+ * Return a SurfaceControl which can be used for parenting Surfaces to
+ * this SurfaceView.
+ *
+ * @return The SurfaceControl for this SurfaceView.
*/
public SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
-
- class SurfaceControlWithBackground extends SurfaceControl {
- SurfaceControl mBackgroundControl;
- private boolean mOpaque = true;
- public boolean mVisible = false;
-
- public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
- throws Exception {
- super(b.setName(name).build());
-
- mBackgroundControl = b.setName("Background for -" + name)
- .setFormat(OPAQUE)
- // Unset the buffer size of the background color layer.
- .setBufferSize(0, 0)
- .setColorLayer(true)
- .build();
- mOpaque = opaque;
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- mBackgroundControl.setAlpha(alpha);
- }
-
- @Override
- public void setLayer(int zorder) {
- super.setLayer(zorder);
- // -3 is below all other child layers as SurfaceView never goes below -2
- mBackgroundControl.setLayer(-3);
- }
-
- @Override
- public void setPosition(float x, float y) {
- super.setPosition(x, y);
- mBackgroundControl.setPosition(x, y);
- }
-
- @Override
- public void setBufferSize(int w, int h) {
- super.setBufferSize(w, h);
- // The background surface is a color layer so we do not set a size.
- }
-
- @Override
- public void setWindowCrop(Rect crop) {
- super.setWindowCrop(crop);
- mBackgroundControl.setWindowCrop(crop);
- }
-
- @Override
- public void setWindowCrop(int width, int height) {
- super.setWindowCrop(width, height);
- mBackgroundControl.setWindowCrop(width, height);
- }
-
- @Override
- public void setLayerStack(int layerStack) {
- super.setLayerStack(layerStack);
- mBackgroundControl.setLayerStack(layerStack);
- }
-
- @Override
- public void setOpaque(boolean isOpaque) {
- super.setOpaque(isOpaque);
- mOpaque = isOpaque;
- updateBackgroundVisibility();
- }
-
- @Override
- public void setSecure(boolean isSecure) {
- super.setSecure(isSecure);
- }
-
- @Override
- public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
- super.setMatrix(dsdx, dtdx, dsdy, dtdy);
- mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
- }
-
- @Override
- public void hide() {
- super.hide();
- mVisible = false;
- updateBackgroundVisibility();
- }
-
- @Override
- public void show() {
- super.show();
- mVisible = true;
- updateBackgroundVisibility();
- }
-
- @Override
- public void destroy() {
- super.destroy();
- mBackgroundControl.destroy();
- }
-
- @Override
- public void release() {
- super.release();
- mBackgroundControl.release();
- }
-
- @Override
- public void setTransparentRegionHint(Region region) {
- super.setTransparentRegionHint(region);
- mBackgroundControl.setTransparentRegionHint(region);
- }
-
- @Override
- public void deferTransactionUntil(IBinder handle, long frame) {
- super.deferTransactionUntil(handle, frame);
- mBackgroundControl.deferTransactionUntil(handle, frame);
- }
-
- @Override
- public void deferTransactionUntil(Surface barrier, long frame) {
- super.deferTransactionUntil(barrier, frame);
- mBackgroundControl.deferTransactionUntil(barrier, frame);
- }
-
- /** Set the color to fill the background with. */
- private void setBackgroundColor(int bgColor) {
- final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
- Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
-
- SurfaceControl.openTransaction();
- try {
- mBackgroundControl.setColor(colorComponents);
- } finally {
- SurfaceControl.closeTransaction();
- }
- }
-
- void updateBackgroundVisibility() {
- if (mOpaque && mVisible) {
- mBackgroundControl.show();
- } else {
- mBackgroundControl.hide();
- }
- }
- }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2131e6d..2014ec2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8227,7 +8227,15 @@
* </ul>
*/
public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
- onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "onProvideContentCaptureStructure() for " + getClass().getSimpleName());
+ }
+ try {
+ onProvideStructure(structure, VIEW_STRUCTURE_FOR_CONTENT_CAPTURE, flags);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
}
/** @hide */
@@ -9017,6 +9025,18 @@
* </ol>
*/
private void notifyAppearedOrDisappearedForContentCaptureIfNeeded(boolean appeared) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "notifyContentCapture(" + appeared + ") for " + getClass().getSimpleName());
+ }
+ try {
+ notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(appeared);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ }
+
+ private void notifyAppearedOrDisappearedForContentCaptureIfNeededNoTrace(boolean appeared) {
// First check if context has client, so it saves a service lookup when it doesn't
if (!mContext.isContentCaptureSupported()) return;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 86c5f18..10b99ec 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -922,19 +922,6 @@
}
}
- /**
- * Returns a list of VR InputMethod currently installed.
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
- public List<InputMethodInfo> getVrInputMethodList() {
- try {
- return mService.getVrInputMethodList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
public List<InputMethodInfo> getEnabledInputMethodList() {
try {
return mService.getEnabledInputMethodList();
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index a27dbea..18c4b46 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -74,6 +74,16 @@
private static final boolean LOG_INOTIFY = false;
+ protected static final String SUPPORTED_QUERY_ARGS = joinNewline(
+ DocumentsContract.QUERY_ARG_DISPLAY_NAME,
+ DocumentsContract.QUERY_ARG_FILE_SIZE_OVER,
+ DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
+ DocumentsContract.QUERY_ARG_MIME_TYPES);
+
+ private static String joinNewline(String... args) {
+ return TextUtils.join("\n", args);
+ }
+
private String[] mDefaultProjection;
@GuardedBy("mObservers")
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 051a96c..6454352 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -53,6 +53,7 @@
public static final boolean DETAILED_TRACKING_DEFAULT = true;
public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100;
public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000;
+ private static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
private static class OverflowBinder extends Binder {}
@@ -347,7 +348,7 @@
callStat.callingUid = uid;
callStat.recordedCallCount = 1;
callStat.callCount = 1;
- callStat.methodName = "__DEBUG_" + variableName;
+ callStat.methodName = DEBUG_ENTRY_PREFIX + variableName;
callStat.latencyMicros = value;
return callStat;
}
@@ -398,6 +399,10 @@
final List<ExportedCallStat> exportedCallStats = getExportedCallStats();
exportedCallStats.sort(BinderCallsStats::compareByCpuDesc);
for (ExportedCallStat e : exportedCallStats) {
+ if (e.methodName.startsWith(DEBUG_ENTRY_PREFIX)) {
+ // Do not dump debug entries.
+ continue;
+ }
sb.setLength(0);
sb.append(" ")
.append(packageMap.mapUid(e.callingUid))
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 9a7fb9f..0f0eedd 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -37,6 +37,7 @@
* @hide Only for use within the system server.
*/
public class LooperStats implements Looper.Observer {
+ public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
private static final int SESSION_POOL_SIZE = 50;
@GuardedBy("mLock")
@@ -165,7 +166,7 @@
}
private ExportedEntry createDebugEntry(String variableName, long value) {
- final Entry entry = new Entry("__DEBUG_" + variableName);
+ final Entry entry = new Entry(DEBUG_ENTRY_PREFIX + variableName);
entry.messageCount = 1;
entry.recordedMessageCount = 1;
entry.totalLatencyMicro = value;
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 705bae4..b28f4cd 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -126,7 +126,7 @@
private Zygote() {}
/** Called for some security initialization before any fork. */
- native static void nativeSecurityInit();
+ static native void nativeSecurityInit();
/**
* Forks a new VM instance. The current VM must have been started
@@ -170,9 +170,9 @@
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkAndSpecialize(
- uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
- fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
- packagesForUid, visibleVolIds);
+ uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
+ packagesForUid, visibleVolIds);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -184,15 +184,20 @@
return pid;
}
- native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags,
- int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
- int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- String packageName, String[] packagesForUid, String[] visibleVolIds);
+ private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
+ int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
+ int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
+ String appDataDir, String packageName, String[] packagesForUid, String[] visibleVolIds);
+
+ private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids,
+ int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
+ boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
+ String[] packagesForUid, String[] visibleVolIds);
/**
* Called to do any initialization before starting an application.
*/
- native static void nativePreApplicationInit();
+ static native void nativePreApplicationInit();
/**
* Special method to start the system server process. In addition to the
@@ -223,7 +228,8 @@
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkSystemServer(
- uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities);
+ uid, gid, gids, runtimeFlags, rlimits,
+ permittedCapabilities, effectiveCapabilities);
// Enable tracing as soon as we enter the system_server.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -232,13 +238,13 @@
return pid;
}
- native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
+ private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
/**
* Lets children of the zygote inherit open file descriptors to this path.
*/
- native protected static void nativeAllowFileAcrossFork(String path);
+ protected static native void nativeAllowFileAcrossFork(String path);
/**
* Installs a seccomp filter that limits setresuid()/setresgid() to the passed-in range
@@ -251,7 +257,22 @@
* Zygote unmount storage space on initializing.
* This method is called once.
*/
- native protected static void nativeUnmountStorageOnInit();
+ protected static native void nativeUnmountStorageOnInit();
+
+ protected static native void nativeGetSocketFDs(boolean isPrimary);
+
+ private static native int nativeGetBlastulaPoolCount();
+
+ private static native int nativeGetBlastulaPoolEventFD();
+
+ private static native int nativeForkBlastula(int readPipeFD,
+ int writePipeFD,
+ int[] sessionSocketRawFDs);
+
+ private static native int[] nativeGetBlastulaPipeFDs();
+
+ private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID);
+
private static void callPostForkSystemServerHooks() {
// SystemServer specific post fork hooks run before child post fork hooks.
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 605df04..f91b837 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -327,10 +327,6 @@
}
}
- public static @NonNull <T> List<T> defeatNullable(@Nullable List<T> val) {
- return (val != null) ? val : Collections.emptyList();
- }
-
/**
* @return the first element if not empty/null, null otherwise
*/
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 356d178..8194a92 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -35,7 +35,6 @@
// TODO: Use ParceledListSlice instead
List<InputMethodInfo> getInputMethodList();
- List<InputMethodInfo> getVrInputMethodList();
// TODO: Use ParceledListSlice instead
List<InputMethodInfo> getEnabledInputMethodList();
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c5dfe6c..be12700 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -186,6 +186,7 @@
"android_hardware_UsbDevice.cpp",
"android_hardware_UsbDeviceConnection.cpp",
"android_hardware_UsbRequest.cpp",
+ "android_hardware_location_ActivityRecognitionHardware.cpp",
"android_util_FileObserver.cpp",
"android/opengl/poly_clip.cpp", // TODO: .arm
"android/opengl/util.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5befab9..18d9b5a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -101,6 +101,7 @@
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
+extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
extern int register_android_media_AudioRecord(JNIEnv *env);
@@ -1460,6 +1461,7 @@
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
+ REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
REG_JNI(register_android_media_AudioEffectDescriptor),
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioRecord),
diff --git a/core/jni/android_app_ActivityThread.cpp b/core/jni/android_app_ActivityThread.cpp
index d56e4c5..93f2525 100644
--- a/core/jni/android_app_ActivityThread.cpp
+++ b/core/jni/android_app_ActivityThread.cpp
@@ -24,6 +24,8 @@
#include "core_jni_helpers.h"
#include <unistd.h>
+#include <bionic_malloc.h>
+
namespace android {
static void android_app_ActivityThread_purgePendingResources(JNIEnv* env, jobject clazz) {
@@ -38,13 +40,18 @@
minikin::Layout::dumpMinikinStats(fd);
}
+static void android_app_ActivityThread_initZygoteChildHeapProfiling(JNIEnv* env, jobject clazz) {
+ android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0);
+}
static JNINativeMethod gActivityThreadMethods[] = {
// ------------ Regular JNI ------------------
{ "nPurgePendingResources", "()V",
(void*) android_app_ActivityThread_purgePendingResources },
{ "nDumpGraphicsInfo", "(Ljava/io/FileDescriptor;)V",
- (void*) android_app_ActivityThread_dumpGraphics }
+ (void*) android_app_ActivityThread_dumpGraphics },
+ { "nInitZygoteChildHeapProfiling", "()V",
+ (void*) android_app_ActivityThread_initZygoteChildHeapProfiling }
};
int register_android_app_ActivityThread(JNIEnv* env) {
diff --git a/core/jni/android_graphics_ColorSpace.cpp b/core/jni/android_graphics_ColorSpace.cpp
index 7a9963e..7648fd0 100644
--- a/core/jni/android_graphics_ColorSpace.cpp
+++ b/core/jni/android_graphics_ColorSpace.cpp
@@ -20,7 +20,6 @@
#include "SkColor.h"
#include "SkColorSpace.h"
-#include "SkHalf.h"
using namespace android;
@@ -42,6 +41,12 @@
///////////////////////////////////////////////////////////////////////////////
+static float halfToFloat(uint16_t bits) {
+ __fp16 h;
+ memcpy(&h, &bits, 2);
+ return (float)h;
+}
+
SkColor4f GraphicsJNI::convertColorLong(jlong color) {
if ((color & 0x3f) == 0) {
// This corresponds to sRGB, which is treated differently than the rest.
@@ -54,9 +59,9 @@
}
// These match the implementation of android.graphics.Color#red(long) etc.
- float r = SkHalfToFloat((SkHalf)(color >> 48 & 0xffff));
- float g = SkHalfToFloat((SkHalf)(color >> 32 & 0xffff));
- float b = SkHalfToFloat((SkHalf)(color >> 16 & 0xffff));
+ float r = halfToFloat((uint16_t)(color >> 48 & 0xffff));
+ float g = halfToFloat((uint16_t)(color >> 32 & 0xffff));
+ float b = halfToFloat((uint16_t)(color >> 16 & 0xffff));
float a = (color >> 6 & 0x3ff) / 1023.0f;
return SkColor4f{r, g, b, a};
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
new file mode 100644
index 0000000..1c9ab94
--- /dev/null
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ActivityRecognitionHardware"
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+
+// #include <hardware/activity_recognition.h>
+// The activity recognition HAL is being deprecated. This means -
+// i) Android framework code shall not depend on activity recognition
+// being provided through the activity_recognition.h interface.
+// ii) activity recognition HAL will not be binderized as the other HALs.
+//
+
+/**
+ * Initializes the ActivityRecognitionHardware class from the native side.
+ */
+static void class_init(JNIEnv* /*env*/, jclass /*clazz*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * Initializes and connect the callbacks handlers in the HAL.
+ */
+static void initialize(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * De-initializes the ActivityRecognitionHardware from the native side.
+ */
+static void release(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+}
+
+/**
+ * Returns true if ActivityRecognition HAL is supported, false otherwise.
+ */
+static jboolean is_supported(JNIEnv* /*env*/, jclass /*clazz*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return JNI_FALSE;
+}
+
+/**
+ * Gets an array representing the supported activities.
+ */
+static jobjectArray get_supported_activities(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return NULL;
+}
+
+/**
+ * Enables a given activity event to be actively monitored.
+ */
+static int enable_activity_event(
+ JNIEnv* /*env*/,
+ jobject /*obj*/,
+ jint /*activity_handle*/,
+ jint /*event_type*/,
+ jlong /*report_latency_ns*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+/**
+ * Disables a given activity event from being actively monitored.
+ */
+static int disable_activity_event(
+ JNIEnv* /*env*/,
+ jobject /*obj*/,
+ jint /*activity_handle*/,
+ jint /*event_type*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+/**
+ * Request flush for al batch buffers.
+ */
+static int flush(JNIEnv* /*env*/, jobject /*obj*/) {
+ ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+ __FUNCTION__);
+ return android::NO_INIT;
+}
+
+
+static const JNINativeMethod sMethods[] = {
+ // {"name", "signature", (void*) functionPointer },
+ { "nativeClassInit", "()V", (void*) class_init },
+ { "nativeInitialize", "()V", (void*) initialize },
+ { "nativeRelease", "()V", (void*) release },
+ { "nativeIsSupported", "()Z", (void*) is_supported },
+ { "nativeGetSupportedActivities", "()[Ljava/lang/String;", (void*) get_supported_activities },
+ { "nativeEnableActivityEvent", "(IIJ)I", (void*) enable_activity_event },
+ { "nativeDisableActivityEvent", "(II)I", (void*) disable_activity_event },
+ { "nativeFlush", "()I", (void*) flush },
+};
+
+/**
+ * Registration method invoked in JNI load.
+ */
+int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env) {
+ return jniRegisterNativeMethods(
+ env,
+ "android/hardware/location/ActivityRecognitionHardware",
+ sMethods,
+ NELEM(sMethods));
+}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 897427f..0453195 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -126,7 +126,12 @@
jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
jint windowType, jint ownerUid) {
ScopedUtfChars name(env, nameStr);
- sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
+ sp<SurfaceComposerClient> client;
+ if (sessionObj != NULL) {
+ client = android_view_SurfaceSession_getClient(env, sessionObj);
+ } else {
+ client = SurfaceComposerClient::getDefault();
+ }
SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
sp<SurfaceControl> surface;
status_t err = client->createSurfaceChecked(
@@ -277,6 +282,21 @@
transaction->setPosition(ctrl, x, y);
}
+static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+ jobject sourceObj, jobject dstObj, jlong orientation) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+ Rect source, dst;
+ if (sourceObj != NULL) {
+ source = rectFromObj(env, sourceObj);
+ }
+ if (dstObj != NULL) {
+ dst = rectFromObj(env, dstObj);
+ }
+ transaction->setGeometry(ctrl, source, dst, orientation);
+}
+
static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz,
jlong transactionObj,
jlong nativeObject) {
@@ -868,13 +888,13 @@
static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject,
- jobject newParentObject) {
+ jlong newParentObject) {
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- sp<IBinder> parentHandle = ibinderForJavaObject(env, newParentObject);
+ auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject);
{
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->reparent(ctrl, parentHandle);
+ transaction->reparent(ctrl, newParent != NULL ? newParent->getHandle() : NULL);
}
}
@@ -1063,7 +1083,7 @@
(void*)nativeDeferTransactionUntilSurface },
{"nativeReparentChildren", "(JJLandroid/os/IBinder;)V",
(void*)nativeReparentChildren } ,
- {"nativeReparent", "(JJLandroid/os/IBinder;)V",
+ {"nativeReparent", "(JJJ)V",
(void*)nativeReparent },
{"nativeSeverChildren", "(JJ)V",
(void*)nativeSeverChildren } ,
@@ -1087,6 +1107,8 @@
{"nativeGetDisplayedContentSample",
"(Landroid/os/IBinder;JJ)Landroid/hardware/display/DisplayedContentSample;",
(void*)nativeGetDisplayedContentSample },
+ {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
+ (void*)nativeSetGeometry }
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index e81b627..2e7184b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -29,13 +29,17 @@
#include <sys/mount.h>
#include <linux/fs.h>
+#include <array>
+#include <atomic>
#include <functional>
#include <list>
#include <optional>
#include <sstream>
#include <string>
+#include <string_view>
#include <android/fdsan.h>
+#include <arpa/inet.h>
#include <fcntl.h>
#include <grp.h>
#include <inttypes.h>
@@ -46,9 +50,11 @@
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/cdefs.h>
+#include <sys/eventfd.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/resource.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -56,10 +62,11 @@
#include <sys/wait.h>
#include <unistd.h>
-#include "android-base/logging.h"
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
#include <cutils/fs.h>
#include <cutils/multiuser.h>
#include <private/android_filesystem_config.h>
@@ -81,6 +88,9 @@
namespace {
+// TODO (chriswailes): Add a function to initialize native Zygote data.
+// TODO (chriswailes): Fix mixed indentation style (2 and 4 spaces).
+
using namespace std::placeholders;
using android::String8;
@@ -92,6 +102,9 @@
#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
append(StringPrintf(__VA_ARGS__))
+// This type is duplicated in fd_utils.h
+typedef const std::function<void(std::string)>& fail_fn_t;
+
static pid_t gSystemServerPid = 0;
static const char kIsolatedStorage[] = "persist.sys.isolated_storage";
@@ -103,6 +116,152 @@
static bool g_is_security_enforced = true;
+/**
+ * The maximum number of characters (not including a null terminator) that a
+ * process name may contain.
+ */
+static constexpr size_t MAX_NAME_LENGTH = 15;
+
+/**
+ * The prefix string for environmental variables storing socket FDs created by
+ * init.
+ */
+
+static constexpr std::string_view ANDROID_SOCKET_PREFIX("ANDROID_SOCKET_");
+
+/**
+ * The file descriptor for the Zygote socket opened by init.
+ */
+
+static int gZygoteSocketFD = -1;
+
+/**
+ * The file descriptor for the Blastula pool socket opened by init.
+ */
+
+static int gBlastulaPoolSocketFD = -1;
+
+/**
+ * The number of Blastulas currently in this Zygote's pool.
+ */
+static std::atomic_uint32_t gBlastulaPoolCount = 0;
+
+/**
+ * Event file descriptor used to communicate reaped blastulas to the
+ * ZygoteServer.
+ */
+static int gBlastulaPoolEventFD = -1;
+
+/**
+ * The maximum value that the gBlastulaPoolMax variable may take. This value
+ * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT
+ */
+static constexpr int BLASTULA_POOL_MAX_LIMIT = 10;
+
+/**
+ * A helper class containing accounting information for Blastulas.
+ */
+class BlastulaTableEntry {
+ public:
+ struct EntryStorage {
+ int32_t pid;
+ int32_t read_pipe_fd;
+
+ bool operator!=(const EntryStorage& other) {
+ return pid != other.pid || read_pipe_fd != other.read_pipe_fd;
+ }
+ };
+
+ private:
+ static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1};
+
+ std::atomic<EntryStorage> mStorage;
+ static_assert(decltype(mStorage)::is_always_lock_free);
+
+ public:
+ constexpr BlastulaTableEntry() : mStorage(INVALID_ENTRY_VALUE) {}
+
+ /**
+ * If the provided PID matches the one stored in this entry, the entry will
+ * be invalidated and the associated file descriptor will be closed. If the
+ * PIDs don't match nothing will happen.
+ *
+ * @param pid The ID of the process who's entry we want to clear.
+ * @return True if the entry was cleared; false otherwise
+ */
+ bool ClearForPID(int32_t pid) {
+ EntryStorage storage = mStorage.load();
+
+ if (storage.pid == pid) {
+ /*
+ * There are three possible outcomes from this compare-and-exchange:
+ * 1) It succeeds, in which case we close the FD
+ * 2) It fails and the new value is INVALID_ENTRY_VALUE, in which case
+ * the entry has already been cleared.
+ * 3) It fails and the new value isn't INVALID_ENTRY_VALUE, in which
+ * case the entry has already been cleared and re-used.
+ *
+ * In all three cases the goal of the caller has been met and we can
+ * return true.
+ */
+ if (mStorage.compare_exchange_strong(storage, INVALID_ENTRY_VALUE)) {
+ close(storage.read_pipe_fd);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @return A copy of the data stored in this entry.
+ */
+ std::optional<EntryStorage> GetValues() {
+ EntryStorage storage = mStorage.load();
+
+ if (storage != INVALID_ENTRY_VALUE) {
+ return storage;
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ /**
+ * Sets the entry to the given values if it is currently invalid.
+ *
+ * @param pid The process ID for the new entry.
+ * @param read_pipe_fd The read end of the blastula control pipe for this
+ * process.
+ * @return True if the entry was set; false otherwise.
+ */
+ bool SetIfInvalid(int32_t pid, int32_t read_pipe_fd) {
+ EntryStorage new_value_storage;
+
+ new_value_storage.pid = pid;
+ new_value_storage.read_pipe_fd = read_pipe_fd;
+
+ EntryStorage expected = INVALID_ENTRY_VALUE;
+
+ return mStorage.compare_exchange_strong(expected, new_value_storage);
+ }
+};
+
+/**
+ * A table containing information about the Blastulas currently in the pool.
+ *
+ * Multiple threads may be attempting to modify the table, either from the
+ * signal handler or from the ZygoteServer poll loop. Atomic loads/stores in
+ * the BlastulaTableEntry class prevent data races during these concurrent
+ * operations.
+ */
+static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable;
+
+/**
+ * The list of open zygote file descriptors.
+ */
+static FileDescriptorTable* gOpenFdTable = nullptr;
+
// Must match values in com.android.internal.os.Zygote.
enum MountExternalKind {
MOUNT_EXTERNAL_NONE = 0,
@@ -119,6 +278,9 @@
DEBUG_ENABLE_JDWP = 1,
};
+// Forward declaration so we don't have to move the signal handler.
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid);
+
static void RuntimeAbort(JNIEnv* env, int line, const char* msg) {
std::ostringstream oss;
oss << __FILE__ << ":" << line << ": " << msg;
@@ -129,6 +291,7 @@
static void SigChldHandler(int /*signal_number*/) {
pid_t pid;
int status;
+ int64_t blastulas_removed = 0;
// It's necessary to save and restore the errno during this function.
// Since errno is stored per thread, changing it here modifies the errno
@@ -162,6 +325,11 @@
ALOGE("Exit zygote because system server (%d) has terminated", pid);
kill(getpid(), SIGKILL);
}
+
+ // Check to see if the PID is in the blastula pool and remove it if it is.
+ if (RemoveBlastulaTableEntry(pid)) {
+ ++blastulas_removed;
+ }
}
// Note that we shouldn't consider ECHILD an error because
@@ -170,6 +338,15 @@
ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno));
}
+ if (blastulas_removed > 0) {
+ if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) {
+ // If this write fails something went terribly wrong. We will now kill
+ // the zygote and let the system bring it back up.
+ ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno));
+ kill(getpid(), SIGKILL);
+ }
+ }
+
errno = saved_errno;
}
@@ -194,13 +371,13 @@
struct sigaction sig_chld = {};
sig_chld.sa_handler = SigChldHandler;
- if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) {
+ if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) {
ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
}
struct sigaction sig_hup = {};
sig_hup.sa_handler = SIG_IGN;
- if (sigaction(SIGHUP, &sig_hup, NULL) < 0) {
+ if (sigaction(SIGHUP, &sig_hup, nullptr) < 0) {
ALOGW("Error setting SIGHUP handler: %s", strerror(errno));
}
}
@@ -211,64 +388,57 @@
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
- if (sigaction(SIGCHLD, &sa, NULL) < 0) {
+ if (sigaction(SIGCHLD, &sa, nullptr) < 0) {
ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno));
}
}
// Calls POSIX setgroups() using the int[] object as an argument.
-// A NULL argument is tolerated.
-static bool SetGids(JNIEnv* env, jintArray javaGids, std::string* error_msg) {
- if (javaGids == NULL) {
- return true;
+// A nullptr argument is tolerated.
+static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) {
+ if (managed_gids == nullptr) {
+ return;
}
- ScopedIntArrayRO gids(env, javaGids);
- if (gids.get() == NULL) {
- *error_msg = CREATE_ERROR("Getting gids int array failed");
- return false;
- }
- int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
- if (rc == -1) {
- *error_msg = CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size());
- return false;
+ ScopedIntArrayRO gids(env, managed_gids);
+ if (gids.get() == nullptr) {
+ fail_fn(CREATE_ERROR("Getting gids int array failed"));
}
- return true;
+ if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) {
+ fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()));
+ }
}
// Sets the resource limits via setrlimit(2) for the values in the
// two-dimensional array of integers that's passed in. The second dimension
-// contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is
+// contains a tuple of length 3: (resource, rlim_cur, rlim_max). nullptr is
// treated as an empty array.
-static bool SetRLimits(JNIEnv* env, jobjectArray javaRlimits, std::string* error_msg) {
- if (javaRlimits == NULL) {
- return true;
+static void SetRLimits(JNIEnv* env, jobjectArray managed_rlimits, fail_fn_t fail_fn) {
+ if (managed_rlimits == nullptr) {
+ return;
}
rlimit rlim;
memset(&rlim, 0, sizeof(rlim));
- for (int i = 0; i < env->GetArrayLength(javaRlimits); ++i) {
- ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i));
- ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get()));
- if (javaRlimit.size() != 3) {
- *error_msg = CREATE_ERROR("rlimits array must have a second dimension of size 3");
- return false;
+ for (int i = 0; i < env->GetArrayLength(managed_rlimits); ++i) {
+ ScopedLocalRef<jobject>
+ managed_rlimit_object(env, env->GetObjectArrayElement(managed_rlimits, i));
+ ScopedIntArrayRO rlimit_handle(env, reinterpret_cast<jintArray>(managed_rlimit_object.get()));
+
+ if (rlimit_handle.size() != 3) {
+ fail_fn(CREATE_ERROR("rlimits array must have a second dimension of size 3"));
}
- rlim.rlim_cur = javaRlimit[1];
- rlim.rlim_max = javaRlimit[2];
+ rlim.rlim_cur = rlimit_handle[1];
+ rlim.rlim_max = rlimit_handle[2];
- int rc = setrlimit(javaRlimit[0], &rlim);
- if (rc == -1) {
- *error_msg = CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur,
- rlim.rlim_max);
- return false;
+ if (setrlimit(rlimit_handle[0], &rlim) == -1) {
+ fail_fn(CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed",
+ rlimit_handle[0], rlim.rlim_cur, rlim.rlim_max));
}
}
-
- return true;
}
static void EnableDebugger() {
@@ -323,10 +493,7 @@
// Apply system or app filter based on uid.
if (uid >= AID_APP_START) {
if (is_child_zygote) {
- // set_app_zygote_seccomp_filter();
- // TODO(b/111434506) install the filter; for now, install the app filter
- // which is more restrictive.
- set_app_seccomp_filter();
+ set_app_zygote_seccomp_filter();
} else {
set_app_seccomp_filter();
}
@@ -335,32 +502,26 @@
}
}
-static bool EnableKeepCapabilities(std::string* error_msg) {
- int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
- if (rc == -1) {
- *error_msg = CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno));
- return false;
+static void EnableKeepCapabilities(fail_fn_t fail_fn) {
+ if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+ fail_fn(CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)));
}
- return true;
}
-static bool DropCapabilitiesBoundingSet(std::string* error_msg) {
- for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
- int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
- if (rc == -1) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+ for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+ if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
if (errno == EINVAL) {
ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
"your kernel is compiled with file capabilities support");
} else {
- *error_msg = CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno)));
}
}
}
- return true;
}
-static bool SetInheritable(uint64_t inheritable, std::string* error_msg) {
+static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -368,23 +529,19 @@
__user_cap_data_struct capdata[2];
if (capget(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capget failed: %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capget failed: %s", strerror(errno)));
}
capdata[0].inheritable = inheritable;
capdata[1].inheritable = inheritable >> 32;
if (capset(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno)));
}
-
- return true;
}
-static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
- std::string* error_msg) {
+static void SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
+ fail_fn_t fail_fn) {
__user_cap_header_struct capheader;
memset(&capheader, 0, sizeof(capheader));
capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -400,27 +557,23 @@
capdata[1].inheritable = inheritable >> 32;
if (capset(&capheader, &capdata[0]) == -1) {
- *error_msg = CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
- "failed: %s", permitted, effective, inheritable, strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
+ "failed: %s", permitted, effective, inheritable, strerror(errno)));
}
- return true;
}
-static bool SetSchedulerPolicy(std::string* error_msg) {
+static void SetSchedulerPolicy(fail_fn_t fail_fn) {
errno = -set_sched_policy(0, SP_DEFAULT);
if (errno != 0) {
- *error_msg = CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)));
}
- return true;
}
static int UnmountTree(const char* path) {
size_t path_len = strlen(path);
FILE* fp = setmntent("/proc/mounts", "r");
- if (fp == NULL) {
+ if (fp == nullptr) {
ALOGE("Error opening /proc/mounts: %s", strerror(errno));
return -errno;
}
@@ -429,7 +582,7 @@
// reverse order to give us the best chance of success.
std::list<std::string> toUnmount;
mntent* mentry;
- while ((mentry = getmntent(fp)) != NULL) {
+ while ((mentry = getmntent(fp)) != nullptr) {
if (strncmp(mentry->mnt_dir, path, path_len) == 0) {
toUnmount.push_front(std::string(mentry->mnt_dir));
}
@@ -444,56 +597,55 @@
return 0;
}
-static bool createPkgSandbox(uid_t uid, const std::string& package_name, std::string* error_msg) {
+static void CreatePkgSandbox(uid_t uid, const std::string& package_name, fail_fn_t fail_fn) {
// Create /mnt/user/0/package/<package-name>
userid_t user_id = multiuser_get_user_id(uid);
std::string pkg_sandbox_dir = StringPrintf("/mnt/user/%d", user_id);
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0751, AID_ROOT, AID_ROOT) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
+
StringAppendF(&pkg_sandbox_dir, "/package");
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0700, AID_ROOT, AID_ROOT) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
+
StringAppendF(&pkg_sandbox_dir, "/%s", package_name.c_str());
if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0755, uid, uid) != 0) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()));
}
- return true;
}
-static bool bindMount(const std::string& sourceDir, const std::string& targetDir,
- std::string* error_msg) {
- if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(),
- nullptr, MS_BIND | MS_REC, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to %s: %s",
- sourceDir.c_str(), targetDir.c_str(), strerror(errno));
- return false;
+static void BindMount(const std::string& sourceDir, const std::string& targetDir,
+ fail_fn_t fail_fn) {
+ if (TEMP_FAILURE_RETRY(mount(sourceDir.c_str(), targetDir.c_str(), nullptr,
+ MS_BIND | MS_REC, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to %s: %s",
+ sourceDir.c_str(), targetDir.c_str(), strerror(errno)));
}
- if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(),
- nullptr, MS_SLAVE | MS_REC, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str());
- return false;
+
+ if (TEMP_FAILURE_RETRY(mount(nullptr, targetDir.c_str(), nullptr,
+ MS_SLAVE | MS_REC, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to set MS_SLAVE for %s", targetDir.c_str()));
}
- return true;
}
-static bool mountPkgSpecificDir(const std::string& mntSourceRoot,
- const std::string& mntTargetRoot, const std::string& packageName,
- const char* dirName, std::string* error_msg) {
+static void MountPkgSpecificDir(const std::string& mntSourceRoot,
+ const std::string& mntTargetRoot,
+ const std::string& packageName,
+ const char* dirName,
+ fail_fn_t fail_fn) {
std::string mntSourceDir = StringPrintf("%s/Android/%s/%s",
mntSourceRoot.c_str(), dirName, packageName.c_str());
std::string mntTargetDir = StringPrintf("%s/Android/%s/%s",
mntTargetRoot.c_str(), dirName, packageName.c_str());
- return bindMount(mntSourceDir, mntTargetDir, error_msg);
+
+ BindMount(mntSourceDir, mntTargetDir, fail_fn);
}
-static bool preparePkgSpecificDirs(const std::vector<std::string>& packageNames,
- const std::vector<std::string>& volumeLabels, bool mountAllObbs,
- userid_t userId, std::string* error_msg) {
+static void PreparePkgSpecificDirs(const std::vector<std::string>& packageNames,
+ const std::vector<std::string>& volumeLabels,
+ bool mountAllObbs, userid_t userId, fail_fn_t fail_fn) {
for (auto& label : volumeLabels) {
std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str());
std::string mntTarget = StringPrintf("/storage/%s", label.c_str());
@@ -501,28 +653,29 @@
StringAppendF(&mntSource, "/%d", userId);
StringAppendF(&mntTarget, "/%d", userId);
}
+
for (auto& package : packageNames) {
- mountPkgSpecificDir(mntSource, mntTarget, package, "data", error_msg);
- mountPkgSpecificDir(mntSource, mntTarget, package, "media", error_msg);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "data", fail_fn);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "media", fail_fn);
if (!mountAllObbs) {
- mountPkgSpecificDir(mntSource, mntTarget, package, "obb", error_msg);
+ MountPkgSpecificDir(mntSource, mntTarget, package, "obb", fail_fn);
}
}
+
if (mountAllObbs) {
StringAppendF(&mntSource, "/Android/obb");
StringAppendF(&mntTarget, "/Android/obb");
- bindMount(mntSource, mntTarget, error_msg);
+ BindMount(mntSource, mntTarget, fail_fn);
}
}
- return true;
}
// Create a private mount namespace and bind mount appropriate emulated
// storage for the given user.
-static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
- bool force_mount_namespace, std::string* error_msg, const std::string& package_name,
+static void MountEmulatedStorage(uid_t uid, jint mount_mode,
+ bool force_mount_namespace, const std::string& package_name,
const std::vector<std::string>& packages_for_uid,
- const std::vector<std::string>& visible_vol_ids) {
+ const std::vector<std::string>& visible_vol_ids, fail_fn_t fail_fn) {
// See storage config details at http://source.android.com/tech/storage/
String8 storageSource;
@@ -534,18 +687,17 @@
storageSource = "/mnt/runtime/write";
} else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
// Sane default of no storage visible
- return true;
+ return;
}
// Create a second private mount namespace for our process
if (unshare(CLONE_NEWNS) == -1) {
- *error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno)));
}
// Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
if (mount_mode == MOUNT_EXTERNAL_NONE) {
- return true;
+ return;
}
if (GetBoolProperty(kIsolatedStorageSnapshot, GetBoolProperty(kIsolatedStorage, false))) {
@@ -553,66 +705,64 @@
storageSource = (mount_mode == MOUNT_EXTERNAL_FULL)
? "/mnt/runtime/full" : "/mnt/runtime/write";
if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
- NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- storageSource.string(),
- strerror(errno));
- return false;
+ NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ storageSource.string(),
+ strerror(errno)));
}
// Mount user-specific symlink helper into place
userid_t user_id = multiuser_get_user_id(uid);
const String8 userSource(String8::format("/mnt/user/%d", user_id));
if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s (%s)",
+ userSource.string(), strerror(errno)));
}
- if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
- NULL, MS_BIND, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
- userSource.string(),
- strerror(errno));
- return false;
+
+ if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", nullptr, MS_BIND,
+ nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+ userSource.string(),
+ strerror(errno)));
}
} else {
if (package_name.empty()) {
- return true;
+ return;
}
+
userid_t user_id = multiuser_get_user_id(uid);
- std::string pkgSandboxDir = StringPrintf("/mnt/user/%d/package/%s",
- user_id, package_name.c_str());
+ std::string pkgSandboxDir =
+ StringPrintf("/mnt/user/%d/package/%s", user_id, package_name.c_str());
struct stat sb;
bool sandboxAlreadyCreated = true;
if (TEMP_FAILURE_RETRY(lstat(pkgSandboxDir.c_str(), &sb)) == -1) {
if (errno == ENOENT) {
ALOGD("Sandbox not yet created for %s", pkgSandboxDir.c_str());
sandboxAlreadyCreated = false;
- if (!createPkgSandbox(uid, package_name, error_msg)) {
- return false;
- }
+ CreatePkgSandbox(uid, package_name, fail_fn);
} else {
- ALOGE("Failed to lstat %s", pkgSandboxDir.c_str());
- return false;
+ fail_fn(CREATE_ERROR("Failed to lstat %s: %s",
+ pkgSandboxDir.c_str(), strerror(errno)));
}
}
+
if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage",
- nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- pkgSandboxDir.c_str(), strerror(errno));
- return false;
+ nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ pkgSandboxDir.c_str(), strerror(errno)));
}
+
if (access("/storage/obb_mount", F_OK) == 0) {
if (mount_mode != MOUNT_EXTERNAL_INSTALLER) {
remove("/storage/obb_mount");
}
} else {
if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
- int fd = TEMP_FAILURE_RETRY(open("/storage/obb_mount",
- O_RDWR | O_CREAT, 0660));
+ int fd =
+ TEMP_FAILURE_RETRY(open("/storage/obb_mount", O_RDWR | O_CREAT, 0660));
if (fd == -1) {
- *error_msg = CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
- strerror(errno));
- return false;
+ fail_fn(CREATE_ERROR("Couldn't create /storage/obb_mount: %s",
+ strerror(errno)));
}
close(fd);
}
@@ -621,38 +771,32 @@
// pkg specific directories. Otherwise, leave as is and bind mounts will be taken
// care of by vold later.
if (sandboxAlreadyCreated) {
- if (!preparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
- mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, error_msg)) {
- return false;
- }
+ PreparePkgSpecificDirs(packages_for_uid, visible_vol_ids,
+ mount_mode == MOUNT_EXTERNAL_INSTALLER, user_id, fail_fn);
}
}
} else {
- if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
- NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
- storageSource.string(),
- strerror(errno));
- return false;
+ if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", nullptr,
+ MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+ storageSource.string(),
+ strerror(errno)));
}
// Mount user-specific symlink helper into place
userid_t user_id = multiuser_get_user_id(uid);
const String8 userSource(String8::format("/mnt/user/%d", user_id));
if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
- *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
- return false;
+ fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s",
+ userSource.string()));
}
+
if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
- NULL, MS_BIND, NULL)) == -1) {
- *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
- userSource.string(),
- strerror(errno));
- return false;
+ nullptr, MS_BIND, nullptr)) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+ userSource.string(), strerror(errno)));
}
}
-
- return true;
}
static bool NeedsNoRandomizeWorkaround() {
@@ -680,55 +824,45 @@
// descriptor (if any) is closed via dup2(), replacing it with a valid
// (open) descriptor to /dev/null.
-static bool DetachDescriptors(JNIEnv* env, jintArray fdsToClose, std::string* error_msg) {
- if (!fdsToClose) {
- return true;
- }
- jsize count = env->GetArrayLength(fdsToClose);
- ScopedIntArrayRO ar(env, fdsToClose);
- if (ar.get() == NULL) {
- *error_msg = "Bad fd array";
- return false;
- }
- jsize i;
- int devnull;
- for (i = 0; i < count; i++) {
- devnull = open("/dev/null", O_RDWR);
- if (devnull < 0) {
- *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
- return false;
+static void DetachDescriptors(JNIEnv* env,
+ const std::vector<int>& fds_to_close,
+ fail_fn_t fail_fn) {
+
+ if (fds_to_close.size() > 0) {
+ android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR));
+ if (devnull_fd == -1) {
+ fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
- ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno));
- if (dup2(devnull, ar[i]) < 0) {
- *error_msg = StringPrintf("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno));
- return false;
+
+ for (int fd : fds_to_close) {
+ ALOGV("Switching descriptor %d to /dev/null", fd);
+ if (dup2(devnull_fd, fd) == -1) {
+ fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno)));
+ }
}
- close(devnull);
}
- return true;
}
-void SetThreadName(const char* thread_name) {
+void SetThreadName(const std::string& thread_name) {
bool hasAt = false;
bool hasDot = false;
- const char* s = thread_name;
- while (*s) {
- if (*s == '.') {
+
+ for (const char str_el : thread_name) {
+ if (str_el == '.') {
hasDot = true;
- } else if (*s == '@') {
+ } else if (str_el == '@') {
hasAt = true;
}
- s++;
}
- const int len = s - thread_name;
- if (len < 15 || hasAt || !hasDot) {
- s = thread_name;
- } else {
- s = thread_name + len - 15;
+
+ const char* name_start_ptr = thread_name.c_str();
+ if (thread_name.length() >= MAX_NAME_LENGTH && !hasAt && hasDot) {
+ name_start_ptr += thread_name.length() - MAX_NAME_LENGTH;
}
+
// pthread_setname_np fails rather than truncating long strings.
char buf[16]; // MAX_TASK_COMM_LEN=16 is hard-coded into bionic
- strlcpy(buf, s, sizeof(buf)-1);
+ strlcpy(buf, name_start_ptr, sizeof(buf) - 1);
errno = pthread_setname_np(pthread_self(), buf);
if (errno != 0) {
ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno));
@@ -737,28 +871,16 @@
android::base::SetDefaultTag(buf);
}
-// The list of open zygote file descriptors.
-static FileDescriptorTable* gOpenFdTable = NULL;
-
-static bool FillFileDescriptorVector(JNIEnv* env,
- jintArray managed_fds,
- std::vector<int>* fds,
- std::string* error_msg) {
- CHECK(fds != nullptr);
- if (managed_fds != nullptr) {
- ScopedIntArrayRO ar(env, managed_fds);
- if (ar.get() == nullptr) {
- *error_msg = "Bad fd array";
- return false;
- }
- fds->reserve(ar.size());
- for (size_t i = 0; i < ar.size(); ++i) {
- fds->push_back(ar[i]);
- }
- }
- return true;
-}
-
+/**
+ * A failure function used to report fatal errors to the managed runtime. This
+ * function is often curried with the process name information and then passed
+ * to called functions.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param msg The error message to be reported
+ */
[[noreturn]]
static void ZygoteFailure(JNIEnv* env,
const char* process_name,
@@ -779,12 +901,25 @@
__builtin_unreachable();
}
+/**
+ * A helper method for converting managed strings to native strings. A fatal
+ * error is generated if a problem is encountered in extracting a non-null
+ * string.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_string The managed string to extract
+ *
+ * @return An empty option if the managed string is null. A optional-wrapped
+ * string otherwise.
+ */
static std::optional<std::string> ExtractJString(JNIEnv* env,
const char* process_name,
jstring managed_process_name,
jstring managed_string) {
if (managed_string == nullptr) {
- return std::optional<std::string>();
+ return std::nullopt;
} else {
ScopedUtfChars scoped_string_chars(env, managed_string);
@@ -796,15 +931,124 @@
}
}
-// Utility routine to fork a zygote.
-static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
- jintArray managed_fds_to_close, jintArray managed_fds_to_ignore) {
- SetSignalHandlers();
+/**
+ * A helper method for converting managed string arrays to native vectors. A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_array The managed integer array to extract
+ *
+ * @return An empty option if the managed array is null. A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<int>> ExtractJIntArray(JNIEnv* env,
+ const char* process_name,
+ jstring managed_process_name,
+ jintArray managed_array) {
+ if (managed_array == nullptr) {
+ return std::nullopt;
+ } else {
+ ScopedIntArrayRO managed_array_handle(env, managed_array);
- // Block SIGCHLD prior to fork.
- sigset_t sigchld;
- sigemptyset(&sigchld);
- sigaddset(&sigchld, SIGCHLD);
+ if (managed_array_handle.get() != nullptr) {
+ std::vector<int> native_array;
+ native_array.reserve(managed_array_handle.size());
+
+ for (size_t array_index = 0; array_index < managed_array_handle.size(); ++array_index) {
+ native_array.push_back(managed_array_handle[array_index]);
+ }
+
+ return std::move(native_array);
+
+ } else {
+ ZygoteFailure(env, process_name, managed_process_name, "Failed to extract JIntArray.");
+ }
+ }
+}
+
+/**
+ * A helper method for converting managed string arrays to native vectors. A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param managed_array The managed string array to extract
+ *
+ * @return An empty option if the managed array is null. A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<std::string>> ExtractJStringArray(JNIEnv* env,
+ const char* process_name,
+ jstring managed_process_name,
+ jobjectArray managed_array) {
+ if (managed_array == nullptr) {
+ return std::nullopt;
+ } else {
+ jsize element_count = env->GetArrayLength(managed_array);
+ std::vector<std::string> native_string_vector;
+ native_string_vector.reserve(element_count);
+
+ for (jsize array_index = 0; array_index < element_count; ++array_index) {
+ jstring managed_string = (jstring) env->GetObjectArrayElement(managed_array, array_index);
+ auto native_string = ExtractJString(env, process_name, managed_process_name, managed_string);
+
+ if (LIKELY(native_string.has_value())) {
+ native_string_vector.emplace_back(std::move(native_string.value()));
+ } else {
+ ZygoteFailure(env, process_name, managed_process_name,
+ "Null string found in managed string array.");
+ }
+ }
+
+ return std::move(native_string_vector);
+ }
+}
+
+/**
+ * A utility function for blocking signals.
+ *
+ * @param signum Signal number to block
+ * @param fail_fn Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void BlockSignal(int signum, fail_fn_t fail_fn) {
+ sigset_t sigs;
+ sigemptyset(&sigs);
+ sigaddset(&sigs, signum);
+
+ if (sigprocmask(SIG_BLOCK, &sigs, nullptr) == -1) {
+ fail_fn(CREATE_ERROR("Failed to block signal %s: %s", strsignal(signum), strerror(errno)));
+ }
+}
+
+
+/**
+ * A utility function for unblocking signals.
+ *
+ * @param signum Signal number to unblock
+ * @param fail_fn Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void UnblockSignal(int signum, fail_fn_t fail_fn) {
+ sigset_t sigs;
+ sigemptyset(&sigs);
+ sigaddset(&sigs, signum);
+
+ if (sigprocmask(SIG_UNBLOCK, &sigs, nullptr) == -1) {
+ fail_fn(CREATE_ERROR("Failed to un-block signal %s: %s", strsignal(signum), strerror(errno)));
+ }
+}
+
+// Utility routine to fork a process from the zygote.
+static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
+ const std::vector<int>& fds_to_close,
+ const std::vector<int>& fds_to_ignore) {
+ SetSignalHandlers();
// Curry a failure function.
auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
@@ -815,9 +1059,7 @@
// This would cause failures because the FDs are not whitelisted.
//
// Note that the zygote process is single threaded at this point.
- if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) {
- fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
- }
+ BlockSignal(SIGCHLD, fail_fn);
// Close any logging related FDs before we start evaluating the list of
// file descriptors.
@@ -827,19 +1069,10 @@
// If this is the first fork for this zygote, create the open FD table. If
// it isn't, we just need to check whether the list of open files has changed
// (and it shouldn't in the normal case).
- std::string error_msg;
- std::vector<int> fds_to_ignore;
- if (!FillFileDescriptorVector(env, managed_fds_to_ignore, &fds_to_ignore, &error_msg)) {
- fail_fn(error_msg);
- }
-
if (gOpenFdTable == nullptr) {
- gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, &error_msg);
- if (gOpenFdTable == nullptr) {
- fail_fn(error_msg);
- }
- } else if (!gOpenFdTable->Restat(fds_to_ignore, &error_msg)) {
- fail_fn(error_msg);
+ gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn);
+ } else {
+ gOpenFdTable->Restat(fds_to_ignore, fail_fn);
}
android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level();
@@ -851,24 +1084,18 @@
PreApplicationInit();
// Clean up any descriptors which must be closed immediately
- if (!DetachDescriptors(env, managed_fds_to_close, &error_msg)) {
- fail_fn(error_msg);
- }
+ DetachDescriptors(env, fds_to_close, fail_fn);
// Re-open all remaining open file descriptors so that they aren't shared
// with the zygote across a fork.
- if (!gOpenFdTable->ReopenOrDetach(&error_msg)) {
- fail_fn(error_msg);
- }
+ gOpenFdTable->ReopenOrDetach(fail_fn);
// Turn fdsan back on.
android_fdsan_set_error_level(fdsan_error_level);
}
// We blocked SIGCHLD prior to a fork, we unblock it here.
- if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) {
- fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
- }
+ UnblockSignal(SIGCHLD, fail_fn);
return pid;
}
@@ -883,10 +1110,9 @@
jstring managed_app_data_dir, jstring managed_package_name,
jobjectArray managed_pacakges_for_uid,
jobjectArray managed_visible_vol_ids) {
- auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
- managed_nice_name, _1);
- auto extract_fn = std::bind(ExtractJString, env, is_system_server ? "system_server" : "zygote",
- managed_nice_name, _1);
+ const char* process_name = is_system_server ? "system_server" : "zygote";
+ auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
auto se_info = extract_fn(managed_se_info);
auto nice_name = extract_fn(managed_nice_name);
@@ -894,22 +1120,14 @@
auto app_data_dir = extract_fn(managed_app_data_dir);
auto package_name = extract_fn(managed_package_name);
- std::string error_msg;
-
// Keep capabilities across UID change, unless we're staying root.
if (uid != 0) {
- if (!EnableKeepCapabilities(&error_msg)) {
- fail_fn(error_msg);
- }
+ EnableKeepCapabilities(fail_fn);
}
- if (!SetInheritable(permitted_capabilities, &error_msg)) {
- fail_fn(error_msg);
- }
+ SetInheritable(permitted_capabilities, fail_fn);
- if (!DropCapabilitiesBoundingSet(&error_msg)) {
- fail_fn(error_msg);
- }
+ DropCapabilitiesBoundingSet(fail_fn);
bool use_native_bridge = !is_system_server &&
instruction_set.has_value() &&
@@ -934,56 +1152,21 @@
}
}
- std::vector<std::string> packages_for_uid;
- if (managed_pacakges_for_uid != nullptr) {
- jsize count = env->GetArrayLength(managed_pacakges_for_uid);
- for (jsize package_index = 0; package_index < count; ++package_index) {
- jstring managed_package_for_uid =
- (jstring) env->GetObjectArrayElement(managed_pacakges_for_uid, package_index);
+ std::vector<std::string> packages_for_uid =
+ ExtractJStringArray(env, process_name, managed_nice_name, managed_pacakges_for_uid).
+ value_or(std::vector<std::string>());
- auto package_for_uid = extract_fn(managed_package_for_uid);
- if (LIKELY(package_for_uid.has_value())) {
- packages_for_uid.emplace_back(std::move(package_for_uid.value()));
- } else {
- fail_fn("Null string found in managed packages_for_uid.");
- }
- }
- }
+ std::vector<std::string> visible_vol_ids =
+ ExtractJStringArray(env, process_name, managed_nice_name, managed_visible_vol_ids).
+ value_or(std::vector<std::string>());
- std::vector<std::string> visible_vol_ids;
- if (managed_visible_vol_ids != nullptr) {
- jsize count = env->GetArrayLength(managed_visible_vol_ids);
- for (jsize vol_id_index = 0; vol_id_index < count; ++vol_id_index) {
- jstring managed_visible_vol_id =
- (jstring) env->GetObjectArrayElement(managed_visible_vol_ids, vol_id_index);
-
- auto visible_vol_id = extract_fn(managed_visible_vol_id);
- if (LIKELY(visible_vol_id.has_value())) {
- visible_vol_ids.emplace_back(std::move(visible_vol_id.value()));
- } else {
- fail_fn("Null string found in managed visible_vol_ids.");
- }
- }
- }
-
- if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg,
- package_name.value(), packages_for_uid, visible_vol_ids)) {
- ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno));
- if (errno == ENOTCONN || errno == EROFS) {
- // When device is actively encrypting, we get ENOTCONN here
- // since FUSE was mounted before the framework restarted.
- // When encrypted device is booting, we get EROFS since
- // FUSE hasn't been created yet by init.
- // In either case, continue without external storage.
- } else {
- fail_fn(error_msg);
- }
- }
+ MountEmulatedStorage(uid, mount_external, use_native_bridge, package_name.value(),
+ packages_for_uid, visible_vol_ids, fail_fn);
// If this zygote isn't root, it won't be able to create a process group,
// since the directory is owned by root.
if (!is_system_server && getuid() == 0) {
- int rc = createProcessGroup(uid, getpid());
+ const int rc = createProcessGroup(uid, getpid());
if (rc == -EROFS) {
ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
} else if (rc != 0) {
@@ -991,13 +1174,8 @@
}
}
- if (!SetGids(env, gids, &error_msg)) {
- fail_fn(error_msg);
- }
-
- if (!SetRLimits(env, rlimits, &error_msg)) {
- fail_fn(error_msg);
- }
+ SetGids(env, gids, fail_fn);
+ SetRLimits(env, rlimits, fail_fn);
if (use_native_bridge) {
// Due to the logic behind use_native_bridge we know that both app_data_dir
@@ -1056,14 +1234,9 @@
}
}
- if (!SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
- &error_msg)) {
- fail_fn(error_msg);
- }
+ SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
- if (!SetSchedulerPolicy(&error_msg)) {
- fail_fn(error_msg);
- }
+ SetSchedulerPolicy(fail_fn);
const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
@@ -1076,7 +1249,7 @@
// Make it easier to debug audit logs by setting the main thread's name to the
// nice name rather than "app_process".
if (nice_name.has_value()) {
- SetThreadName(nice_name.value().c_str());
+ SetThreadName(nice_name.value());
} else if (is_system_server) {
SetThreadName("system_server");
}
@@ -1089,6 +1262,7 @@
if (env->ExceptionCheck()) {
fail_fn("Error calling post fork system server hooks.");
}
+
// TODO(oth): Remove hardcoded label here (b/117874058).
static const char* kSystemServerLabel = "u:r:system_server:s0";
if (selinux_android_setcon(kSystemServerLabel) != 0) {
@@ -1192,6 +1366,74 @@
return capabilities & GetEffectiveCapabilityMask(env);
}
+
+/**
+ * Adds the given information about a newly created blastula to the Zygote's
+ * blastula table.
+ *
+ * @param blastula_pid Process ID of the newly created blastula
+ * @param read_pipe_fd File descriptor for the read end of the blastula
+ * reporting pipe. Used in the ZygoteServer poll loop to track blastula
+ * specialization.
+ */
+static void AddBlastulaTableEntry(pid_t blastula_pid, int read_pipe_fd) {
+ static int sBlastulaTableInsertIndex = 0;
+
+ int search_index = sBlastulaTableInsertIndex;
+
+ do {
+ if (gBlastulaTable[search_index].SetIfInvalid(blastula_pid, read_pipe_fd)) {
+ // Start our next search right after where we finished this one.
+ sBlastulaTableInsertIndex = (search_index + 1) % gBlastulaTable.size();
+
+ return;
+ }
+
+ search_index = (search_index + 1) % gBlastulaTable.size();
+ } while (search_index != sBlastulaTableInsertIndex);
+
+ // Much like money in the banana stand, there should always be an entry
+ // in the blastula table.
+ __builtin_unreachable();
+}
+
+/**
+ * Invalidates the entry in the BlastulaTable corresponding to the provided
+ * process ID if it is present. If an entry was removed the blastula pool
+ * count is decremented.
+ *
+ * @param blastula_pid Process ID of the blastula entry to invalidate
+ * @return True if an entry was invalidated; false otherwise
+ */
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid) {
+ for (BlastulaTableEntry& entry : gBlastulaTable) {
+ if (entry.ClearForPID(blastula_pid)) {
+ --gBlastulaPoolCount;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @return A vector of the read pipe FDs for each of the active blastulas.
+ */
+std::vector<int> MakeBlastulaPipeReadFDVector() {
+ std::vector<int> fd_vec;
+ fd_vec.reserve(gBlastulaTable.size());
+
+ for (BlastulaTableEntry& entry : gBlastulaTable) {
+ auto entry_values = entry.GetValues();
+
+ if (entry_values.has_value()) {
+ fd_vec.push_back(entry_values.value().read_pipe_fd);
+ }
+ }
+
+ return fd_vec;
+}
+
} // anonymous namespace
namespace android {
@@ -1210,12 +1452,35 @@
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
- jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
+ jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jstring package_name,
jobjectArray packages_for_uid, jobjectArray visible_vol_ids) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+ if (UNLIKELY(managed_fds_to_close == nullptr)) {
+ ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector.");
+ }
+
+ std::vector<int> fds_to_close =
+ ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_close).value();
+ std::vector<int> fds_to_ignore =
+ ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_ignore)
+ .value_or(std::vector<int>());
+
+ std::vector<int> blastula_pipes = MakeBlastulaPipeReadFDVector();
+
+ fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end());
+ fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end());
+
+// fds_to_close.push_back(gBlastulaPoolSocketFD);
+
+ if (gBlastulaPoolEventFD != -1) {
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ }
+
pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore);
+
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
@@ -1230,9 +1495,19 @@
JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities,
jlong effective_capabilities) {
+ std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+ fds_to_ignore(fds_to_close);
+
+// fds_to_close.push_back(gBlastulaPoolSocketFD);
+
+ if (gBlastulaPoolEventFD != -1) {
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ }
+
pid_t pid = ForkCommon(env, true,
- /* managed_fds_to_close= */ nullptr,
- /* managed_fds_to_ignore= */ nullptr);
+ fds_to_close,
+ fds_to_ignore);
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities,
@@ -1266,6 +1541,52 @@
return pid;
}
+/**
+ * A JNI function that forks a blastula from the Zygote while ensuring proper
+ * file descriptor hygiene.
+ *
+ * @param env Managed runtime environment
+ * @param read_pipe_fd The read FD for the blastula reporting pipe. Manually closed by blastlas
+ * in managed code.
+ * @param write_pipe_fd The write FD for the blastula reporting pipe. Manually closed by the
+ * zygote in managed code.
+ * @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by
+ * the FD hygiene code and automatically "closed" in the new blastula.
+ * @return
+ */
+static jint com_android_internal_os_Zygote_nativeForkBlastula(JNIEnv* env, jclass,
+ jint read_pipe_fd, jint write_pipe_fd, jintArray managed_session_socket_fds) {
+ std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+ fds_to_ignore(fds_to_close);
+
+ std::vector<int> session_socket_fds =
+ ExtractJIntArray(env, "blastula", nullptr, managed_session_socket_fds)
+ .value_or(std::vector<int>());
+
+ // The Blastula Pool Event FD is created during the initialization of the
+ // blastula pool and should always be valid here.
+
+ fds_to_close.push_back(gZygoteSocketFD);
+ fds_to_close.push_back(gBlastulaPoolEventFD);
+ fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+ fds_to_ignore.push_back(gZygoteSocketFD);
+ fds_to_ignore.push_back(gBlastulaPoolSocketFD);
+ fds_to_ignore.push_back(gBlastulaPoolEventFD);
+ fds_to_ignore.push_back(read_pipe_fd);
+ fds_to_ignore.push_back(write_pipe_fd);
+ fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+ pid_t blastula_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore);
+
+ if (blastula_pid != 0) {
+ ++gBlastulaPoolCount;
+ AddBlastulaTableEntry(blastula_pid, read_pipe_fd);
+ }
+
+ return blastula_pid;
+}
+
static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork(
JNIEnv* env, jclass, jstring path) {
ScopedUtfChars path_native(env, path);
@@ -1321,14 +1642,125 @@
return;
}
- // TODO(b/111434506) install the filter
-
- /*
bool installed = install_setuidgid_seccomp_filter(uidGidMin, uidGidMax);
if (!installed) {
RuntimeAbort(env, __LINE__, "Could not install setuid/setgid seccomp filter.");
}
- */
+}
+
+/**
+ * Called from a blastula to specialize the process for a specific application.
+ *
+ * @param env Managed runtime environment
+ * @param uid User ID of the new application
+ * @param gid Group ID of the new application
+ * @param gids Extra groups that the process belongs to
+ * @param runtime_flags Flags for changing the behavior of the managed runtime
+ * @param rlimits Resource limits
+ * @param mount_external The mode (read/write/normal) that external storage will be mounted with
+ * @param se_info SELinux policy information
+ * @param nice_name New name for this process
+ * @param is_child_zygote If the process is to become a WebViewZygote
+ * @param instruction_set The instruction set expected/requested by the new application
+ * @param app_data_dir Path to the application's data directory
+ */
+static void com_android_internal_os_Zygote_nativeSpecializeBlastula(
+ JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
+ jint runtime_flags, jobjectArray rlimits,
+ jint mount_external, jstring se_info, jstring nice_name,
+ jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir,
+ jstring package_name, jobjectArray packages_for_uid, jobjectArray visible_vol_ids) {
+ jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+
+ SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
+ capabilities, capabilities,
+ mount_external, se_info, nice_name, false,
+ is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+ package_name, packages_for_uid, visible_vol_ids);
+}
+
+/**
+ * A helper method for fetching socket file descriptors that were opened by init from the
+ * environment.
+ *
+ * @param env Managed runtime environment
+ * @param is_primary If this process is the primary or secondary Zygote; used to compute the name
+ * of the environment variable storing the file descriptors.
+ */
+static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass,
+ jboolean is_primary) {
+ std::string android_socket_prefix(ANDROID_SOCKET_PREFIX);
+ std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary");
+ char* env_var_val = getenv(env_var_name.c_str());
+
+ if (env_var_val != nullptr) {
+ gZygoteSocketFD = atoi(env_var_val);
+ ALOGV("Zygote:zygoteSocketFD = %d", gZygoteSocketFD);
+ } else {
+ ALOGE("Unable to fetch Zygote socket file descriptor");
+ }
+
+ env_var_name = android_socket_prefix + (is_primary ? "blastula_pool" : "blastula_pool_secondary");
+ env_var_val = getenv(env_var_name.c_str());
+
+ if (env_var_val != nullptr) {
+ gBlastulaPoolSocketFD = atoi(env_var_val);
+ ALOGV("Zygote:blastulaPoolSocketFD = %d", gBlastulaPoolSocketFD);
+ } else {
+ ALOGE("Unable to fetch Blastula pool socket file descriptor");
+ }
+}
+
+/**
+ * @param env Managed runtime environment
+ * @return A managed array of raw file descriptors for the read ends of the blastula reporting
+ * pipes.
+ */
+static jintArray com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs(JNIEnv* env, jclass) {
+ std::vector<int> blastula_fds = MakeBlastulaPipeReadFDVector();
+
+ jintArray managed_blastula_fds = env->NewIntArray(blastula_fds.size());
+ env->SetIntArrayRegion(managed_blastula_fds, 0, blastula_fds.size(), blastula_fds.data());
+
+ return managed_blastula_fds;
+}
+
+/**
+ * A JNI wrapper around RemoveBlastulaTableEntry.
+ *
+ * @param env Managed runtime environment
+ * @param blastula_pid Process ID of the blastula entry to invalidate
+ * @return True if an entry was invalidated; false otherwise.
+ */
+static jboolean com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry(JNIEnv* env, jclass,
+ jint blastula_pid) {
+ return RemoveBlastulaTableEntry(blastula_pid);
+}
+
+/**
+ * Creates the blastula pool event FD if it doesn't exist and returns it. This is used by the
+ * ZygoteServer poll loop to know when to re-fill the blastula pool.
+ *
+ * @param env Managed runtime environment
+ * @return A raw event file descriptor used to communicate (from the signal handler) when the
+ * Zygote receives a SIGCHLD for a blastula
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD(JNIEnv* env, jclass) {
+ if (gBlastulaPoolEventFD == -1) {
+ if ((gBlastulaPoolEventFD = eventfd(0, 0)) == -1) {
+ ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno)));
+ }
+ }
+
+ return gBlastulaPoolEventFD;
+}
+
+/**
+ * @param env Managed runtime environment
+ * @return The number of blastulas currently in the blastula pool
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* env, jclass) {
+ return gBlastulaPoolCount;
}
static const JNINativeMethod gMethods[] = {
@@ -1346,7 +1778,22 @@
{ "nativePreApplicationInit", "()V",
(void *) com_android_internal_os_Zygote_nativePreApplicationInit },
{ "nativeInstallSeccompUidGidFilter", "(II)V",
- (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter }
+ (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter },
+ { "nativeForkBlastula", "(II[I)I",
+ (void *) com_android_internal_os_Zygote_nativeForkBlastula },
+ { "nativeSpecializeBlastula",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
+ (void *) com_android_internal_os_Zygote_nativeSpecializeBlastula },
+ { "nativeGetSocketFDs", "(Z)V",
+ (void *) com_android_internal_os_Zygote_nativeGetSocketFDs },
+ { "nativeGetBlastulaPipeFDs", "()[I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs },
+ { "nativeRemoveBlastulaTableEntry", "(I)Z",
+ (void *) com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry },
+ { "nativeGetBlastulaPoolEventFD", "()I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD },
+ { "nativeGetBlastulaPoolCount", "()I",
+ (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount }
};
int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index d60d1a6..53dde80 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -38,6 +38,8 @@
"/dev/null",
"/dev/socket/zygote",
"/dev/socket/zygote_secondary",
+ "/dev/socket/blastula_pool",
+ "/dev/socket/blastula_pool_secondary",
"/dev/socket/webview_zygote",
"/dev/socket/heapprofd",
"/sys/kernel/debug/tracing/trace_marker",
@@ -134,15 +136,14 @@
// open zygote file descriptor.
class FileDescriptorInfo {
public:
- // Create a FileDescriptorInfo for a given file descriptor. Returns
- // |NULL| if an error occurred.
- static FileDescriptorInfo* CreateFromFd(int fd, std::string* error_msg);
+ // Create a FileDescriptorInfo for a given file descriptor.
+ static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
// Checks whether the file descriptor associated with this object
// refers to the same description.
- bool Restat() const;
+ bool RefersToSameFile() const;
- bool ReopenOrDetach(std::string* error_msg) const;
+ void ReopenOrDetach(fail_fn_t fail_fn) const;
const int fd;
const struct stat stat;
@@ -168,19 +169,18 @@
// address).
static bool GetSocketName(const int fd, std::string* result);
- bool DetachSocket(std::string* error_msg) const;
+ void DetachSocket(fail_fn_t fail_fn) const;
DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
};
// static
-FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_msg) {
+FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
struct stat f_stat;
// This should never happen; the zygote should always have the right set
// of permissions required to stat all its open files.
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
- *error_msg = android::base::StringPrintf("Unable to stat %d", fd);
- return nullptr;
+ fail_fn(android::base::StringPrintf("Unable to stat %d", fd));
}
const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get();
@@ -188,15 +188,13 @@
if (S_ISSOCK(f_stat.st_mode)) {
std::string socket_name;
if (!GetSocketName(fd, &socket_name)) {
- *error_msg = "Unable to get socket name";
- return nullptr;
+ fail_fn("Unable to get socket name");
}
if (!whitelist->IsAllowed(socket_name)) {
- *error_msg = android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
- socket_name.c_str(),
- fd);
- return nullptr;
+ fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
+ socket_name.c_str(),
+ fd));
}
return new FileDescriptorInfo(fd);
@@ -209,26 +207,35 @@
// S_ISDIR : Not supported. (We could if we wanted to, but it's unused).
// S_ISLINK : Not supported.
// S_ISBLK : Not supported.
- // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate
- // with the child process across forks but those should have been closed
- // before we got to this point.
+ // S_ISFIFO : Not supported. Note that the Zygote and blastulas use pipes to
+ // communicate with the child processes across forks but those should have been
+ // added to the redirection exemption list.
if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
- *error_msg = android::base::StringPrintf("Unsupported st_mode %u", f_stat.st_mode);
- return nullptr;
+ std::string mode = "Unknown";
+
+ if (S_ISDIR(f_stat.st_mode)) {
+ mode = "DIR";
+ } else if (S_ISLNK(f_stat.st_mode)) {
+ mode = "LINK";
+ } else if (S_ISBLK(f_stat.st_mode)) {
+ mode = "BLOCK";
+ } else if (S_ISFIFO(f_stat.st_mode)) {
+ mode = "FIFO";
+ }
+
+ fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d: %s", fd, mode.c_str()));
}
std::string file_path;
const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
if (!android::base::Readlink(fd_path, &file_path)) {
- *error_msg = android::base::StringPrintf("Could not read fd link %s: %s",
- fd_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Could not read fd link %s: %s",
+ fd_path.c_str(),
+ strerror(errno)));
}
if (!whitelist->IsAllowed(file_path)) {
- *error_msg = std::string("Not whitelisted : ").append(file_path);
- return nullptr;
+ fail_fn(std::string("Not whitelisted : ").append(file_path));
}
// File descriptor flags : currently on FD_CLOEXEC. We can set these
@@ -236,11 +243,10 @@
// there won't be any races.
const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
if (fd_flags == -1) {
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
- fd,
- file_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
+ fd,
+ file_path.c_str(),
+ strerror(errno)));
}
// File status flags :
@@ -257,11 +263,10 @@
// their presence and pass them in to open().
int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
if (fs_flags == -1) {
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
- fd,
- file_path.c_str(),
- strerror(errno));
- return nullptr;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
+ fd,
+ file_path.c_str(),
+ strerror(errno)));
}
// File offset : Ignore the offset for non seekable files.
@@ -276,7 +281,7 @@
return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
}
-bool FileDescriptorInfo::Restat() const {
+bool FileDescriptorInfo::RefersToSameFile() const {
struct stat f_stat;
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
PLOG(ERROR) << "Unable to restat fd " << fd;
@@ -286,9 +291,9 @@
return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev;
}
-bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const {
+void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const {
if (is_sock) {
- return DetachSocket(error_msg);
+ return DetachSocket(fail_fn);
}
// NOTE: This might happen if the file was unlinked after being opened.
@@ -297,57 +302,50 @@
const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
if (new_fd == -1) {
- *error_msg = android::base::StringPrintf("Failed open(%s, %i): %s",
- file_path.c_str(),
- open_flags,
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s",
+ file_path.c_str(),
+ open_flags,
+ strerror(errno)));
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
- new_fd,
- fd_flags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
+ new_fd,
+ fd_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
- new_fd,
- fs_flags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
+ new_fd,
+ fs_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
- new_fd,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
+ new_fd,
+ file_path.c_str(),
+ strerror(errno)));
}
- int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
- if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) {
+ int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
+ if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
- fd,
- new_fd,
- dupFlags,
- file_path.c_str(),
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
+ fd,
+ new_fd,
+ dup_flags,
+ file_path.c_str(),
+ strerror(errno)));
}
close(new_fd);
-
- return true;
}
FileDescriptorInfo::FileDescriptorInfo(int fd) :
@@ -373,7 +371,6 @@
is_sock(false) {
}
-// static
bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
sockaddr_storage ss;
sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
@@ -417,86 +414,75 @@
return true;
}
-bool FileDescriptorInfo::DetachSocket(std::string* error_msg) const {
+void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
const int dev_null_fd = open("/dev/null", O_RDWR);
if (dev_null_fd < 0) {
- *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
- return false;
+ fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
if (dup2(dev_null_fd, fd) == -1) {
- *error_msg = android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
- fd,
- strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+ fd,
+ strerror(errno)));
}
if (close(dev_null_fd) == -1) {
- *error_msg = android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno));
- return false;
+ fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)));
}
-
- return true;
}
// static
FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
- std::string* error_msg) {
- DIR* d = opendir(kFdPath);
- if (d == nullptr) {
- *error_msg = std::string("Unable to open directory ").append(kFdPath);
- return nullptr;
+ fail_fn_t fail_fn) {
+ DIR* proc_fd_dir = opendir(kFdPath);
+ if (proc_fd_dir == nullptr) {
+ fail_fn(std::string("Unable to open directory ").append(kFdPath));
}
- int dir_fd = dirfd(d);
- dirent* e;
+
+ int dir_fd = dirfd(proc_fd_dir);
+ dirent* dir_entry;
std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
- while ((e = readdir(d)) != NULL) {
- const int fd = ParseFd(e, dir_fd);
+ while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+ const int fd = ParseFd(dir_entry, dir_fd);
if (fd == -1) {
continue;
}
+
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
}
- FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
- if (info == NULL) {
- if (closedir(d) == -1) {
- PLOG(ERROR) << "Unable to close directory";
- }
- return NULL;
- }
- open_fd_map[fd] = info;
+ open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
- if (closedir(d) == -1) {
- *error_msg = "Unable to close directory";
- return nullptr;
+ if (closedir(proc_fd_dir) == -1) {
+ fail_fn("Unable to close directory");
}
+
return new FileDescriptorTable(open_fd_map);
}
-bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg) {
+void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) {
std::set<int> open_fds;
// First get the list of open descriptors.
- DIR* d = opendir(kFdPath);
- if (d == NULL) {
- *error_msg = android::base::StringPrintf("Unable to open directory %s: %s",
- kFdPath,
- strerror(errno));
- return false;
+ DIR* proc_fd_dir = opendir(kFdPath);
+ if (proc_fd_dir == nullptr) {
+ fail_fn(android::base::StringPrintf("Unable to open directory %s: %s",
+ kFdPath,
+ strerror(errno)));
}
- int dir_fd = dirfd(d);
- dirent* e;
- while ((e = readdir(d)) != NULL) {
- const int fd = ParseFd(e, dir_fd);
+ int dir_fd = dirfd(proc_fd_dir);
+ dirent* dir_entry;
+ while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+ const int fd = ParseFd(dir_entry, dir_fd);
if (fd == -1) {
continue;
}
+
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
@@ -505,27 +491,24 @@
open_fds.insert(fd);
}
- if (closedir(d) == -1) {
- *error_msg = android::base::StringPrintf("Unable to close directory: %s", strerror(errno));
- return false;
+ if (closedir(proc_fd_dir) == -1) {
+ fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno)));
}
- return RestatInternal(open_fds, error_msg);
+ RestatInternal(open_fds, fail_fn);
}
-// Reopens all file descriptors that are contained in the table. Returns true
-// if all descriptors were successfully re-opened or detached, and false if an
-// error occurred.
-bool FileDescriptorTable::ReopenOrDetach(std::string* error_msg) {
+// Reopens all file descriptors that are contained in the table.
+void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
const FileDescriptorInfo* info = it->second;
- if (info == NULL || !info->ReopenOrDetach(error_msg)) {
- return false;
+ if (info == nullptr) {
+ return;
+ } else {
+ info->ReopenOrDetach(fail_fn);
}
}
-
- return true;
}
FileDescriptorTable::FileDescriptorTable(
@@ -533,9 +516,7 @@
: open_fd_map_(map) {
}
-bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* error_msg) {
- bool error = false;
-
+void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
// Iterate through the list of file descriptors we've already recorded
// and check whether :
//
@@ -558,28 +539,18 @@
} else {
// The entry from the file descriptor table is still open. Restat
// it and check whether it refers to the same file.
- const bool same_file = it->second->Restat();
- if (!same_file) {
+ if (!it->second->RefersToSameFile()) {
// The file descriptor refers to a different description. We must
// update our entry in the table.
delete it->second;
- it->second = FileDescriptorInfo::CreateFromFd(*element, error_msg);
- if (it->second == NULL) {
- // The descriptor no longer no longer refers to a whitelisted file.
- // We flag an error and remove it from the list of files we're
- // tracking.
- error = true;
- it = open_fd_map_.erase(it);
- } else {
- // Successfully restatted the file, move on to the next open FD.
- ++it;
- }
+ it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
} else {
// It's the same file. Nothing to do here. Move on to the next open
// FD.
- ++it;
}
+ ++it;
+
// Finally, remove the FD from the set of open_fds. We do this last because
// |element| will not remain valid after a call to erase.
open_fds.erase(element);
@@ -598,25 +569,15 @@
std::set<int>::const_iterator it;
for (it = open_fds.begin(); it != open_fds.end(); ++it) {
const int fd = (*it);
- FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
- if (info == NULL) {
- // A newly opened file is not on the whitelist. Flag an error and
- // continue.
- error = true;
- } else {
- // Track the newly opened file.
- open_fd_map_[fd] = info;
- }
+ open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
}
-
- return !error;
}
// static
-int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) {
+int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) {
char* end;
- const int fd = strtol(e->d_name, &end, 10);
+ const int fd = strtol(dir_entry->d_name, &end, 10);
if ((*end) != '\0') {
return -1;
}
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index 09022a2..2caf157 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -30,6 +30,9 @@
class FileDescriptorInfo;
+// This type is duplicated in com_android_internal_os_Zygote.cpp
+typedef const std::function<void(std::string)>& fail_fn_t;
+
// Whitelist of open paths that the zygote is allowed to keep open.
//
// In addition to the paths listed in kPathWhitelist in file_utils.cpp, and
@@ -76,19 +79,19 @@
// /proc/self/fd for the list of open file descriptors and collects
// information about them. Returns NULL if an error occurs.
static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore,
- std::string* error_msg);
+ fail_fn_t fail_fn);
- bool Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg);
+ void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn);
// Reopens all file descriptors that are contained in the table. Returns true
// if all descriptors were successfully re-opened or detached, and false if an
// error occurred.
- bool ReopenOrDetach(std::string* error_msg);
+ void ReopenOrDetach(fail_fn_t fail_fn);
private:
explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
- bool RestatInternal(std::set<int>& open_fds, std::string* error_msg);
+ void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
static int ParseFd(dirent* e, int dir_fd);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7aee5fb..16d1d1a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -381,7 +381,7 @@
<!-- Configuration of Ethernet interfaces in the following format:
- <interface name|mac address>;[Network Capabilities];[IP config]
+ <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]
Where
[Network Capabilities] Optional. A comma seprated list of network capabilities.
Values must be from NetworkCapabilities#NET_CAPABILITIES_* constants.
@@ -389,11 +389,16 @@
use the following format to specify static IP configuration:
ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
domains=<comma-sep-domains>
+ [Override Transport] Optional. An override network transport type to allow
+ the propagation of an interface type on the other end of a local Ethernet
+ interface. Value must be from NetworkCapabilities#TRANSPORT_* constants. If
+ left out, this will default to TRANSPORT_ETHERNET.
-->
<string-array translatable="false" name="config_ethernet_interfaces">
<!--
<item>eth1;12,13,14,15;ip=192.168.0.10/24 gateway=192.168.0.1 dns=4.4.4.4,8.8.8.8</item>
<item>eth2;;ip=192.168.0.11/24</item>
+ <item>eth3;12,13,14,15;ip=192.168.0.12/24;1</item>
-->
</string-array>
@@ -1750,6 +1755,19 @@
config_enableGeofenceOverlay is false. -->
<string name="config_geofenceProviderPackageName" translatable="false">@null</string>
+ <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware
+ Activity-Recognition to be replaced by an app at run-time. When disabled, only the
+ config_activityRecognitionHardwarePackageName package will be searched for
+ its implementation, otherwise packages whose signature matches the
+ signatures of config_locationProviderPackageNames will be searched, and
+ the service with the highest version number will be picked. Anyone who
+ wants to disable the overlay mechanism can set it to false.
+ -->
+ <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool>
+ <!-- Package name providing Hardware Activity-Recognition API support. Used only when
+ config_enableActivityRecognitionHardwareOverlay is false. -->
+ <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string>
+
<!-- Package name(s) containing location provider support.
These packages can contain services implementing location providers,
such as the Geocode Provider, Network Location Provider, and
@@ -3735,4 +3753,7 @@
<!-- Enable Zram writeback feature to allow unused pages in zram be written to flash. -->
<bool name="config_zramWriteback">false</bool>
+
+ <!-- Whether cbrs is supported on the device or not -->
+ <bool translatable="false" name="config_cbrs_supported">false</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0878562..f2b4b9c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3353,9 +3353,9 @@
<string name="wifi_available_action_all_networks">All networks</string>
<!-- Notification title for a connection to a app suggested wireless network.-->
- <string name="wifi_suggestion_title">Connected to Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g></string>
+ <string name="wifi_suggestion_title">A Wi\u2011Fi network proposed by <xliff:g id="name" example="App123">%s</xliff:g> is available</string>
<!-- Notification content for a connection to a app suggested wireless network.-->
- <string name="wifi_suggestion_content">Do you want to let <xliff:g id="name" example="App123">%s</xliff:g> propose networks for you?</string>
+ <string name="wifi_suggestion_content">Do you want to connect to networks proposed by <xliff:g id="name" example="App123">%s</xliff:g>?</string>
<!-- Notification action for allowing app specified in the notification body.-->
<string name="wifi_suggestion_action_allow_app">Yes</string>
<!-- Notification action for disallowing app specified in the notification body.-->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dfe0440..7517218 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1854,6 +1854,7 @@
<java-symbol type="bool" name="config_enableNightMode" />
<java-symbol type="bool" name="config_tintNotificationActionButtons" />
<java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" />
+ <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
<java-symbol type="bool" name="config_enableFusedLocationOverlay" />
<java-symbol type="bool" name="config_enableHardwareFlpOverlay" />
<java-symbol type="bool" name="config_enableGeocoderOverlay" />
@@ -2022,6 +2023,7 @@
<java-symbol type="string" name="car_mode_disable_notification_title" />
<java-symbol type="string" name="chooser_wallpaper" />
<java-symbol type="string" name="config_datause_iface" />
+ <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
<java-symbol type="string" name="config_fusedLocationProviderPackageName" />
<java-symbol type="string" name="config_hardwareFlpPackageName" />
<java-symbol type="string" name="config_geocoderProviderPackageName" />
@@ -3542,4 +3544,7 @@
<java-symbol type="bool" name="config_silenceSensorAvailable" />
<java-symbol type="bool" name="config_zramWriteback" />
+
+ <!-- For CBRS -->
+ <java-symbol type="bool" name="config_cbrs_supported" />
</resources>
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 425ab89..e94d60c 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -341,12 +341,37 @@
}
public void testGetAttributionId() {
- WorkSource ws1 = new WorkSource();
- WorkChain wc = ws1.createWorkChain();
- wc.addNode(100, "tag");
- assertEquals(100, wc.getAttributionUid());
- wc.addNode(200, "tag2");
- assertEquals(100, wc.getAttributionUid());
+ WorkSource ws = new WorkSource();
+ WorkChain wc1 = ws.createWorkChain();
+ wc1.addNode(100, "tag");
+ assertEquals(100, wc1.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ wc1.addNode(200, "tag2");
+ assertEquals(100, wc1.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ WorkChain wc2 = ws.createWorkChain();
+ wc2.addNode(300, "tag3");
+ assertEquals(300, wc2.getAttributionUid());
+ assertEquals(100, ws.getAttributionUid());
+ }
+
+ public void testGetAttributionIdWithoutWorkChain() {
+ WorkSource ws1 = new WorkSource(100);
+ ws1.add(200);
+ WorkSource ws2 = new WorkSource();
+ ws2.add(100);
+ ws2.add(200);
+ assertEquals(100, ws1.getAttributionUid());
+ assertEquals(100, ws2.getAttributionUid());
+ }
+
+ public void testGetAttributionWhenEmpty() {
+ WorkSource ws = new WorkSource();
+ assertEquals(-1, ws.getAttributionUid());
+ WorkChain wc = ws.createWorkChain();
+ assertEquals(-1, ws.getAttributionUid());
+ assertEquals(-1, wc.getAttributionUid());
+ assertNull(wc.getAttributionTag());
}
public void testGetAttributionTag() {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 0dd7685..683d16b 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -88,32 +88,32 @@
public void setOnKeyEventResult(boolean handled, int sequence) {}
- public float getMagnificationScale() {
+ public float getMagnificationScale(int displayId) {
return 0.0f;
}
- public float getMagnificationCenterX() {
+ public float getMagnificationCenterX(int displayId) {
return 0.0f;
}
- public float getMagnificationCenterY() {
+ public float getMagnificationCenterY(int displayId) {
return 0.0f;
}
- public Region getMagnificationRegion() {
+ public Region getMagnificationRegion(int displayId) {
return null;
}
- public boolean resetMagnification(boolean animate) {
+ public boolean resetMagnification(int displayId, boolean animate) {
return false;
}
- public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
- boolean animate) {
+ public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
+ float centerY, boolean animate) {
return false;
}
- public void setMagnificationCallbackEnabled(boolean enabled) {}
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {}
public boolean setSoftKeyboardShowMode(int showMode) {
return false;
diff --git a/core/tests/hdmitests/Android.mk b/core/tests/hdmitests/Android.mk
index 2ca31a6..f155feb 100644
--- a/core/tests/hdmitests/Android.mk
+++ b/core/tests/hdmitests/Android.mk
@@ -20,7 +20,7 @@
# Include all test java files
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules frameworks-base-testutils truth-prebuilt
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := HdmiCecTests
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
new file mode 100644
index 0000000..fdc6b84
--- /dev/null
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiUtilsTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+/**
+ * Tests for {@link HdmiUtils}.
+ */
+@RunWith(JUnit4.class)
+@SmallTest
+public class HdmiUtilsTest {
+ @Test
+ public void testInvalidAddress() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0, -1))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFF, 0xFFFF))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0xFFFFF, 0))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN);
+ }
+
+ @Test
+ public void testSameAddress() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SAME);
+ }
+
+ @Test
+ public void testDirectlyAbove() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyAbove_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x2000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyAbove_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1240, 0x1245))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_ABOVE);
+ }
+
+ @Test
+ public void testAbove() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x1000, 0x1210))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE);
+ }
+
+ @Test
+ public void testAbove_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x0000, 0x1200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_ABOVE);
+ }
+
+ @Test
+ public void testDirectlyBelow() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x2250, 0x2200))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testDirectlyBelow_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5000, 0x0000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testDirectlyBelow_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3249, 0x3240))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW);
+ }
+
+ @Test
+ public void testBelow() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x5143, 0x5100))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW);
+ }
+
+ @Test
+ public void testBelow_rootDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x3420, 0x0000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_BELOW);
+ }
+
+ @Test
+ public void testSibling() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x4000, 0x5000))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING);
+ }
+
+ @Test
+ public void testSibling_leafDevice() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x798F))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_SIBLING);
+ }
+
+ @Test
+ public void testDifferentBranch() {
+ assertThat(HdmiUtils.getHdmiAddressRelativePosition(0x798A, 0x7970))
+ .isEqualTo(HdmiUtils.HDMI_RELATIVE_POSITION_DIFFERENT_BRANCH);
+ }
+
+ @Test
+ public void isValidPysicalAddress_true() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(0)).isTrue();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFE)).isTrue();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x1200)).isTrue();
+ }
+
+ @Test
+ public void isValidPysicalAddress_outOfRange() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(-1)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0xFFFF)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x10000)).isFalse();
+ }
+
+ @Test
+ public void isValidPysicalAddress_nonTrailingZeros() {
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x0001)).isFalse();
+ assertThat(HdmiUtils.isValidPhysicalAddress(0x0213)).isFalse();
+ }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 597d14a..904c3fb 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -180,6 +180,7 @@
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
+ <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
<permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
<permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
</privapp-permissions>
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 154bd56..3d0afb09 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -16,6 +16,8 @@
package android.location;
+import android.Manifest;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -161,6 +163,7 @@
private WorkSource mWorkSource = null;
@UnsupportedAppUsage
private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
+ private boolean mLocationSettingsIgnored = false;
@UnsupportedAppUsage
private String mProvider = LocationManager.FUSED_PROVIDER;
@@ -261,6 +264,7 @@
mWorkSource = src.mWorkSource;
mHideFromAppOps = src.mHideFromAppOps;
mLowPowerMode = src.mLowPowerMode;
+ mLocationSettingsIgnored = src.mLocationSettingsIgnored;
}
/**
@@ -375,6 +379,32 @@
}
/**
+ * Requests that user location settings be ignored in order to satisfy this request. This API
+ * is only for use in extremely rare scenarios where it is appropriate to ignore user location
+ * settings, such as a user initiated emergency (dialing 911 for instance).
+ *
+ * @param locationSettingsIgnored Whether to ignore location settings
+ * @return the same object, so that setters can be chained
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @SystemApi
+ public LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
+ mLocationSettingsIgnored = locationSettingsIgnored;
+ return this;
+ }
+
+ /**
+ * Returns true if location settings will be ignored in order to satisfy this request.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isLocationSettingsIgnored() {
+ return mLocationSettingsIgnored;
+ }
+
+ /**
* Explicitly set the fastest interval for location updates, in
* milliseconds.
*
diff --git a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
new file mode 100644
index 0000000..843dd67
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import android.annotation.NonNull;
+
+import java.security.InvalidParameterException;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ * @hide
+ */
+public class ActivityChangedEvent {
+ private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+ public ActivityChangedEvent(List<ActivityRecognitionEvent> activityRecognitionEvents) {
+ if (activityRecognitionEvents == null) {
+ throw new InvalidParameterException(
+ "Parameter 'activityRecognitionEvents' must not be null.");
+ }
+
+ mActivityRecognitionEvents = activityRecognitionEvents;
+ }
+
+ @NonNull
+ public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+ return mActivityRecognitionEvents;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+ for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+ builder.append("\n ");
+ builder.append(event.toString());
+ }
+ builder.append("\n]");
+
+ return builder.toString();
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..e54dea4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.location.provider;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ * @hide
+ */
+public class ActivityRecognitionEvent {
+ private final String mActivity;
+ private final int mEventType;
+ private final long mTimestampNs;
+
+ public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+ mActivity = activity;
+ mEventType = eventType;
+ mTimestampNs = timestampNs;
+ }
+
+ public String getActivity() {
+ return mActivity;
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ @Override
+ public String toString() {
+ String eventString;
+ switch (mEventType) {
+ case ActivityRecognitionProvider.EVENT_TYPE_ENTER:
+ eventString = "Enter";
+ break;
+ case ActivityRecognitionProvider.EVENT_TYPE_EXIT:
+ eventString = "Exit";
+ break;
+ case ActivityRecognitionProvider.EVENT_TYPE_FLUSH_COMPLETE:
+ eventString = "FlushComplete";
+ break;
+ default:
+ eventString = "<Invalid>";
+ break;
+ }
+
+ return String.format(
+ "Activity='%s', EventType=%s(%s), TimestampNs=%s",
+ mActivity,
+ eventString,
+ mEventType,
+ mTimestampNs);
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
new file mode 100644
index 0000000..0eff7d3
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import com.android.internal.util.Preconditions;
+
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareSink;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A class that exposes {@link IActivityRecognitionHardware} functionality to unbundled services.
+ * @hide
+ */
+public final class ActivityRecognitionProvider {
+ private final IActivityRecognitionHardware mService;
+ private final HashSet<Sink> mSinkSet = new HashSet<>();
+
+ // the following constants must remain in sync with activity_recognition.h
+
+ public static final String ACTIVITY_IN_VEHICLE = "android.activity_recognition.in_vehicle";
+ public static final String ACTIVITY_ON_BICYCLE = "android.activity_recognition.on_bicycle";
+ public static final String ACTIVITY_WALKING = "android.activity_recognition.walking";
+ public static final String ACTIVITY_RUNNING = "android.activity_recognition.running";
+ public static final String ACTIVITY_STILL = "android.activity_recognition.still";
+ public static final String ACTIVITY_TILTING = "android.activity_recognition.tilting";
+
+ // NOTE: when adding an additional EVENT_TYPE_, EVENT_TYPE_COUNT needs to be updated in
+ // android.hardware.location.ActivityRecognitionHardware
+ public static final int EVENT_TYPE_FLUSH_COMPLETE = 0;
+ public static final int EVENT_TYPE_ENTER = 1;
+ public static final int EVENT_TYPE_EXIT = 2;
+
+ // end constants activity_recognition.h
+
+ /**
+ * Used to receive Activity-Recognition events.
+ */
+ public interface Sink {
+ void onActivityChanged(ActivityChangedEvent event);
+ }
+
+ public ActivityRecognitionProvider(IActivityRecognitionHardware service)
+ throws RemoteException {
+ Preconditions.checkNotNull(service);
+ mService = service;
+ mService.registerSink(new SinkTransport());
+ }
+
+ public String[] getSupportedActivities() throws RemoteException {
+ return mService.getSupportedActivities();
+ }
+
+ public boolean isActivitySupported(String activity) throws RemoteException {
+ return mService.isActivitySupported(activity);
+ }
+
+ public void registerSink(Sink sink) {
+ Preconditions.checkNotNull(sink);
+ synchronized (mSinkSet) {
+ mSinkSet.add(sink);
+ }
+ }
+
+ // TODO: if this functionality is exposed to 3rd party developers, handle unregistration (here
+ // and in the service) of all sinks while failing to disable all events
+ public void unregisterSink(Sink sink) {
+ Preconditions.checkNotNull(sink);
+ synchronized (mSinkSet) {
+ mSinkSet.remove(sink);
+ }
+ }
+
+ public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs)
+ throws RemoteException {
+ return mService.enableActivityEvent(activity, eventType, reportLatencyNs);
+ }
+
+ public boolean disableActivityEvent(String activity, int eventType) throws RemoteException {
+ return mService.disableActivityEvent(activity, eventType);
+ }
+
+ public boolean flush() throws RemoteException {
+ return mService.flush();
+ }
+
+ private final class SinkTransport extends IActivityRecognitionHardwareSink.Stub {
+ @Override
+ public void onActivityChanged(android.hardware.location.ActivityChangedEvent event) {
+ Collection<Sink> sinks;
+ synchronized (mSinkSet) {
+ if (mSinkSet.isEmpty()) {
+ return;
+ }
+ sinks = new ArrayList<>(mSinkSet);
+ }
+
+ // translate the event from platform internal and GmsCore types
+ ArrayList<ActivityRecognitionEvent> gmsEvents = new ArrayList<>();
+ for (android.hardware.location.ActivityRecognitionEvent reportingEvent
+ : event.getActivityRecognitionEvents()) {
+ ActivityRecognitionEvent gmsEvent = new ActivityRecognitionEvent(
+ reportingEvent.getActivity(),
+ reportingEvent.getEventType(),
+ reportingEvent.getTimestampNs());
+ gmsEvents.add(gmsEvent);
+ }
+ ActivityChangedEvent gmsEvent = new ActivityChangedEvent(gmsEvents);
+
+ for (Sink sink : sinks) {
+ sink.onActivityChanged(gmsEvent);
+ }
+ }
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java
new file mode 100644
index 0000000..326d901
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderClient.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import android.annotation.NonNull;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A client class for interaction with an Activity-Recognition provider.
+ * @hide
+ */
+public abstract class ActivityRecognitionProviderClient {
+ private static final String TAG = "ArProviderClient";
+
+ protected ActivityRecognitionProviderClient() {}
+
+ private IActivityRecognitionHardwareClient.Stub mClient =
+ new IActivityRecognitionHardwareClient.Stub() {
+ @Override
+ public void onAvailabilityChanged(
+ boolean isSupported,
+ IActivityRecognitionHardware instance) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+ return;
+ }
+ ActivityRecognitionProvider provider;
+ try {
+ provider = isSupported ? new ActivityRecognitionProvider(instance) : null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating Hardware Activity-Recognition Provider.", e);
+ return;
+ }
+ onProviderChanged(isSupported, provider);
+ }
+ };
+
+ /**
+ * Gets the binder needed to interact with proxy provider in the platform.
+ */
+ @NonNull
+ public IBinder getBinder() {
+ return mClient;
+ }
+
+ /**
+ * Called when a change in the availability of {@link ActivityRecognitionProvider} is detected.
+ *
+ * @param isSupported whether the platform supports the provider natively
+ * @param instance the available provider's instance
+ */
+ public abstract void onProviderChanged(
+ boolean isSupported,
+ ActivityRecognitionProvider instance);
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
new file mode 100644
index 0000000..42f77b4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A watcher class for Activity-Recognition instances.
+ *
+ * @deprecated use {@link ActivityRecognitionProviderClient} instead.
+ * @hide
+ */
+@Deprecated
+public class ActivityRecognitionProviderWatcher {
+ private static final String TAG = "ActivityRecognitionProviderWatcher";
+
+ private static ActivityRecognitionProviderWatcher sWatcher;
+ private static final Object sWatcherLock = new Object();
+
+ private ActivityRecognitionProvider mActivityRecognitionProvider;
+
+ private ActivityRecognitionProviderWatcher() {}
+
+ public static ActivityRecognitionProviderWatcher getInstance() {
+ synchronized (sWatcherLock) {
+ if (sWatcher == null) {
+ sWatcher = new ActivityRecognitionProviderWatcher();
+ }
+ return sWatcher;
+ }
+ }
+
+ private IActivityRecognitionHardwareWatcher.Stub mWatcherStub =
+ new IActivityRecognitionHardwareWatcher.Stub() {
+ @Override
+ public void onInstanceChanged(IActivityRecognitionHardware instance) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+ return;
+ }
+
+ try {
+ mActivityRecognitionProvider = new ActivityRecognitionProvider(instance);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating Hardware Activity-Recognition", e);
+ }
+ }
+ };
+
+ /**
+ * Gets the binder needed to interact with proxy provider in the platform.
+ */
+ @NonNull
+ public IBinder getBinder() {
+ return mWatcherStub;
+ }
+
+ /**
+ * Gets an object that supports the functionality of {@link ActivityRecognitionProvider}.
+ *
+ * @return Non-null value if the functionality is supported by the platform, false otherwise.
+ */
+ @Nullable
+ public ActivityRecognitionProvider getActivityRecognitionProvider() {
+ return mActivityRecognitionProvider;
+ }
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 2848b89..af016d5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1100,7 +1100,8 @@
(1 << STREAM_RING) |
(1 << STREAM_NOTIFICATION) |
(1 << STREAM_SYSTEM) |
- (1 << STREAM_VOICE_CALL);
+ (1 << STREAM_VOICE_CALL) |
+ (1 << STREAM_BLUETOOTH_SCO);
/**
* Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes.
diff --git a/media/java/android/media/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl
index a591c11..74c05c4 100644
--- a/media/java/android/media/IRemoteVolumeController.aidl
+++ b/media/java/android/media/IRemoteVolumeController.aidl
@@ -9,6 +9,7 @@
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
+ *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
@@ -16,7 +17,7 @@
package android.media;
-import android.media.session.ISessionController;
+import android.media.session.MediaSession;
/**
* AIDL for the MediaSessionService to report interesting events on remote playback
@@ -25,8 +26,8 @@
* @hide
*/
oneway interface IRemoteVolumeController {
- void remoteVolumeChanged(in ISessionController session, int flags);
+ void remoteVolumeChanged(in MediaSession.Token sessionToken, int flags);
// sets the default session to use with the slider, replaces remoteSliderVisibility
// on IVolumeController
- void updateRemoteController(in ISessionController session);
+ void updateRemoteController(in MediaSession.Token sessionToken);
}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index dd97195..814bc72 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -71,6 +71,8 @@
private final Object mLock = new Object();
//@GuardedBy("mLock")
+ private boolean mClosed;
+ //@GuardedBy("mLock")
private int mNextSeqNumber;
//@GuardedBy("mLock")
private Session2Link mSessionBinder;
@@ -141,7 +143,14 @@
@Override
public void close() {
synchronized (mLock) {
+ if (mClosed) {
+ // Already closed. Ignore rest of clean up code.
+ // Note: unbindService() throws IllegalArgumentException when it's called twice.
+ return;
+ }
+ mClosed = true;
if (mServiceConnection != null) {
+ // Note: This should be called even when the bindService() has returned false.
mContext.unbindService(mServiceConnection);
}
if (mSessionBinder != null) {
@@ -167,7 +176,7 @@
* If it is not connected yet, it returns {@code null}.
* <p>
* This may differ with the {@link Session2Token} from the constructor. For example, if the
- * controller is created with the token for MediaSession2Service, this would return
+ * controller is created with the token for {@link MediaSession2Service}, this would return
* token for the {@link MediaSession2} in the service.
*
* @return Session2Token of the connected session, or {@code null} if not connected
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 3adac72..76ef27a 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -90,6 +90,8 @@
private boolean mClosed;
//@GuardedBy("mLock")
private boolean mPlaybackActive;
+ //@GuardedBy("mLock")
+ private ForegroundServiceEventCallback mForegroundServiceEventCallback;
MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity,
@NonNull Executor callbackExecutor, @NonNull SessionCallback callback) {
@@ -119,6 +121,7 @@
public void close() {
try {
List<ControllerInfo> controllerInfos;
+ ForegroundServiceEventCallback callback;
synchronized (mLock) {
if (mClosed) {
return;
@@ -126,11 +129,15 @@
mClosed = true;
controllerInfos = getConnectedControllers();
mConnectedControllers.clear();
- mCallback.onSessionClosed(this);
+ callback = mForegroundServiceEventCallback;
+ mForegroundServiceEventCallback = null;
}
synchronized (MediaSession2.class) {
SESSION_ID_LIST.remove(mSessionId);
}
+ if (callback != null) {
+ callback.onSessionClosed(this);
+ }
for (ControllerInfo info : controllerInfos) {
info.notifyDisconnected();
}
@@ -224,11 +231,16 @@
* @param playbackActive {@code true} if the playback active, {@code false} otherwise.
**/
public void setPlaybackActive(boolean playbackActive) {
+ final ForegroundServiceEventCallback serviceCallback;
synchronized (mLock) {
if (mPlaybackActive == playbackActive) {
return;
}
mPlaybackActive = playbackActive;
+ serviceCallback = mForegroundServiceEventCallback;
+ }
+ if (serviceCallback != null) {
+ serviceCallback.onPlaybackActiveChanged(this, playbackActive);
}
List<ControllerInfo> controllerInfos = getConnectedControllers();
for (ControllerInfo controller : controllerInfos) {
@@ -257,6 +269,18 @@
return mCallback;
}
+ void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
+ synchronized (mLock) {
+ if (mForegroundServiceEventCallback == callback) {
+ return;
+ }
+ if (mForegroundServiceEventCallback != null && callback != null) {
+ throw new IllegalStateException("A session cannot be added to multiple services");
+ }
+ mForegroundServiceEventCallback = callback;
+ }
+ }
+
// Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect
void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq,
Bundle connectionRequest) {
@@ -695,8 +719,6 @@
* This API is not generally intended for third party application developers.
*/
public abstract static class SessionCallback {
- ForegroundServiceEventCallback mForegroundServiceEventCallback;
-
/**
* Called when a controller is created for this session. Return allowed commands for
* controller. By default it returns {@code null}.
@@ -753,19 +775,10 @@
public void onCommandResult(@NonNull MediaSession2 session,
@NonNull ControllerInfo controller, @NonNull Object token,
@NonNull Session2Command command, @NonNull Session2Command.Result result) {}
+ }
- final void onSessionClosed(MediaSession2 session) {
- if (mForegroundServiceEventCallback != null) {
- mForegroundServiceEventCallback.onSessionClosed(session);
- }
- }
-
- void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
- mForegroundServiceEventCallback = callback;
- }
-
- abstract static class ForegroundServiceEventCallback {
- public void onSessionClosed(MediaSession2 session) {}
- }
+ abstract static class ForegroundServiceEventCallback {
+ public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {}
+ public void onSessionClosed(MediaSession2 session) {}
}
}
diff --git a/media/java/android/media/MediaSession2Service.java b/media/java/android/media/MediaSession2Service.java
index 8fb00fe..5bb746a 100644
--- a/media/java/android/media/MediaSession2Service.java
+++ b/media/java/android/media/MediaSession2Service.java
@@ -19,7 +19,10 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -28,8 +31,6 @@
import android.util.ArrayMap;
import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
-
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -42,11 +43,7 @@
* Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
* <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
* for consistent behavior across all devices.
- * @hide
*/
-// TODO: Unhide
-// TODO: Add onUpdateNotification(), and calls it to get Notification for startForegroundService()
-// when a session's player state becomes playing.
public abstract class MediaSession2Service extends Service {
/**
* The {@link Intent} that must be declared as handled by the service.
@@ -56,10 +53,29 @@
private static final String TAG = "MediaSession2Service";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private Map<String, MediaSession2> mSessions = new ArrayMap<>();
+ private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback =
+ new MediaSession2.ForegroundServiceEventCallback() {
+ @Override
+ public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
+ MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive);
+ }
+ @Override
+ public void onSessionClosed(MediaSession2 session) {
+ removeSession(session);
+ }
+ };
+
+ private final Object mLock = new Object();
+ //@GuardedBy("mLock")
+ private NotificationManager mNotificationManager;
+ //@GuardedBy("mLock")
+ private Intent mStartSelfIntent;
+ //@GuardedBy("mLock")
+ private Map<String, MediaSession2> mSessions = new ArrayMap<>();
+ //@GuardedBy("mLock")
+ private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>();
+ //@GuardedBy("mLock")
private MediaSession2ServiceStub mStub;
/**
@@ -72,7 +88,12 @@
@Override
public void onCreate() {
super.onCreate();
- mStub = new MediaSession2ServiceStub(this);
+ synchronized (mLock) {
+ mStub = new MediaSession2ServiceStub(this);
+ mStartSelfIntent = new Intent(this, this.getClass());
+ mNotificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ }
}
@CallSuper
@@ -80,18 +101,13 @@
@Nullable
public IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return mStub;
+ synchronized (mLock) {
+ return mStub;
+ }
}
return null;
}
- @CallSuper
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // TODO: Dispatch media key events to the primary session.
- return START_STICKY;
- }
-
/**
* Called by the system to notify that it is no longer used and is being removed. Do not call
* this method directly.
@@ -104,10 +120,12 @@
public void onDestroy() {
super.onDestroy();
synchronized (mLock) {
- for (MediaSession2 session : mSessions.values()) {
- session.getCallback().setForegroundServiceEventCallback(null);
+ List<MediaSession2> sessions = getSessions();
+ for (MediaSession2 session : sessions) {
+ removeSession(session);
}
mSessions.clear();
+ mNotifications.clear();
}
mStub.close();
}
@@ -144,6 +162,24 @@
public abstract MediaSession2 onGetPrimarySession();
/**
+ * Called when notification UI needs update. Override this method to show or cancel your own
+ * notification UI.
+ * <p>
+ * This would be called on {@link MediaSession2}'s callback executor when playback state is
+ * changed.
+ * <p>
+ * With the notification returned here, the service becomes foreground service when the playback
+ * is started. Apps must request the permission
+ * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes
+ * background service after the playback is stopped.
+ *
+ * @param session a session that needs notification update.
+ * @return a {@link MediaNotification}. Can be {@code null}.
+ */
+ @Nullable
+ public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session);
+
+ /**
* Adds a session to this service.
* <p>
* Added session will be removed automatically when it's closed, or removed when
@@ -161,21 +197,15 @@
}
synchronized (mLock) {
MediaSession2 previousSession = mSessions.get(session.getSessionId());
- if (previousSession != session) {
- if (previousSession != null) {
+ if (previousSession != null) {
+ if (previousSession != session) {
Log.w(TAG, "Session ID should be unique, ID=" + session.getSessionId()
+ ", previous=" + previousSession + ", session=" + session);
}
return;
}
mSessions.put(session.getSessionId(), session);
- session.getCallback().setForegroundServiceEventCallback(
- new MediaSession2.SessionCallback.ForegroundServiceEventCallback() {
- @Override
- public void onSessionClosed(MediaSession2 session) {
- removeSession(session);
- }
- });
+ session.setForegroundServiceEventCallback(mForegroundServiceEventCallback);
}
}
@@ -189,8 +219,21 @@
if (session == null) {
throw new IllegalArgumentException("session shouldn't be null");
}
+ MediaNotification notification;
synchronized (mLock) {
+ if (mSessions.get(session.getSessionId()) != session) {
+ // Session isn't added or removed already.
+ return;
+ }
mSessions.remove(session.getSessionId());
+ notification = mNotifications.remove(session);
+ }
+ session.setForegroundServiceEventCallback(null);
+ if (notification != null) {
+ mNotificationManager.cancel(notification.getNotificationId());
+ }
+ if (getSessions().isEmpty()) {
+ stopForeground(false);
}
}
@@ -207,6 +250,78 @@
return list;
}
+ /**
+ * Called by registered {@link MediaSession2.ForegroundServiceEventCallback}
+ *
+ * @param session session with change
+ * @param playbackActive {@code true} if playback is active.
+ */
+ void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
+ MediaNotification mediaNotification = onUpdateNotification(session);
+ if (mediaNotification == null) {
+ // The service implementation doesn't want to use the automatic start/stopForeground
+ // feature.
+ return;
+ }
+ synchronized (mLock) {
+ mNotifications.put(session, mediaNotification);
+ }
+ int id = mediaNotification.getNotificationId();
+ Notification notification = mediaNotification.getNotification();
+ if (!playbackActive) {
+ mNotificationManager.notify(id, notification);
+ return;
+ }
+ // playbackActive == true
+ startForegroundService(mStartSelfIntent);
+ startForeground(id, notification);
+ }
+
+ /**
+ * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service
+ * foreground service to keep playback running in the background. It's highly recommended to
+ * show media style notification here.
+ */
+ public static class MediaNotification {
+ private final int mNotificationId;
+ private final Notification mNotification;
+
+ /**
+ * Default constructor
+ *
+ * @param notificationId notification id to be used for
+ * {@link NotificationManager#notify(int, Notification)}.
+ * @param notification a notification to make session service run in the foreground. Media
+ * style notification is recommended here.
+ */
+ public MediaNotification(int notificationId, @NonNull Notification notification) {
+ if (notification == null) {
+ throw new IllegalArgumentException("notification shouldn't be null");
+ }
+ mNotificationId = notificationId;
+ mNotification = notification;
+ }
+
+ /**
+ * Gets the id of the notification.
+ *
+ * @return the notification id
+ */
+ public int getNotificationId() {
+ return mNotificationId;
+ }
+
+ /**
+ * Gets the notification.
+ *
+ * @return the notification
+ */
+ @NonNull
+ public Notification getNotification() {
+ return mNotification;
+ }
+ }
+
private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub
implements AutoCloseable {
final WeakReference<MediaSession2Service> mService;
diff --git a/media/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index d8f74c5..023ee46 100644
--- a/media/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -48,14 +48,6 @@
* <p>
* It can be also obtained by {@link android.media.session.MediaSessionManager}.
*/
-// New version of MediaSession2.Token for following reasons
-// - Stop implementing Parcelable for updatable support
-// - Represent session and library service (formerly browser service) in one class.
-// Previously MediaSession2.Token was for session and ComponentName was for service.
-// This helps controller apps to keep target of dispatching media key events in uniform way.
-// For details about the reason, see following. (Android O+)
-// android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged
-// TODO: use @link for MediaSession2Service
public final class Session2Token implements Parcelable {
private static final String TAG = "Session2Token";
@@ -85,12 +77,13 @@
public static final int TYPE_SESSION = 0;
/**
- * Type for MediaSession2Service.
+ * Type for {@link MediaSession2Service}.
*/
public static final int TYPE_SESSION_SERVICE = 1;
private final int mUid;
- private final @TokenType int mType;
+ @TokenType
+ private final int mType;
private final String mPackageName;
private final String mServiceName;
private final Session2Link mSessionLink;
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/java/android/media/session/ControllerCallbackLink.java
index 95e19d2..28845e4 100644
--- a/media/java/android/media/session/ControllerCallbackLink.java
+++ b/media/java/android/media/session/ControllerCallbackLink.java
@@ -254,7 +254,7 @@
@Override
public void notifyPlaybackStateChanged(PlaybackState state) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlaybackStateChanged(state);
@@ -265,7 +265,7 @@
@Override
public void notifyMetadataChanged(MediaMetadata metadata) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onMetadataChanged(metadata);
@@ -276,7 +276,7 @@
@Override
public void notifyQueueChanged(List<QueueItem> queue) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onQueueChanged(queue);
@@ -287,7 +287,7 @@
@Override
public void notifyQueueTitleChanged(CharSequence title) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onQueueTitleChanged(title);
@@ -303,7 +303,7 @@
@Override
public void notifyVolumeInfoChanged(PlaybackInfo info) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onVolumeInfoChanged(info);
@@ -312,7 +312,7 @@
}
}
- private void ensureMediasControlPermission() {
+ private void ensureMediaControlPermission() {
// Allow API calls from the System UI
if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
== PackageManager.PERMISSION_GRANTED) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 9d537c8..057c9cb 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -117,7 +117,7 @@
* @param token The token for the session.
*/
public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) {
- this(context, token.getBinder());
+ this(context, token.getControllerLink());
}
/**
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index f02d9ba..eda913e 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
@@ -33,23 +34,15 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ResultReceiver;
import android.service.media.MediaBrowserService;
import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.util.List;
-import java.util.Objects;
/**
* Allows interaction with media controllers, volume keys, media buttons, and
@@ -74,7 +67,7 @@
* MediaSession objects are thread safe.
*/
public final class MediaSession {
- private static final String TAG = "MediaSession";
+ static final String TAG = "MediaSession";
/**
* Set this flag on the session to indicate that it can handle media button
@@ -121,21 +114,11 @@
FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
public @interface SessionFlags { }
- private final Object mLock = new Object();
- private final int mMaxBitmapSize;
+ private final MediaSessionEngine mImpl;
- private final MediaSession.Token mSessionToken;
- private final MediaController mController;
- private final SessionLink mSessionLink;
- private final SessionCallbackLink mCbStub;
-
- // Do not change the name of mCallback. Support lib accesses this by using reflection.
+ // Do not change the name of mCallbackWrapper. Support lib accesses this by using reflection.
@UnsupportedAppUsage
- private CallbackMessageHandler mCallback;
- private VolumeProvider mVolumeProvider;
- private PlaybackState mPlaybackState;
-
- private boolean mActive = false;
+ private Object mCallback;
/**
* Creates a new session. The session will automatically be registered with
@@ -153,15 +136,15 @@
if (TextUtils.isEmpty(tag)) {
throw new IllegalArgumentException("tag cannot be null or empty");
}
- mMaxBitmapSize = context.getResources().getDimensionPixelSize(
- android.R.dimen.config_mediaMetadataBitmapMaxSize);
- mCbStub = new SessionCallbackLink(context, new CallbackStub(this));
MediaSessionManager manager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
- mSessionLink = manager.createSession(mCbStub, tag);
- mSessionToken = new Token(mSessionLink.getController());
- mController = new MediaController(context, mSessionToken);
+ MediaSessionEngine.CallbackStub cbStub = new MediaSessionEngine.CallbackStub();
+ SessionCallbackLink cbLink = new SessionCallbackLink(context, cbStub);
+ SessionLink sessionLink = manager.createSession(cbLink, tag);
+ mImpl = new MediaSessionEngine(context, sessionLink, cbLink, cbStub,
+ context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize));
} catch (RuntimeException e) {
throw new RuntimeException("Remote error creating session.", e);
}
@@ -177,7 +160,7 @@
* @param callback The callback object
*/
public void setCallback(@Nullable Callback callback) {
- setCallback(callback, null);
+ mImpl.setCallback(callback);
}
/**
@@ -190,24 +173,7 @@
* @param handler The handler that events should be posted on.
*/
public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
- synchronized (mLock) {
- if (mCallback != null) {
- // We're updating the callback, clear the session from the old one.
- mCallback.mCallback.mSession = null;
- mCallback.removeCallbacksAndMessages(null);
- }
- if (callback == null) {
- mCallback = null;
- return;
- }
- if (handler == null) {
- handler = new Handler();
- }
- callback.mSession = this;
- CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
- callback);
- mCallback = msgHandler;
- }
+ mImpl.setCallback(callback, handler);
}
/**
@@ -218,11 +184,7 @@
* @param pi The intent to launch to show UI for this Session.
*/
public void setSessionActivity(@Nullable PendingIntent pi) {
- try {
- mSessionLink.setLaunchPendingIntent(pi);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
- }
+ mImpl.setSessionActivity(pi);
}
/**
@@ -234,11 +196,7 @@
* @param mbr The {@link PendingIntent} to send the media button event to.
*/
public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
- try {
- mSessionLink.setMediaButtonReceiver(mbr);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
- }
+ mImpl.setMediaButtonReceiver(mbr);
}
/**
@@ -247,11 +205,7 @@
* @param flags The flags to set for this session.
*/
public void setFlags(@SessionFlags int flags) {
- try {
- mSessionLink.setFlags(flags);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setFlags.", e);
- }
+ mImpl.setFlags(flags);
}
/**
@@ -266,14 +220,7 @@
* @param attributes The {@link AudioAttributes} for this session's audio.
*/
public void setPlaybackToLocal(AudioAttributes attributes) {
- if (attributes == null) {
- throw new IllegalArgumentException("Attributes cannot be null for local playback.");
- }
- try {
- mSessionLink.setPlaybackToLocal(attributes);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
- }
+ mImpl.setPlaybackToLocal(attributes);
}
/**
@@ -288,26 +235,7 @@
* not be null.
*/
public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
- if (volumeProvider == null) {
- throw new IllegalArgumentException("volumeProvider may not be null!");
- }
- synchronized (mLock) {
- mVolumeProvider = volumeProvider;
- }
- volumeProvider.setCallback(new VolumeProvider.Callback() {
- @Override
- public void onVolumeChanged(VolumeProvider volumeProvider) {
- notifyRemoteVolumeChanged(volumeProvider);
- }
- });
-
- try {
- mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
- volumeProvider.getMaxVolume());
- mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
- }
+ mImpl.setPlaybackToRemote(volumeProvider);
}
/**
@@ -319,15 +247,7 @@
* @param active Whether this session is active or not.
*/
public void setActive(boolean active) {
- if (mActive == active) {
- return;
- }
- try {
- mSessionLink.setActive(active);
- mActive = active;
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Failure in setActive.", e);
- }
+ mImpl.setActive(active);
}
/**
@@ -336,7 +256,7 @@
* @return True if the session is active, false otherwise.
*/
public boolean isActive() {
- return mActive;
+ return mImpl.isActive();
}
/**
@@ -348,14 +268,7 @@
* @param extras Any extras included with the event
*/
public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
- if (TextUtils.isEmpty(event)) {
- throw new IllegalArgumentException("event cannot be null or empty");
- }
- try {
- mSessionLink.sendEvent(event, extras);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Error sending event", e);
- }
+ mImpl.sendSessionEvent(event, extras);
}
/**
@@ -364,11 +277,7 @@
* but it must be released if your activity or service is being destroyed.
*/
public void release() {
- try {
- mSessionLink.destroySession();
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Error releasing session: ", e);
- }
+ mImpl.close();
}
/**
@@ -380,7 +289,7 @@
* session
*/
public @NonNull Token getSessionToken() {
- return mSessionToken;
+ return mImpl.getSessionToken();
}
/**
@@ -390,7 +299,7 @@
* @return A controller for this session.
*/
public @NonNull MediaController getController() {
- return mController;
+ return mImpl.getController();
}
/**
@@ -399,12 +308,7 @@
* @param state The current state of playback
*/
public void setPlaybackState(@Nullable PlaybackState state) {
- mPlaybackState = state;
- try {
- mSessionLink.setPlaybackState(state);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Dead object in setPlaybackState.", e);
- }
+ mImpl.setPlaybackState(state);
}
/**
@@ -416,24 +320,7 @@
* @see android.media.MediaMetadata.Builder#putBitmap
*/
public void setMetadata(@Nullable MediaMetadata metadata) {
- long duration = -1;
- int fields = 0;
- MediaDescription description = null;
- if (metadata != null) {
- metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
- if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
- duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- }
- fields = metadata.size();
- description = metadata.getDescription();
- }
- String metadataDescription = "size=" + fields + ", description=" + description;
-
- try {
- mSessionLink.setMetadata(metadata, duration, metadataDescription);
- } catch (RuntimeException e) {
- Log.wtf(TAG, "Dead object in setPlaybackState.", e);
- }
+ mImpl.setMetadata(metadata);
}
/**
@@ -448,11 +335,7 @@
* @param queue A list of items in the play queue.
*/
public void setQueue(@Nullable List<QueueItem> queue) {
- try {
- mSessionLink.setQueue(queue);
- } catch (RuntimeException e) {
- Log.wtf("Dead object in setQueue.", e);
- }
+ mImpl.setQueue(queue);
}
/**
@@ -463,11 +346,7 @@
* @param title The title of the play queue.
*/
public void setQueueTitle(@Nullable CharSequence title) {
- try {
- mSessionLink.setQueueTitle(title);
- } catch (RuntimeException e) {
- Log.wtf("Dead object in setQueueTitle.", e);
- }
+ mImpl.setQueueTitle(title);
}
/**
@@ -484,11 +363,7 @@
* </ul>
*/
public void setRatingType(@Rating.Style int type) {
- try {
- mSessionLink.setRatingType(type);
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in setRatingType.", e);
- }
+ mImpl.setRatingType(type);
}
/**
@@ -499,11 +374,7 @@
* @param extras The extras associated with the {@link MediaSession}.
*/
public void setExtras(@Nullable Bundle extras) {
- try {
- mSessionLink.setExtras(extras);
- } catch (RuntimeException e) {
- Log.wtf("Dead object in setExtras.", e);
- }
+ mImpl.setExtras(extras);
}
/**
@@ -515,11 +386,7 @@
* @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
*/
public final @NonNull RemoteUserInfo getCurrentControllerInfo() {
- if (mCallback == null || mCallback.mCurrentControllerInfo == null) {
- throw new IllegalStateException(
- "This should be called inside of MediaSession.Callback methods");
- }
- return mCallback.mCurrentControllerInfo;
+ return mImpl.getCurrentControllerInfo();
}
/**
@@ -529,17 +396,7 @@
* @hide
*/
public void notifyRemoteVolumeChanged(VolumeProvider provider) {
- synchronized (mLock) {
- if (provider == null || provider != mVolumeProvider) {
- Log.w(TAG, "Received update from stale volume provider");
- return;
- }
- }
- try {
- mSessionLink.setCurrentVolume(provider.getCurrentVolume());
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in notifyVolumeChanged", e);
- }
+ mImpl.notifyRemoteVolumeChanged(provider);
}
/**
@@ -551,119 +408,7 @@
*/
@UnsupportedAppUsage
public String getCallingPackage() {
- if (mCallback != null && mCallback.mCurrentControllerInfo != null) {
- return mCallback.mCurrentControllerInfo.getPackageName();
- }
- return null;
- }
-
- private void dispatchPrepare(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
- }
-
- private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
- }
-
- private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
- }
-
- private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
- }
-
- private void dispatchPlay(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
- }
-
- private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
- }
-
- private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
- }
-
- private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
- postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
- }
-
- private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
- postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
- }
-
- private void dispatchPause(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
- }
-
- private void dispatchStop(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
- }
-
- private void dispatchNext(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
- }
-
- private void dispatchPrevious(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
- }
-
- private void dispatchFastForward(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
- }
-
- private void dispatchRewind(RemoteUserInfo caller) {
- postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
- }
-
- private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
- postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
- }
-
- private void dispatchRate(RemoteUserInfo caller, Rating rating) {
- postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
- }
-
- private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
- postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
- }
-
- private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
- postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
- }
-
- private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
- long delay) {
- postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
- mediaButtonIntent, null, delay);
- }
-
- private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
- postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
- }
-
- private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
- postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
- }
-
- private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
- ResultReceiver resultCb) {
- Command cmd = new Command(command, args, resultCb);
- postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
- }
-
- private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
- postToCallbackDelayed(caller, what, obj, data, 0);
- }
-
- private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
- long delay) {
- synchronized (mLock) {
- if (mCallback != null) {
- mCallback.post(caller, what, obj, data, delay);
- }
- }
+ return mImpl.getCallingPackage();
}
/**
@@ -672,17 +417,7 @@
* @hide
*/
public static boolean isActiveState(int state) {
- switch (state) {
- case PlaybackState.STATE_FAST_FORWARDING:
- case PlaybackState.STATE_REWINDING:
- case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
- case PlaybackState.STATE_SKIPPING_TO_NEXT:
- case PlaybackState.STATE_BUFFERING:
- case PlaybackState.STATE_CONNECTING:
- case PlaybackState.STATE_PLAYING:
- return true;
- }
- return false;
+ return MediaSessionEngine.isActiveState(state);
}
/**
@@ -692,13 +427,13 @@
*/
public static final class Token implements Parcelable {
- private ControllerLink mBinder;
+ private ControllerLink mControllerLink;
/**
* @hide
*/
- public Token(ControllerLink binder) {
- mBinder = binder;
+ public Token(ControllerLink controllerLink) {
+ mControllerLink = controllerLink;
}
@Override
@@ -708,14 +443,15 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mBinder, flags);
+ dest.writeParcelable(mControllerLink, flags);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((mBinder == null) ? 0 : mBinder.getBinder().hashCode());
+ result = prime * result + ((mControllerLink == null)
+ ? 0 : mControllerLink.getBinder().hashCode());
return result;
}
@@ -728,17 +464,23 @@
if (getClass() != obj.getClass())
return false;
Token other = (Token) obj;
- if (mBinder == null) {
- if (other.mBinder != null)
+ if (mControllerLink == null) {
+ if (other.mControllerLink != null) {
return false;
- } else if (!mBinder.getBinder().equals(other.mBinder.getBinder())) {
+ }
+ } else if (!mControllerLink.getBinder().equals(other.mControllerLink.getBinder())) {
return false;
}
return true;
}
- ControllerLink getBinder() {
- return mBinder;
+ /**
+ * Gets the controller link in this token.
+ * @hide
+ */
+ @SystemApi
+ ControllerLink getControllerLink() {
+ return mControllerLink;
}
public static final Parcelable.Creator<Token> CREATOR =
@@ -762,9 +504,7 @@
*/
public abstract static class Callback {
- private MediaSession mSession;
- private CallbackMessageHandler mHandler;
- private boolean mMediaPlayPauseKeyPending;
+ MediaSessionEngine.MediaButtonEventDelegate mMediaButtonEventDelegate;
public Callback() {
}
@@ -796,110 +536,12 @@
* @return True if the event was handled, false otherwise.
*/
public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
- if (mSession != null && mHandler != null
- && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
- KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
- if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
- PlaybackState state = mSession.mPlaybackState;
- long validActions = state == null ? 0 : state.getActions();
- switch (ke.getKeyCode()) {
- case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_HEADSETHOOK:
- if (ke.getRepeatCount() > 0) {
- // Consider long-press as a single tap.
- handleMediaPlayPauseKeySingleTapIfPending();
- } else if (mMediaPlayPauseKeyPending) {
- // Consider double tap as the next.
- mHandler.removeMessages(CallbackMessageHandler
- .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
- mMediaPlayPauseKeyPending = false;
- if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
- onSkipToNext();
- }
- } else {
- mMediaPlayPauseKeyPending = true;
- mSession.dispatchMediaButtonDelayed(
- mSession.getCurrentControllerInfo(),
- mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
- }
- return true;
- default:
- // If another key is pressed within double tap timeout, consider the
- // pending play/pause as a single tap to handle media keys in order.
- handleMediaPlayPauseKeySingleTapIfPending();
- break;
- }
-
- switch (ke.getKeyCode()) {
- case KeyEvent.KEYCODE_MEDIA_PLAY:
- if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
- onPlay();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
- if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
- onPause();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_NEXT:
- if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
- onSkipToNext();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
- onSkipToPrevious();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_STOP:
- if ((validActions & PlaybackState.ACTION_STOP) != 0) {
- onStop();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
- onFastForward();
- return true;
- }
- break;
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
- onRewind();
- return true;
- }
- break;
- }
- }
+ if (mMediaButtonEventDelegate != null) {
+ return mMediaButtonEventDelegate.onMediaButtonIntent(mediaButtonIntent);
}
return false;
}
- private void handleMediaPlayPauseKeySingleTapIfPending() {
- if (!mMediaPlayPauseKeyPending) {
- return;
- }
- mMediaPlayPauseKeyPending = false;
- mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
- PlaybackState state = mSession.mPlaybackState;
- long validActions = state == null ? 0 : state.getActions();
- boolean isPlaying = state != null
- && state.getState() == PlaybackState.STATE_PLAYING;
- boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
- | PlaybackState.ACTION_PLAY)) != 0;
- boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
- | PlaybackState.ACTION_PAUSE)) != 0;
- if (isPlaying && canPause) {
- onPause();
- } else if (!isPlaying && canPlay) {
- onPlay();
- }
- }
-
/**
* Override to handle requests to prepare playback. During the preparation, a session should
* not hold audio focus in order to allow other sessions play seamlessly. The state of
@@ -1042,251 +684,14 @@
*/
public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
}
- }
- /**
- * @hide
- */
- public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
- private WeakReference<MediaSession> mMediaSession;
-
- public CallbackStub(MediaSession session) {
- mMediaSession = new WeakReference<>(session);
- }
-
- private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- return new RemoteUserInfo(packageName, pid, uid,
- caller != null ? caller.getBinder() : null);
- }
-
- @Override
- public void onCommand(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
- command, args, cb);
- }
- }
-
- @Override
- public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
- int sequenceNumber, ResultReceiver cb) {
- MediaSession session = mMediaSession.get();
- try {
- if (session != null) {
- session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, null),
- mediaButtonIntent);
- }
- } finally {
- if (cb != null) {
- cb.send(sequenceNumber, null);
- }
- }
- }
-
- @Override
- public void onMediaButtonFromController(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Intent mediaButtonIntent) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
- mediaButtonIntent);
- }
- }
-
- @Override
- public void onPrepare(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPrepareFromMediaId(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String mediaId,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromMediaId(
- createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
- }
- }
-
- @Override
- public void onPrepareFromSearch(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String query,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromSearch(
- createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
- }
- }
-
- @Override
- public void onPrepareFromUri(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Uri uri, Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrepareFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
- uri, extras);
- }
- }
-
- @Override
- public void onPlay(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPlayFromMediaId(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String mediaId,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromMediaId(createRemoteUserInfo(packageName, pid, uid, caller),
- mediaId, extras);
- }
- }
-
- @Override
- public void onPlayFromSearch(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String query,
- Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromSearch(createRemoteUserInfo(packageName, pid, uid, caller),
- query, extras);
- }
- }
-
- @Override
- public void onPlayFromUri(String packageName, int pid, int uid,
- ControllerCallbackLink caller, Uri uri, Bundle extras) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPlayFromUri(createRemoteUserInfo(packageName, pid, uid, caller),
- uri, extras);
- }
- }
-
- @Override
- public void onSkipToTrack(String packageName, int pid, int uid,
- ControllerCallbackLink caller, long id) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSkipToItem(createRemoteUserInfo(packageName, pid, uid, caller), id);
- }
- }
-
- @Override
- public void onPause(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onStop(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onNext(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onPrevious(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onFastForward(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchFastForward(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onRewind(String packageName, int pid, int uid,
- ControllerCallbackLink caller) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
- }
- }
-
- @Override
- public void onSeekTo(String packageName, int pid, int uid,
- ControllerCallbackLink caller, long pos) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSeekTo(createRemoteUserInfo(packageName, pid, uid, caller), pos);
- }
- }
-
- @Override
- public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
- Rating rating) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchRate(createRemoteUserInfo(packageName, pid, uid, caller), rating);
- }
- }
-
- @Override
- public void onCustomAction(String packageName, int pid, int uid,
- ControllerCallbackLink caller, String action, Bundle args) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchCustomAction(createRemoteUserInfo(packageName, pid, uid, caller),
- action, args);
- }
- }
-
- @Override
- public void onAdjustVolume(String packageName, int pid, int uid,
- ControllerCallbackLink caller, int direction) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchAdjustVolume(createRemoteUserInfo(packageName, pid, uid, caller),
- direction);
- }
- }
-
- @Override
- public void onSetVolumeTo(String packageName, int pid, int uid,
- ControllerCallbackLink caller, int value) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.dispatchSetVolumeTo(createRemoteUserInfo(packageName, pid, uid, caller),
- value);
- }
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void onSetMediaButtonEventDelegate(
+ @NonNull MediaSessionEngine.MediaButtonEventDelegate delegate) {
+ mMediaButtonEventDelegate = delegate;
}
}
@@ -1300,7 +705,7 @@
*/
public static final int UNKNOWN_ID = -1;
- private final MediaDescription mDescription;
+ private final MediaSessionEngine.QueueItem mImpl;
@UnsupportedAppUsage
private final long mId;
@@ -1312,18 +717,12 @@
* play queue and cannot be {@link #UNKNOWN_ID}.
*/
public QueueItem(MediaDescription description, long id) {
- if (description == null) {
- throw new IllegalArgumentException("Description cannot be null.");
- }
- if (id == UNKNOWN_ID) {
- throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
- }
- mDescription = description;
+ mImpl = new MediaSessionEngine.QueueItem(description, id);
mId = id;
}
private QueueItem(Parcel in) {
- mDescription = MediaDescription.CREATOR.createFromParcel(in);
+ mImpl = new MediaSessionEngine.QueueItem(in);
mId = in.readLong();
}
@@ -1331,20 +730,19 @@
* Get the description for this item.
*/
public MediaDescription getDescription() {
- return mDescription;
+ return mImpl.getDescription();
}
/**
* Get the queue id for this item.
*/
public long getQueueId() {
- return mId;
+ return mImpl.getQueueId();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- mDescription.writeToParcel(dest, flags);
- dest.writeLong(mId);
+ mImpl.writeToParcel(dest, flags);
}
@Override
@@ -1368,9 +766,7 @@
@Override
public String toString() {
- return "MediaSession.QueueItem {" +
- "Description=" + mDescription +
- ", Id=" + mId + " }";
+ return mImpl.toString();
}
@Override
@@ -1383,167 +779,7 @@
return false;
}
- final QueueItem item = (QueueItem) o;
- if (mId != item.mId) {
- return false;
- }
-
- if (!Objects.equals(mDescription, item.mDescription)) {
- return false;
- }
-
- return true;
- }
- }
-
- private static final class Command {
- public final String command;
- public final Bundle extras;
- public final ResultReceiver stub;
-
- public Command(String command, Bundle extras, ResultReceiver stub) {
- this.command = command;
- this.extras = extras;
- this.stub = stub;
- }
- }
-
- private class CallbackMessageHandler extends Handler {
- private static final int MSG_COMMAND = 1;
- private static final int MSG_MEDIA_BUTTON = 2;
- private static final int MSG_PREPARE = 3;
- private static final int MSG_PREPARE_MEDIA_ID = 4;
- private static final int MSG_PREPARE_SEARCH = 5;
- private static final int MSG_PREPARE_URI = 6;
- private static final int MSG_PLAY = 7;
- private static final int MSG_PLAY_MEDIA_ID = 8;
- private static final int MSG_PLAY_SEARCH = 9;
- private static final int MSG_PLAY_URI = 10;
- private static final int MSG_SKIP_TO_ITEM = 11;
- private static final int MSG_PAUSE = 12;
- private static final int MSG_STOP = 13;
- private static final int MSG_NEXT = 14;
- private static final int MSG_PREVIOUS = 15;
- private static final int MSG_FAST_FORWARD = 16;
- private static final int MSG_REWIND = 17;
- private static final int MSG_SEEK_TO = 18;
- private static final int MSG_RATE = 19;
- private static final int MSG_CUSTOM_ACTION = 20;
- private static final int MSG_ADJUST_VOLUME = 21;
- private static final int MSG_SET_VOLUME = 22;
- private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
-
- private MediaSession.Callback mCallback;
- private RemoteUserInfo mCurrentControllerInfo;
-
- public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
- super(looper);
- mCallback = callback;
- mCallback.mHandler = this;
- }
-
- public void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
- Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
- Message msg = obtainMessage(what, objWithCaller);
- msg.setAsynchronous(true);
- msg.setData(data);
- if (delayMs > 0) {
- sendMessageDelayed(msg, delayMs);
- } else {
- sendMessage(msg);
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
-
- VolumeProvider vp;
- Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
-
- switch (msg.what) {
- case MSG_COMMAND:
- Command cmd = (Command) obj;
- mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
- break;
- case MSG_MEDIA_BUTTON:
- mCallback.onMediaButtonEvent((Intent) obj);
- break;
- case MSG_PREPARE:
- mCallback.onPrepare();
- break;
- case MSG_PREPARE_MEDIA_ID:
- mCallback.onPrepareFromMediaId((String) obj, msg.getData());
- break;
- case MSG_PREPARE_SEARCH:
- mCallback.onPrepareFromSearch((String) obj, msg.getData());
- break;
- case MSG_PREPARE_URI:
- mCallback.onPrepareFromUri((Uri) obj, msg.getData());
- break;
- case MSG_PLAY:
- mCallback.onPlay();
- break;
- case MSG_PLAY_MEDIA_ID:
- mCallback.onPlayFromMediaId((String) obj, msg.getData());
- break;
- case MSG_PLAY_SEARCH:
- mCallback.onPlayFromSearch((String) obj, msg.getData());
- break;
- case MSG_PLAY_URI:
- mCallback.onPlayFromUri((Uri) obj, msg.getData());
- break;
- case MSG_SKIP_TO_ITEM:
- mCallback.onSkipToQueueItem((Long) obj);
- break;
- case MSG_PAUSE:
- mCallback.onPause();
- break;
- case MSG_STOP:
- mCallback.onStop();
- break;
- case MSG_NEXT:
- mCallback.onSkipToNext();
- break;
- case MSG_PREVIOUS:
- mCallback.onSkipToPrevious();
- break;
- case MSG_FAST_FORWARD:
- mCallback.onFastForward();
- break;
- case MSG_REWIND:
- mCallback.onRewind();
- break;
- case MSG_SEEK_TO:
- mCallback.onSeekTo((Long) obj);
- break;
- case MSG_RATE:
- mCallback.onSetRating((Rating) obj);
- break;
- case MSG_CUSTOM_ACTION:
- mCallback.onCustomAction((String) obj, msg.getData());
- break;
- case MSG_ADJUST_VOLUME:
- synchronized (mLock) {
- vp = mVolumeProvider;
- }
- if (vp != null) {
- vp.onAdjustVolume((int) obj);
- }
- break;
- case MSG_SET_VOLUME:
- synchronized (mLock) {
- vp = mVolumeProvider;
- }
- if (vp != null) {
- vp.onSetVolumeTo((int) obj);
- }
- break;
- case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
- mCallback.handleMediaPlayPauseKeySingleTapIfPending();
- break;
- }
- mCurrentControllerInfo = null;
+ return mImpl.equals(((QueueItem) o).mImpl);
}
}
}
diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/java/android/media/session/MediaSessionEngine.java
new file mode 100644
index 0000000..c4634a9
--- /dev/null
+++ b/media/java/android/media/session/MediaSessionEngine.java
@@ -0,0 +1,1481 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.VolumeProvider;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class MediaSessionEngine implements AutoCloseable {
+ private static final String TAG = MediaSession.TAG;
+
+ private final Object mLock = new Object();
+ private final int mMaxBitmapSize;
+
+ private final MediaSession.Token mSessionToken;
+ private final MediaController mController;
+ private final SessionLink mSessionLink;
+ private final SessionCallbackLink mCbLink;
+
+ // Do not change the name of mCallbackWrapper. Support lib accesses this by using reflection.
+ @UnsupportedAppUsage
+ private CallbackMessageHandler mCallbackHandler;
+ private VolumeProvider mVolumeProvider;
+ private PlaybackState mPlaybackState;
+
+ private boolean mActive = false;
+
+ /**
+ * Creates a new session. The session will automatically be registered with
+ * the system but will not be published until {@link #setActive(boolean)
+ * setActive(true)} is called. You must call {@link #close()} when
+ * finished with the session.
+ *
+ * @param context The context to use to create the session.
+ * @param sessionLink A session link for the binder of MediaSessionRecord
+ * @param cbStub A callback link that handles incoming command to {@link MediaSession.Callback}.
+ */
+ public MediaSessionEngine(@NonNull Context context, @NonNull SessionLink sessionLink,
+ @NonNull SessionCallbackLink cbLink, @NonNull CallbackStub cbStub, int maxBitmapSize) {
+ mSessionLink = sessionLink;
+ mCbLink = cbLink;
+ mMaxBitmapSize = maxBitmapSize;
+
+ cbStub.setSessionImpl(this);
+ mSessionToken = new MediaSession.Token(mSessionLink.getController());
+ mController = new MediaController(context, mSessionToken);
+ }
+
+ /**
+ * Set the callback to receive updates for the MediaSession. This includes
+ * media button events and transport controls. The caller's thread will be
+ * used to post updates.
+ * <p>
+ * Set the callback to null to stop receiving updates.
+ *
+ * @param callback The callback object
+ */
+ public void setCallback(@Nullable MediaSession.Callback callback) {
+ setCallback(callback, new Handler());
+ }
+
+ /**
+ * Set the callback to receive updates for the MediaSession. This includes
+ * media button events and transport controls.
+ * <p>
+ * Set the callback to null to stop receiving updates.
+ *
+ * @param callback The callback to receive updates on.
+ * @param handler The handler that events should be posted on.
+ */
+ public void setCallback(@Nullable MediaSession.Callback callback, @NonNull Handler handler) {
+ setCallbackInternal(callback == null ? null : new CallbackWrapper(callback), handler);
+ }
+
+ private void setCallbackInternal(CallbackWrapper callback, Handler handler) {
+ synchronized (mLock) {
+ if (mCallbackHandler != null) {
+ // We're updating the callback, clear the session from the old one.
+ mCallbackHandler.mCallbackWrapper.mSessionImpl = null;
+ mCallbackHandler.removeCallbacksAndMessages(null);
+ }
+ if (callback == null) {
+ mCallbackHandler = null;
+ return;
+ }
+ callback.mSessionImpl = this;
+ CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
+ callback);
+ mCallbackHandler = msgHandler;
+ }
+ }
+
+ /**
+ * Set an intent for launching UI for this Session. This can be used as a
+ * quick link to an ongoing media screen. The intent should be for an
+ * activity that may be started using {@link Activity#startActivity(Intent)}.
+ *
+ * @param pi The intent to launch to show UI for this Session.
+ */
+ public void setSessionActivity(@Nullable PendingIntent pi) {
+ try {
+ mSessionLink.setLaunchPendingIntent(pi);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
+ }
+ }
+
+ /**
+ * Set a pending intent for your media button receiver to allow restarting
+ * playback after the session has been stopped. If your app is started in
+ * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
+ * the pending intent.
+ *
+ * @param mbr The {@link PendingIntent} to send the media button event to.
+ */
+ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
+ try {
+ mSessionLink.setMediaButtonReceiver(mbr);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+ }
+ }
+
+ /**
+ * Set any flags for the session.
+ *
+ * @param flags The flags to set for this session.
+ */
+ public void setFlags(@MediaSession.SessionFlags int flags) {
+ try {
+ mSessionLink.setFlags(flags);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setFlags.", e);
+ }
+ }
+
+ /**
+ * Set the attributes for this session's audio. This will affect the
+ * system's volume handling for this session. If
+ * {@link #setPlaybackToRemote} was previously called it will stop receiving
+ * volume commands and the system will begin sending volume changes to the
+ * appropriate stream.
+ * <p>
+ * By default sessions use attributes for media.
+ *
+ * @param attributes The {@link AudioAttributes} for this session's audio.
+ */
+ public void setPlaybackToLocal(AudioAttributes attributes) {
+ if (attributes == null) {
+ throw new IllegalArgumentException("Attributes cannot be null for local playback.");
+ }
+ try {
+ mSessionLink.setPlaybackToLocal(attributes);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
+ }
+ }
+
+ /**
+ * Configure this session to use remote volume handling. This must be called
+ * to receive volume button events, otherwise the system will adjust the
+ * appropriate stream volume for this session. If
+ * {@link #setPlaybackToLocal} was previously called the system will stop
+ * handling volume changes for this session and pass them to the volume
+ * provider instead.
+ *
+ * @param volumeProvider The provider that will handle volume changes. May
+ * not be null.
+ */
+ public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
+ if (volumeProvider == null) {
+ throw new IllegalArgumentException("volumeProvider may not be null!");
+ }
+ synchronized (mLock) {
+ mVolumeProvider = volumeProvider;
+ }
+ volumeProvider.setCallback(new VolumeProvider.Callback() {
+ @Override
+ public void onVolumeChanged(VolumeProvider volumeProvider) {
+ notifyRemoteVolumeChanged(volumeProvider);
+ }
+ });
+
+ try {
+ mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
+ volumeProvider.getMaxVolume());
+ mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
+ }
+ }
+
+ /**
+ * Set if this session is currently active and ready to receive commands. If
+ * set to false your session's controller may not be discoverable. You must
+ * set the session to active before it can start receiving media button
+ * events or transport commands.
+ *
+ * @param active Whether this session is active or not.
+ */
+ public void setActive(boolean active) {
+ if (mActive == active) {
+ return;
+ }
+ try {
+ mSessionLink.setActive(active);
+ mActive = active;
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Failure in setActive.", e);
+ }
+ }
+
+ /**
+ * Get the current active state of this session.
+ *
+ * @return True if the session is active, false otherwise.
+ */
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * Send a proprietary event to all MediaControllers listening to this
+ * Session. It's up to the Controller/Session owner to determine the meaning
+ * of any events.
+ *
+ * @param event The name of the event to send
+ * @param extras Any extras included with the event
+ */
+ public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
+ if (TextUtils.isEmpty(event)) {
+ throw new IllegalArgumentException("event cannot be null or empty");
+ }
+ try {
+ mSessionLink.sendEvent(event, extras);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Error sending event", e);
+ }
+ }
+
+ /**
+ * This must be called when an app has finished performing playback. If
+ * playback is expected to start again shortly the session can be left open,
+ * but it must be released if your activity or service is being destroyed.
+ */
+ public void close() {
+ try {
+ mSessionLink.destroySession();
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Error releasing session: ", e);
+ }
+ }
+
+ /**
+ * Retrieve a token object that can be used by apps to create a
+ * {@link MediaController} for interacting with this session. The owner of
+ * the session is responsible for deciding how to distribute these tokens.
+ *
+ * @return A token that can be used to create a MediaController for this
+ * session
+ */
+ public @NonNull MediaSession.Token getSessionToken() {
+ return mSessionToken;
+ }
+
+ /**
+ * Get a controller for this session. This is a convenience method to avoid
+ * having to cache your own controller in process.
+ *
+ * @return A controller for this session.
+ */
+ public @NonNull MediaController getController() {
+ return mController;
+ }
+
+ /**
+ * Update the current playback state.
+ *
+ * @param state The current state of playback
+ */
+ public void setPlaybackState(@Nullable PlaybackState state) {
+ mPlaybackState = state;
+ try {
+ mSessionLink.setPlaybackState(state);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+ }
+ }
+
+ /**
+ * Update the current metadata. New metadata can be created using
+ * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to
+ * the size of the bitmap to replace large bitmaps with a scaled down copy.
+ *
+ * @param metadata The new metadata
+ * @see android.media.MediaMetadata.Builder#putBitmap
+ */
+ public void setMetadata(@Nullable MediaMetadata metadata) {
+ long duration = -1;
+ int fields = 0;
+ MediaDescription description = null;
+ if (metadata != null) {
+ metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+ duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ }
+ fields = metadata.size();
+ description = metadata.getDescription();
+ }
+ String metadataDescription = "size=" + fields + ", description=" + description;
+
+ try {
+ mSessionLink.setMetadata(metadata, duration, metadataDescription);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Dead object in setPlaybackState.", e);
+ }
+ }
+
+ /**
+ * Update the list of items in the play queue. It is an ordered list and
+ * should contain the current item, and previous or upcoming items if they
+ * exist. Specify null if there is no current play queue.
+ * <p>
+ * The queue should be of reasonable size. If the play queue is unbounded
+ * within your app, it is better to send a reasonable amount in a sliding
+ * window instead.
+ *
+ * @param queue A list of items in the play queue.
+ */
+ public void setQueue(@Nullable List<MediaSession.QueueItem> queue) {
+ try {
+ mSessionLink.setQueue(queue);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setQueue.", e);
+ }
+ }
+
+ /**
+ * Set the title of the play queue. The UI should display this title along
+ * with the play queue itself.
+ * e.g. "Play Queue", "Now Playing", or an album name.
+ *
+ * @param title The title of the play queue.
+ */
+ public void setQueueTitle(@Nullable CharSequence title) {
+ try {
+ mSessionLink.setQueueTitle(title);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setQueueTitle.", e);
+ }
+ }
+
+ /**
+ * Set the style of rating used by this session. Apps trying to set the
+ * rating should use this style. Must be one of the following:
+ * <ul>
+ * <li>{@link Rating#RATING_NONE}</li>
+ * <li>{@link Rating#RATING_3_STARS}</li>
+ * <li>{@link Rating#RATING_4_STARS}</li>
+ * <li>{@link Rating#RATING_5_STARS}</li>
+ * <li>{@link Rating#RATING_HEART}</li>
+ * <li>{@link Rating#RATING_PERCENTAGE}</li>
+ * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
+ * </ul>
+ */
+ public void setRatingType(@Rating.Style int type) {
+ try {
+ mSessionLink.setRatingType(type);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in setRatingType.", e);
+ }
+ }
+
+ /**
+ * Set some extras that can be associated with the {@link MediaSession}. No assumptions should
+ * be made as to how a {@link MediaController} will handle these extras.
+ * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
+ *
+ * @param extras The extras associated with the {@link MediaSession}.
+ */
+ public void setExtras(@Nullable Bundle extras) {
+ try {
+ mSessionLink.setExtras(extras);
+ } catch (RuntimeException e) {
+ Log.wtf("Dead object in setExtras.", e);
+ }
+ }
+
+ /**
+ * Gets the controller information who sent the current request.
+ * <p>
+ * Note: This is only valid while in a request callback, such as
+ * {@link MediaSession.Callback#onPlay}.
+ *
+ * @throws IllegalStateException If this method is called outside of
+ * {@link MediaSession.Callback} methods.
+ * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
+ */
+ public @NonNull RemoteUserInfo getCurrentControllerInfo() {
+ if (mCallbackHandler == null || mCallbackHandler.mCurrentControllerInfo == null) {
+ throw new IllegalStateException(
+ "This should be called inside of MediaSession.Callback methods");
+ }
+ return mCallbackHandler.mCurrentControllerInfo;
+ }
+
+ /**
+ * Notify the system that the remote volume changed.
+ *
+ * @param provider The provider that is handling volume changes.
+ * @hide
+ */
+ public void notifyRemoteVolumeChanged(VolumeProvider provider) {
+ synchronized (mLock) {
+ if (provider == null || provider != mVolumeProvider) {
+ Log.w(TAG, "Received update from stale volume provider");
+ return;
+ }
+ }
+ try {
+ mSessionLink.setCurrentVolume(provider.getCurrentVolume());
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error in notifyVolumeChanged", e);
+ }
+ }
+
+ /**
+ * Returns the name of the package that sent the last media button, transport control, or
+ * command from controllers and the system. This is only valid while in a request callback, such
+ * as {@link MediaSession.Callback#onPlay}.
+ */
+ public String getCallingPackage() {
+ if (mCallbackHandler != null && mCallbackHandler.mCurrentControllerInfo != null) {
+ return mCallbackHandler.mCurrentControllerInfo.getPackageName();
+ }
+ return null;
+ }
+
+ private void dispatchPrepare(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
+ }
+
+ private void dispatchPrepareFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
+ }
+
+ private void dispatchPrepareFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
+ }
+
+ private void dispatchPrepareFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
+ }
+
+ private void dispatchPlay(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY, null, null);
+ }
+
+ private void dispatchPlayFromMediaId(RemoteUserInfo caller, String mediaId, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
+ }
+
+ private void dispatchPlayFromSearch(RemoteUserInfo caller, String query, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
+ }
+
+ private void dispatchPlayFromUri(RemoteUserInfo caller, Uri uri, Bundle extras) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
+ }
+
+ private void dispatchSkipToItem(RemoteUserInfo caller, long id) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SKIP_TO_ITEM, id, null);
+ }
+
+ private void dispatchPause(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PAUSE, null, null);
+ }
+
+ private void dispatchStop(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_STOP, null, null);
+ }
+
+ private void dispatchNext(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_NEXT, null, null);
+ }
+
+ private void dispatchPrevious(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_PREVIOUS, null, null);
+ }
+
+ private void dispatchFastForward(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_FAST_FORWARD, null, null);
+ }
+
+ private void dispatchRewind(RemoteUserInfo caller) {
+ postToCallback(caller, CallbackMessageHandler.MSG_REWIND, null, null);
+ }
+
+ private void dispatchSeekTo(RemoteUserInfo caller, long pos) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SEEK_TO, pos, null);
+ }
+
+ private void dispatchRate(RemoteUserInfo caller, Rating rating) {
+ postToCallback(caller, CallbackMessageHandler.MSG_RATE, rating, null);
+ }
+
+ private void dispatchCustomAction(RemoteUserInfo caller, String action, Bundle args) {
+ postToCallback(caller, CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
+ }
+
+ private void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
+ postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
+ }
+
+ private void dispatchMediaButtonDelayed(RemoteUserInfo info, Intent mediaButtonIntent,
+ long delay) {
+ postToCallbackDelayed(info, CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
+ mediaButtonIntent, null, delay);
+ }
+
+ private void dispatchAdjustVolume(RemoteUserInfo caller, int direction) {
+ postToCallback(caller, CallbackMessageHandler.MSG_ADJUST_VOLUME, direction, null);
+ }
+
+ private void dispatchSetVolumeTo(RemoteUserInfo caller, int volume) {
+ postToCallback(caller, CallbackMessageHandler.MSG_SET_VOLUME, volume, null);
+ }
+
+ private void dispatchCommand(RemoteUserInfo caller, String command, Bundle args,
+ ResultReceiver resultCb) {
+ Command cmd = new Command(command, args, resultCb);
+ postToCallback(caller, CallbackMessageHandler.MSG_COMMAND, cmd, null);
+ }
+
+ private void postToCallback(RemoteUserInfo caller, int what, Object obj, Bundle data) {
+ postToCallbackDelayed(caller, what, obj, data, 0);
+ }
+
+ private void postToCallbackDelayed(RemoteUserInfo caller, int what, Object obj, Bundle data,
+ long delay) {
+ synchronized (mLock) {
+ if (mCallbackHandler != null) {
+ mCallbackHandler.post(caller, what, obj, data, delay);
+ }
+ }
+ }
+
+ /**
+ * Return true if this is considered an active playback state.
+ */
+ public static boolean isActiveState(int state) {
+ switch (state) {
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_PLAYING:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Interface for handling MediaButtoneEvent
+ */
+ public interface MediaButtonEventDelegate {
+ /**
+ * Called when a media button is pressed and this session has the
+ * highest priority or a controller sends a media button event to the
+ * session.
+ *
+ * @param mediaButtonIntent an intent containing the KeyEvent as an extra
+ * @return True if the event was handled, false otherwise.
+ */
+ boolean onMediaButtonIntent(Intent mediaButtonIntent);
+ }
+
+ /**
+ * Receives media buttons, transport controls, and commands from controllers
+ * and the system. A callback may be set using {@link #setCallback}.
+ * @hide
+ */
+ public static class CallbackWrapper implements MediaButtonEventDelegate {
+
+ private final MediaSession.Callback mCallback;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ MediaSessionEngine mSessionImpl;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ CallbackMessageHandler mHandler;
+ private boolean mMediaPlayPauseKeyPending;
+
+ public CallbackWrapper(MediaSession.Callback callback) {
+ mCallback = callback;
+ if (mCallback != null) {
+ mCallback.onSetMediaButtonEventDelegate(this);
+ }
+ }
+
+ /**
+ * Called when a controller has sent a command to this session.
+ * The owner of the session may handle custom commands but is not
+ * required to.
+ *
+ * @param command The command name.
+ * @param args Optional parameters for the command, may be null.
+ * @param cb A result receiver to which a result may be sent by the command, may be null.
+ */
+ public void onCommand(@NonNull String command, @Nullable Bundle args,
+ @Nullable ResultReceiver cb) {
+ if (mCallback != null) {
+ mCallback.onCommand(command, args, cb);
+ }
+ }
+
+ /**
+ * Called when a media button is pressed and this session has the
+ * highest priority or a controller sends a media button event to the
+ * session. The default behavior will call the relevant method if the
+ * action for it was set.
+ * <p>
+ * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
+ * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
+ *
+ * @param mediaButtonIntent an intent containing the KeyEvent as an
+ * extra
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
+ return mCallback == null ? false : mCallback.onMediaButtonEvent(mediaButtonIntent);
+ }
+
+ private void handleMediaPlayPauseKeySingleTapIfPending() {
+ if (!mMediaPlayPauseKeyPending) {
+ return;
+ }
+ mMediaPlayPauseKeyPending = false;
+ mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+ PlaybackState state = mSessionImpl.mPlaybackState;
+ long validActions = state == null ? 0 : state.getActions();
+ boolean isPlaying = state != null
+ && state.getState() == PlaybackState.STATE_PLAYING;
+ boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_PLAY)) != 0;
+ boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
+ | PlaybackState.ACTION_PAUSE)) != 0;
+ if (isPlaying && canPause) {
+ onPause();
+ } else if (!isPlaying && canPlay) {
+ onPlay();
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare playback. During the preparation, a session should
+ * not hold audio focus in order to allow other sessions play seamlessly. The state of
+ * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is
+ * done.
+ */
+ public void onPrepare() {
+ if (mCallback != null) {
+ mCallback.onPrepare();
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare for playing a specific mediaId that was provided
+ * by your app's {@link MediaBrowserService}. During the preparation, a session should not
+ * hold audio focus in order to allow other sessions play seamlessly. The state of playback
+ * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * The playback of the prepared content should start in the implementation of
+ * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting
+ * playback without preparation.
+ */
+ public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromMediaId(mediaId, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare playback from a search query. An empty query
+ * indicates that the app may prepare any music. The implementation should attempt to make a
+ * smart choice about what to play. During the preparation, a session should not hold audio
+ * focus in order to allow other sessions play seamlessly. The state of playback should be
+ * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback
+ * of the prepared content should start in the implementation of {@link #onPlay}. Override
+ * {@link #onPlayFromSearch} to handle requests for starting playback without preparation.
+ */
+ public void onPrepareFromSearch(String query, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromSearch(query, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to prepare a specific media item represented by a URI.
+ * During the preparation, a session should not hold audio focus in order to allow
+ * other sessions play seamlessly. The state of playback should be updated to
+ * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
+ * The playback of the prepared content should start in the implementation of
+ * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests
+ * for starting playback without preparation.
+ */
+ public void onPrepareFromUri(Uri uri, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPrepareFromUri(uri, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to begin playback.
+ */
+ public void onPlay() {
+ if (mCallback != null) {
+ mCallback.onPlay();
+ }
+ }
+
+ /**
+ * Override to handle requests to begin playback from a search query. An
+ * empty query indicates that the app may play any music. The
+ * implementation should attempt to make a smart choice about what to
+ * play.
+ */
+ public void onPlayFromSearch(String query, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromSearch(query, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play a specific mediaId that was
+ * provided by your app's {@link MediaBrowserService}.
+ */
+ public void onPlayFromMediaId(String mediaId, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromMediaId(mediaId, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play a specific media item represented by a URI.
+ */
+ public void onPlayFromUri(Uri uri, Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onPlayFromUri(uri, extras);
+ }
+ }
+
+ /**
+ * Override to handle requests to play an item with a given id from the
+ * play queue.
+ */
+ public void onSkipToQueueItem(long id) {
+ if (mCallback != null) {
+ mCallback.onSkipToQueueItem(id);
+ }
+ }
+
+ /**
+ * Override to handle requests to pause playback.
+ */
+ public void onPause() {
+ if (mCallback != null) {
+ mCallback.onPause();
+ }
+ }
+
+ /**
+ * Override to handle requests to skip to the next media item.
+ */
+ public void onSkipToNext() {
+ if (mCallback != null) {
+ mCallback.onSkipToNext();
+ }
+ }
+
+ /**
+ * Override to handle requests to skip to the previous media item.
+ */
+ public void onSkipToPrevious() {
+ if (mCallback != null) {
+ mCallback.onSkipToPrevious();
+ }
+ }
+
+ /**
+ * Override to handle requests to fast forward.
+ */
+ public void onFastForward() {
+ if (mCallback != null) {
+ mCallback.onFastForward();
+ }
+ }
+
+ /**
+ * Override to handle requests to rewind.
+ */
+ public void onRewind() {
+ if (mCallback != null) {
+ mCallback.onRewind();
+ }
+ }
+
+ /**
+ * Override to handle requests to stop playback.
+ */
+ public void onStop() {
+ if (mCallback != null) {
+ mCallback.onStop();
+ }
+ }
+
+ /**
+ * Override to handle requests to seek to a specific position in ms.
+ *
+ * @param pos New position to move to, in milliseconds.
+ */
+ public void onSeekTo(long pos) {
+ if (mCallback != null) {
+ mCallback.onSeekTo(pos);
+ }
+ }
+
+ /**
+ * Override to handle the item being rated.
+ *
+ * @param rating
+ */
+ public void onSetRating(@NonNull Rating rating) {
+ if (mCallback != null) {
+ mCallback.onSetRating(rating);
+ }
+ }
+
+ /**
+ * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be
+ * performed.
+ *
+ * @param action The action that was originally sent in the
+ * {@link PlaybackState.CustomAction}.
+ * @param extras Optional extras specified by the {@link MediaController}.
+ */
+ public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
+ if (mCallback != null) {
+ mCallback.onCustomAction(action, extras);
+ }
+ }
+
+ @Override
+ public boolean onMediaButtonIntent(Intent mediaButtonIntent) {
+ if (mSessionImpl != null && mHandler != null
+ && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
+ KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
+ PlaybackState state = mSessionImpl.mPlaybackState;
+ long validActions = state == null ? 0 : state.getActions();
+ switch (ke.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ if (ke.getRepeatCount() > 0) {
+ // Consider long-press as a single tap.
+ handleMediaPlayPauseKeySingleTapIfPending();
+ } else if (mMediaPlayPauseKeyPending) {
+ // Consider double tap as the next.
+ mHandler.removeMessages(CallbackMessageHandler
+ .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+ mMediaPlayPauseKeyPending = false;
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+ onSkipToNext();
+ }
+ } else {
+ mMediaPlayPauseKeyPending = true;
+ mSessionImpl.dispatchMediaButtonDelayed(
+ mSessionImpl.getCurrentControllerInfo(),
+ mediaButtonIntent, ViewConfiguration.getDoubleTapTimeout());
+ }
+ return true;
+ default:
+ // If another key is pressed within double tap timeout, consider the
+ // pending play/pause as a single tap to handle media keys in order.
+ handleMediaPlayPauseKeySingleTapIfPending();
+ break;
+ }
+
+ switch (ke.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
+ onPlay();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
+ onPause();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
+ onSkipToNext();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
+ onSkipToPrevious();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ if ((validActions & PlaybackState.ACTION_STOP) != 0) {
+ onStop();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ onFastForward();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
+ onRewind();
+ return true;
+ }
+ break;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final class CallbackStub extends SessionCallbackLink.CallbackStub {
+ private WeakReference<MediaSessionEngine> mSessionImpl;
+
+ private static RemoteUserInfo createRemoteUserInfo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ return new RemoteUserInfo(packageName, pid, uid,
+ caller != null ? caller.getBinder() : null);
+ }
+
+ public CallbackStub() {
+ }
+
+ @Override
+ public void onCommand(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchCommand(createRemoteUserInfo(packageName, pid, uid, caller),
+ command, args, cb);
+ }
+ }
+
+ @Override
+ public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
+ int sequenceNumber, ResultReceiver cb) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ try {
+ if (sessionImpl != null) {
+ sessionImpl.dispatchMediaButton(
+ createRemoteUserInfo(packageName, pid, uid, null), mediaButtonIntent);
+ }
+ } finally {
+ if (cb != null) {
+ cb.send(sequenceNumber, null);
+ }
+ }
+ }
+
+ @Override
+ public void onMediaButtonFromController(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Intent mediaButtonIntent) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid, caller),
+ mediaButtonIntent);
+ }
+ }
+
+ @Override
+ public void onPrepare(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepare(createRemoteUserInfo(packageName, pid, uid, caller));
+ }
+ }
+
+ @Override
+ public void onPrepareFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromMediaId(
+ createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
+ }
+ }
+
+ @Override
+ public void onPrepareFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromSearch(
+ createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
+ }
+ }
+
+ @Override
+ public void onPrepareFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrepareFromUri(
+ createRemoteUserInfo(packageName, pid, uid, caller), uri, extras);
+ }
+ }
+
+ @Override
+ public void onPlay(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlay(createRemoteUserInfo(packageName, pid, uid, caller));
+ }
+ }
+
+ @Override
+ public void onPlayFromMediaId(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String mediaId,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromMediaId(
+ createRemoteUserInfo(packageName, pid, uid, caller), mediaId, extras);
+ }
+ }
+
+ @Override
+ public void onPlayFromSearch(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String query,
+ Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromSearch(
+ createRemoteUserInfo(packageName, pid, uid, caller), query, extras);
+ }
+ }
+
+ @Override
+ public void onPlayFromUri(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, Uri uri, Bundle extras) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPlayFromUri(
+ createRemoteUserInfo(packageName, pid, uid, caller), uri, extras);
+ }
+ }
+
+ @Override
+ public void onSkipToTrack(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long id) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSkipToItem(
+ createRemoteUserInfo(packageName, pid, uid, caller), id);
+ }
+ }
+
+ @Override
+ public void onPause(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPause(createRemoteUserInfo(packageName, pid, uid, caller));
+ }
+ }
+
+ @Override
+ public void onStop(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchStop(createRemoteUserInfo(packageName, pid, uid, caller));
+ }
+ }
+
+ @Override
+ public void onNext(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchNext(createRemoteUserInfo(packageName, pid, uid, caller));
+ }
+ }
+
+ @Override
+ public void onPrevious(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchPrevious(createRemoteUserInfo(packageName, pid, uid, caller));
+ }
+ }
+
+ @Override
+ public void onFastForward(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchFastForward(
+ createRemoteUserInfo(packageName, pid, uid, caller));
+ }
+ }
+
+ @Override
+ public void onRewind(String packageName, int pid, int uid,
+ ControllerCallbackLink caller) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchRewind(createRemoteUserInfo(packageName, pid, uid, caller));
+ }
+ }
+
+ @Override
+ public void onSeekTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, long pos) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSeekTo(
+ createRemoteUserInfo(packageName, pid, uid, caller), pos);
+ }
+ }
+
+ @Override
+ public void onRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
+ Rating rating) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchRate(
+ createRemoteUserInfo(packageName, pid, uid, caller), rating);
+ }
+ }
+
+ @Override
+ public void onCustomAction(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, String action, Bundle args) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchCustomAction(
+ createRemoteUserInfo(packageName, pid, uid, caller), action, args);
+ }
+ }
+
+ @Override
+ public void onAdjustVolume(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int direction) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchAdjustVolume(
+ createRemoteUserInfo(packageName, pid, uid, caller), direction);
+ }
+ }
+
+ @Override
+ public void onSetVolumeTo(String packageName, int pid, int uid,
+ ControllerCallbackLink caller, int value) {
+ MediaSessionEngine sessionImpl = mSessionImpl.get();
+ if (sessionImpl != null) {
+ sessionImpl.dispatchSetVolumeTo(
+ createRemoteUserInfo(packageName, pid, uid, caller), value);
+ }
+ }
+
+ void setSessionImpl(MediaSessionEngine sessionImpl) {
+ mSessionImpl = new WeakReference<>(sessionImpl);
+ }
+ }
+
+ /**
+ * A single item that is part of the play queue. It contains a description
+ * of the item and its id in the queue.
+ */
+ public static final class QueueItem {
+ /**
+ * This id is reserved. No items can be explicitly assigned this id.
+ */
+ public static final int UNKNOWN_ID = -1;
+
+ private final MediaDescription mDescription;
+ private final long mId;
+
+ /**
+ * Create a new {@link MediaSession.QueueItem}.
+ *
+ * @param description The {@link MediaDescription} for this item.
+ * @param id An identifier for this item. It must be unique within the
+ * play queue and cannot be {@link #UNKNOWN_ID}.
+ */
+ public QueueItem(MediaDescription description, long id) {
+ if (description == null) {
+ throw new IllegalArgumentException("Description cannot be null.");
+ }
+ if (id == UNKNOWN_ID) {
+ throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
+ }
+ mDescription = description;
+ mId = id;
+ }
+
+ public QueueItem(Parcel in) {
+ mDescription = MediaDescription.CREATOR.createFromParcel(in);
+ mId = in.readLong();
+ }
+
+ /**
+ * Get the description for this item.
+ */
+ public MediaDescription getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Get the queue id for this item.
+ */
+ public long getQueueId() {
+ return mId;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ mDescription.writeToParcel(dest, flags);
+ dest.writeLong(mId);
+ }
+
+ @Override
+ public String toString() {
+ return "MediaSession.QueueItem {" + "Description=" + mDescription + ", Id=" + mId
+ + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (!(o instanceof QueueItem)) {
+ return false;
+ }
+
+ final QueueItem item = (QueueItem) o;
+ if (mId != item.mId) {
+ return false;
+ }
+
+ if (!Objects.equals(mDescription, item.mDescription)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private static final class Command {
+ public final String command;
+ public final Bundle extras;
+ public final ResultReceiver stub;
+
+ Command(String command, Bundle extras, ResultReceiver stub) {
+ this.command = command;
+ this.extras = extras;
+ this.stub = stub;
+ }
+ }
+
+ private class CallbackMessageHandler extends Handler {
+ private static final int MSG_COMMAND = 1;
+ private static final int MSG_MEDIA_BUTTON = 2;
+ private static final int MSG_PREPARE = 3;
+ private static final int MSG_PREPARE_MEDIA_ID = 4;
+ private static final int MSG_PREPARE_SEARCH = 5;
+ private static final int MSG_PREPARE_URI = 6;
+ private static final int MSG_PLAY = 7;
+ private static final int MSG_PLAY_MEDIA_ID = 8;
+ private static final int MSG_PLAY_SEARCH = 9;
+ private static final int MSG_PLAY_URI = 10;
+ private static final int MSG_SKIP_TO_ITEM = 11;
+ private static final int MSG_PAUSE = 12;
+ private static final int MSG_STOP = 13;
+ private static final int MSG_NEXT = 14;
+ private static final int MSG_PREVIOUS = 15;
+ private static final int MSG_FAST_FORWARD = 16;
+ private static final int MSG_REWIND = 17;
+ private static final int MSG_SEEK_TO = 18;
+ private static final int MSG_RATE = 19;
+ private static final int MSG_CUSTOM_ACTION = 20;
+ private static final int MSG_ADJUST_VOLUME = 21;
+ private static final int MSG_SET_VOLUME = 22;
+ private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ CallbackWrapper mCallbackWrapper;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ RemoteUserInfo mCurrentControllerInfo;
+
+ CallbackMessageHandler(Looper looper, CallbackWrapper callbackWrapper) {
+ super(looper);
+ mCallbackWrapper = callbackWrapper;
+ mCallbackWrapper.mHandler = this;
+ }
+
+ void post(RemoteUserInfo caller, int what, Object obj, Bundle data, long delayMs) {
+ Pair<RemoteUserInfo, Object> objWithCaller = Pair.create(caller, obj);
+ Message msg = obtainMessage(what, objWithCaller);
+ msg.setAsynchronous(true);
+ msg.setData(data);
+ if (delayMs > 0) {
+ sendMessageDelayed(msg, delayMs);
+ } else {
+ sendMessage(msg);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mCurrentControllerInfo = ((Pair<RemoteUserInfo, Object>) msg.obj).first;
+
+ VolumeProvider vp;
+ Object obj = ((Pair<RemoteUserInfo, Object>) msg.obj).second;
+
+ switch (msg.what) {
+ case MSG_COMMAND:
+ Command cmd = (Command) obj;
+ mCallbackWrapper.onCommand(cmd.command, cmd.extras, cmd.stub);
+ break;
+ case MSG_MEDIA_BUTTON:
+ mCallbackWrapper.onMediaButtonEvent((Intent) obj);
+ break;
+ case MSG_PREPARE:
+ mCallbackWrapper.onPrepare();
+ break;
+ case MSG_PREPARE_MEDIA_ID:
+ mCallbackWrapper.onPrepareFromMediaId((String) obj, msg.getData());
+ break;
+ case MSG_PREPARE_SEARCH:
+ mCallbackWrapper.onPrepareFromSearch((String) obj, msg.getData());
+ break;
+ case MSG_PREPARE_URI:
+ mCallbackWrapper.onPrepareFromUri((Uri) obj, msg.getData());
+ break;
+ case MSG_PLAY:
+ mCallbackWrapper.onPlay();
+ break;
+ case MSG_PLAY_MEDIA_ID:
+ mCallbackWrapper.onPlayFromMediaId((String) obj, msg.getData());
+ break;
+ case MSG_PLAY_SEARCH:
+ mCallbackWrapper.onPlayFromSearch((String) obj, msg.getData());
+ break;
+ case MSG_PLAY_URI:
+ mCallbackWrapper.onPlayFromUri((Uri) obj, msg.getData());
+ break;
+ case MSG_SKIP_TO_ITEM:
+ mCallbackWrapper.onSkipToQueueItem((Long) obj);
+ break;
+ case MSG_PAUSE:
+ mCallbackWrapper.onPause();
+ break;
+ case MSG_STOP:
+ mCallbackWrapper.onStop();
+ break;
+ case MSG_NEXT:
+ mCallbackWrapper.onSkipToNext();
+ break;
+ case MSG_PREVIOUS:
+ mCallbackWrapper.onSkipToPrevious();
+ break;
+ case MSG_FAST_FORWARD:
+ mCallbackWrapper.onFastForward();
+ break;
+ case MSG_REWIND:
+ mCallbackWrapper.onRewind();
+ break;
+ case MSG_SEEK_TO:
+ mCallbackWrapper.onSeekTo((Long) obj);
+ break;
+ case MSG_RATE:
+ mCallbackWrapper.onSetRating((Rating) obj);
+ break;
+ case MSG_CUSTOM_ACTION:
+ mCallbackWrapper.onCustomAction((String) obj, msg.getData());
+ break;
+ case MSG_ADJUST_VOLUME:
+ synchronized (mLock) {
+ vp = mVolumeProvider;
+ }
+ if (vp != null) {
+ vp.onAdjustVolume((int) obj);
+ }
+ break;
+ case MSG_SET_VOLUME:
+ synchronized (mLock) {
+ vp = mVolumeProvider;
+ }
+ if (vp != null) {
+ vp.onSetVolumeTo((int) obj);
+ }
+ break;
+ case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
+ mCallbackWrapper.handleMediaPlayPauseKeySingleTapIfPending();
+ break;
+ }
+ mCurrentControllerInfo = null;
+ }
+ }
+}
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/java/android/media/session/SessionCallbackLink.java
index 0265687b..0dbf427 100644
--- a/media/java/android/media/session/SessionCallbackLink.java
+++ b/media/java/android/media/session/SessionCallbackLink.java
@@ -669,7 +669,7 @@
@Override
public void notifyCommand(String packageName, int pid, int uid,
ControllerCallbackLink caller, String command, Bundle args, ResultReceiver cb) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onCommand(packageName, pid, uid, caller, command, args, cb);
@@ -681,7 +681,7 @@
@Override
public void notifyMediaButton(String packageName, int pid, int uid,
Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onMediaButton(packageName, pid, uid, mediaButtonIntent,
@@ -694,7 +694,7 @@
@Override
public void notifyMediaButtonFromController(String packageName, int pid, int uid,
ControllerCallbackLink caller, Intent mediaButtonIntent) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onMediaButtonFromController(packageName, pid, uid, caller,
@@ -707,7 +707,7 @@
@Override
public void notifyPrepare(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrepare(packageName, pid, uid, caller);
@@ -719,7 +719,7 @@
@Override
public void notifyPrepareFromMediaId(String packageName, int pid, int uid,
ControllerCallbackLink caller, String mediaId, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrepareFromMediaId(packageName, pid, uid, caller, mediaId, extras);
@@ -731,7 +731,7 @@
@Override
public void notifyPrepareFromSearch(String packageName, int pid, int uid,
ControllerCallbackLink caller, String query, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrepareFromSearch(packageName, pid, uid, caller, query, extras);
@@ -743,7 +743,7 @@
@Override
public void notifyPrepareFromUri(String packageName, int pid, int uid,
ControllerCallbackLink caller, Uri uri, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrepareFromUri(packageName, pid, uid, caller, uri, extras);
@@ -755,7 +755,7 @@
@Override
public void notifyPlay(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlay(packageName, pid, uid, caller);
@@ -767,7 +767,7 @@
@Override
public void notifyPlayFromMediaId(String packageName, int pid, int uid,
ControllerCallbackLink caller, String mediaId, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlayFromMediaId(packageName, pid, uid, caller, mediaId, extras);
@@ -779,7 +779,7 @@
@Override
public void notifyPlayFromSearch(String packageName, int pid, int uid,
ControllerCallbackLink caller, String query, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlayFromSearch(packageName, pid, uid, caller, query, extras);
@@ -791,7 +791,7 @@
@Override
public void notifyPlayFromUri(String packageName, int pid, int uid,
ControllerCallbackLink caller, Uri uri, Bundle extras) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPlayFromUri(packageName, pid, uid, caller, uri, extras);
@@ -803,7 +803,7 @@
@Override
public void notifySkipToTrack(String packageName, int pid, int uid,
ControllerCallbackLink caller, long id) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onSkipToTrack(packageName, pid, uid, caller, id);
@@ -815,7 +815,7 @@
@Override
public void notifyPause(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPause(packageName, pid, uid, caller);
@@ -827,7 +827,7 @@
@Override
public void notifyStop(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onStop(packageName, pid, uid, caller);
@@ -839,7 +839,7 @@
@Override
public void notifyNext(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onNext(packageName, pid, uid, caller);
@@ -851,7 +851,7 @@
@Override
public void notifyPrevious(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onPrevious(packageName, pid, uid, caller);
@@ -863,7 +863,7 @@
@Override
public void notifyFastForward(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onFastForward(packageName, pid, uid, caller);
@@ -875,7 +875,7 @@
@Override
public void notifyRewind(String packageName, int pid, int uid,
ControllerCallbackLink caller) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onRewind(packageName, pid, uid, caller);
@@ -887,7 +887,7 @@
@Override
public void notifySeekTo(String packageName, int pid, int uid,
ControllerCallbackLink caller, long pos) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onSeekTo(packageName, pid, uid, caller, pos);
@@ -899,7 +899,7 @@
@Override
public void notifyRate(String packageName, int pid, int uid, ControllerCallbackLink caller,
Rating rating) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onRate(packageName, pid, uid, caller, rating);
@@ -910,7 +910,7 @@
public void notifyCustomAction(String packageName, int pid, int uid,
ControllerCallbackLink caller, String action, Bundle args) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onCustomAction(packageName, pid, uid, caller, action, args);
@@ -922,7 +922,7 @@
@Override
public void notifyAdjustVolume(String packageName, int pid, int uid,
ControllerCallbackLink caller, int direction) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onAdjustVolume(packageName, pid, uid, caller, direction);
@@ -934,7 +934,7 @@
@Override
public void notifySetVolumeTo(String packageName, int pid, int uid,
ControllerCallbackLink caller, int value) {
- ensureMediasControlPermission();
+ ensureMediaControlPermission();
final long token = Binder.clearCallingIdentity();
try {
mCallbackStub.onSetVolumeTo(packageName, pid, uid, caller, value);
@@ -943,7 +943,7 @@
}
}
- private void ensureMediasControlPermission() {
+ private void ensureMediaControlPermission() {
// Allow API calls from the System UI
if (mContext.checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
== PackageManager.PERMISSION_GRANTED) {
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 5cb8fb8..30907a5 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -33,7 +33,10 @@
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiUtils;
+import android.hardware.hdmi.HdmiUtils.HdmiAddressRelativePosition;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -49,7 +52,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Retention;
@@ -145,6 +147,8 @@
// Attributes specific to HDMI
private final HdmiDeviceInfo mHdmiDeviceInfo;
private final boolean mIsConnectedToHdmiSwitch;
+ @HdmiAddressRelativePosition
+ private final int mHdmiConnectionRelativePosition;
private final String mParentId;
private final Bundle mExtras;
@@ -260,7 +264,9 @@
private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput,
CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected,
String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo,
- boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) {
+ boolean isConnectedToHdmiSwitch,
+ @HdmiAddressRelativePosition int hdmiConnectionRelativePosition, String parentId,
+ Bundle extras) {
mService = service;
mId = id;
mType = type;
@@ -275,6 +281,7 @@
mTunerCount = tunerCount;
mHdmiDeviceInfo = hdmiDeviceInfo;
mIsConnectedToHdmiSwitch = isConnectedToHdmiSwitch;
+ mHdmiConnectionRelativePosition = hdmiConnectionRelativePosition;
mParentId = parentId;
mExtras = extras;
}
@@ -419,6 +426,7 @@
/**
* Returns {@code true}, if a CEC device for this TV input is connected to an HDMI switch, i.e.,
* the device isn't directly connected to a HDMI port.
+ * TODO(b/110094868): add @Deprecated for Q
* @hide
*/
@SystemApi
@@ -427,6 +435,16 @@
}
/**
+ * Returns the relative position of this HDMI input.
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ @HdmiAddressRelativePosition
+ public int getHdmiConnectionRelativePosition() {
+ return mHdmiConnectionRelativePosition;
+ }
+
+ /**
* Checks if this TV input is marked hidden by the user in the settings.
*
* @param context Supplies a {@link Context} used to check if this TV input is hidden.
@@ -555,6 +573,7 @@
&& mTunerCount == obj.mTunerCount
&& Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo)
&& mIsConnectedToHdmiSwitch == obj.mIsConnectedToHdmiSwitch
+ && mHdmiConnectionRelativePosition == obj.mHdmiConnectionRelativePosition
&& TextUtils.equals(mParentId, obj.mParentId)
&& Objects.equals(mExtras, obj.mExtras);
}
@@ -589,6 +608,7 @@
dest.writeInt(mTunerCount);
dest.writeParcelable(mHdmiDeviceInfo, flags);
dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0);
+ dest.writeInt(mHdmiConnectionRelativePosition);
dest.writeString(mParentId);
dest.writeBundle(mExtras);
}
@@ -630,6 +650,7 @@
mTunerCount = in.readInt();
mHdmiDeviceInfo = in.readParcelable(null);
mIsConnectedToHdmiSwitch = in.readByte() == 1;
+ mHdmiConnectionRelativePosition = in.readInt();
mParentId = in.readString();
mExtras = in.readBundle();
}
@@ -883,12 +904,17 @@
int type;
boolean isHardwareInput = false;
boolean isConnectedToHdmiSwitch = false;
+ @HdmiAddressRelativePosition
+ int hdmiConnectionRelativePosition = HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
if (mHdmiDeviceInfo != null) {
id = generateInputId(componentName, mHdmiDeviceInfo);
type = TYPE_HDMI;
isHardwareInput = true;
- isConnectedToHdmiSwitch = (mHdmiDeviceInfo.getPhysicalAddress() & 0x0FFF) != 0;
+ hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo);
+ isConnectedToHdmiSwitch =
+ hdmiConnectionRelativePosition
+ != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW;
} else if (mTvInputHardwareInfo != null) {
id = generateInputId(componentName, mTvInputHardwareInfo);
type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
@@ -901,7 +927,8 @@
return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId,
mIcon, mIconStandby, mIconDisconnected, mSetupActivity,
mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount,
- mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras);
+ mHdmiDeviceInfo, isConnectedToHdmiSwitch, hdmiConnectionRelativePosition,
+ mParentId, mExtras);
}
private static String generateInputId(ComponentName name) {
@@ -923,6 +950,16 @@
+ tvInputHardwareInfo.getDeviceId();
}
+ private static int getRelativePosition(Context context, HdmiDeviceInfo info) {
+ HdmiControlManager hcm =
+ (HdmiControlManager) context.getSystemService(Context.HDMI_CONTROL_SERVICE);
+ if (hcm == null) {
+ return HdmiUtils.HDMI_RELATIVE_POSITION_UNKNOWN;
+ }
+ return HdmiUtils.getHdmiAddressRelativePosition(
+ info.getPhysicalAddress(), hcm.getPhysicalAddress());
+ }
+
private void parseServiceMetadata(int inputType) {
ServiceInfo si = mResolveInfo.serviceInfo;
PackageManager pm = mContext.getPackageManager();
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 73d4c45..7c1af4a 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -56,6 +56,7 @@
shared_libs: [
"liblog",
+ "libhidlbase",
"libcutils",
"libandroidfw",
"libinput",
@@ -70,6 +71,8 @@
"libnetd_client",
"libhwui",
"libxml2",
+ "android.hardware.configstore@1.0",
+ "android.hardware.configstore-utils",
],
static_libs: [
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 97ebe38..8b45af0 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -209,7 +209,7 @@
AStorageManager_unmountObb;
ASurfaceControl_create; # introduced=29
ASurfaceControl_createFromWindow; # introduced=29
- ASurfaceControl_destroy; # introduced=29
+ ASurfaceControl_release; # introduced=29
ASurfaceTexture_acquireANativeWindow; # introduced=28
ASurfaceTexture_attachToGLContext; # introduced=28
ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -218,13 +218,24 @@
ASurfaceTexture_getTransformMatrix; # introduced=28
ASurfaceTexture_release; # introduced=28
ASurfaceTexture_updateTexImage; # introduced=28
+ ASurfaceTransactionStats_getAcquireTime; # introduced=29
+ ASurfaceTransactionStats_getASurfaceControls; # introduced=29
+ ASurfaceTransactionStats_getLatchTime; # introduced=29
+ ASurfaceTransactionStats_getPresentFenceFd; # introduced=29
+ ASurfaceTransactionStats_getPreviousReleaseFenceFd; # introduced=29
+ ASurfaceTransactionStats_releaseASurfaceControls; # introduced=29
ASurfaceTransaction_apply; # introduced=29
ASurfaceTransaction_create; # introduced=29
ASurfaceTransaction_delete; # introduced=29
+ ASurfaceTransaction_reparent; # introduced=29
ASurfaceTransaction_setBuffer; # introduced=29
+ ASurfaceTransaction_setBufferAlpha; # introduced=29
ASurfaceTransaction_setBufferTransparency; # introduced=29
ASurfaceTransaction_setDamageRegion; # introduced=29
+ ASurfaceTransaction_setDesiredPresentTime; # introduced=29
ASurfaceTransaction_setGeometry; # introduced=29
+ ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
+ ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
ASurfaceTransaction_setOnComplete; # introduced=29
ASurfaceTransaction_setVisibility; # introduced=29
ASurfaceTransaction_setZOrder; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index ead5b0b..f0100a9 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -14,14 +14,26 @@
* limitations under the License.
*/
+#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/native_window.h>
#include <android/surface_control.h>
+#include <configstore/Utils.h>
+
+#include <gui/HdrMetadata.h>
+#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SurfaceControl.h>
+#include <ui/HdrCapabilities.h>
+
+#include <utils/Timers.h>
+
+using namespace android::hardware::configstore;
+using namespace android::hardware::configstore::V1_0;
using namespace android;
+using android::hardware::configstore::V1_0::ISurfaceFlingerConfigs;
using Transaction = SurfaceComposerClient::Transaction;
@@ -95,10 +107,9 @@
return reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
}
-void ASurfaceControl_destroy(ASurfaceControl* aSurfaceControl) {
+void ASurfaceControl_release(ASurfaceControl* aSurfaceControl) {
sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
- Transaction().reparent(surfaceControl, nullptr).apply();
SurfaceControl_release(surfaceControl.get());
}
@@ -120,6 +131,86 @@
transaction->apply();
}
+typedef struct ASurfaceControlStats {
+ int64_t acquireTime;
+ sp<Fence> previousReleaseFence;
+} ASurfaceControlStats;
+
+struct ASurfaceTransactionStats {
+ std::unordered_map<ASurfaceControl*, ASurfaceControlStats> aSurfaceControlStats;
+ int64_t latchTime;
+ sp<Fence> presentFence;
+};
+
+int64_t ASurfaceTransactionStats_getLatchTime(ASurfaceTransactionStats* aSurfaceTransactionStats) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ return aSurfaceTransactionStats->latchTime;
+}
+
+int ASurfaceTransactionStats_getPresentFenceFd(ASurfaceTransactionStats* aSurfaceTransactionStats) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ auto& presentFence = aSurfaceTransactionStats->presentFence;
+ return (presentFence) ? presentFence->dup() : -1;
+}
+
+void ASurfaceTransactionStats_getASurfaceControls(ASurfaceTransactionStats* aSurfaceTransactionStats,
+ ASurfaceControl*** outASurfaceControls,
+ size_t* outASurfaceControlsSize) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(outASurfaceControls);
+ CHECK_NOT_NULL(outASurfaceControlsSize);
+
+ size_t size = aSurfaceTransactionStats->aSurfaceControlStats.size();
+
+ SurfaceControl** surfaceControls = new SurfaceControl*[size];
+ ASurfaceControl** aSurfaceControls = reinterpret_cast<ASurfaceControl**>(surfaceControls);
+
+ size_t i = 0;
+ for (auto& [aSurfaceControl, aSurfaceControlStats] : aSurfaceTransactionStats->aSurfaceControlStats) {
+ aSurfaceControls[i] = aSurfaceControl;
+ i++;
+ }
+
+ *outASurfaceControls = aSurfaceControls;
+ *outASurfaceControlsSize = size;
+}
+
+int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* aSurfaceTransactionStats,
+ ASurfaceControl* aSurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ const auto& aSurfaceControlStats =
+ aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl);
+ LOG_ALWAYS_FATAL_IF(
+ aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
+ "ASurfaceControl not found");
+
+ return aSurfaceControlStats->second.acquireTime;
+}
+
+int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
+ ASurfaceTransactionStats* aSurfaceTransactionStats, ASurfaceControl* aSurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransactionStats);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ const auto& aSurfaceControlStats =
+ aSurfaceTransactionStats->aSurfaceControlStats.find(aSurfaceControl);
+ LOG_ALWAYS_FATAL_IF(
+ aSurfaceControlStats == aSurfaceTransactionStats->aSurfaceControlStats.end(),
+ "ASurfaceControl not found");
+
+ auto& previousReleaseFence = aSurfaceControlStats->second.previousReleaseFence;
+ return (previousReleaseFence) ? previousReleaseFence->dup() : -1;
+}
+
+void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** aSurfaceControls) {
+ CHECK_NOT_NULL(aSurfaceControls);
+
+ SurfaceControl** surfaceControls = reinterpret_cast<SurfaceControl**>(aSurfaceControls);
+ delete[] surfaceControls;
+}
+
void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* aSurfaceTransaction, void* context,
ASurfaceTransaction_OnComplete func) {
CHECK_NOT_NULL(aSurfaceTransaction);
@@ -127,9 +218,23 @@
CHECK_NOT_NULL(func);
TransactionCompletedCallbackTakesContext callback = [func](void* callback_context,
- const TransactionStats& stats) {
- int fence = (stats.presentFence) ? stats.presentFence->dup() : -1;
- (*func)(callback_context, fence);
+ nsecs_t latchTime,
+ const sp<Fence>& presentFence,
+ const std::vector<SurfaceControlStats>& surfaceControlStats) {
+ ASurfaceTransactionStats aSurfaceTransactionStats;
+
+ aSurfaceTransactionStats.latchTime = latchTime;
+ aSurfaceTransactionStats.presentFence = presentFence;
+
+ auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
+
+ for (const auto& [surfaceControl, acquireTime, previousReleaseFence] : surfaceControlStats) {
+ ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
+ aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
+ aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
+ }
+
+ (*func)(callback_context, &aSurfaceTransactionStats);
};
Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
@@ -137,7 +242,23 @@
transaction->addTransactionCompletedCallback(callback, context);
}
-void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_reparent(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ ASurfaceControl* newParentASurfaceControl) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ sp<SurfaceControl> newParentSurfaceControl = ASurfaceControl_to_SurfaceControl(
+ newParentASurfaceControl);
+ sp<IBinder> newParentHandle = (newParentSurfaceControl)? newParentSurfaceControl->getHandle() : nullptr;
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->reparent(surfaceControl, newParentHandle);
+}
+
+void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
int8_t visibility) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -157,7 +278,8 @@
}
}
-void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_setZOrder(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
int32_t z_order) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -168,8 +290,9 @@
transaction->setLayer(surfaceControl, z_order);
}
-void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
- AHardwareBuffer* buffer, int fence_fd) {
+void ASurfaceTransaction_setBuffer(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ AHardwareBuffer* buffer, int acquire_fence_fd) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -179,8 +302,8 @@
sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer));
transaction->setBuffer(surfaceControl, graphic_buffer);
- if (fence_fd != -1) {
- sp<Fence> fence = new Fence(fence_fd);
+ if (acquire_fence_fd != -1) {
+ sp<Fence> fence = new Fence(acquire_fence_fd);
transaction->setAcquireFence(surfaceControl, fence);
}
}
@@ -215,7 +338,8 @@
transaction->setFlags(surfaceControl, flags, layer_state_t::eLayerOpaque);
}
-void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
+void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
const ARect rects[], uint32_t count) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -230,3 +354,80 @@
transaction->setSurfaceDamageRegion(surfaceControl, region);
}
+
+void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* aSurfaceTransaction,
+ int64_t desiredPresentTime) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setDesiredPresentTime(static_cast<nsecs_t>(desiredPresentTime));
+}
+
+void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ float alpha) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ LOG_ALWAYS_FATAL_IF(alpha < 0.0 || alpha > 1.0, "invalid alpha");
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setAlpha(surfaceControl, alpha);
+}
+
+void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ struct AHdrMetadata_smpte2086* metadata) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ HdrMetadata hdrMetadata;
+
+ if (metadata) {
+ hdrMetadata.smpte2086.displayPrimaryRed.x = metadata->displayPrimaryRed.x;
+ hdrMetadata.smpte2086.displayPrimaryRed.y = metadata->displayPrimaryRed.y;
+ hdrMetadata.smpte2086.displayPrimaryGreen.x = metadata->displayPrimaryGreen.x;
+ hdrMetadata.smpte2086.displayPrimaryGreen.y = metadata->displayPrimaryGreen.y;
+ hdrMetadata.smpte2086.displayPrimaryBlue.x = metadata->displayPrimaryBlue.x;
+ hdrMetadata.smpte2086.displayPrimaryBlue.y = metadata->displayPrimaryBlue.y;
+ hdrMetadata.smpte2086.whitePoint.x = metadata->whitePoint.x;
+ hdrMetadata.smpte2086.whitePoint.y = metadata->whitePoint.y;
+ hdrMetadata.smpte2086.minLuminance = metadata->minLuminance;
+ hdrMetadata.smpte2086.maxLuminance = metadata->maxLuminance;
+
+ hdrMetadata.validTypes |= HdrMetadata::SMPTE2086;
+ } else {
+ hdrMetadata.validTypes &= ~HdrMetadata::SMPTE2086;
+ }
+
+ transaction->setHdrMetadata(surfaceControl, hdrMetadata);
+}
+
+void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ struct AHdrMetadata_cta861_3* metadata) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ HdrMetadata hdrMetadata;
+
+ if (metadata) {
+ hdrMetadata.cta8613.maxContentLightLevel = metadata->maxContentLightLevel;
+ hdrMetadata.cta8613.maxFrameAverageLightLevel = metadata->maxFrameAverageLightLevel;
+
+ hdrMetadata.validTypes |= HdrMetadata::CTA861_3;
+ } else {
+ hdrMetadata.validTypes &= ~HdrMetadata::CTA861_3;
+ }
+
+ transaction->setHdrMetadata(surfaceControl, hdrMetadata);
+}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8d04702..da3416b 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -76,7 +76,7 @@
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
- Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
+ Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_QUERY_ARGS
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
@@ -444,6 +444,7 @@
row.add(Root.COLUMN_FLAGS, root.flags);
row.add(Root.COLUMN_TITLE, root.title);
row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
+ row.add(Root.COLUMN_QUERY_ARGS, SUPPORTED_QUERY_ARGS);
long availableBytes = -1;
if (root.reportAvailableBytes) {
diff --git a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
index 9f30eda..716fc8d 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
+++ b/packages/SettingsLib/EntityHeaderWidgets/res/layout/app_entities_header.xml
@@ -33,7 +33,7 @@
android:textAppearance="@style/AppEntitiesHeader.Text.HeaderTitle"/>
<LinearLayout
- android:id="@+id/all_apps_view"
+ android:id="@+id/app_views_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
@@ -61,4 +61,12 @@
android:layout_height="48dp"
android:gravity="center"/>
+ <TextView
+ android:id="@+id/empty_view"
+ android:layout_width="match_parent"
+ android:layout_height="106dp"
+ android:gravity="center"
+ android:visibility="gone"
+ android:textAppearance="@style/AppEntitiesHeader.Text.Summary"/>
+
</LinearLayout>
diff --git a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
index 73cb8db..330049f 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
+++ b/packages/SettingsLib/EntityHeaderWidgets/src/com/android/settingslib/widget/AppEntitiesHeaderController.java
@@ -77,9 +77,11 @@
private final Context mContext;
private final TextView mHeaderTitleView;
+ private final TextView mHeaderEmptyView;
private final Button mHeaderDetailsView;
private final AppEntityInfo[] mAppEntityInfos;
+ private final View mAppViewsContainer;
private final View[] mAppEntityViews;
private final ImageView[] mAppIconViews;
private final TextView[] mAppTitleViews;
@@ -87,6 +89,7 @@
private int mHeaderTitleRes;
private int mHeaderDetailsRes;
+ private int mHeaderEmptyRes;
private View.OnClickListener mDetailsOnClickListener;
/**
@@ -104,6 +107,8 @@
mContext = context;
mHeaderTitleView = appEntitiesHeaderView.findViewById(R.id.header_title);
mHeaderDetailsView = appEntitiesHeaderView.findViewById(R.id.header_details);
+ mHeaderEmptyView = appEntitiesHeaderView.findViewById(R.id.empty_view);
+ mAppViewsContainer = appEntitiesHeaderView.findViewById(R.id.app_views_container);
mAppEntityInfos = new AppEntityInfo[MAXIMUM_APPS];
mAppIconViews = new ImageView[MAXIMUM_APPS];
@@ -152,6 +157,14 @@
}
/**
+ * Sets the string resource id for the empty text.
+ */
+ public AppEntitiesHeaderController setHeaderEmptyRes(@StringRes int emptyRes) {
+ mHeaderEmptyRes = emptyRes;
+ return this;
+ }
+
+ /**
* Set an app entity at a specified position view.
*
* @param index the index at which the specified view is to be inserted
@@ -192,6 +205,12 @@
*/
public void apply() {
bindHeaderTitleView();
+
+ if (isAppEntityInfosEmpty()) {
+ setEmptyViewVisible(true);
+ return;
+ }
+ setEmptyViewVisible(false);
bindHeaderDetailsView();
// Rebind all apps view
@@ -245,4 +264,22 @@
mAppSummaryViews[index].setText(summary);
}
}
+
+ private void setEmptyViewVisible(boolean visible) {
+ if (mHeaderEmptyRes != 0) {
+ mHeaderEmptyView.setText(mHeaderEmptyRes);
+ }
+ mHeaderEmptyView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mHeaderDetailsView.setVisibility(visible ? View.GONE : View.VISIBLE);
+ mAppViewsContainer.setVisibility(visible ? View.GONE : View.VISIBLE);
+ }
+
+ private boolean isAppEntityInfosEmpty() {
+ for (AppEntityInfo info : mAppEntityInfos) {
+ if (info != null) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index f9f1dda..9b12a31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -622,7 +622,7 @@
.append(KEY_PREFIX_FQDN)
.append(config.FQDN).toString();
} else {
- return getKey(config.SSID, config.BSSID, getSecurity(config));
+ return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config));
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
index 8c18c35..4c68c14 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
@@ -60,6 +60,7 @@
.setOnClickListener(v -> {
})
.build();
+ mController.setAppEntity(0, mAppEntityInfo);
}
@Test
@@ -172,6 +173,8 @@
mController.setAppEntity(0, mAppEntityInfo)
.setAppEntity(1, mAppEntityInfo)
.setAppEntity(2, mAppEntityInfo).apply();
+ final View appViewsContainer = mAppEntitiesHeaderView.findViewById(
+ R.id.app_views_container);
final View app1View = mAppEntitiesHeaderView.findViewById(R.id.app1_view);
final View app2View = mAppEntitiesHeaderView.findViewById(R.id.app2_view);
final View app3View = mAppEntitiesHeaderView.findViewById(R.id.app3_view);
@@ -181,8 +184,28 @@
assertThat(app3View.getVisibility()).isEqualTo(View.VISIBLE);
mController.clearAllAppEntities().apply();
- assertThat(app1View.getVisibility()).isEqualTo(View.GONE);
- assertThat(app2View.getVisibility()).isEqualTo(View.GONE);
- assertThat(app3View.getVisibility()).isEqualTo(View.GONE);
+
+ assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void apply_noAppEntitySet_shouldOnlyShowTitleAndEmptyView() {
+ mController.setHeaderTitleRes(R.string.expand_button_title)
+ .setAppEntity(0, mAppEntityInfo)
+ .setAppEntity(1, mAppEntityInfo)
+ .setAppEntity(2, mAppEntityInfo).apply();
+ final View titleView = mAppEntitiesHeaderView.findViewById(R.id.header_title);
+ final View detailsView = mAppEntitiesHeaderView.findViewById(R.id.header_details);
+ final View emptyView = mAppEntitiesHeaderView.findViewById(R.id.empty_view);
+ final View appViewsContainer = mAppEntitiesHeaderView.findViewById(
+ R.id.app_views_container);
+
+ mController.clearAllAppEntities().apply();
+
+ assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(emptyView.getVisibility()).isEqualTo(View.VISIBLE);
+
+ assertThat(detailsView.getVisibility()).isEqualTo(View.GONE);
+ assertThat(appViewsContainer.getVisibility()).isEqualTo(View.GONE);
}
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 98f0cbe..f2be2e7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -458,6 +458,11 @@
heads-up notifications. -->
<bool name="config_smart_replies_in_notifications_show_in_heads_up">true</bool>
+ <!-- Smart replies in notifications: Minimum number of system generated smart replies that
+ should be shown in a notification. If we cannot show at least this many replies we instead
+ show none. -->
+ <integer name="config_smart_replies_in_notifications_min_num_system_generated_replies">0</integer>
+
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
Blank sends the user to the Chooser first.
This name is in the ComponentName flattened format (package/class) -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f384d8f..d64e2f9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -150,6 +150,9 @@
<!-- Option to always allow USB debugging from the attached computer -->
<string name="usb_debugging_always">Always allow from this computer</string>
+ <!-- Button label for confirming acceptance of enabling USB debugging [CHAR LIMIT=15] -->
+ <string name="usb_debugging_allow">Allow</string>
+
<!-- Title of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. -->
<string name="usb_debugging_secondary_user_title">USB debugging not allowed</string>
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 1d2d7fa..2aecc24 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,14 +16,18 @@
package com.android.systemui;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.app.WallpaperManager;
import android.content.ComponentCallbacks2;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region.Op;
+import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Trace;
@@ -33,7 +37,6 @@
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceHolder;
-import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -94,7 +97,7 @@
float mYOffset = 0f;
float mScale = 1f;
- private Display mDefaultDisplay;
+ private Display mDisplay;
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
boolean mVisible = true;
@@ -138,10 +141,20 @@
super.onCreate(surfaceHolder);
//noinspection ConstantConditions
- mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
+ final Context displayContext = getDisplayContext();
+ final int displayId = displayContext == null ? DEFAULT_DISPLAY :
+ displayContext.getDisplayId();
+ DisplayManager dm = getSystemService(DisplayManager.class);
+ if (dm != null) {
+ mDisplay = dm.getDisplay(displayId);
+ if (mDisplay == null) {
+ Log.e(TAG, "Cannot find display! Fallback to default.");
+ mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+ }
+ }
setOffsetNotificationsEnabled(false);
- updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);
+ updateSurfaceSize(surfaceHolder, getDisplayInfo(), false /* forDraw */);
}
@Override
@@ -165,9 +178,26 @@
hasWallpaper = false;
}
- // Set surface size equal to bitmap size, prevent memory waste
- int surfaceWidth = Math.max(MIN_BACKGROUND_WIDTH, mBackgroundWidth);
- int surfaceHeight = Math.max(MIN_BACKGROUND_HEIGHT, mBackgroundHeight);
+ // Expected surface size.
+ int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
+ int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
+
+ // Calculate the minimum drawing area of the surface, which saves memory and does not
+ // distort the image.
+ final float scale = Math.min(
+ (float) mBackgroundHeight / (float) surfaceHeight,
+ (float) mBackgroundWidth / (float) surfaceWidth);
+ surfaceHeight = (int) (scale * surfaceHeight);
+ surfaceWidth = (int) (scale * surfaceWidth);
+
+ // Set surface size to at least MIN size.
+ if (surfaceWidth < MIN_BACKGROUND_WIDTH || surfaceHeight < MIN_BACKGROUND_HEIGHT) {
+ final float scaleUp = Math.max(
+ (float) MIN_BACKGROUND_WIDTH / (float) surfaceWidth,
+ (float) MIN_BACKGROUND_HEIGHT / (float) surfaceHeight);
+ surfaceWidth = (int) ((float) surfaceWidth * scaleUp);
+ surfaceHeight = (int) ((float) surfaceHeight * scaleUp);
+ }
// Used a fixed size surface, because we are special. We can do
// this because we know the current design of window animations doesn't
@@ -267,8 +297,8 @@
}
@VisibleForTesting
- DisplayInfo getDefaultDisplayInfo() {
- mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo);
+ DisplayInfo getDisplayInfo() {
+ mDisplay.getDisplayInfo(mTmpDisplayInfo);
return mTmpDisplayInfo;
}
@@ -278,7 +308,7 @@
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawWallpaper");
- DisplayInfo displayInfo = getDefaultDisplayInfo();
+ DisplayInfo displayInfo = getDisplayInfo();
int newRotation = displayInfo.rotation;
// Sometimes a wallpaper is not large enough to cover the screen in one dimension.
@@ -445,7 +475,7 @@
if (DEBUG) {
Log.d(TAG, "Wallpaper loaded: " + mBackground);
}
- updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
+ updateSurfaceSize(getSurfaceHolder(), getDisplayInfo(),
false /* forDraw */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 3666400..9efa656 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -604,8 +604,14 @@
if (absDelta >= size) {
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
- delta = maxScrollDistance * (float) Math.sin(
- (delta / size) * (Math.PI / 2));
+ int startPosition = mCallback.getConstrainSwipeStartPosition();
+ if (absDelta > startPosition) {
+ int signedStartPosition =
+ (int) (startPosition * Math.signum(delta));
+ delta = signedStartPosition
+ + maxScrollDistance * (float) Math.sin(
+ ((delta - signedStartPosition) / size) * (Math.PI / 2));
+ }
}
}
@@ -742,6 +748,14 @@
float getFalsingThresholdFactor();
/**
+ * @return The position, in pixels, at which a constrained swipe should start being
+ * constrained.
+ */
+ default int getConstrainSwipeStartPosition() {
+ return 0;
+ }
+
+ /**
* @return If true, the given view is draggable.
*/
default boolean canChildBeDragged(@NonNull View animView) { return true; }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index d066567..0c37666 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5818,6 +5818,15 @@
}
@Override
+ public int getConstrainSwipeStartPosition() {
+ NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
+ if (menuRow != null) {
+ return Math.abs(menuRow.getMenuSnapTarget());
+ }
+ return 0;
+ }
+
+ @Override
public boolean canChildBeDismissed(View v) {
return NotificationStackScrollLayout.this.canChildBeDismissed(v);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
index 2a11c26..d022808 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
@@ -97,10 +97,11 @@
}
return mClickableChildren
.stream()
- .filter(v -> v.isAttachedToWindow())
+ .filter(View::isAttachedToWindow)
.map(v -> new Pair<>(distance(v, event), v))
.min(Comparator.comparingInt(f -> f.first))
- .get().second;
+ .map(data -> data.second)
+ .orElse(null);
}
private int distance(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 3bd0d45..db04620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -46,18 +46,21 @@
private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
"edit_choices_before_sending";
private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up";
+ private static final String KEY_MIN_NUM_REPLIES = "min_num_system_generated_replies";
private final boolean mDefaultEnabled;
private final boolean mDefaultRequiresP;
private final int mDefaultMaxSqueezeRemeasureAttempts;
private final boolean mDefaultEditChoicesBeforeSending;
private final boolean mDefaultShowInHeadsUp;
+ private final int mDefaultMinNumSystemGeneratedReplies;
private boolean mEnabled;
private boolean mRequiresTargetingP;
private int mMaxSqueezeRemeasureAttempts;
private boolean mEditChoicesBeforeSending;
private boolean mShowInHeadsUp;
+ private int mMinNumSystemGeneratedReplies;
private final Context mContext;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -78,6 +81,8 @@
R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
mDefaultShowInHeadsUp = resources.getBoolean(
R.bool.config_smart_replies_in_notifications_show_in_heads_up);
+ mDefaultMinNumSystemGeneratedReplies = resources.getInteger(
+ R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -105,6 +110,8 @@
mEditChoicesBeforeSending = mParser.getBoolean(
KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp);
+ mMinNumSystemGeneratedReplies =
+ mParser.getInt(KEY_MIN_NUM_REPLIES, mDefaultMinNumSystemGeneratedReplies);
}
}
@@ -155,4 +162,12 @@
public boolean getShowInHeadsUp() {
return mShowInHeadsUp;
}
+
+ /**
+ * Returns the minimum number of system generated replies to show in a notification.
+ * If we cannot show at least this many system generated replies we should show none.
+ */
+ public int getMinNumSystemGeneratedReplies() {
+ return mMinNumSystemGeneratedReplies;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index d6eff94..c4f027f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -88,6 +88,12 @@
private View mSmartReplyContainer;
+ /**
+ * Whether the smart replies in this view were generated by the notification assistant. If not
+ * they're provided by the app.
+ */
+ private boolean mSmartRepliesGeneratedByAssistant = false;
+
@ColorInt
private int mCurrentBackgroundColor;
@ColorInt
@@ -202,6 +208,7 @@
getContext(), this, i, smartReplies, smartReplyController, entry);
addView(replyButton);
}
+ this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
}
}
reallocateCandidateButtonQueueForSqueezing();
@@ -344,10 +351,11 @@
mCandidateButtonQueueForSqueezing.clear();
}
- int measuredWidth = mPaddingLeft + mPaddingRight;
- int maxChildHeight = 0;
+ SmartSuggestionMeasures accumulatedMeasures = new SmartSuggestionMeasures(
+ mPaddingLeft + mPaddingRight,
+ 0 /* maxChildHeight */,
+ mSingleLineButtonPaddingHorizontal);
int displayedChildCount = 0;
- int buttonPaddingHorizontal = mSingleLineButtonPaddingHorizontal;
// Set up a list of suggestions where actions come before replies. Note that the Buttons
// themselves have already been added to the view hierarchy in an order such that Smart
@@ -360,11 +368,15 @@
smartSuggestions.addAll(smartReplies);
List<View> coveredSuggestions = new ArrayList<>();
+ // SmartSuggestionMeasures for all action buttons, this will be filled in when the first
+ // reply button is added.
+ SmartSuggestionMeasures actionsMeasures = null;
+
for (View child : smartSuggestions) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- child.setPadding(buttonPaddingHorizontal, child.getPaddingTop(),
- buttonPaddingHorizontal, child.getPaddingBottom());
+ child.setPadding(accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingTop(),
+ accumulatedMeasures.mButtonPaddingHorizontal, child.getPaddingBottom());
child.measure(MEASURE_SPEC_ANY_LENGTH, heightMeasureSpec);
coveredSuggestions.add(child);
@@ -380,45 +392,52 @@
}
// Remember the current measurements in case the current button doesn't fit in.
- final int originalMaxChildHeight = maxChildHeight;
- final int originalMeasuredWidth = measuredWidth;
- final int originalButtonPaddingHorizontal = buttonPaddingHorizontal;
+ SmartSuggestionMeasures originalMeasures = accumulatedMeasures.clone();
+ if (actionsMeasures == null && lp.buttonType == SmartButtonType.REPLY) {
+ // We've added all actions (we go through actions first), now add their
+ // measurements.
+ actionsMeasures = accumulatedMeasures.clone();
+ }
final int spacing = displayedChildCount == 0 ? 0 : mSpacing;
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
- measuredWidth += spacing + childWidth;
- maxChildHeight = Math.max(maxChildHeight, childHeight);
+ accumulatedMeasures.mMeasuredWidth += spacing + childWidth;
+ accumulatedMeasures.mMaxChildHeight =
+ Math.max(accumulatedMeasures.mMaxChildHeight, childHeight);
// Do we need to increase the number of lines in smart reply buttons to two?
final boolean increaseToTwoLines =
- buttonPaddingHorizontal == mSingleLineButtonPaddingHorizontal
- && (lineCount == 2 || measuredWidth > targetWidth);
+ (accumulatedMeasures.mButtonPaddingHorizontal
+ == mSingleLineButtonPaddingHorizontal)
+ && (lineCount == 2 || accumulatedMeasures.mMeasuredWidth > targetWidth);
if (increaseToTwoLines) {
- measuredWidth += (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
- buttonPaddingHorizontal = mDoubleLineButtonPaddingHorizontal;
+ accumulatedMeasures.mMeasuredWidth +=
+ (displayedChildCount + 1) * mSingleToDoubleLineButtonWidthIncrease;
+ accumulatedMeasures.mButtonPaddingHorizontal =
+ mDoubleLineButtonPaddingHorizontal;
}
// If the last button doesn't fit into the remaining width, try squeezing preceding
// smart reply buttons.
- if (measuredWidth > targetWidth) {
+ if (accumulatedMeasures.mMeasuredWidth > targetWidth) {
// Keep squeezing preceding and current smart reply buttons until they all fit.
- while (measuredWidth > targetWidth
+ while (accumulatedMeasures.mMeasuredWidth > targetWidth
&& !mCandidateButtonQueueForSqueezing.isEmpty()) {
final Button candidate = mCandidateButtonQueueForSqueezing.poll();
final int squeezeReduction = squeezeButton(candidate, heightMeasureSpec);
if (squeezeReduction != SQUEEZE_FAILED) {
- maxChildHeight = Math.max(maxChildHeight, candidate.getMeasuredHeight());
- measuredWidth -= squeezeReduction;
+ accumulatedMeasures.mMaxChildHeight =
+ Math.max(accumulatedMeasures.mMaxChildHeight,
+ candidate.getMeasuredHeight());
+ accumulatedMeasures.mMeasuredWidth -= squeezeReduction;
}
}
// If the current button still doesn't fit after squeezing all buttons, undo the
// last squeezing round.
- if (measuredWidth > targetWidth) {
- measuredWidth = originalMeasuredWidth;
- maxChildHeight = originalMaxChildHeight;
- buttonPaddingHorizontal = originalButtonPaddingHorizontal;
+ if (accumulatedMeasures.mMeasuredWidth > targetWidth) {
+ accumulatedMeasures = originalMeasures;
// Mark all buttons from the last squeezing round as "failed to squeeze", so
// that they're re-measured without squeezing later.
@@ -440,16 +459,75 @@
displayedChildCount++;
}
+ if (mSmartRepliesGeneratedByAssistant) {
+ if (!gotEnoughSmartReplies(smartReplies)) {
+ // We don't have enough smart replies - hide all of them.
+ for (View smartReplyButton : smartReplies) {
+ final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
+ lp.show = false;
+ }
+ // Reset our measures back to when we had only added actions (before adding
+ // replies).
+ accumulatedMeasures = actionsMeasures;
+ }
+ }
+
// We're done squeezing buttons, so we can clear the priority queue.
mCandidateButtonQueueForSqueezing.clear();
// Finally, we need to re-measure some buttons.
- remeasureButtonsIfNecessary(buttonPaddingHorizontal, maxChildHeight);
+ remeasureButtonsIfNecessary(accumulatedMeasures.mButtonPaddingHorizontal,
+ accumulatedMeasures.mMaxChildHeight);
setMeasuredDimension(
- resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec),
- resolveSize(Math.max(getSuggestedMinimumHeight(),
- mPaddingTop + maxChildHeight + mPaddingBottom), heightMeasureSpec));
+ resolveSize(Math.max(getSuggestedMinimumWidth(),
+ accumulatedMeasures.mMeasuredWidth),
+ widthMeasureSpec),
+ resolveSize(Math.max(getSuggestedMinimumHeight(), mPaddingTop
+ + accumulatedMeasures.mMaxChildHeight + mPaddingBottom),
+ heightMeasureSpec));
+ }
+
+ /**
+ * Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
+ * on which suggestions are added.
+ */
+ private static class SmartSuggestionMeasures {
+ int mMeasuredWidth = -1;
+ int mMaxChildHeight = -1;
+ int mButtonPaddingHorizontal = -1;
+
+ SmartSuggestionMeasures(int measuredWidth, int maxChildHeight,
+ int buttonPaddingHorizontal) {
+ this.mMeasuredWidth = measuredWidth;
+ this.mMaxChildHeight = maxChildHeight;
+ this.mButtonPaddingHorizontal = buttonPaddingHorizontal;
+ }
+
+ public SmartSuggestionMeasures clone() {
+ return new SmartSuggestionMeasures(
+ mMeasuredWidth, mMaxChildHeight, mButtonPaddingHorizontal);
+ }
+ }
+
+ /**
+ * Returns whether our notification contains at least N smart replies (or 0) where N is
+ * determined by {@link SmartReplyConstants}.
+ */
+ private boolean gotEnoughSmartReplies(List<View> smartReplies) {
+ int numShownReplies = 0;
+ for (View smartReplyButton : smartReplies) {
+ final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
+ if (lp.show) {
+ numShownReplies++;
+ }
+ }
+ if (numShownReplies == 0
+ || numShownReplies >= mConstants.getMinNumSystemGeneratedReplies()) {
+ // We have enough replies, yay!
+ return true;
+ }
+ return false;
}
private List<View> filterActionsOrReplies(SmartButtonType buttonType) {
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index ed2ad79..12006fa 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -75,7 +75,7 @@
final AlertController.AlertParams ap = mAlertParams;
ap.mTitle = getString(R.string.usb_debugging_title);
ap.mMessage = getString(R.string.usb_debugging_message, fingerprints);
- ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mPositiveButtonText = getString(R.string.usb_debugging_allow);
ap.mNegativeButtonText = getString(android.R.string.cancel);
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
index 712ea27..8b00eee 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/MediaSessions.java
@@ -25,9 +25,9 @@
import android.content.pm.ResolveInfo;
import android.media.IRemoteVolumeController;
import android.media.MediaMetadata;
-import android.media.session.ISessionController;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
+import android.media.session.MediaSession;
import android.media.session.MediaSession.QueueItem;
import android.media.session.MediaSession.Token;
import android.media.session.MediaSessionManager;
@@ -113,17 +113,17 @@
r.controller.setVolumeTo(level, 0);
}
- private void onRemoteVolumeChangedH(ISessionController session, int flags) {
- final MediaController controller = new MediaController(mContext, session);
+ private void onRemoteVolumeChangedH(MediaSession.Token sessionToken, int flags) {
+ final MediaController controller = new MediaController(mContext, sessionToken);
if (D.BUG) Log.d(TAG, "remoteVolumeChangedH " + controller.getPackageName() + " "
+ Util.audioManagerFlagsToString(flags));
final Token token = controller.getSessionToken();
mCallbacks.onRemoteVolumeChanged(token, flags);
}
- private void onUpdateRemoteControllerH(ISessionController session) {
- final MediaController controller = session != null ? new MediaController(mContext, session)
- : null;
+ private void onUpdateRemoteControllerH(MediaSession.Token sessionToken) {
+ final MediaController controller =
+ sessionToken != null ? new MediaController(mContext, sessionToken) : null;
final String pkg = controller != null ? controller.getPackageName() : null;
if (D.BUG) Log.d(TAG, "updateRemoteControllerH " + pkg);
// this may be our only indication that a remote session is changed, refresh
@@ -332,15 +332,16 @@
private final IRemoteVolumeController mRvc = new IRemoteVolumeController.Stub() {
@Override
- public void remoteVolumeChanged(ISessionController session, int flags)
+ public void remoteVolumeChanged(MediaSession.Token sessionToken, int flags)
throws RemoteException {
- mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0, session).sendToTarget();
+ mHandler.obtainMessage(H.REMOTE_VOLUME_CHANGED, flags, 0,
+ sessionToken).sendToTarget();
}
@Override
- public void updateRemoteController(final ISessionController session)
+ public void updateRemoteController(final MediaSession.Token sessionToken)
throws RemoteException {
- mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, session).sendToTarget();
+ mHandler.obtainMessage(H.UPDATE_REMOTE_CONTROLLER, sessionToken).sendToTarget();
}
};
@@ -360,10 +361,10 @@
onActiveSessionsUpdatedH(mMgr.getActiveSessions(null));
break;
case REMOTE_VOLUME_CHANGED:
- onRemoteVolumeChangedH((ISessionController) msg.obj, msg.arg1);
+ onRemoteVolumeChangedH((MediaSession.Token) msg.obj, msg.arg1);
break;
case UPDATE_REMOTE_CONTROLLER:
- onUpdateRemoteControllerH((ISessionController) msg.obj);
+ onUpdateRemoteControllerH((MediaSession.Token) msg.obj);
break;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 53ad0b5..fc57909 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -68,7 +68,7 @@
public Engine onCreateEngine() {
return new DrawableEngine() {
@Override
- DisplayInfo getDefaultDisplayInfo() {
+ DisplayInfo getDisplayInfo() {
return mDisplayInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
index 667a508..4dee438 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
@@ -17,7 +17,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -171,6 +170,19 @@
ev.recycle();
}
+ @Test
+ public void testViewNotAttachedNoCrash() {
+ View view = mockViewAt(0, 20, 10, 10);
+ when(view.isAttachedToWindow()).thenReturn(false);
+ mNearestTouchFrame.addView(view);
+ mNearestTouchFrame.onMeasure(0, 0);
+
+ MotionEvent ev = MotionEvent.obtain(0, 0, 0, 5 /* x */, 18 /* y */, 0);
+ mNearestTouchFrame.onTouchEvent(ev);
+ verify(view, never()).onTouchEvent(eq(ev));
+ ev.recycle();
+ }
+
private View mockViewAt(int x, int y, int width, int height) {
View v = spy(new View(mContext));
doAnswer(invocation -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 3cbf902..03b7c95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -55,6 +55,9 @@
resources.addOverride(
R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
resources.addOverride(R.bool.config_smart_replies_in_notifications_show_in_heads_up, true);
+ resources.addOverride(
+ R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies,
+ 2);
mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
}
@@ -178,6 +181,19 @@
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
}
+ @Test
+ public void testGetMinNumSystemGeneratedRepliesWithNoConfig() {
+ assertTrue(mConstants.isEnabled());
+ assertEquals(2, mConstants.getMinNumSystemGeneratedReplies());
+ }
+
+ @Test
+ public void testGetMinNumSystemGeneratedRepliesWithValidConfig() {
+ overrideSetting("enabled=true,min_num_system_generated_replies=5");
+ triggerConstantsOnChange();
+ assertEquals(5, mConstants.getMinNumSystemGeneratedReplies());
+ }
+
private void triggerConstantsOnChange() {
// Since Settings.Global is mocked in TestableContext, we need to manually trigger the
// content observer.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 1066bc1..d1c4d01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -96,6 +96,7 @@
@Mock private SmartReplyController mLogger;
private NotificationEntry mEntry;
private Notification mNotification;
+ @Mock private SmartReplyConstants mConstants;
@Mock ActivityStarter mActivityStarter;
@Mock HeadsUpManager mHeadsUpManager;
@@ -108,10 +109,14 @@
mDependency.get(KeyguardDismissUtil.class).setDismissHandler(action -> action.onDismiss());
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
+ mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
mContainer = new View(mContext, null);
mView = SmartReplyView.inflate(mContext, null);
+ // Any number of replies are fine.
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
+ when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
final Resources res = mContext.getResources();
mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
@@ -403,7 +408,7 @@
}
private void setSmartReplies(CharSequence[] choices) {
- setSmartReplies(choices, false);
+ setSmartReplies(choices, false /* fromAssistant */);
}
private void setSmartReplies(CharSequence[] choices, boolean fromAssistant) {
@@ -440,9 +445,14 @@
}
private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
- setSmartReplies(choices);
+ setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */);
+ }
+
+ private void setSmartRepliesAndActions(
+ CharSequence[] choices, String[] actionTitles, boolean fromAssistant) {
+ setSmartReplies(choices, fromAssistant);
mView.addSmartActions(
- new SmartReplyView.SmartActions(createActions(actionTitles), false),
+ new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
mLogger,
mEntry,
mHeadsUpManager);
@@ -943,4 +953,78 @@
expectedView.getChildAt(3), mView.getChildAt(4)); // a1
assertReplyButtonHidden(mView.getChildAt(5)); // long action
}
+
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"reply1", "reply2"};
+ String[] actions = new String[] {"action1"};
+
+ ViewGroup expectedView = buildExpectedView(new String[] {}, 1,
+ createActions(new String[] {"action1"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonHidden(mView.getChildAt(0));
+ assertReplyButtonHidden(mView.getChildAt(1));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(2));
+ }
+
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_enoughReplies() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"reply1", "reply2"};
+ String[] actions = new String[] {"action1"};
+
+ ViewGroup expectedView = buildExpectedView(new String[] {"reply1", "reply2"}, 1,
+ createActions(new String[] {"action1"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(0));
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(1), mView.getChildAt(1));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(2), mView.getChildAt(2));
+ }
+
+ /**
+ * Ensure actions that are squeezed when shown together with smart replies are unsqueezed if the
+ * replies are never added (because of the SmartReplyConstants.getMinNumSystemGeneratedReplies()
+ * flag).
+ */
+ @Test
+ public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() {
+ when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+
+ // Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
+ String[] choices = new String[] {"This is a very long two-line reply."};
+ String[] actions = new String[] {"Short action"};
+
+ // The action should be displayed on one line only - since it fits!
+ ViewGroup expectedView = buildExpectedView(new String[] {}, 1 /* lineCount */,
+ createActions(new String[] {"Short action"}));
+ expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+
+ assertEqualMeasures(expectedView, mView);
+ // smart replies
+ assertReplyButtonHidden(mView.getChildAt(0));
+ // smart actions
+ assertReplyButtonShownWithEqualMeasures(expectedView.getChildAt(0), mView.getChildAt(1));
+ }
}
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index f56320f..efa4e79 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -73,6 +73,10 @@
// The view switched to summary mode (most relevant for notifications)
TYPE_COLLAPSE = 14;
+
+ // The notification was adjusted by the assistant. Enum value is
+ // out of sequence due to b/122737498.
+ TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT = 1573;
}
// Types of alerts, as bit field values
@@ -232,6 +236,17 @@
BLOCKING_HELPER_CLICK_UNDO = 7;
}
+ // The (visual) location of a Notification.
+ enum NotificationLocation {
+ LOCATION_UNKNOWN = 0;
+ LOCATION_FIRST_HEADS_UP = 1; // visible heads-up
+ LOCATION_HIDDEN_TOP = 2; // hidden/scrolled away on the top
+ LOCATION_MAIN_AREA = 3; // visible in the shade
+ LOCATION_BOTTOM_STACK_PEEKING = 4; // in the bottom stack, and peeking
+ LOCATION_BOTTOM_STACK_HIDDEN = 5; // in the bottom stack, and hidden
+ LOCATION_GONE = 6; // the view isn't laid out at all
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -4049,6 +4064,8 @@
// - AUTOFILL_INVALID_DATASET_AUTHENTICATION
// NOTE: starting on OS Q, it also added the following fields:
// Tag FIELD_AUTOFILL_TEXT_LEN: length of the error message provided by the service
+ // Tag FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS: number of requests made to the augmented
+ // autofill service
AUTOFILL_REQUEST = 907;
// Tag of a field for a package of an autofill service
@@ -6823,6 +6840,27 @@
// OPEN: Settings > Display > Adaptive sleep
// OS: Q
SETTINGS_ADAPTIVE_SLEEP = 1628;
+
+ // Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION.
+ // The UI location of the notification containing the smart suggestions.
+ // This is a NotificationLocation object (see the NotificationLocation
+ // enum).
+ // OS: Q
+ NOTIFICATION_LOCATION = 1629;
+
+ // The autofill system made request to the system-provided augmented autofill service.
+ // OS: Q
+ // Package: Package of app that is autofilled
+ // Tag FIELD_CLASS_NAME: Class name of the activity that is autofilled.
+ // Tag FIELD_AUTOFILL_SERVICE: Package of the augmented autofill service that processed the
+ // request
+ // Tag FIELD_AUTOFILL_SESSION_ID: id of the autofill session associated with this metric.
+ // Tag FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
+ AUTOFILL_AUGMENTED_REQUEST = 1630;
+
+ // Tag of a field for the number of augmented autofill requests in a session
+ // OS: Q
+ FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631;
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 1212676..6ff2b35 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -216,9 +216,9 @@
// Package: android
NOTE_SOFTAP_CONFIG_CHANGED = 50;
- // Notify the user that connected to app suggested network.
+ // Notify the user that an app suggested network is available for connection.
// Package: android
- NOTE_CONNECTED_TO_NETWORK_SUGGESTION = 51;
+ NOTE_NETWORK_SUGGESTION_AVAILABLE = 51;
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index bcc43a7..79b63bc 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -488,6 +488,9 @@
// Counts the occurrences of each Wifi usability score provided by external app
repeated WifiUsabilityScoreCount wifi_usability_score_count = 127;
+
+ // List of PNO scan stats, one element for each mobility state
+ repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128;
}
// Information that gets logged for every WiFi connection.
@@ -1816,4 +1819,33 @@
// The list of timestamped wifi usability stats
repeated WifiUsabilityStatsEntry stats = 2;
-}
\ No newline at end of file
+}
+
+message DeviceMobilityStatePnoScanStats {
+ // see WifiManager.DEVICE_MOBILITY_STATE_* constants
+ enum DeviceMobilityState {
+ // Unknown mobility
+ UNKNOWN = 0;
+
+ // High movement
+ HIGH_MVMT = 1;
+
+ // Low movement
+ LOW_MVMT = 2;
+
+ // Stationary
+ STATIONARY = 3;
+ }
+
+ // The device mobility state
+ optional DeviceMobilityState device_mobility_state = 1;
+
+ // The number of times that this state was entered
+ optional int32 num_times_entered_state = 2;
+
+ // The total duration elapsed while in this mobility state, in ms
+ optional int64 total_duration_ms = 3;
+
+ // the total duration elapsed while in this mobility state with PNO scans running, in ms
+ optional int64 pno_duration_ms = 4;
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 6eba914..2c075dc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -53,6 +53,7 @@
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.server.accessibility.AccessibilityManagerService.RemoteAccessibilityConnection;
@@ -751,7 +752,7 @@
}
@Override
- public float getMagnificationScale() {
+ public float getMagnificationScale(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 1.0f;
@@ -759,14 +760,14 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- return mSystemSupport.getMagnificationController().getScale();
+ return mSystemSupport.getMagnificationController().getScale(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public Region getMagnificationRegion() {
+ public Region getMagnificationRegion(int displayId) {
synchronized (mLock) {
final Region region = Region.obtain();
if (!isCalledForCurrentUserLocked()) {
@@ -775,22 +776,22 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- magnificationController.getMagnificationRegion(region);
+ magnificationController.getMagnificationRegion(displayId, region);
return region;
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
@Override
- public float getMagnificationCenterX() {
+ public float getMagnificationCenterX(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 0.0f;
@@ -798,21 +799,21 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- return magnificationController.getCenterX();
+ return magnificationController.getCenterX(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
@Override
- public float getMagnificationCenterY() {
+ public float getMagnificationCenterY(int displayId) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return 0.0f;
@@ -820,31 +821,31 @@
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
boolean registeredJustForThisCall =
- registerMagnificationIfNeeded(magnificationController);
+ registerMagnificationIfNeeded(displayId, magnificationController);
final long identity = Binder.clearCallingIdentity();
try {
- return magnificationController.getCenterY();
+ return magnificationController.getCenterY(displayId);
} finally {
Binder.restoreCallingIdentity(identity);
if (registeredJustForThisCall) {
- magnificationController.unregister();
+ magnificationController.unregister(displayId);
}
}
}
}
- private boolean registerMagnificationIfNeeded(
+ private boolean registerMagnificationIfNeeded(int displayId,
MagnificationController magnificationController) {
- if (!magnificationController.isRegisteredLocked()
+ if (!magnificationController.isRegistered(displayId)
&& mSecurityPolicy.canControlMagnification(this)) {
- magnificationController.register();
+ magnificationController.register(displayId);
return true;
}
return false;
}
@Override
- public boolean resetMagnification(boolean animate) {
+ public boolean resetMagnification(int displayId, boolean animate) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
@@ -857,16 +858,16 @@
try {
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
- return (magnificationController.reset(animate)
- || !magnificationController.isMagnifying());
+ return (magnificationController.reset(displayId, animate)
+ || !magnificationController.isMagnifying(displayId));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
- boolean animate) {
+ public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
+ float centerY, boolean animate) {
synchronized (mLock) {
if (!isCalledForCurrentUserLocked()) {
return false;
@@ -878,11 +879,11 @@
try {
MagnificationController magnificationController =
mSystemSupport.getMagnificationController();
- if (!magnificationController.isRegisteredLocked()) {
- magnificationController.register();
+ if (!magnificationController.isRegistered(displayId)) {
+ magnificationController.register(displayId);
}
return magnificationController
- .setScaleAndCenter(scale, centerX, centerY, animate, mId);
+ .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -890,12 +891,12 @@
}
@Override
- public void setMagnificationCallbackEnabled(boolean enabled) {
- mInvocationHandler.setMagnificationCallbackEnabled(enabled);
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+ mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
}
- public boolean isMagnificationCallbackEnabled() {
- return mInvocationHandler.mIsMagnificationCallbackEnabled;
+ public boolean isMagnificationCallbackEnabled(int displayId) {
+ return mInvocationHandler.isMagnificationCallbackEnabled(displayId);
}
@Override
@@ -1106,10 +1107,10 @@
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
}
- public void notifyMagnificationChangedLocked(@NonNull Region region,
+ public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
mInvocationHandler
- .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ .notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
public void notifySoftKeyboardShowModeChangedLocked(int showState) {
@@ -1128,12 +1129,12 @@
* Called by the invocation handler to notify the service that the
* state of magnification has changed.
*/
- private void notifyMagnificationChangedInternal(@NonNull Region region,
+ private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- listener.onMagnificationChanged(region, scale, centerX, centerY);
+ listener.onMagnificationChanged(displayId, region, scale, centerX, centerY);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
}
@@ -1251,7 +1252,9 @@
private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
- private boolean mIsMagnificationCallbackEnabled = false;
+ /** List of magnification callback states, mapping from displayId -> Boolean */
+ @GuardedBy("mlock")
+ private final SparseArray<Boolean> mMagnificationCallbackState = new SparseArray<>(0);
private boolean mIsSoftKeyboardCallbackEnabled = false;
public InvocationHandler(Looper looper) {
@@ -1277,7 +1280,8 @@
final float scale = (float) args.arg2;
final float centerX = (float) args.arg3;
final float centerY = (float) args.arg4;
- notifyMagnificationChangedInternal(region, scale, centerX, centerY);
+ final int displayId = args.argi1;
+ notifyMagnificationChangedInternal(displayId, region, scale, centerX, centerY);
args.recycle();
} break;
@@ -1301,11 +1305,12 @@
}
}
- public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
- float centerX, float centerY) {
- if (!mIsMagnificationCallbackEnabled) {
- // Callback is disabled, don't bother packing args.
- return;
+ public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
+ float scale, float centerX, float centerY) {
+ synchronized (mLock) {
+ if (mMagnificationCallbackState.get(displayId) == null) {
+ return;
+ }
}
final SomeArgs args = SomeArgs.obtain();
@@ -1313,13 +1318,26 @@
args.arg2 = scale;
args.arg3 = centerX;
args.arg4 = centerY;
+ args.argi1 = displayId;
final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args);
msg.sendToTarget();
}
- public void setMagnificationCallbackEnabled(boolean enabled) {
- mIsMagnificationCallbackEnabled = enabled;
+ public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
+ synchronized (mLock) {
+ if (enabled) {
+ mMagnificationCallbackState.put(displayId, true);
+ } else {
+ mMagnificationCallbackState.remove(displayId);
+ }
+ }
+ }
+
+ public boolean isMagnificationCallbackEnabled(int displayId) {
+ synchronized (mLock) {
+ return mMagnificationCallbackState.get(displayId) != null;
+ }
}
public void notifySoftKeyboardShowModeChangedLocked(int showState) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 763c16f..dbe86c1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -208,6 +208,8 @@
private final WindowManagerInternal mWindowManagerService;
+ private final DisplayManager mDisplayManager;
+
private AppWidgetManagerInternal mAppWidgetService;
private final SecurityPolicy mSecurityPolicy;
@@ -304,10 +306,12 @@
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mMainHandler = new MainHandler(mContext.getMainLooper());
mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
context.getContentResolver());
+ registerDisplayListener(mMainHandler);
}
@Override
@@ -523,6 +527,30 @@
}, UserHandle.ALL, intentFilter, null, null);
}
+ private void registerDisplayListener(Handler handler) {
+ mDisplayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ synchronized (mLock) {
+ UserState userState = getCurrentUserStateLocked();
+ updateMagnificationLocked(userState);
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ if (mMagnificationController != null) {
+ mMagnificationController.onDisplayRemoved(displayId);
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ // do nothing
+ }
+ }, handler);
+ }
+
@Override
public long addClient(IAccessibilityManagerClient callback, int userId) {
synchronized (mLock) {
@@ -968,17 +996,18 @@
* Called by the MagnificationController when the state of display
* magnification changes.
*
+ * @param displayId The logical display id.
* @param region the new magnified region, may be empty if
* magnification is not enabled (e.g. scale is 1)
* @param scale the new scale
* @param centerX the new screen-relative center X coordinate
* @param centerY the new screen-relative center Y coordinate
*/
- public void notifyMagnificationChanged(@NonNull Region region,
+ public void notifyMagnificationChanged(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
synchronized (mLock) {
notifyClearAccessibilityCacheLocked();
- notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
}
@@ -1203,12 +1232,12 @@
}
}
- private void notifyMagnificationChangedLocked(@NonNull Region region,
+ private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
final UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- service.notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
}
@@ -2191,15 +2220,44 @@
return;
}
- if (!mUiAutomationManager.suppressingAccessibilityServicesLocked()
- && (userState.mIsDisplayMagnificationEnabled
- || userState.mIsNavBarMagnificationEnabled
- || userHasListeningMagnificationServicesLocked(userState))) {
- // Initialize the magnification controller if necessary
- getMagnificationController();
- mMagnificationController.register();
- } else if (mMagnificationController != null) {
- mMagnificationController.unregister();
+ if (mUiAutomationManager.suppressingAccessibilityServicesLocked()
+ && mMagnificationController != null) {
+ mMagnificationController.unregisterAll();
+ return;
+ }
+
+ // register all display if global magnification is enabled.
+ final Display[] displays = mDisplayManager.getDisplays();
+ if (userState.mIsDisplayMagnificationEnabled
+ || userState.mIsNavBarMagnificationEnabled) {
+ for (int i = 0; i < displays.length; i++) {
+ final Display display = displays[i];
+ // Overlay display uses overlay window to simulate secondary displays in
+ // one display. It's not a real display and there's no input events for it.
+ // We should ignore it.
+ if (display.getType() == Display.TYPE_OVERLAY) {
+ continue;
+ }
+ getMagnificationController().register(display.getDisplayId());
+ }
+ return;
+ }
+
+ // register if display has listening magnification services.
+ for (int i = 0; i < displays.length; i++) {
+ final Display display = displays[i];
+ // Overlay display uses overlay window to simulate secondary displays in
+ // one display. It's not a real display and there's no input events for it.
+ // We should ignore it.
+ if (display.getType() == Display.TYPE_OVERLAY) {
+ continue;
+ }
+ final int displayId = display.getDisplayId();
+ if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
+ getMagnificationController().register(displayId);
+ } else if (mMagnificationController != null) {
+ mMagnificationController.unregister(displayId);
+ }
}
}
@@ -2222,12 +2280,13 @@
* Returns whether the specified user has any services that are capable of
* controlling magnification and are actively listening for magnification updates.
*/
- private boolean userHasListeningMagnificationServicesLocked(UserState userState) {
+ private boolean userHasListeningMagnificationServicesLocked(UserState userState,
+ int displayId) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
final AccessibilityServiceConnection service = services.get(i);
if (mSecurityPolicy.canControlMagnification(service)
- && service.isMagnificationCallbackEnabled()) {
+ && service.isMagnificationCallbackEnabled(displayId)) {
return true;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 86132a8..a19a847 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -109,7 +109,7 @@
UserState userState = mUserStateWeakReference.get();
if (userState == null) return;
userState.removeServiceLocked(this);
- mSystemSupport.getMagnificationController().resetIfNeeded(mId);
+ mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
resetLocked();
}
@@ -256,7 +256,7 @@
userState.serviceDisconnectedLocked(this);
}
resetLocked();
- mSystemSupport.getMagnificationController().resetIfNeeded(mId);
+ mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
mSystemSupport.onClientChangeLocked(false);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 6a97fbb..e784056 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -19,7 +19,6 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,6 +31,7 @@
import android.text.TextUtils;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.MagnificationSpec;
import android.view.View;
@@ -39,6 +39,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerInternal;
@@ -68,9 +69,7 @@
private final Object mLock;
- private final AccessibilityManagerService mAms;
-
- private final SettingsBridge mSettingsBridge;
+ private final ControllerContext mControllerCtx;
private final ScreenStateObserver mScreenStateObserver;
@@ -78,11 +77,9 @@
private final long mMainThreadId;
- private Handler mHandler;
-
- private final WindowManagerInternal mWindowManager;
-
- private final DisplayMagnification mDisplay;
+ /** List of display Magnification, mapping from displayId -> DisplayMagnification. */
+ @GuardedBy("mLock")
+ private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -107,46 +104,82 @@
// Flag indicating that we are registered with window manager.
private boolean mRegistered;
private boolean mUnregisterPending;
+ private boolean mDeleteAfterUnregister;
private final int mDisplayId;
private static final int INVALID_ID = -1;
private int mIdOfLastServiceToMagnify = INVALID_ID;
-
- DisplayMagnification(int displayId, SpecAnimationBridge specAnimation) {
+ DisplayMagnification(int displayId) {
mDisplayId = displayId;
- mSpecAnimationBridge = specAnimation;
+ mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
}
- void register() {
- synchronized (mLock) {
- if (!mRegistered) {
- mWindowManager.setMagnificationCallbacks(this);
- mSpecAnimationBridge.setEnabled(true);
- // Obtain initial state.
- mWindowManager.getMagnificationRegion(mMagnificationRegion);
- mMagnificationRegion.getBounds(mMagnificationBounds);
- mRegistered = true;
- }
+ /**
+ * Registers magnification callback and get current magnification region from
+ * window manager.
+ *
+ * @return true if callback registers successful.
+ */
+ @GuardedBy("mLock")
+ boolean register() {
+ mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks(
+ mDisplayId, this);
+ if (!mRegistered) {
+ Slog.w(LOG_TAG, "set magnification callbacks fail, displayId:" + mDisplayId);
+ return false;
}
+ mSpecAnimationBridge.setEnabled(true);
+ // Obtain initial state.
+ mControllerCtx.getWindowManager().getMagnificationRegion(
+ mDisplayId, mMagnificationRegion);
+ mMagnificationRegion.getBounds(mMagnificationBounds);
+ return true;
}
- void unregister() {
- synchronized (mLock) {
- if (!isMagnifying()) {
- unregisterInternalLocked();
- } else {
- mUnregisterPending = true;
- reset(true);
- }
+ /**
+ * Unregisters magnification callback from window manager. Callbacks to
+ * {@link MagnificationController#unregisterCallbackLocked(int, boolean)} after
+ * unregistered.
+ *
+ * @param delete true if this instance should be removed from the SparseArray in
+ * MagnificationController after unregistered, for example, display removed.
+ */
+ @GuardedBy("mLock")
+ void unregister(boolean delete) {
+ if (mRegistered) {
+ mSpecAnimationBridge.setEnabled(false);
+ mControllerCtx.getWindowManager().setMagnificationCallbacks(
+ mDisplayId, null);
+ mMagnificationRegion.setEmpty();
+ mRegistered = false;
+ unregisterCallbackLocked(mDisplayId, delete);
}
+ mUnregisterPending = false;
}
- boolean isRegisteredLocked() {
+ /**
+ * Reset magnification status with animation enabled. {@link #unregister(boolean)} will be
+ * called after animation finished.
+ *
+ * @param delete true if this instance should be removed from the SparseArray in
+ * MagnificationController after unregistered, for example, display removed.
+ */
+ @GuardedBy("mLock")
+ void unregisterPending(boolean delete) {
+ mDeleteAfterUnregister = delete;
+ mUnregisterPending = true;
+ reset(true);
+ }
+
+ boolean isRegistered() {
return mRegistered;
}
+ boolean isMagnifying() {
+ return mCurrentMagnificationSpec.scale > 1.0f;
+ }
float getScale() {
return mCurrentMagnificationSpec.scale;
@@ -156,18 +189,20 @@
return mCurrentMagnificationSpec.offsetX;
}
- float getCenterX() {
- synchronized (mLock) {
- return (mMagnificationBounds.width() / 2.0f
- + mMagnificationBounds.left - getOffsetX()) / getScale();
- }
+ float getOffsetY() {
+ return mCurrentMagnificationSpec.offsetY;
}
+ @GuardedBy("mLock")
+ float getCenterX() {
+ return (mMagnificationBounds.width() / 2.0f
+ + mMagnificationBounds.left - getOffsetX()) / getScale();
+ }
+
+ @GuardedBy("mLock")
float getCenterY() {
- synchronized (mLock) {
- return (mMagnificationBounds.height() / 2.0f
- + mMagnificationBounds.top - getOffsetY()) / getScale();
- }
+ return (mMagnificationBounds.height() / 2.0f
+ + mMagnificationBounds.top - getOffsetY()) / getScale();
}
/**
@@ -203,64 +238,35 @@
return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
}
- boolean resetIfNeeded(boolean animate) {
- synchronized (mLock) {
- if (isMagnifying()) {
- reset(animate);
- return true;
- }
- return false;
- }
- }
-
- float getOffsetY() {
- return mCurrentMagnificationSpec.offsetY;
- }
-
- boolean isMagnifying() {
- return mCurrentMagnificationSpec.scale > 1.0f;
- }
-
- void unregisterInternalLocked() {
- if (mRegistered) {
- mSpecAnimationBridge.setEnabled(false);
- mWindowManager.setMagnificationCallbacks(null);
- mMagnificationRegion.setEmpty();
-
- mRegistered = false;
- }
- mUnregisterPending = false;
- }
-
-
@Override
public void onMagnificationRegionChanged(Region magnificationRegion) {
final Message m = PooledLambda.obtainMessage(
- DisplayMagnification.this::updateMagnificationRegion,
+ DisplayMagnification::updateMagnificationRegion, this,
Region.obtain(magnificationRegion));
- mHandler.sendMessage(m);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
final Message m = PooledLambda.obtainMessage(
- DisplayMagnification.this::requestRectangleOnScreen, left, top, right, bottom);
- mHandler.sendMessage(m);
+ DisplayMagnification::requestRectangleOnScreen, this,
+ left, top, right, bottom);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onRotationChanged(int rotation) {
// Treat as context change and reset
- final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded,
- true);
- mHandler.sendMessage(m);
+ final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
+ MagnificationController.this, mDisplayId, true);
+ mControllerCtx.getHandler().sendMessage(m);
}
@Override
public void onUserContextChanged() {
- final Message m = PooledLambda.obtainMessage(DisplayMagnification.this::resetIfNeeded,
- true);
- mHandler.sendMessage(m);
+ final Message m = PooledLambda.obtainMessage(MagnificationController::resetIfNeeded,
+ MagnificationController.this, mDisplayId, true);
+ mControllerCtx.getHandler().sendMessage(m);
}
/**
@@ -298,8 +304,9 @@
mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
} else {
final Message m = PooledLambda.obtainMessage(
- this.mSpecAnimationBridge::updateSentSpecMainThread, spec, animate);
- mHandler.sendMessage(m);
+ SpecAnimationBridge::updateSentSpecMainThread,
+ mSpecAnimationBridge, spec, animate);
+ mControllerCtx.getHandler().sendMessage(m);
}
}
@@ -313,30 +320,26 @@
}
void onMagnificationChangedLocked() {
- mAms.notifyMagnificationChanged(mMagnificationRegion,
+ mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, mMagnificationRegion,
getScale(), getCenterX(), getCenterY());
if (mUnregisterPending && !isMagnifying()) {
- unregisterInternalLocked();
+ unregister(mDeleteAfterUnregister);
}
}
+ @GuardedBy("mLock")
boolean magnificationRegionContains(float x, float y) {
- synchronized (mLock) {
- return mMagnificationRegion.contains((int) x, (int) y);
-
- }
+ return mMagnificationRegion.contains((int) x, (int) y);
}
+ @GuardedBy("mLock")
void getMagnificationBounds(@NonNull Rect outBounds) {
- synchronized (mLock) {
- outBounds.set(mMagnificationBounds);
- }
+ outBounds.set(mMagnificationBounds);
}
+ @GuardedBy("mLock")
void getMagnificationRegion(@NonNull Region outRegion) {
- synchronized (mLock) {
- outRegion.set(mMagnificationRegion);
- }
+ outRegion.set(mMagnificationRegion);
}
void requestRectangleOnScreen(int left, int top, int right, int bottom) {
@@ -392,94 +395,76 @@
outFrame.scale(1.0f / scale);
}
- /**
- * Resets magnification if last magnifying service is disabled.
- *
- * @param connectionId the connection ID be disabled.
- * @return {@code true} on success, {@code false} on failure
- */
- boolean resetIfNeeded(int connectionId) {
- if (mIdOfLastServiceToMagnify == connectionId) {
- return resetIfNeeded(true /*animate*/);
- }
- return false;
- }
-
+ @GuardedBy("mLock")
void setForceShowMagnifiableBounds(boolean show) {
if (mRegistered) {
- mWindowManager.setForceShowMagnifiableBounds(show);
+ mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
+ mDisplayId, show);
}
}
+ @GuardedBy("mLock")
boolean reset(boolean animate) {
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- final MagnificationSpec spec = mCurrentMagnificationSpec;
- final boolean changed = !spec.isNop();
- if (changed) {
- spec.clear();
- onMagnificationChangedLocked();
- }
- mIdOfLastServiceToMagnify = INVALID_ID;
- sendSpecToAnimation(spec, animate);
- return changed;
+ if (!mRegistered) {
+ return false;
}
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final boolean changed = !spec.isNop();
+ if (changed) {
+ spec.clear();
+ onMagnificationChangedLocked();
+ }
+ mIdOfLastServiceToMagnify = INVALID_ID;
+ sendSpecToAnimation(spec, animate);
+ return changed;
}
-
+ @GuardedBy("mLock")
boolean setScale(float scale, float pivotX, float pivotY,
boolean animate, int id) {
-
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- // Constrain scale immediately for use in the pivot calculations.
- scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
-
- final Rect viewport = mTempRect;
- mMagnificationRegion.getBounds(viewport);
- final MagnificationSpec spec = mCurrentMagnificationSpec;
- final float oldScale = spec.scale;
- final float oldCenterX
- = (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
- final float oldCenterY
- = (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
- final float normPivotX = (pivotX - spec.offsetX) / oldScale;
- final float normPivotY = (pivotY - spec.offsetY) / oldScale;
- final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
- final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
- final float centerX = normPivotX + offsetX;
- final float centerY = normPivotY + offsetY;
- mIdOfLastServiceToMagnify = id;
-
- return setScaleAndCenter(scale, centerX, centerY, animate, id);
+ if (!mRegistered) {
+ return false;
}
+ // Constrain scale immediately for use in the pivot calculations.
+ scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+
+ final Rect viewport = mTempRect;
+ mMagnificationRegion.getBounds(viewport);
+ final MagnificationSpec spec = mCurrentMagnificationSpec;
+ final float oldScale = spec.scale;
+ final float oldCenterX =
+ (viewport.width() / 2.0f - spec.offsetX + viewport.left) / oldScale;
+ final float oldCenterY =
+ (viewport.height() / 2.0f - spec.offsetY + viewport.top) / oldScale;
+ final float normPivotX = (pivotX - spec.offsetX) / oldScale;
+ final float normPivotY = (pivotY - spec.offsetY) / oldScale;
+ final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
+ final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
+ final float centerX = normPivotX + offsetX;
+ final float centerY = normPivotY + offsetY;
+ mIdOfLastServiceToMagnify = id;
+ return setScaleAndCenter(scale, centerX, centerY, animate, id);
}
+ @GuardedBy("mLock")
boolean setScaleAndCenter(float scale, float centerX, float centerY,
boolean animate, int id) {
-
- synchronized (mLock) {
- if (!mRegistered) {
- return false;
- }
- if (DEBUG) {
- Slog.i(LOG_TAG,
- "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
- + ", centerY = " + centerY + ", animate = " + animate
- + ", id = " + id
- + ")");
- }
- final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
- sendSpecToAnimation(mCurrentMagnificationSpec, animate);
- if (isMagnifying() && (id != INVALID_ID)) {
- mIdOfLastServiceToMagnify = id;
- }
- return changed;
+ if (!mRegistered) {
+ return false;
}
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+ + ", centerY = " + centerY + ", animate = " + animate
+ + ", id = " + id
+ + ")");
+ }
+ final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
+ sendSpecToAnimation(mCurrentMagnificationSpec, animate);
+ if (isMagnifying() && (id != INVALID_ID)) {
+ mIdOfLastServiceToMagnify = id;
+ }
+ return changed;
}
/**
@@ -527,22 +512,21 @@
return changed;
}
+ @GuardedBy("mLock")
void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
- synchronized (mLock) {
- if (!mRegistered) {
- return;
- }
-
- final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
- final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
- onMagnificationChangedLocked();
- }
- if (id != INVALID_ID) {
- mIdOfLastServiceToMagnify = id;
- }
- sendSpecToAnimation(mCurrentMagnificationSpec, false);
+ if (!mRegistered) {
+ return;
}
+
+ final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+ final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+ if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+ onMagnificationChangedLocked();
+ }
+ if (id != INVALID_ID) {
+ mIdOfLastServiceToMagnify = id;
+ }
+ sendSpecToAnimation(mCurrentMagnificationSpec, false);
}
boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
@@ -593,44 +577,38 @@
@Override
public String toString() {
- return "DisplayMagnification{" +
- "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec +
- ", mMagnificationRegion=" + mMagnificationRegion +
- ", mMagnificationBounds=" + mMagnificationBounds +
- ", mDisplayId=" + mDisplayId +
- ", mUserId=" + mUserId +
- ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify +
- ", mRegistered=" + mRegistered +
- ", mUnregisterPending=" + mUnregisterPending +
- '}';
+ return "DisplayMagnification["
+ + "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec
+ + ", mMagnificationRegion=" + mMagnificationRegion
+ + ", mMagnificationBounds=" + mMagnificationBounds
+ + ", mDisplayId=" + mDisplayId
+ + ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify
+ + ", mRegistered=" + mRegistered
+ + ", mUnregisterPending=" + mUnregisterPending
+ + ']';
}
-
}
- public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) {
- this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class),
- new ValueAnimator(), new SettingsBridge(context.getContentResolver()));
- mHandler = new Handler(context.getMainLooper());
+ /**
+ * MagnificationController Constructor
+ */
+ public MagnificationController(@NonNull Context context,
+ @NonNull AccessibilityManagerService ams, @NonNull Object lock) {
+ this(new ControllerContext(context, ams,
+ LocalServices.getService(WindowManagerInternal.class),
+ new Handler(context.getMainLooper()),
+ context.getResources().getInteger(R.integer.config_longAnimTime)), lock);
}
- public MagnificationController(
- Context context,
- AccessibilityManagerService ams,
- Object lock,
- Handler handler,
- WindowManagerInternal windowManagerInternal,
- ValueAnimator valueAnimator,
- SettingsBridge settingsBridge) {
- mHandler = handler;
- mWindowManager = windowManagerInternal;
- mMainThreadId = context.getMainLooper().getThread().getId();
- mAms = ams;
- mScreenStateObserver = new ScreenStateObserver(context, this);
+ /**
+ * Constructor for tests
+ */
+ @VisibleForTesting
+ public MagnificationController(@NonNull ControllerContext ctx, @NonNull Object lock) {
+ mControllerCtx = ctx;
mLock = lock;
- mSettingsBridge = settingsBridge;
- //TODO (multidisplay): Magnification is supported only for the default display.
- mDisplay = new DisplayMagnification(Display.DEFAULT_DISPLAY,
- new SpecAnimationBridge(context, mLock, mWindowManager, valueAnimator));
+ mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
+ mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
}
/**
@@ -639,54 +617,114 @@
*
* This tracking imposes a cost on the system, so we avoid tracking this data unless it's
* required.
+ *
+ * @param displayId The logical display id.
*/
- public void register() {
+ public void register(int displayId) {
synchronized (mLock) {
- mScreenStateObserver.register();
+ DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ display = new DisplayMagnification(displayId);
+ }
+ if (display.isRegistered()) {
+ return;
+ }
+ if (display.register()) {
+ mDisplays.put(displayId, display);
+ mScreenStateObserver.registerIfNecessary();
+ }
}
- mDisplay.register();
}
/**
* Stop requiring tracking the magnification region. We may remain registered while we
* reset magnification.
- */
- public void unregister() {
- synchronized (mLock) {
- mScreenStateObserver.unregister();
- }
- mDisplay.unregister();
- }
-
- /**
- * Check if we are registered. Note that we may be planning to unregister at any moment.
*
- * @return {@code true} if the controller is registered. {@code false} otherwise.
+ * @param displayId The logical display id.
*/
- public boolean isRegisteredLocked() {
- return mDisplay.isRegisteredLocked();
+ public void unregister(int displayId) {
+ synchronized (mLock) {
+ unregisterLocked(displayId, false);
+ }
}
/**
+ * Stop tracking all displays' magnification region.
+ */
+ public void unregisterAll() {
+ synchronized (mLock) {
+ // display will be removed from array after unregister, we need to clone it to
+ // prevent error.
+ final SparseArray<DisplayMagnification> displays = mDisplays.clone();
+ for (int i = 0; i < displays.size(); i++) {
+ unregisterLocked(displays.keyAt(i), false);
+ }
+ }
+ }
+
+ /**
+ * Remove the display magnification with given id.
+ *
+ * @param displayId The logical display id.
+ */
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mLock) {
+ unregisterLocked(displayId, true);
+ }
+ }
+
+ /**
+ * Check if we are registered on specified display. Note that we may be planning to unregister
+ * at any moment.
+ *
+ * @return {@code true} if the controller is registered on specified display.
+ * {@code false} otherwise.
+ *
+ * @param displayId The logical display id.
+ */
+ public boolean isRegistered(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.isRegistered();
+ }
+ }
+
+ /**
+ * @param displayId The logical display id.
* @return {@code true} if magnification is active, e.g. the scale
* is > 1, {@code false} otherwise
*/
- public boolean isMagnifying() {
- return mDisplay.isMagnifying();
+ public boolean isMagnifying(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.isMagnifying();
+ }
}
/**
* Returns whether the magnification region contains the specified
* screen-relative coordinates.
*
+ * @param displayId The logical display id.
* @param x the screen-relative X coordinate to check
* @param y the screen-relative Y coordinate to check
* @return {@code true} if the coordinate is contained within the
* magnified region, or {@code false} otherwise
*/
- public boolean magnificationRegionContains(float x, float y) {
- return mDisplay.magnificationRegionContains(x, y);
-
+ public boolean magnificationRegionContains(int displayId, float x, float y) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.magnificationRegionContains(x, y);
+ }
}
/**
@@ -694,11 +732,18 @@
* magnification region. If magnification is not enabled, the returned
* bounds will be empty.
*
+ * @param displayId The logical display id.
* @param outBounds rect to populate with the bounds of the magnified
* region
*/
- public void getMagnificationBounds(@NonNull Rect outBounds) {
- mDisplay.getMagnificationBounds(outBounds);
+ public void getMagnificationBounds(int displayId, @NonNull Rect outBounds) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.getMagnificationBounds(outBounds);
+ }
}
/**
@@ -706,76 +751,122 @@
* region. If magnification is not enabled, then the returned region
* will be empty.
*
+ * @param displayId The logical display id.
* @param outRegion the region to populate
*/
- public void getMagnificationRegion(@NonNull Region outRegion) {
- mDisplay.getMagnificationRegion(outRegion);
+ public void getMagnificationRegion(int displayId, @NonNull Region outRegion) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.getMagnificationRegion(outRegion);
+ }
}
/**
* Returns the magnification scale. If an animation is in progress,
* this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the scale
*/
- public float getScale() {
- return mDisplay.getScale();
+ public float getScale(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 1.0f;
+ }
+ return display.getScale();
+ }
}
/**
* Returns the X offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the X offset
*/
- public float getOffsetX() {
- return mDisplay.getOffsetX();
+ public float getOffsetX(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getOffsetX();
+ }
}
-
/**
* Returns the screen-relative X coordinate of the center of the
* magnification viewport.
*
+ * @param displayId The logical display id.
* @return the X coordinate
*/
- public float getCenterX() {
- return mDisplay.getCenterX();
+ public float getCenterX(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getCenterX();
+ }
}
/**
* Returns the Y offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
*
+ * @param displayId The logical display id.
* @return the Y offset
*/
- public float getOffsetY() {
- return mDisplay.getOffsetY();
+ public float getOffsetY(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getOffsetY();
+ }
}
/**
* Returns the screen-relative Y coordinate of the center of the
* magnification viewport.
*
+ * @param displayId The logical display id.
* @return the Y coordinate
*/
- public float getCenterY() {
- return mDisplay.getCenterY();
+ public float getCenterY(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return 0.0f;
+ }
+ return display.getCenterY();
+ }
}
/**
* Resets the magnification scale and center, optionally animating the
* transition.
*
+ * @param displayId The logical display id.
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean reset(boolean animate) {
-
- return mDisplay.reset(animate);
-
+ public boolean reset(int displayId, boolean animate) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.reset(animate);
+ }
}
/**
@@ -783,6 +874,7 @@
* optionally animating the transition. If animation is disabled, the
* transition is immediate.
*
+ * @param displayId The logical display id.
* @param scale the target scale, must be >= 1
* @param pivotX the screen-relative X coordinate around which to scale
* @param pivotY the screen-relative Y coordinate around which to scale
@@ -792,15 +884,22 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setScale(float scale, float pivotX, float pivotY, boolean animate, int id) {
- return mDisplay.
- setScale(scale, pivotX, pivotY, animate, id);
+ public boolean setScale(int displayId, float scale, float pivotX, float pivotY,
+ boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScale(scale, pivotX, pivotY, animate, id);
+ }
}
/**
* Sets the center of the magnified region, optionally animating the
* transition. If animation is disabled, the transition is immediate.
*
+ * @param displayId The logical display id.
* @param centerX the screen-relative X coordinate around which to
* center
* @param centerY the screen-relative Y coordinate around which to
@@ -811,9 +910,14 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setCenter(float centerX, float centerY, boolean animate, int id) {
- return mDisplay.
- setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+ public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScaleAndCenter(Float.NaN, centerX, centerY, animate, id);
+ }
}
/**
@@ -821,6 +925,7 @@
* animating the transition. If animation is disabled, the transition
* is immediate.
*
+ * @param displayId The logical display id.
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
@@ -832,53 +937,66 @@
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
- public boolean setScaleAndCenter(
- float scale, float centerX, float centerY, boolean animate, int id) {
- return mDisplay.
- setScaleAndCenter(scale, centerX, centerY, animate, id);
+ public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+ boolean animate, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return false;
+ }
+ return display.setScaleAndCenter(scale, centerX, centerY, animate, id);
+ }
}
/**
* Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
* opposite direction as the offsets passed in here.
*
+ * @param displayId The logical display id.
* @param offsetX the amount in pixels to offset the region in the X direction, in current
* screen pixels.
* @param offsetY the amount in pixels to offset the region in the Y direction, in current
* screen pixels.
* @param id the ID of the service requesting the change
*/
- public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
- mDisplay.offsetMagnifiedRegion(offsetX, offsetY,
- id);
+ public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.offsetMagnifiedRegion(offsetX, offsetY, id);
+ }
}
/**
* Get the ID of the last service that changed the magnification spec.
*
+ * @param displayId The logical display id.
* @return The id
*/
- public int getIdOfLastServiceToMagnify() {
- return mDisplay.getIdOfLastServiceToMagnify();
+ public int getIdOfLastServiceToMagnify(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return -1;
+ }
+ return display.getIdOfLastServiceToMagnify();
+ }
}
/**
- * Persists the current magnification scale to the current user's settings.
+ * Persists the default display magnification scale to the current user's settings.
*/
public void persistScale() {
- persistScale(Display.DEFAULT_DISPLAY);
- }
- /**
- * Persists the current magnification scale to the current user's settings.
- */
- public void persistScale(int displayId) {
- final float scale = mDisplay.getScale();
+ // TODO: b/123047354, Need support multi-display?
+ final float scale = getScale(Display.DEFAULT_DISPLAY);
final int userId = mUserId;
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
- mSettingsBridge.putMagnificationScale(scale, displayId, userId);
+ mControllerCtx.putMagnificationScale(scale, userId);
return null;
}
}.execute();
@@ -892,7 +1010,7 @@
* scale if none is available
*/
public float getPersistedScale() {
- return mSettingsBridge.getMagnificationScale(Display.DEFAULT_DISPLAY, mUserId);
+ return mControllerCtx.getMagnificationScale(mUserId);
}
/**
@@ -901,50 +1019,136 @@
* @param userId the currently active user ID
*/
public void setUserId(int userId) {
- if (mUserId != userId) {
- mUserId = userId;
+ if (mUserId == userId) {
+ return;
+ }
+ mUserId = userId;
+ resetAllIfNeeded(false);
+ }
- synchronized (mLock) {
- if (isMagnifying()) {
- reset(false);
- }
+ /**
+ * Resets all displays' magnification if last magnifying service is disabled.
+ *
+ * @param connectionId
+ */
+ public void resetAllIfNeeded(int connectionId) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ resetIfNeeded(mDisplays.keyAt(i), connectionId);
}
}
}
- /**
+ /**
* Resets magnification if magnification and auto-update are both enabled.
*
+ * @param displayId The logical display id.
* @param animate whether the animate the transition
- * @return whether was {@link #isMagnifying magnifying}
+ * @return whether was {@link #isMagnifying(int) magnifying}
*/
- public boolean resetIfNeeded(boolean animate) {
- return mDisplay.resetIfNeeded(animate);
+ boolean resetIfNeeded(int displayId, boolean animate) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null || !display.isMagnifying()) {
+ return false;
+ }
+ display.reset(animate);
+ return true;
+ }
}
/**
* Resets magnification if last magnifying service is disabled.
*
+ * @param displayId The logical display id.
* @param connectionId the connection ID be disabled.
* @return {@code true} on success, {@code false} on failure
*/
- public boolean resetIfNeeded(int connectionId) {
- return mDisplay.resetIfNeeded(connectionId);
+ boolean resetIfNeeded(int displayId, int connectionId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null || !display.isMagnifying()
+ || connectionId != display.getIdOfLastServiceToMagnify()) {
+ return false;
+ }
+ display.reset(true);
+ return true;
+ }
}
- void setForceShowMagnifiableBounds(boolean show) {
- mDisplay.setForceShowMagnifiableBounds(show);
+ void setForceShowMagnifiableBounds(int displayId, boolean show) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.setForceShowMagnifiableBounds(show);
+ }
}
private void onScreenTurnedOff() {
final Message m = PooledLambda.obtainMessage(
- mDisplay::resetIfNeeded, false);
- mHandler.sendMessage(m);
+ MagnificationController::resetAllIfNeeded, this, false);
+ mControllerCtx.getHandler().sendMessage(m);
+ }
+
+ private void resetAllIfNeeded(boolean animate) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ resetIfNeeded(mDisplays.keyAt(i), animate);
+ }
+ }
+ }
+
+ private void unregisterLocked(int displayId, boolean delete) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ if (!display.isRegistered()) {
+ if (delete) {
+ mDisplays.remove(displayId);
+ }
+ return;
+ }
+ if (!display.isMagnifying()) {
+ display.unregister(delete);
+ } else {
+ display.unregisterPending(delete);
+ }
+ }
+
+ /**
+ * Callbacks from DisplayMagnification after display magnification unregistered. It will remove
+ * DisplayMagnification instance if delete is true, and unregister screen state if
+ * there is no registered display magnification.
+ */
+ private void unregisterCallbackLocked(int displayId, boolean delete) {
+ if (delete) {
+ mDisplays.remove(displayId);
+ }
+ // unregister screen state if necessary
+ boolean hasRegister = false;
+ for (int i = 0; i < mDisplays.size(); i++) {
+ final DisplayMagnification display = mDisplays.valueAt(i);
+ hasRegister = display.isRegistered();
+ if (hasRegister) {
+ break;
+ }
+ }
+ if (!hasRegister) {
+ mScreenStateObserver.unregister();
+ }
}
@Override
public String toString() {
- return mDisplay.toString();
+ StringBuilder builder = new StringBuilder();
+ builder.append("MagnificationController[");
+ builder.append("mUserId=").append(mUserId);
+ builder.append(", mDisplays=").append(mDisplays);
+ builder.append("]");
+ return builder.toString();
}
/**
@@ -952,7 +1156,7 @@
* updates to the window manager.
*/
private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
- private final WindowManagerInternal mWindowManager;
+ private final ControllerContext mControllerCtx;
/**
* The magnification spec that was sent to the window manager. This should
@@ -973,16 +1177,17 @@
private final Object mLock;
+ private final int mDisplayId;
+
@GuardedBy("mLock")
private boolean mEnabled = false;
- private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm,
- ValueAnimator animator) {
+ private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
+ mControllerCtx = ctx;
mLock = lock;
- mWindowManager = wm;
- final long animationDuration = context.getResources().getInteger(
- R.integer.config_longAnimTime);
- mValueAnimator = animator;
+ mDisplayId = displayId;
+ final long animationDuration = mControllerCtx.getAnimationDuration();
+ mValueAnimator = mControllerCtx.newValueAnimator();
mValueAnimator.setDuration(animationDuration);
mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
mValueAnimator.setFloatValues(0.0f, 1.0f);
@@ -999,7 +1204,8 @@
mEnabled = enabled;
if (!mEnabled) {
mSentMagnificationSpec.clear();
- mWindowManager.setMagnificationSpec(mSentMagnificationSpec);
+ mControllerCtx.getWindowManager().setMagnificationSpec(
+ mDisplayId, mSentMagnificationSpec);
}
}
}
@@ -1031,7 +1237,8 @@
}
mSentMagnificationSpec.setTo(spec);
- mWindowManager.setMagnificationSpec(spec);
+ mControllerCtx.getWindowManager().setMagnificationSpec(
+ mDisplayId, mSentMagnificationSpec);
}
}
@@ -1054,9 +1261,7 @@
mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
(mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
* fract;
- synchronized (mLock) {
- setMagnificationSpecLocked(mTmpMagnificationSpec);
- }
+ setMagnificationSpecLocked(mTmpMagnificationSpec);
}
}
}
@@ -1072,7 +1277,7 @@
mController = controller;
}
- public void register() {
+ public void registerIfNecessary() {
if (!mRegistered) {
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
mRegistered = true;
@@ -1092,26 +1297,97 @@
}
}
- // Extra class to get settings so tests can mock it
- public static class SettingsBridge {
- private final ContentResolver mContentResolver;
+ /**
+ * This class holds resources used between the classes in MagnificationController, and
+ * functions for tests to mock it.
+ */
+ @VisibleForTesting
+ public static class ControllerContext {
+ private final Context mContext;
+ private final AccessibilityManagerService mAms;
+ private final WindowManagerInternal mWindowManager;
+ private final Handler mHandler;
+ private final Long mAnimationDuration;
- public SettingsBridge(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
+ /**
+ * Constructor for ControllerContext.
+ */
+ public ControllerContext(@NonNull Context context,
+ @NonNull AccessibilityManagerService ams,
+ @NonNull WindowManagerInternal windowManager,
+ @NonNull Handler handler,
+ long animationDuration) {
+ mContext = context;
+ mAms = ams;
+ mWindowManager = windowManager;
+ mHandler = handler;
+ mAnimationDuration = animationDuration;
}
- public void putMagnificationScale(float value, int displayId, int userId) {
- Settings.Secure.putFloatForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE + (
- Display.DEFAULT_DISPLAY == displayId ? "" : displayId),
- value, userId);
+ /**
+ * @return A context.
+ */
+ @NonNull
+ public Context getContext() {
+ return mContext;
}
- public float getMagnificationScale(int displayId, int userId) {
- return Settings.Secure.getFloatForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE
- + (Display.DEFAULT_DISPLAY == displayId ? "" : displayId),
+ /**
+ * @return AccessibilityManagerService
+ */
+ @NonNull
+ public AccessibilityManagerService getAms() {
+ return mAms;
+ }
+
+ /**
+ * @return WindowManagerInternal
+ */
+ @NonNull
+ public WindowManagerInternal getWindowManager() {
+ return mWindowManager;
+ }
+
+ /**
+ * @return Handler for main looper
+ */
+ @NonNull
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Create a new ValueAnimator.
+ *
+ * @return ValueAnimator
+ */
+ @NonNull
+ public ValueAnimator newValueAnimator() {
+ return new ValueAnimator();
+ }
+
+ /**
+ * Write Settings of magnification scale.
+ */
+ public void putMagnificationScale(float value, int userId) {
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
+ }
+
+ /**
+ * Get Settings of magnification scale.
+ */
+ public float getMagnificationScale(int userId) {
+ return Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
DEFAULT_MAGNIFICATION_SCALE, userId);
}
+
+ /**
+ * @return Configuration of animation duration.
+ */
+ public long getAnimationDuration() {
+ return mAnimationDuration;
+ }
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 80049e8..49db488 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -43,6 +43,7 @@
import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
+import android.view.Display;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
@@ -251,14 +252,16 @@
mScreenStateReceiver.unregister();
}
// Check if need to reset when MagnificationGestureHandler is the last magnifying service.
- mMagnificationController.resetIfNeeded(
+ mMagnificationController.resetAllIfNeeded(
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
clearAndTransitionToStateDetecting();
}
void notifyShortcutTriggered() {
if (mDetectShortcutTrigger) {
- boolean wasMagnifying = mMagnificationController.resetIfNeeded(/* animate */ true);
+ // TODO: multi-display support for magnification gesture handler
+ boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY,
+ /* animate */ true);
if (wasMagnifying) {
clearAndTransitionToStateDetecting();
} else {
@@ -419,8 +422,9 @@
Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
- mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX,
+ distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* event consumed: */ true;
}
@@ -436,7 +440,8 @@
return mScaling;
}
- final float initialScale = mMagnificationController.getScale();
+ // TODO: multi-display support for magnification gesture handler
+ final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY);
final float targetScale = initialScale * detector.getScaleFactor();
// Don't allow a gesture to move the user further outside the
@@ -458,7 +463,8 @@
final float pivotX = detector.getFocusX();
final float pivotY = detector.getFocusY();
if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
- mMagnificationController.setScale(scale, pivotX, pivotY, false,
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* handled: */ true;
}
@@ -518,8 +524,10 @@
}
final float eventX = event.getX();
final float eventY = event.getY();
- if (mMagnificationController.magnificationRegionContains(eventX, eventY)) {
- mMagnificationController.setCenter(eventX, eventY,
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.magnificationRegionContains(
+ Display.DEFAULT_DISPLAY, eventX, eventY)) {
+ mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY,
/* animate */ mLastMoveOutsideMagnifiedRegion,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mLastMoveOutsideMagnifiedRegion = false;
@@ -657,8 +665,9 @@
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+ // TODO: multi-display support for magnification gesture handler
if (!mMagnificationController.magnificationRegionContains(
- event.getX(), event.getY())) {
+ Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -667,11 +676,16 @@
// 3tap and hold
afterLongTapTimeoutTransitionToDraggingState(event);
+ } else if (isTapOutOfDistanceSlop()) {
+
+ transitionToDelegatingStateAndClear();
+
} else if (mDetectTripleTap
// If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
// to ensure reachability of
// STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
- || mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
afterMultiTapTimeoutTransitionToDelegatingState();
@@ -683,7 +697,8 @@
}
break;
case ACTION_POINTER_DOWN: {
- if (mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
transitionTo(mPanningScalingState);
clear();
} else {
@@ -712,8 +727,9 @@
mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
+ // TODO: multi-display support for magnification gesture handler
if (!mMagnificationController.magnificationRegionContains(
- event.getX(), event.getY())) {
+ Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -864,7 +880,8 @@
clear();
// Toggle zoom
- if (mMagnificationController.isMagnifying()) {
+ // TODO: multi-display support for magnification gesture handler
+ if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
zoomOff();
} else {
zoomOn(up.getX(), up.getY());
@@ -876,8 +893,9 @@
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
clear();
+ // TODO: multi-display support for magnification gesture handler
mViewportDraggingState.mZoomedInBeforeDrag =
- mMagnificationController.isMagnifying();
+ mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY);
zoomOn(down.getX(), down.getY());
@@ -904,7 +922,33 @@
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
mShortcutTriggered = state;
- mMagnificationController.setForceShowMagnifiableBounds(state);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state);
+ }
+
+ /**
+ * Detects if last action down is out of distance slop between with previous
+ * one, when triple tap is enabled.
+ *
+ * @return true if tap is out of distance slop
+ */
+ boolean isTapOutOfDistanceSlop() {
+ if (!mDetectTripleTap) return false;
+ if (mPreLastDown == null || mLastDown == null) {
+ return false;
+ }
+ final boolean outOfDistanceSlop =
+ GestureUtils.distance(mPreLastDown, mLastDown) > mMultiTapMaxDistance;
+ if (tapCount() > 0) {
+ return outOfDistanceSlop;
+ }
+ // There's no tap in the queue here. We still need to check if this is the case that
+ // user tap screen quickly and out of distance slop.
+ if (outOfDistanceSlop
+ && !GestureUtils.isTimedOut(mPreLastDown, mLastDown, mMultiTapMaxDelay)) {
+ return true;
+ }
+ return false;
}
}
@@ -914,7 +958,8 @@
final float scale = MathUtils.constrain(
mMagnificationController.getPersistedScale(),
MIN_SCALE, MAX_SCALE);
- mMagnificationController.setScaleAndCenter(
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY,
scale, centerX, centerY,
/* animate */ true,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -922,8 +967,8 @@
private void zoomOff() {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
-
- mMagnificationController.reset(/* animate */ true);
+ // TODO: multi-display support for magnification gesture handler
+ mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true);
}
private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7dfd8fe..9f7d33f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -261,6 +261,12 @@
private Runnable mAugmentedAutofillDestroyer;
/**
+ * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics.
+ */
+ @GuardedBy("mLock")
+ private ArrayList<LogMaker> mAugmentedRequestsLogs;
+
+ /**
* Receiver of assist data from the app's {@link Activity}.
*/
private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
@@ -2541,8 +2547,8 @@
}
mService.resetLastResponse();
- // The default autofill service cannot fullfill the request, let's check if the intelligence
- // service can.
+ // The default autofill service cannot fullfill the request, let's check if the augmented
+ // autofill service can.
mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked();
if (mAugmentedAutofillDestroyer == null) {
if (sVerbose) {
@@ -2605,8 +2611,10 @@
}
if (sVerbose) {
- Slog.v(TAG, "calling IntelligenseService on view " + mCurrentViewId
- + " using suggestion mode " + smartSuggestionFlagsToString(mode)
+ Slog.v(TAG, "calling Augmented Autofill Service ("
+ + remoteService.getComponentName().toShortString() + ") on view "
+ + mCurrentViewId + " using suggestion mode "
+ + smartSuggestionFlagsToString(mode)
+ " when server returned null for session " + this.id);
}
@@ -2620,8 +2628,15 @@
final AutofillValue currentValue = mViewStates.get(mCurrentViewId).getCurrentValue();
// TODO(b/111330312): we might need to add a new state in the AutofillManager to optimize
- // furgher AFM -> AFMS calls.
- // TODO(b/119638958): add CTS tests
+ // further AFM -> AFMS calls.
+
+ if (mAugmentedRequestsLogs == null) {
+ mAugmentedRequestsLogs = new ArrayList<>();
+ }
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST,
+ remoteService.getComponentName().getPackageName());
+ mAugmentedRequestsLogs.add(log);
+
remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, mCurrentViewId,
currentValue);
@@ -2912,6 +2927,11 @@
if (mAugmentedAutofillDestroyer != null) {
pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
}
+ if (mAugmentedRequestsLogs != null) {
+ pw.print(prefix); pw.print("number augmented requests: ");
+ pw.println(mAugmentedRequestsLogs.size());
+ }
+
mRemoteFillService.dump(prefix, pw);
}
@@ -3053,8 +3073,26 @@
mMetricsLogger.write(log);
}
}
- mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests));
+
+ final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0
+ : mAugmentedRequestsLogs.size();
+ if (totalAugmentedRequests > 0) {
+ if (sVerbose) {
+ Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests");
+ }
+ for (int i = 0; i < totalAugmentedRequests; i++) {
+ final LogMaker log = mAugmentedRequestsLogs.get(i);
+ mMetricsLogger.write(log);
+ }
+ }
+
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests);
+ if (totalAugmentedRequests > 0) {
+ log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS,
+ totalAugmentedRequests);
+ }
+ mMetricsLogger.write(log);
return mRemoteFillService;
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 0fa996e..fcd136c 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -64,6 +64,7 @@
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
@@ -1131,23 +1132,23 @@
final IBinder mListener;
final WorkSource mWorkSource;
final int mUid;
+ final int mCreatorUid;
final String mTag;
final BroadcastStats mBroadcastStats;
final FilterStats mFilterStats;
final int mAlarmType;
- InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener,
- WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag,
- long nowELAPSED) {
- mPendingIntent = pendingIntent;
+ InFlight(AlarmManagerService service, Alarm alarm, long nowELAPSED) {
+ mPendingIntent = alarm.operation;
mWhenElapsed = nowELAPSED;
- mListener = listener != null ? listener.asBinder() : null;
- mWorkSource = workSource;
- mUid = uid;
- mTag = tag;
- mBroadcastStats = (pendingIntent != null)
- ? service.getStatsLocked(pendingIntent)
- : service.getStatsLocked(uid, alarmPkg);
+ mListener = alarm.listener != null ? alarm.listener.asBinder() : null;
+ mWorkSource = alarm.workSource;
+ mUid = alarm.uid;
+ mCreatorUid = alarm.creatorUid;
+ mTag = alarm.statsTag;
+ mBroadcastStats = (alarm.operation != null)
+ ? service.getStatsLocked(alarm.operation)
+ : service.getStatsLocked(alarm.uid, alarm.packageName);
FilterStats fs = mBroadcastStats.filterStats.get(mTag);
if (fs == null) {
fs = new FilterStats(mBroadcastStats, mTag);
@@ -1155,7 +1156,7 @@
}
fs.lastTime = nowELAPSED;
mFilterStats = fs;
- mAlarmType = alarmType;
+ mAlarmType = alarm.type;
}
@Override
@@ -1165,6 +1166,7 @@
+ ", when=" + mWhenElapsed
+ ", workSource=" + mWorkSource
+ ", uid=" + mUid
+ + ", creatorUid=" + mCreatorUid
+ ", tag=" + mTag
+ ", broadcastStats=" + mBroadcastStats
+ ", filterStats=" + mFilterStats
@@ -3811,12 +3813,10 @@
/**
* Attribute blame for a WakeLock.
- * @param pi PendingIntent to attribute blame to if ws is null.
* @param ws WorkSource to attribute blame.
- * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender
+ * @param knownUid attribution uid; < 0 values are ignored.
*/
- void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag,
- int knownUid, boolean first) {
+ void setWakelockWorkSource(WorkSource ws, int knownUid, String tag, boolean first) {
try {
mWakeLock.setHistoryTag(first ? tag : null);
@@ -3825,11 +3825,8 @@
return;
}
- final int uid = (knownUid >= 0)
- ? knownUid
- : ActivityManager.getService().getUidForIntentSender(pi.getTarget());
- if (uid >= 0) {
- mWakeLock.setWorkSource(new WorkSource(uid));
+ if (knownUid >= 0) {
+ mWakeLock.setWorkSource(new WorkSource(knownUid));
return;
}
} catch (Exception e) {
@@ -3839,6 +3836,14 @@
mWakeLock.setWorkSource(null);
}
+ private static int getAlarmAttributionUid(Alarm alarm) {
+ if (alarm.workSource != null && !alarm.workSource.isEmpty()) {
+ return alarm.workSource.getAttributionUid();
+ }
+
+ return alarm.creatorUid;
+ }
+
@VisibleForTesting
class AlarmHandler extends Handler {
public static final int ALARM_EVENT = 1;
@@ -4285,8 +4290,8 @@
// the next of our alarms is now in flight. reattribute the wakelock.
if (mInFlight.size() > 0) {
InFlight inFlight = mInFlight.get(0);
- setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
- inFlight.mAlarmType, inFlight.mTag, -1, false);
+ setWakelockWorkSource(inFlight.mWorkSource, inFlight.mCreatorUid, inFlight.mTag,
+ false);
} else {
// should never happen
mLog.w("Alarm wakelock still held but sent queue empty");
@@ -4369,64 +4374,70 @@
*/
@GuardedBy("mLock")
public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
- if (alarm.operation != null) {
- // PendingIntent alarm
- mSendCount++;
+ final long workSourceToken = ThreadLocalWorkSource.setUid(
+ getAlarmAttributionUid(alarm));
+ try {
+ if (alarm.operation != null) {
+ // PendingIntent alarm
+ mSendCount++;
- try {
- alarm.operation.send(getContext(), 0,
- mBackgroundIntent.putExtra(
- Intent.EXTRA_ALARM_COUNT, alarm.count),
- mDeliveryTracker, mHandler, null,
- allowWhileIdle ? mIdleOptions : null);
- } catch (PendingIntent.CanceledException e) {
- if (alarm.repeatInterval > 0) {
- // This IntentSender is no longer valid, but this
- // is a repeating alarm, so toss it
- removeImpl(alarm.operation, null);
+ try {
+ alarm.operation.send(getContext(), 0,
+ mBackgroundIntent.putExtra(
+ Intent.EXTRA_ALARM_COUNT, alarm.count),
+ mDeliveryTracker, mHandler, null,
+ allowWhileIdle ? mIdleOptions : null);
+ } catch (PendingIntent.CanceledException e) {
+ if (alarm.repeatInterval > 0) {
+ // This IntentSender is no longer valid, but this
+ // is a repeating alarm, so toss it
+ removeImpl(alarm.operation, null);
+ }
+ // No actual delivery was possible, so the delivery tracker's
+ // 'finished' callback won't be invoked. We also don't need
+ // to do any wakelock or stats tracking, so we have nothing
+ // left to do here but go on to the next thing.
+ mSendFinishCount++;
+ return;
}
- // No actual delivery was possible, so the delivery tracker's
- // 'finished' callback won't be invoked. We also don't need
- // to do any wakelock or stats tracking, so we have nothing
- // left to do here but go on to the next thing.
- mSendFinishCount++;
- return;
- }
- } else {
- // Direct listener callback alarm
- mListenerCount++;
+ } else {
+ // Direct listener callback alarm
+ mListenerCount++;
- if (RECORD_ALARMS_IN_HISTORY) {
- if (alarm.listener == mTimeTickTrigger) {
- mTickHistory[mNextTickHistory++] = nowELAPSED;
- if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
- mNextTickHistory = 0;
+ if (RECORD_ALARMS_IN_HISTORY) {
+ if (alarm.listener == mTimeTickTrigger) {
+ mTickHistory[mNextTickHistory++] = nowELAPSED;
+ if (mNextTickHistory >= TICK_HISTORY_DEPTH) {
+ mNextTickHistory = 0;
+ }
}
}
- }
- try {
- if (DEBUG_LISTENER_CALLBACK) {
- Slog.v(TAG, "Alarm to uid=" + alarm.uid
- + " listener=" + alarm.listener.asBinder());
+ try {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.v(TAG, "Alarm to uid=" + alarm.uid
+ + " listener=" + alarm.listener.asBinder());
+ }
+ alarm.listener.doAlarm(this);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
+ alarm.listener.asBinder()),
+ mConstants.LISTENER_TIMEOUT);
+ } catch (Exception e) {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Alarm undeliverable to listener "
+ + alarm.listener.asBinder(), e);
+ }
+ // As in the PendingIntent.CanceledException case, delivery of the
+ // alarm was not possible, so we have no wakelock or timeout or
+ // stats management to do. It threw before we posted the delayed
+ // timeout message, so we're done here.
+ mListenerFinishCount++;
+ return;
}
- alarm.listener.doAlarm(this);
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
- alarm.listener.asBinder()),
- mConstants.LISTENER_TIMEOUT);
- } catch (Exception e) {
- if (DEBUG_LISTENER_CALLBACK) {
- Slog.i(TAG, "Alarm undeliverable to listener "
- + alarm.listener.asBinder(), e);
- }
- // As in the PendingIntent.CanceledException case, delivery of the
- // alarm was not possible, so we have no wakelock or timeout or
- // stats management to do. It threw before we posted the delayed
- // timeout message, so we're done here.
- mListenerFinishCount++;
- return;
}
+ } finally {
+ ThreadLocalWorkSource.restore(workSourceToken);
}
// The alarm is now in flight; now arrange wakelock and stats tracking
@@ -4434,15 +4445,11 @@
Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1));
}
if (mBroadcastRefCount == 0) {
- setWakelockWorkSource(alarm.operation, alarm.workSource,
- alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
- true);
+ setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true);
mWakeLock.acquire();
mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget();
}
- final InFlight inflight = new InFlight(AlarmManagerService.this,
- alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
- alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED);
+ final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED);
mInFlight.add(inflight);
mBroadcastRefCount++;
if (allowWhileIdle) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 919a5ab..fae7a8d 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3183,6 +3183,15 @@
return mMultinetworkPolicyTracker.getAvoidBadWifi();
}
+ @Override
+ public boolean getAvoidBadWifi() {
+ if (!checkNetworkStackPermission()) {
+ throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
+ }
+ return avoidBadWifi();
+ }
+
+
private void rematchForAvoidBadWifiUpdate() {
rematchAllNetworksAndRequests(null, 0);
for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index add5e5f..b3a0f64 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -46,6 +46,7 @@
import android.content.pm.Signature;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.hardware.location.ActivityRecognitionHardware;
import android.location.Address;
import android.location.Criteria;
import android.location.GeocoderParams;
@@ -66,7 +67,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -87,11 +87,11 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.location.AbstractLocationProvider;
+import com.android.server.location.ActivityRecognitionProxy;
import com.android.server.location.GeocoderProxy;
import com.android.server.location.GeofenceManager;
import com.android.server.location.GeofenceProxy;
@@ -246,7 +246,7 @@
public LocationManagerService(Context context) {
super();
mContext = context;
- mHandler = BackgroundThread.getHandler();
+ mHandler = FgThread.getHandler();
// Let the package manager query which are the default location
// providers as they get certain permissions granted by default.
@@ -736,6 +736,25 @@
Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
}
+ // bind to hardware activity recognition
+ boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported();
+ ActivityRecognitionHardware activityRecognitionHardware = null;
+ if (activityRecognitionHardwareIsSupported) {
+ activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext);
+ } else {
+ Slog.d(TAG, "Hardware Activity-Recognition not supported.");
+ }
+ ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
+ mContext,
+ activityRecognitionHardwareIsSupported,
+ activityRecognitionHardware,
+ com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
+ com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames);
+ if (proxy == null) {
+ Slog.d(TAG, "Unable to bind ActivityRecognitionProxy.");
+ }
+
String[] testProviderStrings = resources.getStringArray(
com.android.internal.R.array.config_testLocationProviders);
for (String testProviderString : testProviderStrings) {
@@ -954,7 +973,7 @@
public void onReportLocation(Location location) {
// no security check necessary because this is coming from an internal-only interface
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
handleLocationChangedLocked(location, this);
}
@@ -965,7 +984,7 @@
@Override
public void onReportLocation(List<Location> locations) {
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
@@ -991,7 +1010,7 @@
@Override
public void onSetEnabled(boolean enabled) {
// move calls coming from below LMS onto a different thread to avoid deadlock
- runInternal(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
if (enabled == mEnabled) {
return;
@@ -1113,16 +1132,6 @@
mUseable = false;
updateProviderUseableLocked(this);
}
-
- // binder transactions coming from below LMS (ie location providers) need to be moved onto
- // a different thread to avoid potential deadlock as code reenters the location providers
- private void runInternal(Runnable runnable) {
- if (Looper.myLooper() == mHandler.getLooper()) {
- runnable.run();
- } else {
- mHandler.post(runnable);
- }
- }
}
private class MockLocationProvider extends LocationProvider {
@@ -1278,7 +1287,11 @@
// are high power (has a high power provider with an interval under a threshold).
for (UpdateRecord updateRecord : mUpdateRecords.values()) {
LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
- if (provider == null || !provider.isUseableLocked()) {
+ if (provider == null) {
+ continue;
+ }
+ if (!provider.isUseableLocked()
+ && !updateRecord.mRealRequest.isLocationSettingsIgnored()) {
continue;
}
@@ -1960,14 +1973,22 @@
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
if (records != null) {
for (UpdateRecord record : records) {
- if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
- // Sends a notification message to the receiver
- if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
- if (deadReceivers == null) {
- deadReceivers = new ArrayList<>();
- }
- deadReceivers.add(record.mReceiver);
+ if (!isCurrentProfileLocked(
+ UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ continue;
+ }
+
+ // requests that ignore location settings will never provider notifications
+ if (record.mRealRequest.isLocationSettingsIgnored()) {
+ continue;
+ }
+
+ // Sends a notification message to the receiver
+ if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
+ if (deadReceivers == null) {
+ deadReceivers = new ArrayList<>();
}
+ deadReceivers.add(record.mReceiver);
}
}
}
@@ -2007,39 +2028,47 @@
Binder.restoreCallingIdentity(identity);
}
- if (provider.isUseableLocked() && records != null && !records.isEmpty()) {
+ if (records != null && !records.isEmpty()) {
// initialize the low power mode to true and set to false if any of the records requires
providerRequest.lowPowerMode = true;
for (UpdateRecord record : records) {
- if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
- if (checkLocationAccess(
- record.mReceiver.mIdentity.mPid,
- record.mReceiver.mIdentity.mUid,
- record.mReceiver.mIdentity.mPackageName,
- record.mReceiver.mAllowedResolutionLevel)) {
- LocationRequest locationRequest = record.mRealRequest;
- long interval = locationRequest.getInterval();
+ if (!isCurrentProfileLocked(
+ UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
+ continue;
+ }
+ if (!checkLocationAccess(
+ record.mReceiver.mIdentity.mPid,
+ record.mReceiver.mIdentity.mUid,
+ record.mReceiver.mIdentity.mPackageName,
+ record.mReceiver.mAllowedResolutionLevel)) {
+ continue;
+ }
+ if (!provider.isUseableLocked()
+ && !record.mRealRequest.isLocationSettingsIgnored()) {
+ continue;
+ }
- if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
- if (!record.mIsForegroundUid) {
- interval = Math.max(interval, backgroundThrottleInterval);
- }
- if (interval != locationRequest.getInterval()) {
- locationRequest = new LocationRequest(locationRequest);
- locationRequest.setInterval(interval);
- }
- }
+ LocationRequest locationRequest = record.mRealRequest;
+ long interval = locationRequest.getInterval();
- record.mRequest = locationRequest;
- providerRequest.locationRequests.add(locationRequest);
- if (!locationRequest.isLowPowerMode()) {
- providerRequest.lowPowerMode = false;
- }
- if (interval < providerRequest.interval) {
- providerRequest.reportLocation = true;
- providerRequest.interval = interval;
- }
+ if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
+ if (!record.mIsForegroundUid) {
+ interval = Math.max(interval, backgroundThrottleInterval);
}
+ if (interval != locationRequest.getInterval()) {
+ locationRequest = new LocationRequest(locationRequest);
+ locationRequest.setInterval(interval);
+ }
+ }
+
+ record.mRequest = locationRequest;
+ providerRequest.locationRequests.add(locationRequest);
+ if (!locationRequest.isLowPowerMode()) {
+ providerRequest.lowPowerMode = false;
+ }
+ if (interval < providerRequest.interval) {
+ providerRequest.reportLocation = true;
+ providerRequest.interval = interval;
}
}
@@ -2307,6 +2336,10 @@
mContext.enforceCallingOrSelfPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, null);
}
+ if (request.isLocationSettingsIgnored()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.WRITE_SECURE_SETTINGS, null);
+ }
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
== PERMISSION_GRANTED;
@@ -2376,12 +2409,14 @@
oldRecord.disposeLocked(false);
}
- if (provider.isUseableLocked()) {
- applyRequirementsLocked(name);
- } else {
- // Notify the listener that updates are currently disabled
+ if (!provider.isUseableLocked() && !request.isLocationSettingsIgnored()) {
+ // Notify the listener that updates are currently disabled - but only if the request
+ // does not ignore location settings
receiver.callProviderEnabledLocked(name, false);
}
+
+ applyRequirementsLocked(name);
+
// Update the monitoring here just in case multiple location requests were added to the
// same receiver (this request may be high power and the initial might not have been).
receiver.updateMonitoring(true);
@@ -2981,26 +3016,29 @@
return;
}
- if (!provider.isPassiveLocked()) {
- // notify passive provider of the new location
- mPassiveProvider.updateLocation(location);
+ // only notify passive provider and update last location for locations that come from
+ // useable providers
+ if (provider.isUseableLocked()) {
+ if (!provider.isPassiveLocked()) {
+ mPassiveProvider.updateLocation(location);
+ }
}
if (D) Log.d(TAG, "incoming location: " + location);
long now = SystemClock.elapsedRealtime();
- updateLastLocationLocked(location, provider.getName());
- // mLastLocation should have been updated from the updateLastLocationLocked call above.
- Location lastLocation = mLastLocation.get(provider.getName());
- if (lastLocation == null) {
- Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed");
- return;
+ if (provider.isUseableLocked()) {
+ updateLastLocationLocked(location, provider.getName());
}
// Update last known coarse interval location if enough time has passed.
- Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName());
+ Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
+ provider.getName());
if (lastLocationCoarseInterval == null) {
lastLocationCoarseInterval = new Location(location);
- mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+
+ if (provider.isUseableLocked()) {
+ mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+ }
}
long timeDiffNanos = location.getElapsedRealtimeNanos()
- lastLocationCoarseInterval.getElapsedRealtimeNanos();
@@ -3031,6 +3069,10 @@
Receiver receiver = r.mReceiver;
boolean receiverDead = false;
+ if (!provider.isUseableLocked() && !r.mRealRequest.isLocationSettingsIgnored()) {
+ continue;
+ }
+
int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid);
if (!isCurrentProfileLocked(receiverUserId)
&& !isLocationProviderLocked(receiver.mIdentity.mUid)) {
@@ -3066,7 +3108,7 @@
if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
notifyLocation = coarseLocation; // use coarse location
} else {
- notifyLocation = lastLocation; // use fine location
+ notifyLocation = location; // use fine location
}
if (notifyLocation != null) {
Location lastLoc = r.mLastFixBroadcast;
diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java
index cee98c1..4a8706e 100644
--- a/services/core/java/com/android/server/LooperStatsService.java
+++ b/services/core/java/com/android/server/LooperStatsService.java
@@ -122,6 +122,10 @@
"exception_count"));
pw.println(header);
for (LooperStats.ExportedEntry entry : entries) {
+ if (entry.messageName.startsWith(LooperStats.DEBUG_ENTRY_PREFIX)) {
+ // Do not dump debug entries.
+ continue;
+ }
pw.printf("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
packageMap.mapUid(entry.workSourceUid),
entry.threadName,
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7731c04..9d810cd 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1706,8 +1706,8 @@
int uid, String packageName, int[] ops) {
long maxTime = 0;
final List<AppOpsManager.PackageOps> pkgs = manager.getOpsForPackage(uid, packageName, ops);
- for (AppOpsManager.PackageOps pkg : CollectionUtils.defeatNullable(pkgs)) {
- for (AppOpsManager.OpEntry op : CollectionUtils.defeatNullable(pkg.getOps())) {
+ for (AppOpsManager.PackageOps pkg : CollectionUtils.emptyIfNull(pkgs)) {
+ for (AppOpsManager.OpEntry op : CollectionUtils.emptyIfNull(pkg.getOps())) {
maxTime = Math.max(maxTime, op.getLastAccessTime());
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a96676e..ac392a1 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -82,6 +82,7 @@
import android.webkit.WebViewZygote;
import com.android.internal.R;
+import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.app.procstats.ServiceState;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
@@ -168,6 +169,8 @@
/** Temporary list for holding the results of calls to {@link #collectPackageServicesLocked} */
private ArrayList<ServiceRecord> mTmpCollectionResults = null;
+ private int mNumServiceRestarts = 0;
+
/**
* For keeping ActiveForegroundApps retaining state while the screen is off.
*/
@@ -2327,19 +2330,50 @@
r.restartDelay = mAm.mConstants.BOUND_SERVICE_CRASH_RESTART_DURATION
* (r.crashCount - 1);
} else {
- // If it has been a "reasonably long time" since the service
- // was started, then reset our restart duration back to
- // the beginning, so we don't infinitely increase the duration
- // on a service that just occasionally gets killed (which is
- // a normal case, due to process being killed to reclaim memory).
- if (now > (r.restartTime+resetTime)) {
- r.restartCount = 1;
- r.restartDelay = minDuration;
- } else {
- r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR;
- if (r.restartDelay < minDuration) {
+ mNumServiceRestarts++;
+ if (!mAm.mConstants.FLAG_USE_MEM_AWARE_SERVICE_RESTARTS) {
+ // If it has been a "reasonably long time" since the service
+ // was started, then reset our restart duration back to
+ // the beginning, so we don't infinitely increase the duration
+ // on a service that just occasionally gets killed (which is
+ // a normal case, due to process being killed to reclaim memory).
+ if (now > (r.restartTime + resetTime)) {
+ r.restartCount = 1;
r.restartDelay = minDuration;
+ } else {
+ r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR;
+ if (r.restartDelay < minDuration) {
+ r.restartDelay = minDuration;
+ }
}
+ } else {
+ // The service will be restarted based on the last known oom_adj value
+ // for the process when it was destroyed. A higher oom_adj value
+ // means that the service will be restarted quicker.
+ // If the service seems to keep crashing, the service restart counter will be
+ // incremented and the restart delay will have an exponential backoff.
+ final int lastOomAdj = r.app == null ? r.lastKnownOomAdj : r.app.setAdj;
+ if (r.restartCount > 1) {
+ r.restartDelay *= mAm.mConstants.SERVICE_RESTART_DURATION_FACTOR;
+ if (r.restartDelay < minDuration) {
+ r.restartDelay = minDuration;
+ }
+ } else {
+ if (lastOomAdj <= ProcessList.VISIBLE_APP_ADJ) {
+ r.restartDelay = 1 * 1000; // 1 second
+ } else if (lastOomAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+ r.restartDelay = 2 * 1000; // 2 seconds
+ } else if (lastOomAdj <= ProcessList.SERVICE_ADJ) {
+ r.restartDelay = 5 * 1000; // 5 seconds
+ } else if (lastOomAdj <= ProcessList.PREVIOUS_APP_ADJ) {
+ r.restartDelay = 10 * 1000; // 10 seconds
+ } else {
+ r.restartDelay = 20 * 1000; // 20 seconds
+ }
+ }
+ // If the last time the service restarted was more than a minute ago,
+ // reset the service restart count, otherwise increment by one.
+ r.restartCount = (now > (r.restartTime + resetTime)) ? 1 : (r.restartCount + 1);
}
}
@@ -2347,21 +2381,7 @@
// Make sure that we don't end up restarting a bunch of services
// all at the same time.
- boolean repeat;
- do {
- repeat = false;
- final long restartTimeBetween = mAm.mConstants.SERVICE_MIN_RESTART_TIME_BETWEEN;
- for (int i=mRestartingServices.size()-1; i>=0; i--) {
- ServiceRecord r2 = mRestartingServices.get(i);
- if (r2 != r && r.nextRestartTime >= (r2.nextRestartTime-restartTimeBetween)
- && r.nextRestartTime < (r2.nextRestartTime+restartTimeBetween)) {
- r.nextRestartTime = r2.nextRestartTime + restartTimeBetween;
- r.restartDelay = r.nextRestartTime - now;
- repeat = true;
- break;
- }
- }
- } while (repeat);
+ ensureSpacedServiceRestarts(r, now);
} else {
// Persistent processes are immediately restarted, so there is no
@@ -2391,6 +2411,24 @@
return canceled;
}
+ private void ensureSpacedServiceRestarts(ServiceRecord r, long now) {
+ boolean repeat;
+ do {
+ repeat = false;
+ final long restartTimeBetween = mAm.mConstants.SERVICE_MIN_RESTART_TIME_BETWEEN;
+ for (int i = mRestartingServices.size() - 1; i >= 0; i--) {
+ ServiceRecord r2 = mRestartingServices.get(i);
+ if (r2 != r && r.nextRestartTime >= (r2.nextRestartTime - restartTimeBetween)
+ && r.nextRestartTime < (r2.nextRestartTime + restartTimeBetween)) {
+ r.nextRestartTime = r2.nextRestartTime + restartTimeBetween;
+ r.restartDelay = r.nextRestartTime - now;
+ repeat = true;
+ break;
+ }
+ }
+ } while (repeat);
+ }
+
final void performServiceRestartLocked(ServiceRecord r) {
if (!mRestartingServices.contains(r)) {
return;
@@ -2465,6 +2503,26 @@
return null;
}
+ // If the service is restarting, check the memory pressure - if it's low or critical, then
+ // further delay the restart because it will most likely get killed again; if the pressure
+ // is not low or critical, continue restarting.
+ if (mAm.mConstants.FLAG_USE_MEM_AWARE_SERVICE_RESTARTS && whileRestarting) {
+ final int memLevel = mAm.getMemoryTrimLevel();
+ if (memLevel >= ProcessStats.ADJ_MEM_FACTOR_LOW) {
+ final long now = SystemClock.uptimeMillis();
+ // Delay the restart based on the current memory pressure
+ // Default delay duration is 5 seconds
+ r.restartDelay = memLevel * mAm.mConstants.SERVICE_RESTART_DELAY_DURATION;
+ r.nextRestartTime = now + r.restartDelay;
+ ensureSpacedServiceRestarts(r, now);
+ mAm.mHandler.removeCallbacks(r.restarter);
+ mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
+ Slog.w(TAG, "Delaying restart of crashed service " + r.shortInstanceName
+ + " in " + r.restartDelay + "ms due to continued memory pressure");
+ return null;
+ }
+ }
+
if (DEBUG_SERVICE) {
Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent + " fg=" + r.fgRequired);
}
@@ -3579,6 +3637,7 @@
|| !mAm.mUserController.isUserRunning(sr.userId, 0)) {
bringDownServiceLocked(sr);
} else {
+ sr.lastKnownOomAdj = app.setAdj;
boolean canceled = scheduleServiceRestartLocked(sr, true);
// Should the service remain running? Note that in the
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index dd2b33a..546975a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -76,6 +76,8 @@
static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
+ static final String KEY_USE_MEM_AWARE_SERVICE_RESTARTS = "use_mem_aware_service_restarts";
+ static final String KEY_SERVICE_RESTART_DELAY_DURATION = "service_restart_delay_duration";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -113,6 +115,8 @@
public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000;
public static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000;
+ private static final boolean DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS = true;
+ private static final long DEFAULT_SERVICE_RESTART_DELAY_DURATION = 5 * 1000;
// Maximum number of cached processes we will allow.
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -249,6 +253,12 @@
// How long we'll skip second compactAppFull after first compactAppFull
public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4;
+ // Use memory aware optimized logic to restart services
+ public boolean FLAG_USE_MEM_AWARE_SERVICE_RESTARTS = DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS;
+
+ // How long a service restart will be delayed by if there is memory pressure
+ public long SERVICE_RESTART_DELAY_DURATION = DEFAULT_SERVICE_RESTART_DELAY_DURATION;
+
// Indicates whether the activity starts logging is enabled.
// Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -413,6 +423,10 @@
COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2);
COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3);
COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4);
+ FLAG_USE_MEM_AWARE_SERVICE_RESTARTS = mParser.getBoolean(
+ KEY_USE_MEM_AWARE_SERVICE_RESTARTS, DEFAULT_USE_MEM_AWARE_SERVICE_RESTARTS);
+ SERVICE_RESTART_DELAY_DURATION = mParser.getLong(
+ KEY_SERVICE_RESTART_DELAY_DURATION, DEFAULT_SERVICE_RESTART_DELAY_DURATION);
updateMaxCachedProcesses();
}
@@ -505,6 +519,10 @@
pw.println(TOP_TO_FGS_GRACE_DURATION);
pw.print(" "); pw.print(KEY_USE_COMPACTION); pw.print("=");
pw.println(USE_COMPACTION);
+ pw.print(" "); pw.print(KEY_USE_MEM_AWARE_SERVICE_RESTARTS); pw.print("=");
+ pw.println(FLAG_USE_MEM_AWARE_SERVICE_RESTARTS);
+ pw.print(" "); pw.print(KEY_SERVICE_RESTART_DELAY_DURATION); pw.print("=");
+ pw.println(SERVICE_RESTART_DELAY_DURATION);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index fe27c49..fd402cc 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -64,7 +64,7 @@
final private String COMPACT_ACTION_FILE = "file";
final private String COMPACT_ACTION_ANON = "anon";
- final private String COMPACT_ACTION_FULL = "full";
+ final private String COMPACT_ACTION_FULL = "all";
final private String compactActionSome;
final private String compactActionFull;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index da5ce1c..fc2a444 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -16,10 +16,8 @@
package com.android.server.am;
-import com.android.internal.app.procstats.ServiceState;
-import com.android.internal.os.BatteryStatsImpl;
-import com.android.server.LocalServices;
-import com.android.server.notification.NotificationManagerInternal;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.app.INotificationManager;
import android.app.Notification;
@@ -44,6 +42,11 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
+
+import com.android.internal.app.procstats.ServiceState;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriPermissionOwner;
@@ -52,9 +55,6 @@
import java.util.List;
import java.util.Objects;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-
/**
* A running application service.
*/
@@ -114,6 +114,7 @@
long executingStart; // start time of last execute request.
boolean createdFromFg; // was this service last created due to a foreground process call?
int crashCount; // number of times proc has crashed with service running
+ int lastKnownOomAdj; // last known OOM adjustment for process (used if ProcessRecord is null)
int totalRestartCount; // number of times we have had to restart.
int restartCount; // number of restarts performed in a row.
long restartDelay; // delay until next restart attempt.
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 8f1befe..27edbbf 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -47,6 +47,7 @@
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.StatsLog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -176,11 +177,16 @@
userState.mAttentionCheckCache = new AttentionCheckCache(
SystemClock.uptimeMillis(), result,
timestamp);
+
+ StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
+ result);
}
@Override
public void onFailure(int requestCode, int error) {
callback.onFailure(requestCode, error);
+ StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
+ error);
}
@Override
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 11299b6..de389bc 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1223,11 +1223,13 @@
private void checkMuteAffectedStreams() {
// any stream with a min level > 0 is not muteable by definition
- // STREAM_VOICE_CALL can be muted by applications that has the the MODIFY_PHONE_STATE permission.
+ // STREAM_VOICE_CALL and STREAM_BLUETOOTH_SCO can be muted by applications
+ // that has the the MODIFY_PHONE_STATE permission.
for (int i = 0; i < mStreamStates.length; i++) {
final VolumeStreamState vss = mStreamStates[i];
if (vss.mIndexMin > 0 &&
- vss.mStreamType != AudioSystem.STREAM_VOICE_CALL) {
+ (vss.mStreamType != AudioSystem.STREAM_VOICE_CALL &&
+ vss.mStreamType != AudioSystem.STREAM_BLUETOOTH_SCO)) {
mMuteAffectedStreams &= ~(1 << vss.mStreamType);
}
}
@@ -1711,10 +1713,11 @@
return;
}
- // If adjust is mute and the stream is STREAM_VOICE_CALL, make sure
+ // If adjust is mute and the stream is STREAM_VOICE_CALL or STREAM_BLUETOOTH_SCO, make sure
// that the calling app have the MODIFY_PHONE_STATE permission.
if (isMuteAdjust &&
- streamType == AudioSystem.STREAM_VOICE_CALL &&
+ (streamType == AudioSystem.STREAM_VOICE_CALL ||
+ streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -2038,12 +2041,14 @@
+ " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage);
return;
}
- if ((streamType == AudioManager.STREAM_VOICE_CALL) &&
+ if ((streamType == AudioManager.STREAM_VOICE_CALL ||
+ streamType == AudioManager.STREAM_BLUETOOTH_SCO) &&
(index == 0) &&
(mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED)) {
- Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without"
+ Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL or"
+ + " STREAM_BLUETOOTH_SCO and index 0 without"
+ " MODIFY_PHONE_STATE callingPackage=" + callingPackage);
return;
}
@@ -4746,12 +4751,18 @@
private int getA2dpCodec(BluetoothDevice device) {
synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null) {
- BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
- BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
- return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
+ if (mA2dp == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
}
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+ if (btCodecStatus == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
+ if (btCodecConfig == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index d4b8eb2..944a95d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.content.ComponentName;
import android.view.inputmethod.InputMethodInfo;
import com.android.server.LocalServices;
@@ -42,11 +41,6 @@
public abstract void hideCurrentInputMethod();
/**
- * Switches to VR InputMethod defined in the packageName of {@param componentName}.
- */
- public abstract void startVrInputMethodNoCheck(ComponentName componentName);
-
- /**
* Returns the list of installed input methods for the specified user.
*
* @param userId The user ID to be queried.
@@ -76,10 +70,6 @@
}
@Override
- public void startVrInputMethodNoCheck(ComponentName componentName) {
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
return Collections.emptyList();
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 52074a7..eca371a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -91,8 +91,6 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
-import android.service.vr.IVrManager;
-import android.service.vr.IVrStateCallbacks;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
@@ -203,7 +201,6 @@
static final int MSG_CREATE_SESSION = 1050;
static final int MSG_START_INPUT = 2000;
- static final int MSG_START_VR_INPUT = 2010;
static final int MSG_UNBIND_CLIENT = 3000;
static final int MSG_BIND_CLIENT = 3010;
@@ -381,28 +378,6 @@
}
}
- /**
- * VR state callback.
- * Listens for when VR mode finishes.
- */
- private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
- @Override
- public void onVrStateChanged(boolean enabled) {
- if (!enabled) {
- restoreNonVrImeFromSettingsNoCheck();
- }
- }
- };
-
- private void restoreNonVrImeFromSettingsNoCheck() {
- // switch back to non-VR InputMethod from settings.
- synchronized (mMethodMap) {
- if (!mIsVrImeStarted) return;
- mIsVrImeStarted = false;
- updateFromSettingsLocked(false);
- }
- }
-
private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
private final InputMethodManagerService mImms;
private final IInputMethodClient mClient;
@@ -597,9 +572,6 @@
final ImeDisplayValidator mImeDisplayValidator;
- /** True if VR IME started by {@link #startVrInputMethodNoCheck}. */
- boolean mIsVrImeStarted;
-
/**
* If non-null, this is the input method service we are currently connected
* to.
@@ -977,31 +949,6 @@
}
/**
- * Start a VR InputMethod that matches IME with package name of {@param component}.
- * Note: This method is called from {@link android.app.VrManager}.
- */
- private void startVrInputMethodNoCheck(@Nullable ComponentName component) {
- if (component == null) {
- // clear the current VR-only IME (if any) and restore normal IME.
- restoreNonVrImeFromSettingsNoCheck();
- return;
- }
-
- synchronized (mMethodMap) {
- String packageName = component.getPackageName();
- for (InputMethodInfo info : mMethodList) {
- if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) {
- // set this is as current inputMethod without updating settings.
- setInputMethodEnabledLocked(info.getId(), true);
- setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID);
- mIsVrImeStarted = true;
- break;
- }
- }
- }
- }
-
- /**
* Handles {@link Intent#ACTION_LOCALE_CHANGED}.
*
* <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
@@ -1452,15 +1399,6 @@
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
mSettings, context);
- // Register VR-state listener.
- IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE);
- if (vrManager != null) {
- try {
- vrManager.registerListener(mVrStateCallbacks);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register VR mode state listener.");
- }
- }
}
private void resetDefaultImeLocked(Context context) {
@@ -1688,25 +1626,7 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public List<InputMethodInfo> getVrInputMethodList() {
- final int callingUserId = UserHandle.getCallingUserId();
- synchronized (mMethodMap) {
- final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId,
- mSettings.getCurrentUserId(), null);
- if (resolvedUserIds.length != 1) {
- return Collections.emptyList();
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]);
+ return getInputMethodListLocked(resolvedUserIds[0]);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1732,8 +1652,7 @@
}
@GuardedBy("mMethodMap")
- private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly,
- @UserIdInt int userId) {
+ private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) {
final ArrayList<InputMethodInfo> methodList;
if (userId == mSettings.getCurrentUserId()) {
// Create a copy.
@@ -1747,7 +1666,6 @@
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
methodList);
}
- methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly);
return methodList;
}
@@ -2021,8 +1939,8 @@
}
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
- final int displayIdToShowIme = computeImeDisplayIdForTarget(
- cs.selfReportedDisplayId, mIsVrImeStarted, mImeDisplayValidator);
+ final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
+ mImeDisplayValidator);
if (mCurClient != cs) {
// Was the keyguard locked when switching over to the new client?
@@ -2131,17 +2049,11 @@
* Find the display where the IME should be shown.
*
* @param displayId the ID of the display where the IME client target is.
- * @param isVrImeStarted {@code true} if VR IME started, {@code false} otherwise.
* @param checker instance of {@link ImeDisplayValidator} which is used for
* checking display config to adjust the final target display.
* @return The ID of the display where the IME should be shown.
*/
- static int computeImeDisplayIdForTarget(int displayId, boolean isVrImeStarted,
- @NonNull ImeDisplayValidator checker) {
- // For VR IME, we always show in default display.
- if (isVrImeStarted) {
- return DEFAULT_DISPLAY;
- }
+ static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
// We always assume that the default display id suitable to show the IME window.
return DEFAULT_DISPLAY;
@@ -3627,9 +3539,6 @@
case MSG_SET_INTERACTIVE:
handleSetInteractive(msg.arg1 != 0);
return true;
- case MSG_START_VR_INPUT:
- startVrInputMethodNoCheck((ComponentName) msg.obj);
- return true;
case MSG_REPORT_FULLSCREEN_MODE: {
final boolean fullscreen = msg.arg1 != 0;
final ClientState clientState = (ClientState)msg.obj;
@@ -3716,6 +3625,9 @@
try {
final InputMethodInfo imi = new InputMethodInfo(context, ri,
additionalSubtypeMap.get(imeId));
+ if (imi.isVrOnly()) {
+ continue; // Skip VR-only IME, which isn't supported for now.
+ }
methodList.add(imi);
methodMap.put(imi.getId(), imi);
if (DEBUG) {
@@ -4099,17 +4011,7 @@
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
- // Updates to InputMethod are transient in VR mode. Its not included in history.
- final boolean isVrInput = imi != null && imi.isVrOnly();
- if (!isVrInput) {
- // Update the history of InputMethod and Subtype
- mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
- }
-
- if (isVrInput) {
- // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
- return;
- }
+ mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
// Set Subtype here
if (imi == null || subtypeId < 0) {
@@ -4225,7 +4127,7 @@
private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
synchronized (mMethodMap) {
- return getInputMethodListLocked(false, userId);
+ return getInputMethodListLocked(userId);
}
}
@@ -4257,11 +4159,6 @@
}
@Override
- public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
- mService.mHandler.obtainMessage(MSG_START_VR_INPUT, componentName).sendToTarget();
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(int userId) {
return mService.getInputMethodListAsUser(userId);
}
@@ -4648,7 +4545,7 @@
mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter());
for (int userId : userIds) {
final List<InputMethodInfo> methods = all
- ? getInputMethodListLocked(false, userId)
+ ? getInputMethodListLocked(userId)
: getEnabledInputMethodListLocked(userId);
if (userIds.length > 1) {
pr.print("User #");
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 6f0c5e8..a31b3b4 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -159,11 +159,6 @@
}
@Override
- public void startVrInputMethodNoCheck(ComponentName componentName) {
- reportNotSupported();
- }
-
- @Override
public List<InputMethodInfo> getInputMethodListAsUser(
@UserIdInt int userId) {
return userIdToInputMethodInfoMapper.getAsList(userId);
@@ -1244,13 +1239,6 @@
@BinderThread
@Override
- public List<InputMethodInfo> getVrInputMethodList() {
- reportNotSupported();
- return Collections.emptyList();
- }
-
- @BinderThread
- @Override
public List<InputMethodInfo> getEnabledInputMethodList() {
return mInputMethodInfoMap.getAsList(UserHandle.getUserId(Binder.getCallingUid()));
}
diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
new file mode 100644
index 0000000..22fabb2
--- /dev/null
+++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.hardware.location.ActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareClient;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.ServiceWatcher;
+
+/**
+ * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
+ *
+ * @hide
+ */
+public class ActivityRecognitionProxy {
+
+ private static final String TAG = "ActivityRecognitionProxy";
+
+ /**
+ * Creates an instance of the proxy and binds it to the appropriate FusedProvider.
+ *
+ * @return An instance of the proxy if it could be bound, null otherwise.
+ */
+ public static ActivityRecognitionProxy createAndBind(
+ Context context,
+ boolean activityRecognitionHardwareIsSupported,
+ ActivityRecognitionHardware activityRecognitionHardware,
+ int overlaySwitchResId,
+ int defaultServicePackageNameResId,
+ int initialPackageNameResId) {
+ ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy(
+ context,
+ activityRecognitionHardwareIsSupported,
+ activityRecognitionHardware,
+ overlaySwitchResId,
+ defaultServicePackageNameResId,
+ initialPackageNameResId);
+
+ if (activityRecognitionProxy.mServiceWatcher.start()) {
+ return activityRecognitionProxy;
+ } else {
+ return null;
+ }
+ }
+
+ private final ServiceWatcher mServiceWatcher;
+ private final boolean mIsSupported;
+ private final ActivityRecognitionHardware mInstance;
+
+ private ActivityRecognitionProxy(
+ Context context,
+ boolean activityRecognitionHardwareIsSupported,
+ ActivityRecognitionHardware activityRecognitionHardware,
+ int overlaySwitchResId,
+ int defaultServicePackageNameResId,
+ int initialPackageNameResId) {
+ mIsSupported = activityRecognitionHardwareIsSupported;
+ mInstance = activityRecognitionHardware;
+
+ mServiceWatcher = new ServiceWatcher(
+ context,
+ TAG,
+ "com.android.location.service.ActivityRecognitionProvider",
+ overlaySwitchResId,
+ defaultServicePackageNameResId,
+ initialPackageNameResId,
+ BackgroundThread.getHandler()) {
+ @Override
+ protected void onBind() {
+ runOnBinder(ActivityRecognitionProxy.this::initializeService);
+ }
+ };
+ }
+
+ private void initializeService(IBinder binder) {
+ try {
+ String descriptor = binder.getInterfaceDescriptor();
+
+ if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(
+ descriptor)) {
+ IActivityRecognitionHardwareWatcher watcher =
+ IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
+ if (mInstance != null) {
+ watcher.onInstanceChanged(mInstance);
+ }
+ } else if (IActivityRecognitionHardwareClient.class.getCanonicalName()
+ .equals(descriptor)) {
+ IActivityRecognitionHardwareClient client =
+ IActivityRecognitionHardwareClient.Stub.asInterface(binder);
+ client.onAvailabilityChanged(mIsSupported, mInstance);
+ } else {
+ Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index d8c2432..af790f2 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -78,6 +78,7 @@
private final String mPackageName;
private final String mTag;
private final ControllerLink mController;
+ private final MediaSession.Token mSessionToken;
private final SessionLink mSession;
private final SessionCb mSessionCb;
private final MediaSessionService.ServiceImpl mService;
@@ -128,6 +129,7 @@
mPackageName = ownerPackageName;
mTag = tag;
mController = new ControllerLink(new ControllerStub());
+ mSessionToken = new MediaSession.Token(mController);
mSession = new SessionLink(new SessionStub());
mSessionCb = new SessionCb(cb);
mService = service;
@@ -157,6 +159,15 @@
}
/**
+ * Get the session token for creating {@link MediaController}.
+ *
+ * @return The session token.
+ */
+ public MediaSession.Token getSessionToken() {
+ return mSessionToken;
+ }
+
+ /**
* Get the info for this session.
*
* @return Info that identifies this session.
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index e3ae8a7..1541b1d 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -51,7 +51,6 @@
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession2TokensListener;
-import android.media.session.ISessionController;
import android.media.session.ISessionManager;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
@@ -290,9 +289,7 @@
return;
}
try {
- mRvc.remoteVolumeChanged(
- ISessionController.Stub.asInterface(session.getControllerLink().getBinder()),
- flags);
+ mRvc.remoteVolumeChanged(session.getSessionToken(), flags);
} catch (Exception e) {
Log.wtf(TAG, "Error sending volume change to system UI.", e);
}
@@ -618,7 +615,7 @@
int size = records.size();
ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
for (int i = 0; i < size; i++) {
- tokens.add(new MediaSession.Token(records.get(i).getControllerLink()));
+ tokens.add(records.get(i).getSessionToken());
}
pushRemoteVolumeUpdateLocked(userId);
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
@@ -645,9 +642,7 @@
return;
}
MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
- mRvc.updateRemoteController(record == null ? null
- : ISessionController.Stub.asInterface(
- record.getControllerLink().getBinder()));
+ mRvc.updateRemoteController(record == null ? null : record.getSessionToken());
} catch (RemoteException e) {
Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
}
@@ -864,7 +859,7 @@
MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
if (mediaButtonSession != null) {
mCallback.onAddressedPlayerChangedToMediaSession(
- new MediaSession.Token(mediaButtonSession.getControllerLink()));
+ mediaButtonSession.getSessionToken());
} else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
mCallback.onAddressedPlayerChangedToMediaButtonReceiver(
mCurrentFullUserRecord.mLastMediaButtonReceiver
@@ -1804,7 +1799,7 @@
if (mCurrentFullUserRecord.mCallback != null) {
try {
mCurrentFullUserRecord.mCallback.onMediaKeyEventDispatchedToMediaSession(
- keyEvent, new MediaSession.Token(session.getControllerLink()));
+ keyEvent, session.getSessionToken());
} catch (RemoteException e) {
Log.w(TAG, "Failed to send callback", e);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5598741..02fc51f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1263,7 +1263,7 @@
public LogMaker getAdjustmentLogMaker() {
return getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ITEM)
- .setType(MetricsEvent.NOTIFICATION_ASSISTANT_ADJUSTMENT);
+ .setType(MetricsEvent.TYPE_NOTIFICATION_ASSISTANT_ADJUSTMENT);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 5516b23..c0517fd 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -342,10 +342,21 @@
@WorkerThread
private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
- if (listeners == null) {
- return;
+ if (listeners != null) {
+ notifyRoleHoldersChangedForListeners(listeners, roleName, userId);
}
+ RemoteCallbackList<IOnRoleHoldersChangedListener> allUserListeners = getListeners(
+ UserHandle.USER_ALL);
+ if (allUserListeners != null) {
+ notifyRoleHoldersChangedForListeners(allUserListeners, roleName, userId);
+ }
+ }
+
+ @WorkerThread
+ private void notifyRoleHoldersChangedForListeners(
+ @NonNull RemoteCallbackList<IOnRoleHoldersChangedListener> listeners,
+ @NonNull String roleName, @UserIdInt int userId) {
int broadcastCount = listeners.beginBroadcast();
try {
for (int i = 0; i < broadcastCount; i++) {
@@ -395,7 +406,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return Collections.emptyList();
}
- userId = handleIncomingUser(userId, "getRoleHoldersAsUser");
+ userId = handleIncomingUser(userId, "getRoleHoldersAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"getRoleHoldersAsUser");
@@ -423,7 +434,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "addRoleHolderAsUser");
+ userId = handleIncomingUser(userId, "addRoleHolderAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"addRoleHolderAsUser");
@@ -440,7 +451,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "removeRoleHolderAsUser");
+ userId = handleIncomingUser(userId, "removeRoleHolderAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"removeRoleHolderAsUser");
@@ -457,7 +468,7 @@
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "clearRoleHoldersAsUser");
+ userId = handleIncomingUser(userId, "clearRoleHoldersAsUser", false);
getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
"clearRoleHoldersAsUser");
@@ -468,11 +479,12 @@
public void addOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
Preconditions.checkNotNull(listener, "listener cannot be null");
- if (!mUserManagerInternal.exists(userId)) {
+ if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) {
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser");
+ userId = handleIncomingUser(userId, "addOnRoleHoldersChangedListenerAsUser",
+ true);
getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
"addOnRoleHoldersChangedListenerAsUser");
@@ -485,11 +497,12 @@
public void removeOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
Preconditions.checkNotNull(listener, "listener cannot be null");
- if (!mUserManagerInternal.exists(userId)) {
+ if (userId != UserHandle.USER_ALL && !mUserManagerInternal.exists(userId)) {
Slog.e(LOG_TAG, "user " + userId + " does not exist");
return;
}
- userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser");
+ userId = handleIncomingUser(userId, "removeOnRoleHoldersChangedListenerAsUser",
+ true);
getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
"removeOnRoleHoldersChangedListenerAsUser");
@@ -553,9 +566,10 @@
}
@CheckResult
- private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) {
+ private int handleIncomingUser(@UserIdInt int userId, @NonNull String name,
+ boolean allowAll) {
return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
- false, true, name, null);
+ allowAll, true, name, null);
}
@Override
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index ced5935..423ec4c 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -22,10 +22,8 @@
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.app.admin.DevicePolicyManager;
-import android.hardware.biometrics.BiometricSourceType;
import android.app.trust.ITrustListener;
import android.app.trust.ITrustManager;
-import android.app.UserSwitchObserver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -41,6 +39,7 @@
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricSourceType;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -72,13 +71,15 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+
/**
* Manages trust agents and trust listeners.
@@ -119,7 +120,7 @@
private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000;
private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser";
- private static final long TRUST_TIMEOUT_IN_MILLIS = 20 * 1000; //4 * 60 * 60 * 1000;
+ private static final long TRUST_TIMEOUT_IN_MILLIS = 4 * 60 * 60 * 1000;
private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>();
private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>();
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b3eafa4..45689ce 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -65,7 +65,6 @@
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
-import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.utils.ManagedApplicationService;
import com.android.server.utils.ManagedApplicationService.BinderChecker;
import com.android.server.utils.ManagedApplicationService.LogEvent;
@@ -623,14 +622,6 @@
}
@Override
- public void setVrInputMethod(ComponentName componentName) {
- enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
- InputMethodManagerInternal imm =
- LocalServices.getService(InputMethodManagerInternal.class);
- imm.startVrInputMethodNoCheck(componentName);
- }
-
- @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index caebf15..545b69b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -49,6 +49,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
+import android.view.Display;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
@@ -83,23 +84,36 @@
mService = service;
}
- private DisplayMagnifier mDisplayMagnifier;
+ private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
private WindowsForAccessibilityObserver mWindowsForAccessibilityObserver;
- public void setMagnificationCallbacksLocked(MagnificationCallbacks callbacks) {
+ public boolean setMagnificationCallbacksLocked(int displayId,
+ MagnificationCallbacks callbacks) {
+ boolean result = false;
if (callbacks != null) {
- if (mDisplayMagnifier != null) {
+ if (mDisplayMagnifiers.get(displayId) != null) {
throw new IllegalStateException("Magnification callbacks already set!");
}
- mDisplayMagnifier = new DisplayMagnifier(mService, callbacks);
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+ if (dc != null) {
+ final Display display = dc.getDisplay();
+ if (display != null && display.getType() != Display.TYPE_OVERLAY) {
+ mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
+ mService, dc, display, callbacks));
+ result = true;
+ }
+ }
} else {
- if (mDisplayMagnifier == null) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier == null) {
throw new IllegalStateException("Magnification callbacks already cleared!");
}
- mDisplayMagnifier.destroyLocked();
- mDisplayMagnifier = null;
+ displayMagnifier.destroyLocked();
+ mDisplayMagnifiers.remove(displayId);
+ result = true;
}
+ return result;
}
public void setWindowsForAccessibilityCallback(WindowsForAccessibilityCallback callback) {
@@ -129,58 +143,72 @@
}
}
- public void setMagnificationSpecLocked(MagnificationSpec spec) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.setMagnificationSpecLocked(spec);
+ public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.setMagnificationSpecLocked(spec);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
- public void getMagnificationRegionLocked(Region outMagnificationRegion) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
+ public void getMagnificationRegionLocked(int displayId, Region outMagnificationRegion) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
}
}
- public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
+ public void onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
}
// Not relevant for the window observer.
}
- public void onWindowLayersChangedLocked() {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onWindowLayersChangedLocked();
+ public void onWindowLayersChangedLocked(int displayId) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onWindowLayersChangedLocked();
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onRotationChangedLocked(DisplayContent displayContent) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onRotationChangedLocked(displayContent);
+ final int displayId = displayContent.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onRotationChangedLocked(displayContent);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onAppWindowTransitionLocked(windowState, transition);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onAppWindowTransitionLocked(windowState, transition);
}
// Not relevant for the window observer.
}
public void onWindowTransitionLocked(WindowState windowState, int transition) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.onWindowTransitionLocked(windowState, transition);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.onWindowTransitionLocked(windowState, transition);
}
- if (mWindowsForAccessibilityObserver != null) {
+ // TODO: support multi-display for windows observer
+ if (mWindowsForAccessibilityObserver != null && displayId == Display.DEFAULT_DISPLAY) {
mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
}
}
@@ -197,7 +225,6 @@
}
}
-
public void onSomeWindowResizedOrMovedLocked() {
// Not relevant for the display magnifier.
@@ -207,29 +234,34 @@
}
/** NOTE: This has to be called within a surface transaction. */
- public void drawMagnifiedRegionBorderIfNeededLocked() {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+ public void drawMagnifiedRegionBorderIfNeededLocked(int displayId) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
}
// Not relevant for the window observer.
}
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
- if (mDisplayMagnifier != null) {
- return mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ return displayMagnifier.getMagnificationSpecForWindowLocked(windowState);
}
return null;
}
public boolean hasCallbacksLocked() {
- return (mDisplayMagnifier != null
+ // TODO: support multi-display for windows observer
+ return (mDisplayMagnifiers.size() > 0
|| mWindowsForAccessibilityObserver != null);
}
- public void setForceShowMagnifiableBoundsLocked(boolean show) {
- if (mDisplayMagnifier != null) {
- mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show);
- mDisplayMagnifier.showMagnificationBoundsIfNeeded();
+ public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ displayMagnifier.setForceShowMagnifiableBoundsLocked(show);
+ displayMagnifier.showMagnificationBoundsIfNeeded();
}
}
@@ -263,6 +295,8 @@
private final WindowManagerService mService;
private final MagnifiedViewport mMagnifedViewport;
private final Handler mHandler;
+ private final DisplayContent mDisplayContent;
+ private final Display mDisplay;
private final MagnificationCallbacks mCallbacks;
@@ -271,10 +305,14 @@
private boolean mForceShowMagnifiableBounds = false;
public DisplayMagnifier(WindowManagerService windowManagerService,
+ DisplayContent displayContent,
+ Display display,
MagnificationCallbacks callbacks) {
mContext = windowManagerService.mContext;
mService = windowManagerService;
mCallbacks = callbacks;
+ mDisplayContent = displayContent;
+ mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
mMagnifedViewport = new MagnifiedViewport();
mLongAnimationDuration = mContext.getResources().getInteger(
@@ -285,7 +323,7 @@
mMagnifedViewport.updateMagnificationSpecLocked(spec);
mMagnifedViewport.recomputeBoundsLocked();
- mService.applyMagnificationSpec(spec);
+ mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
}
@@ -482,7 +520,7 @@
if (mContext.getResources().getConfiguration().isScreenRound()) {
mCircularPath = new Path();
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ mDisplay.getRealSize(mTempPoint);
final int centerXY = mTempPoint.x / 2;
mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
} else {
@@ -512,7 +550,7 @@
}
public void recomputeBoundsLocked() {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ mDisplay.getRealSize(mTempPoint);
final int screenWidth = mTempPoint.x;
final int screenHeight = mTempPoint.y;
@@ -671,9 +709,8 @@
}
private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
- final DisplayContent dc = mService.getDefaultDisplayContentLocked();
mTempLayer = 0;
- dc.forAllWindows((w) -> {
+ mDisplayContent.forAllWindows((w) -> {
if (w.isOnScreen() && w.isVisibleLw()
&& (w.mAttrs.alpha != 0)
&& !w.mWinAnimator.mEnterAnimationPending) {
@@ -703,8 +740,9 @@
public ViewportWindow(Context context) {
SurfaceControl surfaceControl = null;
try {
- mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
+ mDisplay.getRealSize(mTempPoint);
+ surfaceControl = mDisplayContent
+ .makeOverlay()
.setName(SURFACE_TITLE)
.setBufferSize(mTempPoint.x, mTempPoint.y) // not a typo
.setFormat(PixelFormat.TRANSLUCENT)
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index e817dd4..65d66f4 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -558,26 +558,22 @@
}
/**
- * Pause all activities in either all of the stacks or just the back stacks. This is done before
- * resuming a new activity and to make sure that previously active activities are
- * paused in stacks that are no longer visible or in pinned windowing mode. This does not
- * pause activities in visible stacks, so if an activity is launched within the same stack/task,
- * then we should explicitly pause that stack's top activity.
+ * Pause all activities in either all of the stacks or just the back stacks.
* @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
* @param resuming The resuming activity.
* @param dontWait The resuming activity isn't going to wait for all activities to be paused
* before resuming.
- * @return {@code true} if any activity was paused as a result of this call.
+ * @return true if any activity was paused as a result of this call.
*/
boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
boolean someActivityPaused = false;
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = mStacks.get(stackNdx);
- final ActivityRecord resumedActivity = stack.getResumedActivity();
- if (resumedActivity != null
- && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
+ // TODO(b/111541062): Check if resumed activity on this display instead
+ if (!mRootActivityContainer.isTopDisplayFocusedStack(stack)
+ && stack.getResumedActivity() != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
- " mResumedActivity=" + resumedActivity);
+ " mResumedActivity=" + stack.getResumedActivity());
someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
dontWait);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6213fa0..b8634d8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,84 +1946,30 @@
try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
WindowVisibilityItem.obtain(true /* showWindow */));
- makeActiveIfNeeded(null /* activeActivity*/);
+ if (shouldPauseWhenBecomingVisible()) {
+ // An activity must be in the {@link PAUSING} state for the system to validate
+ // the move to {@link PAUSED}.
+ setState(PAUSING, "makeVisibleIfNeeded");
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ PauseActivityItem.obtain(finishing, false /* userLeaving */,
+ configChangeFlags, false /* dontReport */));
+ }
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
}
}
- /**
- * Make activity resumed or paused if needed.
- * @param activeActivity an activity that is resumed or just completed pause action.
- * We won't change the state of this activity.
- */
- boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
- if (shouldResumeActivity(activeActivity)) {
- if (DEBUG_VISIBILITY) {
- Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
- }
- return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
- null /* options */);
- } else if (shouldPauseActivity(activeActivity)) {
- if (DEBUG_VISIBILITY) {
- Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
- }
- // An activity must be in the {@link PAUSING} state for the system to validate
- // the move to {@link PAUSED}.
- setState(PAUSING, "makeVisibleIfNeeded");
- try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- PauseActivityItem.obtain(finishing, false /* userLeaving */,
- configChangeFlags, false /* dontReport */));
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
- }
- }
- return false;
- }
-
- /**
- * Check if activity should be moved to PAUSED state. The activity:
- * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
- * - should be non-focusable
- * - should not be currently pausing or paused
- * @param activeActivity the activity that is active or just completed pause action. We won't
- * resume if this activity is active.
- */
- private boolean shouldPauseActivity(ActivityRecord activeActivity) {
- return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED);
- }
-
- /**
- * Check if activity should be moved to RESUMED state. The activity:
- * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
- * - should be focusable
- * @param activeActivity the activity that is active or just completed pause action. We won't
- * resume if this activity is active.
- */
- private boolean shouldResumeActivity(ActivityRecord activeActivity) {
- return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED);
- }
-
- /**
- * Check if activity is eligible to be made active (resumed of paused). The activity:
- * - should be paused, stopped or stopping
- * - should not be the currently active one
- * - should be either the topmost in task, or right below the top activity that is finishing
- * If all of these conditions are not met at the same time, the activity cannot be made active.
- */
- private boolean shouldMakeActive(ActivityRecord activeActivity) {
- // If the activity is stopped, stopping, cycle to an active state. We avoid doing
+ /** Check if activity should be moved to PAUSED state when it becomes visible. */
+ private boolean shouldPauseWhenBecomingVisible() {
+ // If the activity is stopped or stopping, cycle to the paused state. We avoid doing
// this when there is an activity waiting to become translucent as the extra binder
// calls will lead to noticeable jank. A later call to
- // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper
- // active state.
- if (!isState(RESUMED, PAUSED, STOPPED, STOPPING)
- || getActivityStack().mTranslucentActivityWaiting != null) {
- return false;
- }
-
- if (this == activeActivity) {
+ // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper
+ // paused state. We also avoid doing this for the activity the stack supervisor
+ // considers the resumed activity, as normal means will bring the activity from STOPPED
+ // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles.
+ if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null
+ || isResumedActivityOnDisplay()) {
return false;
}
@@ -2033,14 +1979,14 @@
throw new IllegalStateException("Activity not found in its task");
}
if (positionInTask == task.mActivities.size() - 1) {
- // It's the topmost activity in the task - should become resumed now
+ // It's the topmost activity in the task - should become paused now
return true;
}
// Check if activity above is finishing now and this one becomes the topmost in task.
final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
if (activityAbove.finishing && results == null) {
- // We will only allow making active if activity above wasn't launched for result.
- // Otherwise it will cause this activity to resume before getting result.
+ // We will only allow pausing if activity above wasn't launched for result. Otherwise it
+ // will cause this activity to resume before getting result.
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3aef8e1f..891c3da 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -357,11 +357,6 @@
*/
boolean mForceHidden = false;
- /**
- * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
- */
- boolean mInResumeTopActivity = false;
-
private boolean mUpdateBoundsDeferred;
private boolean mUpdateBoundsDeferredCalled;
private boolean mUpdateDisplayedBoundsDeferredCalled;
@@ -1737,7 +1732,6 @@
"Activity paused: token=" + token + ", timeout=" + timeout);
final ActivityRecord r = isInStackLocked(token);
-
if (r != null) {
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
if (mPausingActivity == r) {
@@ -2094,7 +2088,8 @@
boolean aboveTop = top != null;
final boolean stackShouldBeVisible = shouldBeVisible(starting);
boolean behindFullscreenActivity = !stackShouldBeVisible;
- boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
+ boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this)
+ && (isInStackLocked(starting) == null);
final boolean isTopNotPinnedStack =
isAttached() && getDisplay().isTopNotPinnedStack(this);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -2155,10 +2150,6 @@
if (r.handleAlreadyVisible()) {
resumeNextActivity = false;
}
-
- if (notifyClients) {
- r.makeActiveIfNeeded(starting);
- }
} else {
r.makeVisibleIfNeeded(starting, notifyClients);
}
@@ -2336,7 +2327,7 @@
r.setVisible(true);
}
if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */);
+ mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
return true;
}
}
@@ -2514,7 +2505,7 @@
*/
@GuardedBy("mService")
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
- if (mInResumeTopActivity) {
+ if (mStackSupervisor.inResumeTopActivity) {
// Don't even start recursing.
return false;
}
@@ -2522,7 +2513,7 @@
boolean result = false;
try {
// Protect against recursion.
- mInResumeTopActivity = true;
+ mStackSupervisor.inResumeTopActivity = true;
result = resumeTopActivityInnerLocked(prev, options);
// When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2537,7 +2528,7 @@
checkReadyForSleep();
}
} finally {
- mInResumeTopActivity = false;
+ mStackSupervisor.inResumeTopActivity = false;
}
return result;
@@ -2570,7 +2561,7 @@
// Find the next top-most activity to resume in this stack that is not finishing and is
// focusable. If it is not focusable, we will fall into the case below to resume the
// top activity in the next focusable task.
- ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+ final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
final boolean hasRunningActivity = next != null;
@@ -2658,12 +2649,6 @@
if (!mRootActivityContainer.allPausedActivitiesComplete()) {
if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
"resumeTopActivityLocked: Skip resume: some activity pausing.");
-
- // Adding previous activity to the waiting visible list, or it would be stopped
- // before top activity being visible.
- if (prev != null && !next.nowVisible) {
- mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev);
- }
return false;
}
@@ -2873,9 +2858,7 @@
// the screen based on the new activity order.
boolean notUpdated = true;
- // Activity should also be visible if set mLaunchTaskBehind to true (see
- // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
- if (shouldBeVisible(next)) {
+ if (isFocusedStackOnDisplay()) {
// We have special rotation behavior when here is some active activity that
// requests specific orientation or Keyguard is locked. Make sure all activity
// visibilities are set correctly as well as the transition is updated if needed
@@ -4104,12 +4087,6 @@
mStackSupervisor.mFinishingActivities.add(r);
r.resumeKeyDispatchingLocked();
mRootActivityContainer.resumeFocusedStacksTopActivities();
- // If activity was not paused at this point - explicitly pause it to start finishing
- // process. Finishing will be completed once it reports pause back.
- if (r.isState(RESUMED) && mPausingActivity != null) {
- startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */,
- false /* dontWait */);
- }
return r;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a83ef34..3a288ca 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -327,6 +327,9 @@
*/
PowerManager.WakeLock mGoingToSleep;
+ /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
+ boolean inResumeTopActivity;
+
/**
* Temporary rect used during docked stack resize calculation so we don't need to create a new
* object each time.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 43c1206..2807094 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -179,10 +179,7 @@
.setActivityOptions(options.toBundle())
.execute();
mLastHomeActivityStartRecord = tmpOutRecord[0];
- final ActivityDisplay display =
- mService.mRootActivityContainer.getActivityDisplay(displayId);
- final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
- if (homeStack != null && homeStack.mInResumeTopActivity) {
+ if (mSupervisor.inResumeTopActivity) {
// If we are in resume section already, home activity will be initialized, but not
// resumed (to avoid recursive resume) and will stay that way until something pokes it
// again. We need to schedule another resume.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4e2dffc..d36e545 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1629,7 +1629,7 @@
// Also, we don't want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
- mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
+ mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 711ca00..750c5ca 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -25,7 +25,6 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -591,9 +590,7 @@
delayed = runningAppAnimation = true;
}
final WindowState window = findMainWindow();
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (window != null && accessibilityController != null
- && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+ if (window != null && accessibilityController != null) {
accessibilityController.onAppWindowTransitionLocked(window, transit);
}
changed = true;
@@ -2407,7 +2404,7 @@
@Override
protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
if (!mSurfaceAnimator.hasLeash()) {
- t.reparent(mSurfaceControl, newParent.getHandle());
+ t.reparent(mSurfaceControl, newParent);
}
}
@@ -2453,7 +2450,7 @@
t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
// Reparent leash to animation bounds layer.
- t.reparent(leash, mAnimationBoundsLayer.getHandle());
+ t.reparent(leash, mAnimationBoundsLayer);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8026a04..8fefd35 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1466,11 +1466,9 @@
}
}
- // TODO (multi-display): Magnification is supported only for the default display.
// Announce rotation only if we will not animate as we already have the
// windows in final state. Otherwise, we make this call at the rotation end.
- if (screenRotationAnimation == null && mWmService.mAccessibilityController != null
- && isDefaultDisplay) {
+ if (screenRotationAnimation == null && mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onRotationChangedLocked(this);
}
}
@@ -4590,7 +4588,7 @@
* Reparents the given surface to mOverlayLayer.
*/
void reparentToOverlay(Transaction transaction, SurfaceControl surface) {
- transaction.reparent(surface, mOverlayLayer.getHandle());
+ transaction.reparent(surface, mOverlayLayer);
}
void applyMagnificationSpec(MagnificationSpec spec) {
@@ -4833,11 +4831,11 @@
* Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and
* {@link #mOverlayLayer} to the specified surfaceControl.
*
- * @param surfaceControlHandle The handle for the new SurfaceControl, where the DisplayContent's
+ * @param surfaceControlHandle The new SurfaceControl, where the DisplayContent's
* surfaces will be re-parented to.
*/
- void reparentDisplayContent(IBinder surfaceControlHandle) {
- mPendingTransaction.reparent(mWindowingLayer, surfaceControlHandle)
- .reparent(mOverlayLayer, surfaceControlHandle);
+ void reparentDisplayContent(SurfaceControl sc) {
+ mPendingTransaction.reparent(mWindowingLayer, sc)
+ .reparent(mOverlayLayer, sc);
}
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 786a306..3f77e1c 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -120,6 +120,8 @@
// A surface used to catch input events for the drag-and-drop operation.
SurfaceControl mInputSurface;
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
private final Rect mTmpClipRect = new Rect();
/**
@@ -240,7 +242,7 @@
// Clear the internal variables.
if (mSurfaceControl != null) {
- mSurfaceControl.destroy();
+ mTransaction.reparent(mSurfaceControl, null).apply();
mSurfaceControl = null;
}
if (mAnimator != null && !mAnimationCompleted) {
@@ -500,18 +502,13 @@
mCurrentY = y;
// Move the surface to the given touch
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
- mService.openSurfaceTransaction();
- try {
- mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
- if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG "
- + mSurfaceControl + ": pos=(" +
- (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
- } finally {
- mService.closeSurfaceTransaction("notifyMoveLw");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
- TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
+ }
+ mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply();
+ if (SHOW_TRANSACTIONS) {
+ Slog.i(TAG_WM, " DRAG " + mSurfaceControl + ": pos=(" + (int) (x - mThumbOffsetX) + ","
+ + (int) (y - mThumbOffsetY) + ")");
}
notifyLocationLocked(x, y);
}
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index c4a853d..9b72141 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,41 +1108,28 @@
return false;
}
- boolean result = false;
if (targetStack != null && (targetStack.isTopStackOnDisplay()
|| getTopDisplayFocusedStack() == targetStack)) {
- result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+ return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
}
+ // Resume all top activities in focused stacks on all displays.
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- boolean resumedOnDisplay = false;
final ActivityDisplay display = mActivityDisplays.get(displayNdx);
- for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getChildAt(stackNdx);
- final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();
- if (!stack.isFocusableAndVisible() || topRunningActivity == null) {
- continue;
- }
- if (topRunningActivity.isState(RESUMED)) {
- // Kick off any lingering app transitions form the MoveTaskToFront operation.
- stack.executeAppTransition(targetOptions);
- } else {
- resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);
- }
+ final ActivityStack focusedStack = display.getFocusedStack();
+ if (focusedStack == null) {
+ continue;
}
- if (!resumedOnDisplay) {
- // In cases when there are no valid activities (e.g. device just booted or launcher
- // crashed) it's possible that nothing was resumed on a display. Requesting resume
- // of top activity in focused stack explicitly will make sure that at least home
- // activity is started and resumed, and no recursion occurs.
- final ActivityStack focusedStack = display.getFocusedStack();
- if (focusedStack != null) {
- focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
- }
+ final ActivityRecord r = focusedStack.topRunningActivityLocked();
+ if (r == null || !r.isState(RESUMED)) {
+ focusedStack.resumeTopActivityUncheckedLocked(null, null);
+ } else if (r.isState(RESUMED)) {
+ // Kick off any lingering app transitions form the MoveTaskToFront operation.
+ focusedStack.executeAppTransition(targetOptions);
}
}
- return result;
+ return false;
}
void applySleepTokens(boolean applyToStacks) {
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9d9b48a..1a8a911 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -199,7 +199,7 @@
* @see #setLayer
*/
void reparent(Transaction t, SurfaceControl newParent) {
- t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent.getHandle());
+ t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent);
}
/**
@@ -228,8 +228,8 @@
// Cancel source animation, but don't let animation runner cancel the animation.
from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
- t.reparent(surface, mLeash.getHandle());
- t.reparent(mLeash, parent.getHandle());
+ t.reparent(surface, mLeash);
+ t.reparent(mLeash, parent);
mAnimatable.onAnimationLeashCreated(t, mLeash);
mService.mAnimationTransferMap.put(mAnimation, this);
}
@@ -275,7 +275,7 @@
final boolean destroy = mLeash != null && surface != null && parent != null;
if (destroy) {
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent");
- t.reparent(surface, parent.getHandle());
+ t.reparent(surface, parent);
scheduleAnim = true;
}
mService.mAnimationTransferMap.remove(mAnimation);
@@ -308,7 +308,7 @@
if (!hidden) {
t.show(leash);
}
- t.reparent(surface, leash.getHandle());
+ t.reparent(surface, leash);
return leash;
}
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 0529ed1..69dcaf4 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -698,14 +698,6 @@
return false;
}
- final boolean toTopOfStack = position == MAX_VALUE;
- if (toTopOfStack && toStack.getResumedActivity() != null
- && toStack.topRunningActivityLocked() != null) {
- // Pause the resumed activity on the target stack while re-parenting task on top of it.
- toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
- null /* resuming */, false /* pauseImmediately */);
- }
-
final int toStackWindowingMode = toStack.getWindowingMode();
final ActivityRecord topActivity = getTopActivity();
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b8a0739..b8db98b 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -167,13 +167,11 @@
screenRotationAnimation.kill();
displayAnimator.mScreenRotationAnimation = null;
- //TODO (multidisplay): Accessibility supported only for the default
// display.
- if (accessibilityController != null && dc.isDefaultDisplay) {
+ if (accessibilityController != null) {
// We just finished rotation animation which means we did not
// announce the rotation and waited for it to end, announce now.
- accessibilityController.onRotationChangedLocked(
- mService.getDefaultDisplayContentLocked());
+ accessibilityController.onRotationChangedLocked(dc);
}
}
}
@@ -197,9 +195,8 @@
screenRotationAnimation.updateSurfaces(mTransaction);
}
orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (accessibilityController != null && dc.isDefaultDisplay) {
- accessibilityController.drawMagnifiedRegionBorderIfNeededLocked();
+ if (accessibilityController != null) {
+ accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 1691dc0..5267e7e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -212,34 +212,40 @@
* and has access to the raw window data while the accessibility layer serves
* as a controller.
*
+ * @param displayId The logical display id.
* @param callbacks The callbacks to invoke.
+ * @return {@code false} if display id is not valid.
*/
- public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
+ public abstract boolean setMagnificationCallbacks(int displayId,
+ @Nullable MagnificationCallbacks callbacks);
/**
* Set by the accessibility layer to specify the magnification and panning to
* be applied to all windows that should be magnified.
*
+ * @param displayId The logical display id.
* @param spec The MagnficationSpec to set.
*
- * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ * @see #setMagnificationCallbacks(int, MagnificationCallbacks)
*/
- public abstract void setMagnificationSpec(MagnificationSpec spec);
+ public abstract void setMagnificationSpec(int displayId, MagnificationSpec spec);
/**
* Set by the accessibility framework to indicate whether the magnifiable regions of the display
* should be shown.
*
+ * @param displayId The logical display id.
* @param show {@code true} to show magnifiable region bounds, {@code false} to hide
*/
- public abstract void setForceShowMagnifiableBounds(boolean show);
+ public abstract void setForceShowMagnifiableBounds(int displayId, boolean show);
/**
* Obtains the magnification regions.
*
+ * @param displayId The logical display id.
* @param magnificationRegion the current magnification region
*/
- public abstract void getMagnificationRegion(@NonNull Region magnificationRegion);
+ public abstract void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion);
/**
* Gets the magnification and translation applied to a window given its token.
@@ -251,7 +257,7 @@
*
* @return The magnification spec for the window.
*
- * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ * @see #setMagnificationCallbacks(int, MagnificationCallbacks)
*/
public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow(
IBinder windowToken);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index efb38f5..c6679a9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1846,9 +1846,9 @@
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
WindowState window = mWindowMap.get(token);
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (window != null && window.getDisplayId() == DEFAULT_DISPLAY) {
- mAccessibilityController.onRectangleOnScreenRequestedLocked(rectangle);
+ if (window != null) {
+ mAccessibilityController.onRectangleOnScreenRequestedLocked(
+ window.getDisplayId(), rectangle);
}
}
}
@@ -2237,8 +2237,7 @@
win.mDestroying = true;
win.destroySurface(false, stopped);
}
- // TODO(multidisplay): Magnification is supported only for the default display.
- if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
+ if (mAccessibilityController != null) {
mAccessibilityController.onWindowTransitionLocked(win, transit);
}
@@ -6823,10 +6822,10 @@
}
@Override
- public void setMagnificationSpec(MagnificationSpec spec) {
+ public void setMagnificationSpec(int displayId, MagnificationSpec spec) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.setMagnificationSpecLocked(spec);
+ mAccessibilityController.setMagnificationSpecLocked(displayId, spec);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6837,10 +6836,10 @@
}
@Override
- public void setForceShowMagnifiableBounds(boolean show) {
+ public void setForceShowMagnifiableBounds(int displayId, boolean show) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.setForceShowMagnifiableBoundsLocked(show);
+ mAccessibilityController.setForceShowMagnifiableBoundsLocked(displayId, show);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6848,10 +6847,11 @@
}
@Override
- public void getMagnificationRegion(@NonNull Region magnificationRegion) {
+ public void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion) {
synchronized (mGlobalLock) {
if (mAccessibilityController != null) {
- mAccessibilityController.getMagnificationRegionLocked(magnificationRegion);
+ mAccessibilityController.getMagnificationRegionLocked(displayId,
+ magnificationRegion);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -6879,16 +6879,19 @@
}
@Override
- public void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks) {
+ public boolean setMagnificationCallbacks(int displayId,
+ @Nullable MagnificationCallbacks callbacks) {
synchronized (mGlobalLock) {
if (mAccessibilityController == null) {
mAccessibilityController = new AccessibilityController(
WindowManagerService.this);
}
- mAccessibilityController.setMagnificationCallbacksLocked(callbacks);
+ boolean result = mAccessibilityController.setMagnificationCallbacksLocked(
+ displayId, callbacks);
if (!mAccessibilityController.hasCallbacksLocked()) {
mAccessibilityController = null;
}
+ return result;
}
}
@@ -7267,8 +7270,12 @@
}, false /* traverseTopToBottom */);
}
- public void applyMagnificationSpec(MagnificationSpec spec) {
- getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+ /** Called from Accessibility Controller to apply magnification spec */
+ public void applyMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent != null) {
+ displayContent.applyMagnificationSpec(spec);
+ }
}
SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
@@ -7327,7 +7334,7 @@
}
@Override
- public void reparentDisplayContent(int displayId, IBinder surfaceControlHandle) {
+ public void reparentDisplayContent(int displayId, SurfaceControl sc) {
final Display display = mDisplayManager.getDisplay(displayId);
if (display == null) {
throw new IllegalArgumentException(
@@ -7344,7 +7351,7 @@
long token = Binder.clearCallingIdentity();
try {
DisplayContent displayContent = getDisplayContentOrCreate(displayId, null);
- displayContent.reparentDisplayContent(surfaceControlHandle);
+ displayContent.reparentDisplayContent(sc);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8f86c00..ce5eb84 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1607,8 +1607,7 @@
mWmService.mAccessibilityController;
final int winTransit = TRANSIT_EXIT;
mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */);
- //TODO (multidisplay): Magnification is supported only for the default
- if (accessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+ if (accessibilityController != null) {
accessibilityController.onWindowTransitionLocked(this, winTransit);
}
}
@@ -1625,8 +1624,7 @@
if (isVisibleNow()) {
mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
- //TODO (multidisplay): Magnification is supported only for the default
- if (mWmService.mAccessibilityController != null && isDefaultDisplay()) {
+ if (mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onWindowTransitionLocked(this, TRANSIT_EXIT);
}
changed = true;
@@ -1915,9 +1913,7 @@
setDisplayLayoutNeeded();
mWmService.requestTraversal();
}
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mWmService.mAccessibilityController != null
- && displayId == DEFAULT_DISPLAY) {
+ if (mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index fb5c556..6b4d6d2 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
@@ -1308,9 +1307,7 @@
transit = WindowManagerPolicy.TRANSIT_SHOW;
}
applyAnimationLocked(transit, true);
- //TODO (multidisplay): Magnification is supported only for the default display.
- if (mService.mAccessibilityController != null
- && mWin.getDisplayId() == DEFAULT_DISPLAY) {
+ if (mService.mAccessibilityController != null) {
mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fb00aeb..3729eaf 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -53,6 +53,7 @@
],
include_dirs: [
+ "bionic/libc/private",
"frameworks/base/libs",
"frameworks/native/services",
"system/gatekeeper/include",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index dc0d53b..159a496 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -24,6 +24,8 @@
#include <sensorservice/SensorService.h>
#include <sensorservicehidl/SensorManager.h>
+#include <bionic_malloc.h>
+
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -64,6 +66,11 @@
ALOGE_IF(err != OK, "Cannot register %s: %d", ISchedulingPolicyService::descriptor, err);
}
+static void android_server_SystemServer_initZygoteChildHeapProfiling(JNIEnv* /* env */,
+ jobject /* clazz */) {
+ android_mallopt(M_INIT_ZYGOTE_CHILD_PROFILING, nullptr, 0);
+}
+
/*
* JNI registration.
*/
@@ -71,6 +78,8 @@
/* name, signature, funcPtr */
{ "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService },
{ "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices },
+ { "initZygoteChildHeapProfiling", "()V",
+ (void*) android_server_SystemServer_initZygoteChildHeapProfiling },
};
int register_android_server_SystemServer(JNIEnv* env)
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
index 238f077..32513c1 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -134,12 +134,14 @@
}
/** Called when the database is created */
+ @Override
public void onCreate(@NonNull final SQLiteDatabase db) {
db.execSQL(NetworkAttributesContract.CREATE_TABLE);
db.execSQL(PrivateDataContract.CREATE_TABLE);
}
/** Called when the database is upgraded */
+ @Override
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
// No upgrade supported yet.
@@ -149,6 +151,7 @@
}
/** Called when the database is downgraded */
+ @Override
public void onDowngrade(@NonNull final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
// Downgrades always nuke all data and recreate an empty table.
@@ -247,7 +250,9 @@
// result here. 0 results means the key was not found.
if (cursor.getCount() != 1) return EXPIRY_ERROR;
cursor.moveToFirst();
- return cursor.getLong(0); // index in the EXPIRY_COLUMN array
+ final long result = cursor.getLong(0); // index in the EXPIRY_COLUMN array
+ cursor.close();
+ return result;
}
static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
@@ -321,6 +326,8 @@
final byte[] dnsAddressesBlob =
getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+ cursor.close();
+
if (0 != assignedV4AddressInt) {
builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
}
@@ -353,7 +360,9 @@
// get more than one result here. 0 results means the key was not found.
if (cursor.getCount() != 1) return null;
cursor.moveToFirst();
- return cursor.getBlob(0); // index in the DATA_COLUMN array
+ final byte[] result = cursor.getBlob(0); // index in the DATA_COLUMN array
+ cursor.close();
+ return result;
}
// Helper methods
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index 444b299..8b521f4 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -37,6 +37,7 @@
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.SameL3NetworkResponse;
import android.net.ipmemorystore.Status;
import android.net.ipmemorystore.StatusParcelable;
import android.net.ipmemorystore.Utils;
@@ -264,9 +265,40 @@
* Through the listener, a SameL3NetworkResponse containing the answer and confidence.
*/
@Override
- public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
- @NonNull final IOnSameNetworkResponseListener listener) {
- // TODO : implement this.
+ public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2,
+ @Nullable final IOnSameNetworkResponseListener listener) {
+ if (null == listener) return;
+ mExecutor.execute(() -> {
+ try {
+ if (null == l2Key1 || null == l2Key2) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ if (null == mDb) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+ return;
+ }
+ try {
+ final NetworkAttributes attr1 =
+ IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key1);
+ final NetworkAttributes attr2 =
+ IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2);
+ if (null == attr1 || null == attr2) {
+ listener.onSameNetworkResponse(makeStatus(SUCCESS),
+ new SameL3NetworkResponse(l2Key1, l2Key2,
+ -1f /* never connected */).toParcelable());
+ return;
+ }
+ final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2);
+ listener.onSameNetworkResponse(makeStatus(SUCCESS),
+ new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable());
+ } catch (Exception e) {
+ listener.onSameNetworkResponse(makeStatus(ERROR_GENERIC), null);
+ }
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
}
/**
@@ -285,21 +317,22 @@
mExecutor.execute(() -> {
try {
if (null == l2Key) {
- listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
+ listener.onNetworkAttributesRetrieved(
+ makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
return;
}
if (null == mDb) {
- listener.onL2KeyResponse(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
- null);
+ listener.onNetworkAttributesRetrieved(
+ makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, null);
return;
}
try {
final NetworkAttributes attributes =
IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key);
- listener.onL2KeyResponse(makeStatus(SUCCESS), l2Key,
+ listener.onNetworkAttributesRetrieved(makeStatus(SUCCESS), l2Key,
null == attributes ? null : attributes.toParcelable());
} catch (final Exception e) {
- listener.onL2KeyResponse(makeStatus(ERROR_GENERIC), l2Key, null);
+ listener.onNetworkAttributesRetrieved(makeStatus(ERROR_GENERIC), l2Key, null);
}
} catch (final RemoteException e) {
// Client at the other end died
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 32d7649..623990b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -328,6 +328,11 @@
private static native void startHidlServices();
/**
+ * Mark this process' heap as profileable. Only for debug builds.
+ */
+ private static native void initZygoteChildHeapProfiling();
+
+ /**
* The main entry point from zygote.
*/
public static void main(String[] args) {
@@ -449,6 +454,11 @@
// Initialize native services.
System.loadLibrary("android_servers");
+ // Debug builds - allow heap profiling.
+ if (Build.IS_DEBUGGABLE) {
+ initZygoteChildHeapProfiling();
+ }
+
// Check whether we failed to shut down last time we tried.
// This call may not return.
performPendingShutdown();
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 8187ac5..233b86f 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -19,6 +19,7 @@
import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.DhcpResults;
import android.net.INetd;
import android.net.IpPrefix;
@@ -37,7 +38,6 @@
import android.net.metrics.IpManagerEvent;
import android.net.shared.InitialConfiguration;
import android.net.util.InterfaceParams;
-import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
import android.net.util.SharedLog;
import android.os.ConditionVariable;
@@ -391,6 +391,7 @@
protected final IpClientCallbacks mCallback;
private final Dependencies mDependencies;
private final CountDownLatch mShutdownLatch;
+ private final ConnectivityManager mCm;
private final INetworkManagementService mNwService;
private final NetlinkTracker mNetlinkTracker;
private final WakeupMessage mProvisioningTimeoutAlarm;
@@ -408,7 +409,6 @@
*/
private LinkProperties mLinkProperties;
private android.net.shared.ProvisioningConfiguration mConfiguration;
- private MultinetworkPolicyTracker mMultinetworkPolicyTracker;
private IpReachabilityMonitor mIpReachabilityMonitor;
private DhcpClient mDhcpClient;
private DhcpResults mDhcpResults;
@@ -476,6 +476,7 @@
mCallback = new LoggingCallbackWrapper(callback);
mDependencies = deps;
mShutdownLatch = new CountDownLatch(1);
+ mCm = mContext.getSystemService(ConnectivityManager.class);
mNwService = deps.getNMS();
sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
@@ -961,8 +962,9 @@
// Note that we can still be disconnected by IpReachabilityMonitor
// if the IPv6 default gateway (but not the IPv6 DNS servers; see
// accompanying code in IpReachabilityMonitor) is unreachable.
- final boolean ignoreIPv6ProvisioningLoss = (mMultinetworkPolicyTracker != null)
- && !mMultinetworkPolicyTracker.getAvoidBadWifi();
+ final boolean ignoreIPv6ProvisioningLoss =
+ mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
+ && mCm.getAvoidBadWifi();
// Additionally:
//
@@ -1253,7 +1255,7 @@
mCallback.onReachabilityLost(logMsg);
}
},
- mMultinetworkPolicyTracker);
+ mConfiguration.mUsingMultinetworkPolicyTracker);
} catch (IllegalArgumentException iae) {
// Failed to start IpReachabilityMonitor. Log it and call
// onProvisioningFailure() immediately.
@@ -1486,13 +1488,6 @@
return;
}
- if (mConfiguration.mUsingMultinetworkPolicyTracker) {
- mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(
- mContext, getHandler(),
- () -> mLog.log("OBSERVED AvoidBadWifi changed"));
- mMultinetworkPolicyTracker.start();
- }
-
if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
doImmediateProvisioningFailure(
IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
@@ -1510,11 +1505,6 @@
mIpReachabilityMonitor = null;
}
- if (mMultinetworkPolicyTracker != null) {
- mMultinetworkPolicyTracker.shutdown();
- mMultinetworkPolicyTracker = null;
- }
-
if (mDhcpClient != null) {
mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
mDhcpClient.doQuit();
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 29e2f0c..761db68 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -22,6 +22,7 @@
import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.RouteInfo;
import android.net.ip.IpNeighborMonitor.NeighborEvent;
@@ -29,7 +30,6 @@
import android.net.metrics.IpReachabilityEvent;
import android.net.netlink.StructNdMsg;
import android.net.util.InterfaceParams;
-import android.net.util.MultinetworkPolicyTracker;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.PowerManager;
@@ -165,7 +165,8 @@
private final SharedLog mLog;
private final Callback mCallback;
private final Dependencies mDependencies;
- private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+ private final boolean mUsingMultinetworkPolicyTracker;
+ private final ConnectivityManager mCm;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
private LinkProperties mLinkProperties = new LinkProperties();
private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
@@ -174,19 +175,21 @@
public IpReachabilityMonitor(
Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
- MultinetworkPolicyTracker tracker) {
- this(ifParams, h, log, callback, tracker, Dependencies.makeDefault(context, ifParams.name));
+ boolean usingMultinetworkPolicyTracker) {
+ this(context, ifParams, h, log, callback, usingMultinetworkPolicyTracker,
+ Dependencies.makeDefault(context, ifParams.name));
}
@VisibleForTesting
- IpReachabilityMonitor(InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
- MultinetworkPolicyTracker tracker, Dependencies dependencies) {
+ IpReachabilityMonitor(Context context, InterfaceParams ifParams, Handler h, SharedLog log,
+ Callback callback, boolean usingMultinetworkPolicyTracker, Dependencies dependencies) {
if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
mInterfaceParams = ifParams;
mLog = log.forSubComponent(TAG);
mCallback = callback;
- mMultinetworkPolicyTracker = tracker;
+ mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
+ mCm = context.getSystemService(ConnectivityManager.class);
mDependencies = dependencies;
mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
@@ -324,7 +327,7 @@
}
private boolean avoidingBadLinks() {
- return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi();
+ return !mUsingMultinetworkPolicyTracker || mCm.getAvoidBadWifi();
}
public void probeAll() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index feffeef..773b877 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -20,7 +20,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -36,19 +38,15 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
import android.view.MagnificationSpec;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.R;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -81,48 +79,36 @@
static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS);
static final int SERVICE_ID_1 = 1;
static final int SERVICE_ID_2 = 2;
+ static final int DISPLAY_0 = 0;
+ static final int DISPLAY_1 = 1;
+ static final int DISPLAY_COUNT = 2;
+ static final int INVALID_DISPLAY = 2;
+ final MagnificationController.ControllerContext mMockControllerCtx =
+ mock(MagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
- final MessageCapturingHandler mMessageCapturingHandler =
- new MessageCapturingHandler(null);
+ final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(null);
- final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
- MagnificationController.SettingsBridge mMockSettingsBridge;
+ ValueAnimator mMockValueAnimator;
+ ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
MagnificationController mMagnificationController;
- ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
@Before
public void setUp() {
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
// Pretending ID of the Thread associated with looper as main thread ID in controller
when(mMockContext.getMainLooper()).thenReturn(looper);
- Resources mockResources = mock(Resources.class);
- when(mMockContext.getResources()).thenReturn(mockResources);
- when(mockResources.getInteger(R.integer.config_longAnimTime))
- .thenReturn(1000);
- mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class);
- mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(),
- mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
- mMockSettingsBridge);
+ when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
+ when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
+ when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
+ when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
+ when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
+ initMockWindowManager();
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Region regionArg = (Region) args[0];
- regionArg.set(INITIAL_MAGNIFICATION_REGION);
- return null;
- }
- }).when(mMockWindowManager).getMagnificationRegion((Region) anyObject());
-
- ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
- verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
- mTargetAnimationListener = listenerArgumentCaptor.getValue();
- Mockito.reset(mMockValueAnimator); // Ignore other initialization
+ mMagnificationController = new MagnificationController(mMockControllerCtx, new Object());
}
@After
@@ -133,88 +119,132 @@
@Test
public void testRegister_WindowManagerAndContextRegisterListeners() {
- mMagnificationController.register();
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ register(INVALID_DISPLAY);
verify(mMockContext).registerReceiver(
(BroadcastReceiver) anyObject(), (IntentFilter) anyObject());
- verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject());
- assertTrue(mMagnificationController.isRegisteredLocked());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(DISPLAY_0), (MagnificationCallbacks) anyObject());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(DISPLAY_1), (MagnificationCallbacks) anyObject());
+ verify(mMockWindowManager).setMagnificationCallbacks(
+ eq(INVALID_DISPLAY), (MagnificationCallbacks) anyObject());
+ assertTrue(mMagnificationController.isRegistered(DISPLAY_0));
+ assertTrue(mMagnificationController.isRegistered(DISPLAY_1));
+ assertFalse(mMagnificationController.isRegistered(INVALID_DISPLAY));
}
@Test
public void testRegister_WindowManagerAndContextUnregisterListeners() {
- mMagnificationController.register();
- mMagnificationController.unregister();
-
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ mMagnificationController.unregister(DISPLAY_0);
+ verify(mMockContext, times(0)).unregisterReceiver((BroadcastReceiver) anyObject());
+ mMagnificationController.unregister(DISPLAY_1);
verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
- verify(mMockWindowManager).setMagnificationCallbacks(null);
- assertFalse(mMagnificationController.isRegisteredLocked());
+ verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_0), eq(null));
+ verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_1), eq(null));
+ assertFalse(mMagnificationController.isRegistered(DISPLAY_0));
+ assertFalse(mMagnificationController.isRegistered(DISPLAY_1));
}
@Test
public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(
+ int displayId) {
+ register(displayId);
MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f);
Region initialMagRegion = new Region();
Rect initialBounds = new Rect();
- assertEquals(expectedInitialSpec, getCurrentMagnificationSpec());
- mMagnificationController.getMagnificationRegion(initialMagRegion);
- mMagnificationController.getMagnificationBounds(initialBounds);
+ assertEquals(expectedInitialSpec, getCurrentMagnificationSpec(displayId));
+ mMagnificationController.getMagnificationRegion(displayId, initialMagRegion);
+ mMagnificationController.getMagnificationBounds(displayId, initialBounds);
assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
- mMagnificationController.getCenterX(), 0.0f);
+ mMagnificationController.getCenterX(displayId), 0.0f);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
- mMagnificationController.getCenterY(), 0.0f);
+ mMagnificationController.getCenterY(displayId), 0.0f);
}
@Test
public void testNotRegistered_publicMethodsShouldBeBenign() {
- assertFalse(mMagnificationController.isMagnifying());
- assertFalse(mMagnificationController.magnificationRegionContains(100, 100));
- assertFalse(mMagnificationController.reset(true));
- assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0));
- assertFalse(mMagnificationController.setCenter(100, 100, false, 1));
- assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2));
- assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0);
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ notRegistered_publicMethodsShouldBeBenign(i);
+ resetMockWindowManager();
+ }
+ }
- mMagnificationController.getMagnificationRegion(new Region());
- mMagnificationController.getMagnificationBounds(new Rect());
- mMagnificationController.getScale();
- mMagnificationController.getOffsetX();
- mMagnificationController.getOffsetY();
- mMagnificationController.getCenterX();
- mMagnificationController.getCenterY();
- mMagnificationController.offsetMagnifiedRegion(50, 50, 1);
- mMagnificationController.unregister();
+ private void notRegistered_publicMethodsShouldBeBenign(int displayId) {
+ assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mMagnificationController.magnificationRegionContains(displayId, 100, 100));
+ assertFalse(mMagnificationController.reset(displayId, true));
+ assertFalse(mMagnificationController.setScale(displayId, 2, 100, 100, true, 0));
+ assertFalse(mMagnificationController.setCenter(displayId, 100, 100, false, 1));
+ assertFalse(mMagnificationController.setScaleAndCenter(displayId,
+ 1.5f, 100, 100, false, 2));
+ assertTrue(mMagnificationController.getIdOfLastServiceToMagnify(displayId) < 0);
+
+ mMagnificationController.getMagnificationRegion(displayId, new Region());
+ mMagnificationController.getMagnificationBounds(displayId, new Rect());
+ mMagnificationController.getScale(displayId);
+ mMagnificationController.getOffsetX(displayId);
+ mMagnificationController.getOffsetY(displayId);
+ mMagnificationController.getCenterX(displayId);
+ mMagnificationController.getCenterY(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 50, 50, 1);
+ mMagnificationController.unregister(displayId);
}
@Test
public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(int displayId) {
+ register(displayId);
final float scale = 2.0f;
final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
assertTrue(mMagnificationController
- .setScale(scale, center.x, center.y, false, SERVICE_ID_1));
+ .setScale(displayId, scale, center.x, center.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec));
- assertEquals(center.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(center.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(
+ eq(displayId), argThat(closeTo(expectedSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedSpec));
+ assertEquals(center.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(center.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScale_withPivotAndAnimation_stateChangesAndAnimationHappens(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.0f;
PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+ .setScale(displayId, scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
// New center should be halfway between original center and pivot
@@ -223,467 +253,645 @@
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
verify(mMockValueAnimator).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
// Intermediate point
Mockito.reset(mMockWindowManager);
float fraction = 0.5f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
// Final value
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testSetCenter_whileMagnifying_noAnimation_centerMoves() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setCenter_whileMagnifying_noAnimation_centerMoves(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setCenter_whileMagnifying_noAnimation_centerMoves(int displayId) {
+ register(displayId);
// First zoom in
float scale = 2.0f;
- assertTrue(mMagnificationController.setScale(scale,
+ assertTrue(mMagnificationController.setScale(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
false, SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1));
+ .setCenter(displayId, newCenter.x, newCenter.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(
+ eq(displayId), argThat(closeTo(expectedSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_animated_stateChangesAndAnimationHappens(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_animated_stateChangesAndAnimationHappens(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.5f;
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
- assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y,
- true, SERVICE_ID_1));
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale, newCenter.x,
+ newCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
- verify(mMockAms).notifyMagnificationChanged(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec));
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
verify(mMockValueAnimator).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
// Intermediate point
Mockito.reset(mMockWindowManager);
float fraction = 0.33f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
// Final value
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_scaleOutOfBounds_cappedAtLimits(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
MagnificationController.MAX_SCALE);
MagnificationSpec endSpec = getMagnificationSpec(
MagnificationController.MAX_SCALE, offsets);
- assertTrue(mMagnificationController.setScaleAndCenter(
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
MagnificationController.MAX_SCALE + 1.0f,
newCenter.x, newCenter.y, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
Mockito.reset(mMockWindowManager);
// Verify that we can't zoom below 1x
- assertTrue(mMagnificationController.setScaleAndCenter(0.5f,
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, 0.5f,
INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
- mMagnificationController.getCenterX(), 0.5);
+ mMagnificationController.getCenterX(displayId), 0.5);
assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
- mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+ mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
}
@Test
public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setScaleAndCenter_centerOutOfBounds_cappedAtLimits(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setScaleAndCenter_centerOutOfBounds_cappedAtLimits(int displayId) {
+ register(displayId);
float scale = 2.0f;
// Off the edge to the top and left
- assertTrue(mMagnificationController.setScaleAndCenter(
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
scale, -100f, -200f, false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
Mockito.reset(mMockWindowManager);
// Off the edge to the bottom and right
- assertTrue(mMagnificationController.setScaleAndCenter(scale,
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId, scale,
INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
false, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
- verify(mMockWindowManager).setMagnificationSpec(
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
}
@Test
public void testMagnificationRegionChanged_serviceNotified() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ magnificationRegionChanged_serviceNotified(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void magnificationRegionChanged_serviceNotified(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
- verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f,
+ verify(mMockAms).notifyMagnificationChanged(displayId, OTHER_REGION, 1.0f,
OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY());
}
@Test
public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileMagnifying_offsetsMove(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileMagnifying_offsetsMove(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
// First zoom in
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
- mMagnificationController.offsetMagnifiedRegion(
- startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1);
+ mMagnificationController.offsetMagnifiedRegion(displayId,
+ startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
- assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
- assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedSpec)));
+ assertEquals(newCenter.x, mMagnificationController.getCenterX(displayId), 0.0);
+ assertEquals(newCenter.y, mMagnificationController.getCenterY(displayId), 0.0);
verify(mMockValueAnimator, times(0)).start();
}
@Test
public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileNotMagnifying_hasNoEffect(int displayId) {
+ register(displayId);
Mockito.reset(mMockWindowManager);
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
- mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
+ mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void offsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect(int displayId) {
+ register(displayId);
float scale = 2.0f;
// Upper left edges
PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, ulCenter.x, ulCenter.y, false,
+ SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
- MagnificationSpec ulSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec));
+ MagnificationSpec ulSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, -10, -10,
+ SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(ulSpec));
verifyNoMoreInteractions(mMockWindowManager);
// Lower right edges
PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
assertTrue(mMagnificationController
- .setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1));
+ .setScaleAndCenter(displayId, scale, lrCenter.x, lrCenter.y, false,
+ SERVICE_ID_1));
Mockito.reset(mMockWindowManager);
- MagnificationSpec lrSpec = getCurrentMagnificationSpec();
- mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
- assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec));
+ MagnificationSpec lrSpec = getCurrentMagnificationSpec(displayId);
+ mMagnificationController.offsetMagnifiedRegion(displayId, 10, 10,
+ SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(lrSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ getIdOfLastServiceToChange_returnsCorrectValue(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void getIdOfLastServiceToChange_returnsCorrectValue(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
assertTrue(mMagnificationController
- .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1));
- assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify());
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
+ assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
assertTrue(mMagnificationController
- .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2));
- assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify());
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_2));
+ assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify(displayId));
}
@Test
public void testResetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void resetIfNeeded_resetsOnlyIfLastMagnifyingServiceIsDisabled(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
mMagnificationController
- .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ .setScale(displayId, 2.0f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMagnificationController
- .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2);
- assertFalse(mMagnificationController.resetIfNeeded(SERVICE_ID_1));
- assertTrue(mMagnificationController.isMagnifying());
- assertTrue(mMagnificationController.resetIfNeeded(SERVICE_ID_2));
- assertFalse(mMagnificationController.isMagnifying());
+ .setScale(displayId, 1.5f, startCenter.x, startCenter.y, false,
+ SERVICE_ID_2);
+ assertFalse(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_1));
+ assertTrue(mMagnificationController.isMagnifying(displayId));
+ assertTrue(mMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testSetUserId_resetsOnlyIfIdChanges() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ testSetUserId_resetsOnlyIfIdChanges(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void testSetUserId_resetsOnlyIfIdChanges(int displayId) {
final int userId1 = 1;
final int userId2 = 2;
- mMagnificationController.register();
+ register(displayId);
mMagnificationController.setUserId(userId1);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMagnificationController.setUserId(userId1);
- assertTrue(mMagnificationController.isMagnifying());
+ assertTrue(mMagnificationController.isMagnifying(displayId));
mMagnificationController.setUserId(userId2);
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testResetIfNeeded_doesWhatItSays() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ testResetIfNeeded_doesWhatItSays(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void testResetIfNeeded_doesWhatItSays(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
reset(mMockAms);
- assertTrue(mMagnificationController.resetIfNeeded(false));
- verify(mMockAms).notifyMagnificationChanged(
+ assertTrue(mMagnificationController.resetIfNeeded(displayId, false));
+ verify(mMockAms).notifyMagnificationChanged(eq(displayId),
eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat());
- assertFalse(mMagnificationController.isMagnifying());
- assertFalse(mMagnificationController.resetIfNeeded(false));
+ assertFalse(mMagnificationController.isMagnifying(displayId));
+ assertFalse(mMagnificationController.resetIfNeeded(displayId, false));
}
@Test
public void testTurnScreenOff_resetsMagnification() {
- mMagnificationController.register();
+ register(DISPLAY_0);
+ register(DISPLAY_1);
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mMockContext).registerReceiver(
broadcastReceiverCaptor.capture(), (IntentFilter) anyObject());
BroadcastReceiver br = broadcastReceiverCaptor.getValue();
- zoomIn2xToMiddle();
+ zoomIn2xToMiddle(DISPLAY_0);
+ zoomIn2xToMiddle(DISPLAY_1);
mMessageCapturingHandler.sendAllMessages();
br.onReceive(mMockContext, null);
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(DISPLAY_0));
+ assertFalse(mMagnificationController.isMagnifying(DISPLAY_1));
}
@Test
public void testUserContextChange_resetsMagnification() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ contextChange_resetsMagnification(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void contextChange_resetsMagnification(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
callbacks.onUserContextChanged();
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testRotation_resetsMagnification() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ rotation_resetsMagnification(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void rotation_resetsMagnification(int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- assertTrue(mMagnificationController.isMagnifying());
+ assertTrue(mMagnificationController.isMagnifying(displayId));
callbacks.onRotationChanged(0);
mMessageCapturingHandler.sendAllMessages();
- assertFalse(mMagnificationController.isMagnifying());
+ assertFalse(mMagnificationController.isMagnifying(displayId));
}
@Test
public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange(int displayId) {
// Going from a small region to a large one leads to no issues
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() {
- mMagnificationController.register();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileZoomingWithCompatibleSpec_noSpecChange(int displayId) {
+ register(displayId);
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
// setting animate parameter to true is differ from zoomIn2xToMiddle()
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
+ SERVICE_ID_1);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(OTHER_REGION_COMPAT);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained(
+ int displayId) {
// In a large region, pan to the farthest point possible
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(startSpec)));
Mockito.reset(mMockWindowManager);
callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec endSpec = getCurrentMagnificationSpec();
+ MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId);
assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() {
- mMagnificationController.register();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void boundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec(
+ int displayId) {
+ register(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
callbacks.onMagnificationRegionChanged(OTHER_REGION);
mMessageCapturingHandler.sendAllMessages();
PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, true,
+ SERVICE_ID_1);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
when(mMockValueAnimator.isRunning()).thenReturn(true);
callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator).cancel();
- MagnificationSpec endSpec = getCurrentMagnificationSpec();
+ MagnificationSpec endSpec = getCurrentMagnificationSpec(displayId);
assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
}
@Test
public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectAlreadyOnScreen_doesNothing(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectAlreadyOnScreen_doesNothing(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x;
int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y;
callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onRectangleOnScreenRequested(0, 0, 1, 1);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testRequestRectOnScreen_garbageInput_doesNothing() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_garbageInput_doesNothing(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_garbageInput_doesNothing(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
Mockito.reset(mMockWindowManager);
callbacks.onRectangleOnScreenRequested(0, 0, -50, -50);
mMessageCapturingHandler.sendAllMessages();
- assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
verifyNoMoreInteractions(mMockWindowManager);
}
@Test
public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale(
+ int displayId) {
Locale.setDefault(new Locale("en", "us"));
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
Mockito.reset(mMockWindowManager);
Rect wideRect = new Rect(0, 50, 100, 51);
callbacks.onRectangleOnScreenRequested(
wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
Mockito.reset(mMockWindowManager);
// Repeat with RTL
@@ -692,50 +900,66 @@
wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
mMessageCapturingHandler.sendAllMessages();
expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() {
- mMagnificationController.register();
- zoomIn2xToMiddle();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void requestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen(int displayId) {
+ register(displayId);
+ zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
- MagnificationCallbacks callbacks = getMagnificationCallbacks();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
Mockito.reset(mMockWindowManager);
Rect tallRect = new Rect(50, 0, 51, 100);
callbacks.onRectangleOnScreenRequested(
tallRect.left, tallRect.top, tallRect.right, tallRect.bottom);
mMessageCapturingHandler.sendAllMessages();
MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0);
- assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(expectedEndSpec));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(expectedEndSpec)));
}
@Test
public void testChangeMagnification_duringAnimation_animatesToNewValue() {
- mMagnificationController.register();
- MagnificationSpec startSpec = getCurrentMagnificationSpec();
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ changeMagnification_duringAnimation_animatesToNewValue(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void changeMagnification_duringAnimation_animatesToNewValue(int displayId) {
+ register(displayId);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
float scale = 2.5f;
PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
MagnificationSpec firstEndSpec = getMagnificationSpec(
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
- assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y,
- true, SERVICE_ID_1));
+ assertTrue(mMagnificationController.setScaleAndCenter(displayId,
+ scale, firstCenter.x, firstCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
- assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5);
- assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5);
- assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec));
+ assertEquals(firstCenter.x, mMagnificationController.getCenterX(displayId), 0.5);
+ assertEquals(firstCenter.y, mMagnificationController.getCenterY(displayId), 0.5);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(firstEndSpec));
verify(mMockValueAnimator, times(1)).start();
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(startSpec);
- verify(mMockAms).notifyMagnificationChanged(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec));
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y);
Mockito.reset(mMockWindowManager);
@@ -745,32 +969,34 @@
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
MagnificationSpec intermediateSpec1 =
getInterpolatedMagSpec(startSpec, firstEndSpec, fraction);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(intermediateSpec1)));
Mockito.reset(mMockWindowManager);
PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
MagnificationSpec newEndSpec = getMagnificationSpec(
scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
- assertTrue(mMagnificationController.setCenter(
+ assertTrue(mMagnificationController.setCenter(displayId,
newCenter.x, newCenter.y, true, SERVICE_ID_1));
mMessageCapturingHandler.sendAllMessages();
// Animation should have been restarted
verify(mMockValueAnimator, times(2)).start();
- verify(mMockAms).notifyMagnificationChanged(
+ verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
// New starting point should be where we left off
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(intermediateSpec1)));
Mockito.reset(mMockWindowManager);
// Second intermediate point
fraction = 0.5f;
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction))));
Mockito.reset(mMockWindowManager);
@@ -778,21 +1004,54 @@
Mockito.reset(mMockWindowManager);
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
- verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec)));
+ verify(mMockWindowManager).setMagnificationSpec(eq(displayId),
+ argThat(closeTo(newEndSpec)));
}
- private void zoomIn2xToMiddle() {
+ private void initMockWindowManager() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ when(mMockWindowManager.setMagnificationCallbacks(eq(i), any())).thenReturn(true);
+ }
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Region regionArg = (Region) args[1];
+ regionArg.set(INITIAL_MAGNIFICATION_REGION);
+ return null;
+ }
+ }).when(mMockWindowManager).getMagnificationRegion(anyInt(), (Region) anyObject());
+ }
+
+ private void resetMockWindowManager() {
+ Mockito.reset(mMockWindowManager);
+ initMockWindowManager();
+ }
+
+ private void register(int displayId) {
+ mMockValueAnimator = mock(ValueAnimator.class);
+ when(mMockControllerCtx.newValueAnimator()).thenReturn(mMockValueAnimator);
+ mMagnificationController.register(displayId);
+ ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
+ verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
+ mTargetAnimationListener = listenerArgumentCaptor.getValue();
+ Mockito.reset(mMockValueAnimator); // Ignore other initialization
+ }
+
+ private void zoomIn2xToMiddle(int displayId) {
PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
float scale = 2.0f;
- mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
- assertTrue(mMagnificationController.isMagnifying());
+ mMagnificationController.setScale(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1);
+ assertTrue(mMagnificationController.isMagnifying(displayId));
}
- private MagnificationCallbacks getMagnificationCallbacks() {
+ private MagnificationCallbacks getMagnificationCallbacks(int displayId) {
ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor =
ArgumentCaptor.forClass(MagnificationCallbacks.class);
verify(mMockWindowManager)
- .setMagnificationCallbacks(magnificationCallbacksCaptor.capture());
+ .setMagnificationCallbacks(eq(displayId), magnificationCallbacksCaptor.capture());
return magnificationCallbacksCaptor.getValue();
}
@@ -823,9 +1082,10 @@
return spec;
}
- private MagnificationSpec getCurrentMagnificationSpec() {
- return getMagnificationSpec(mMagnificationController.getScale(),
- mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY());
+ private MagnificationSpec getCurrentMagnificationSpec(int displayId) {
+ return getMagnificationSpec(mMagnificationController.getScale(displayId),
+ mMagnificationController.getOffsetX(displayId),
+ mMagnificationController.getOffsetY(displayId));
}
private MagSpecMatcher closeTo(MagnificationSpec spec) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index 032074a..d91ce39 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -26,16 +26,19 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
+import android.os.Handler;
import android.os.Message;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -104,14 +107,9 @@
public static final float DEFAULT_X = 301;
public static final float DEFAULT_Y = 299;
+ private static final int DISPLAY_0 = 0;
+
private Context mContext;
- final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
- final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
- final MessageCapturingHandler mMessageCapturingHandler =
- new MessageCapturingHandler(null);
- final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
- MagnificationController.SettingsBridge mMockSettingsBridge =
- mock(MagnificationController.SettingsBridge.class);
MagnificationController mMagnificationController;
private OffsettableClock mClock;
@@ -123,18 +121,26 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
- mMagnificationController = new MagnificationController(mContext, mMockAms, new Object(),
- mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
- mMockSettingsBridge) {
+ final MagnificationController.ControllerContext mockController =
+ mock(MagnificationController.ControllerContext.class);
+ final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
+ when(mockController.getContext()).thenReturn(mContext);
+ when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
+ when(mockController.getWindowManager()).thenReturn(mockWindowManager);
+ when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
+ when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
+ when(mockController.getAnimationDuration()).thenReturn(1000L);
+ when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+ mMagnificationController = new MagnificationController(mockController, new Object()) {
@Override
- public boolean magnificationRegionContains(float x, float y) {
+ public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
}
@Override
- void setForceShowMagnifiableBounds(boolean show) {}
+ void setForceShowMagnifiableBounds(int displayId, boolean show) {}
};
- mMagnificationController.register();
+ mMagnificationController.register(DISPLAY_0);
mClock = new OffsettableClock.Stopped();
boolean detectTripleTap = true;
@@ -144,7 +150,7 @@
@After
public void tearDown() {
- mMagnificationController.unregister();
+ mMagnificationController.unregister(DISPLAY_0);
}
@NonNull
@@ -302,6 +308,24 @@
assertZoomsImmediatelyOnSwipeFrom(STATE_SHORTCUT_TRIGGERED);
}
+ @Test
+ public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
+ // All delay motion events should be sent, if multi-tap with out of distance slop.
+ // STATE_IDLE will check if tapCount() < 2.
+ allowEventDelegation();
+ assertStaysIn(STATE_IDLE, () -> {
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ });
+ assertStaysIn(STATE_IDLE, () -> {
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ tap();
+ tap(DEFAULT_X * 2, DEFAULT_Y * 2);
+ tap();
+ });
+ }
+
private void assertZoomsImmediatelyOnSwipeFrom(int state) {
goFromStateIdleTo(state);
swipeAndHold();
@@ -509,7 +533,7 @@
}
private boolean isZoomed() {
- return mMgh.mMagnificationController.isMagnifying();
+ return mMgh.mMagnificationController.isMagnifying(DISPLAY_0);
}
private int tapCount() {
@@ -525,6 +549,11 @@
send(upEvent());
}
+ private void tap(float x, float y) {
+ send(downEvent(x, y));
+ send(upEvent(x, y));
+ }
+
private void swipe() {
swipeAndHold();
send(upEvent());
@@ -566,18 +595,26 @@
}
private MotionEvent downEvent() {
+ return downEvent(DEFAULT_X, DEFAULT_Y);
+ }
+
+ private MotionEvent downEvent(float x, float y) {
mLastDownTime = mClock.now();
return fromTouchscreen(MotionEvent.obtain(mLastDownTime, mLastDownTime,
- ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0));
+ ACTION_DOWN, x, y, 0));
}
private MotionEvent upEvent() {
- return upEvent(mLastDownTime);
+ return upEvent(DEFAULT_X, DEFAULT_Y, mLastDownTime);
}
- private MotionEvent upEvent(long downTime) {
+ private MotionEvent upEvent(float x, float y) {
+ return upEvent(x, y, mLastDownTime);
+ }
+
+ private MotionEvent upEvent(float x, float y, long downTime) {
return fromTouchscreen(MotionEvent.obtain(downTime, mClock.now(),
- MotionEvent.ACTION_UP, DEFAULT_X, DEFAULT_Y, 0));
+ MotionEvent.ACTION_UP, x, y, 0));
}
private MotionEvent pointerEvent(int action, float x, float y) {
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index 0328621..8afc3d3 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -58,8 +57,7 @@
// Make sure that there is a short-circuit for DEFAULT_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- DEFAULT_DISPLAY, false /* isVrImeStarted */,
- sMustNotBeCalledChecker));
+ DEFAULT_DISPLAY, sMustNotBeCalledChecker));
}
@Test
@@ -67,17 +65,7 @@
// Make sure that there is a short-circuit for INVALID_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- INVALID_DISPLAY, false /* isVrImeStarted */,
- sMustNotBeCalledChecker));
- }
-
- @Test
- public void testComputeImeDisplayId_VrIme() {
- // Make sure that there is a short-circuit for VR IME.
- assertEquals(DEFAULT_DISPLAY,
- InputMethodManagerService.computeImeDisplayIdForTarget(
- SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, true /* isVrImeStarted */,
- sMustNotBeCalledChecker));
+ INVALID_DISPLAY, sMustNotBeCalledChecker));
}
@Test
@@ -86,8 +74,7 @@
// Make sure IME displayId is DEFAULT_DISPLAY.
assertEquals(DEFAULT_DISPLAY,
InputMethodManagerService.computeImeDisplayIdForTarget(
- NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
- sChecker));
+ NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
}
@Test
@@ -96,7 +83,6 @@
// Make sure IME displayId is the same display.
assertEquals(SYSTEM_DECORATION_SUPPORT_DISPLAY_ID,
InputMethodManagerService.computeImeDisplayIdForTarget(
- SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, false /* isVrImeStarted */,
- sChecker));
+ SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 319ffed..8be63fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -31,7 +31,6 @@
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
-import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
@@ -76,9 +75,6 @@
mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
mTask = mStack.getChildAt(0);
mActivity = mTask.getTopActivity();
-
- doReturn(false).when(mService).isBooting();
- doReturn(true).when(mService).isBooted();
}
@Test
@@ -121,23 +117,22 @@
mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
- // The activity is in the focused stack so it should be resumed.
+ // The activity is in the focused stack so it should not move to paused.
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(RESUMED));
+ assertTrue(mActivity.isState(STOPPED));
assertFalse(pauseFound.value);
- // Make the activity non focusable
- mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
- doReturn(false).when(mActivity).isFocusable();
+ // Clear focused stack
+ final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ when(display.getFocusedStack()).thenReturn(null);
- // If the activity is not focusable, it should move to paused.
+ // In the unfocused stack, the activity should move to paused.
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
assertTrue(mActivity.isState(PAUSING));
assertTrue(pauseFound.value);
// Make sure that the state does not change for current non-stopping states.
mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
- doReturn(true).when(mActivity).isFocusable();
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index ea8f33f..68df87e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -55,7 +55,6 @@
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
-import android.os.PowerManager;
import android.os.Process;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
@@ -426,7 +425,6 @@
doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
// allow background activity starts by default
doReturn(true).when(this).isBackgroundActivityStartsEnabled();
- doNothing().when(this).updateCpuStats();
}
void setup(IntentFirewall intentFirewall, PendingIntentController intentController,
@@ -582,8 +580,6 @@
doNothing().when(this).acquireLaunchWakelock();
doReturn(mKeyguardController).when(this).getKeyguardController();
- mLaunchingActivity = mock(PowerManager.WakeLock.class);
-
initialize();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index d0b9225..ea5ab7b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -70,9 +70,9 @@
mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
verify(mTransaction).reparent(eq(mToken.getSurfaceControl()),
- eq(mToken.mSurfaceAnimator.mLeash.getHandle()));
+ eq(mToken.mSurfaceAnimator.mLeash));
verify(mTransaction).reparent(eq(mToken.mSurfaceAnimator.mLeash),
- eq(mToken.mAnimationBoundsLayer.getHandle()));
+ eq(mToken.mAnimationBoundsLayer));
}
@Test
@@ -111,7 +111,7 @@
mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
verify(mTransaction).reparent(eq(mToken.getSurfaceControl()),
- eq(mToken.mSurfaceAnimator.mLeash.getHandle()));
+ eq(mToken.mSurfaceAnimator.mLeash));
assertThat(mToken.mAnimationBoundsLayer).isNull();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index ad80cd6..9b84215 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -90,7 +90,7 @@
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
OnAnimationFinishedCallback.class);
assertAnimating(mAnimatable);
- verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle()));
+ verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture());
callbackCaptor.getValue().onAnimationFinished(mSpec);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index b996bfb..c595868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -19,6 +19,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
@@ -38,13 +42,12 @@
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
/**
* Tests for the {@link TaskPositioner} class.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:TaskPositionerTests
+ * atest WmTests:TaskPositionerTests
*/
@SmallTest
public class TaskPositionerTests extends WindowTestsBase {
@@ -73,18 +76,17 @@
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
- mPositioner = new TaskPositioner(mWm, Mockito.mock(IActivityTaskManager.class));
+ mPositioner = new TaskPositioner(mWm, mock(IActivityTaskManager.class));
mPositioner.register(mDisplayContent);
- mWindow = Mockito.spy(createWindow(null, TYPE_BASE_APPLICATION, "window"));
- final Task task = Mockito.spy(mWindow.getTask());
- Mockito.when(mWindow.getTask()).thenReturn(task);
-
- Mockito.doAnswer(invocation -> {
+ mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
+ final Task task = mWindow.getTask();
+ spyOn(task);
+ doAnswer(invocation -> {
final Rect rect = (Rect) invocation.getArguments()[0];
rect.set(mDimBounds);
- return (Void) null;
- }).when(task).getDimBounds(Mockito.any(Rect.class));
+ return null;
+ }).when(task).getDimBounds(any(Rect.class));
mWindow.getStack().setWindowingMode(WINDOWING_MODE_FREEFORM);
}
diff --git a/startop/OWNERS b/startop/OWNERS
index bfe96d3..762cd8e 100644
--- a/startop/OWNERS
+++ b/startop/OWNERS
@@ -3,3 +3,4 @@
eholk@google.com
iam@google.com
sehr@google.com
+mathieuc@google.com
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6c4b1af8..0fe5e08 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -793,15 +793,17 @@
* <p>
* Apps must be prepared for this method to return {@code null}, indicating that there currently
* exists no user-chosen default {@code PhoneAccount}.
+ * <p>
+ * The default dialer has access to use this method.
*
* @return The user outgoing phone account selected by the user.
- * @hide
*/
- @UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
try {
if (isServiceConnected()) {
- return getTelecomService().getUserSelectedOutgoingPhoneAccount();
+ return getTelecomService().getUserSelectedOutgoingPhoneAccount(
+ mContext.getOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getUserSelectedOutgoingPhoneAccount", e);
@@ -810,10 +812,14 @@
}
/**
- * Sets the user-chosen default for making outgoing phone calls.
+ * Sets the user-chosen default {@link PhoneAccountHandle} for making outgoing phone calls.
+ *
+ * @param accountHandle The {@link PhoneAccountHandle} which will be used by default for making
+ * outgoing voice calls.
* @hide
*/
- @UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @SystemApi
public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
try {
if (isServiceConnected()) {
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 954a709..e1d5c17 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -45,7 +45,7 @@
/**
* @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount
*/
- PhoneAccountHandle getUserSelectedOutgoingPhoneAccount();
+ PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage);
/**
* @see TelecomServiceImpl#setUserSelectedOutgoingPhoneAccount
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 85c53f2..ca264f7 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -961,6 +961,13 @@
/** @hide */
public static final int RESET_BY_FRAMEWORK = 0x10005;
+ /**
+ * Data handover failed.
+ *
+ * @hide
+ */
+ public static final int HANDOVER_FAILED = 0x10006;
+
/** @hide */
@IntDef(value = {
NONE,
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 0e5c71d..0fa1b41 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -129,6 +129,66 @@
"android.telephony.euicc.action.RESOLVE_ERROR";
/**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * enable or disable a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+ * {@link #EXTRA_ENABLE_SUBSCRIPTION}.
+ *
+ * <p>Unlike {@link #switchToSubscription(int, PendingIntent)}, using this action allows the
+ * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+ * operation. The action is received by the Telephony framework, which in turn selects and
+ * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+ * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+
+ /**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * delete a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID}.
+ *
+ * <p>Unlike {@link #deleteSubscription(int, PendingIntent)}, using this action allows the
+ * underlying eUICC service (i.e. the LPA app) to control the UI experience during this
+ * operation. The action is received by the Telephony framework, which in turn selects and
+ * launches an appropriate LPA activity to present UI to the user. For example, the activity may
+ * show a confirmation dialog, a progress dialog, or an error dialog when necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
+
+ /**
+ * Intent action sent by system apps (such as the Settings app) to the Telephony framework to
+ * rename a subscription. Must be accompanied with {@link #EXTRA_SUBSCRIPTION_ID} and
+ * {@link #EXTRA_SUBSCRIPTION_NICKNAME}.
+ *
+ * <p>Unlike {@link #updateSubscriptionNickname(int, String, PendingIntent)}, using this action
+ * allows the the underlying eUICC service (i.e. the LPA app) to control the UI experience
+ * during this operation. The action is received by the Telephony framework, which in turn
+ * selects and launches an appropriate LPA activity to present UI to the user. For example, the
+ * activity may show a confirmation dialog, a progress dialog, or an error dialog when
+ * necessary.
+ *
+ * <p>The launched activity will immediately finish with
+ * {@link android.app.Activity#RESULT_CANCELED} if {@link #isEnabled} is false.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED =
+ "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
+
+ /**
* Result code for an operation indicating that the operation succeeded.
*/
public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0;
@@ -219,6 +279,37 @@
"android.telephony.euicc.extra.FORCE_PROVISION";
/**
+ * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED},
+ * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and
+ * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SUBSCRIPTION_ID =
+ "android.telephony.euicc.extra.SUBSCRIPTION_ID";
+
+ /**
+ * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean
+ * value of whether to enable or disable the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_ENABLE_SUBSCRIPTION =
+ "android.telephony.euicc.extra.ENABLE_SUBSCRIPTION";
+
+ /**
+ * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new
+ * nickname for the targeted subscription.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SUBSCRIPTION_NICKNAME =
+ "android.telephony.euicc.extra.SUBSCRIPTION_NICKNAME";
+
+ /**
* Optional meta-data attribute for a carrier app providing an icon to use to represent the
* carrier. If not provided, the app's launcher icon will be used as a fallback.
*/
diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/tests/net/java/android/net/ip/IpClientTest.java
index 7a83757..5110ce1 100644
--- a/tests/net/java/android/net/ip/IpClientTest.java
+++ b/tests/net/java/android/net/ip/IpClientTest.java
@@ -33,6 +33,7 @@
import android.app.AlarmManager;
import android.content.Context;
import android.content.res.Resources;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -82,6 +83,7 @@
private static final int TEST_TIMEOUT_MS = 400;
@Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
@Mock private INetworkManagementService mNMService;
@Mock private INetd mNetd;
@Mock private Resources mResources;
@@ -98,6 +100,9 @@
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
+ when(mContext.getSystemServiceName(ConnectivityManager.class))
+ .thenReturn(Context.CONNECTIVITY_SERVICE);
+ when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
.thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
index e65585f..e3b5ddf 100644
--- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
+++ b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
@@ -16,11 +16,10 @@
package android.net.ip;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.net.util.InterfaceParams;
import android.net.util.SharedLog;
import android.os.Handler;
@@ -45,6 +44,7 @@
@Mock IpReachabilityMonitor.Callback mCallback;
@Mock IpReachabilityMonitor.Dependencies mDependencies;
@Mock SharedLog mLog;
+ @Mock Context mContext;
Handler mHandler;
@Before
@@ -56,7 +56,8 @@
IpReachabilityMonitor makeMonitor() {
final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null);
- return new IpReachabilityMonitor(ifParams, mHandler, mLog, mCallback, null, mDependencies);
+ return new IpReachabilityMonitor(
+ mContext, ifParams, mHandler, mLog, mCallback, false, mDependencies);
}
@Test
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index 94bcd28..c748d0f 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -28,9 +28,12 @@
import android.net.ipmemorystore.Blob;
import android.net.ipmemorystore.IOnBlobRetrievedListener;
import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.SameL3NetworkResponse;
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
import android.net.ipmemorystore.Status;
import android.net.ipmemorystore.StatusParcelable;
import android.os.IBinder;
@@ -53,7 +56,6 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
-import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -65,6 +67,8 @@
private static final String TEST_CLIENT_ID = "testClientId";
private static final String TEST_DATA_NAME = "testData";
+ private static final String[] FAKE_KEYS = { "fakeKey1", "fakeKey2", "fakeKey3", "fakeKey4" };
+
@Mock
private Context mMockContext;
private File mDbFile;
@@ -130,8 +134,8 @@
final OnNetworkAttributesRetrievedListener functor) {
return new IOnNetworkAttributesRetrieved() {
@Override
- public void onL2KeyResponse(final StatusParcelable status, final String l2Key,
- final NetworkAttributesParcelable attributes)
+ public void onNetworkAttributesRetrieved(final StatusParcelable status,
+ final String l2Key, final NetworkAttributesParcelable attributes)
throws RemoteException {
functor.onNetworkAttributesRetrieved(new Status(status), l2Key,
null == attributes ? null : new NetworkAttributes(attributes));
@@ -144,6 +148,28 @@
};
}
+ /** Helper method to make an IOnSameNetworkResponseListener */
+ private interface OnSameNetworkResponseListener {
+ void onSameNetworkResponse(Status status, SameL3NetworkResponse answer);
+ }
+ private IOnSameNetworkResponseListener onSameResponse(
+ final OnSameNetworkResponseListener functor) {
+ return new IOnSameNetworkResponseListener() {
+ @Override
+ public void onSameNetworkResponse(final StatusParcelable status,
+ final SameL3NetworkResponseParcelable sameL3Network)
+ throws RemoteException {
+ functor.onSameNetworkResponse(new Status(status),
+ null == sameL3Network ? null : new SameL3NetworkResponse(sameL3Network));
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ }
+
// Helper method to factorize some boilerplate
private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
final CountDownLatch latch = new CountDownLatch(1);
@@ -155,6 +181,19 @@
}
}
+ // Helper methods to factorize more boilerplate
+ private void storeAttributes(final String l2Key, final NetworkAttributes na) {
+ storeAttributes("Did not complete storing attributes", l2Key, na);
+ }
+ private void storeAttributes(final String timeoutMessage, final String l2Key,
+ final NetworkAttributes na) {
+ doLatched(timeoutMessage, latch -> mService.storeNetworkAttributes(l2Key, na.toParcelable(),
+ onStatus(status -> {
+ assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+ latch.countDown();
+ })));
+ }
+
@Test
public void testNetworkAttributes() {
final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
@@ -164,15 +203,9 @@
} catch (UnknownHostException e) { /* Can't happen */ }
na.setGroupHint("hint1");
na.setMtu(219);
- final String l2Key = UUID.randomUUID().toString();
+ final String l2Key = FAKE_KEYS[0];
NetworkAttributes attributes = na.build();
- doLatched("Did not complete storing attributes", latch ->
- mService.storeNetworkAttributes(l2Key, attributes.toParcelable(),
- onStatus(status -> {
- assertTrue("Store status not successful : " + status.resultCode,
- status.isSuccess());
- latch.countDown();
- })));
+ storeAttributes(l2Key, attributes);
doLatched("Did not complete retrieving attributes", latch ->
mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
@@ -190,9 +223,7 @@
new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
} catch (UnknownHostException e) { /* Still can't happen */ }
final NetworkAttributes attributes2 = na2.build();
- doLatched("Did not complete storing attributes 2", latch ->
- mService.storeNetworkAttributes(l2Key, attributes2.toParcelable(),
- onStatus(status -> latch.countDown())));
+ storeAttributes("Did not complete storing attributes 2", l2Key, attributes2);
doLatched("Did not complete retrieving attributes 2", latch ->
mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
@@ -268,7 +299,7 @@
public void testPrivateData() {
final Blob b = new Blob();
b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
- final String l2Key = UUID.randomUUID().toString();
+ final String l2Key = FAKE_KEYS[0];
doLatched("Did not complete storing private data", latch ->
mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
onStatus(status -> {
@@ -306,8 +337,50 @@
// TODO : implement this
}
+ private void assertNetworksSameness(final String key1, final String key2, final int sameness) {
+ doLatched("Did not finish evaluating sameness", latch ->
+ mService.isSameNetwork(key1, key2, onSameResponse((status, answer) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(sameness, answer.getNetworkSameness());
+ })));
+ }
+
@Test
- public void testIsSameNetwork() {
- // TODO : implement this
+ public void testIsSameNetwork() throws UnknownHostException {
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+ na.setAssignedV4Address((Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
+ na.setGroupHint("hint1");
+ na.setMtu(219);
+ na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
+
+ storeAttributes(FAKE_KEYS[0], na.build());
+ // 0 and 1 have identical attributes
+ storeAttributes(FAKE_KEYS[1], na.build());
+
+ // Hopefully only the MTU being different still means it's the same network
+ na.setMtu(200);
+ storeAttributes(FAKE_KEYS[2], na.build());
+
+ // Hopefully different MTU, assigned V4 address and grouphint make a different network,
+ // even with identical DNS addresses
+ na.setAssignedV4Address(null);
+ na.setGroupHint("hint2");
+ storeAttributes(FAKE_KEYS[3], na.build());
+
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[1], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[1], FAKE_KEYS[2], SameL3NetworkResponse.NETWORK_SAME);
+ assertNetworksSameness(FAKE_KEYS[0], FAKE_KEYS[3], SameL3NetworkResponse.NETWORK_DIFFERENT);
+ assertNetworksSameness(FAKE_KEYS[0], "neverInsertedKey",
+ SameL3NetworkResponse.NETWORK_NEVER_CONNECTED);
+
+ doLatched("Did not finish evaluating sameness", latch ->
+ mService.isSameNetwork(null, null, onSameResponse((status, answer) -> {
+ assertFalse("Retrieve network sameness suspiciously successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ assertNull(answer);
+ })));
}
}
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
new file mode 100644
index 0000000..fe19eee
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipmemorystore.NetworkAttributes;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/** Unit tests for {@link NetworkAttributes}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NetworkAttributesTest {
+ private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_";
+ private static final float EPSILON = 0.0001f;
+
+ // This is running two tests to make sure the total weight is the sum of all weights. To be
+ // sure this is not fireproof, but you'd kind of need to do it on purpose to pass.
+ @Test
+ public void testTotalWeight() throws IllegalAccessException, UnknownHostException {
+ // Make sure that TOTAL_WEIGHT is equal to the sum of the fields starting with WEIGHT_
+ float sum = 0f;
+ final Field[] fieldList = NetworkAttributes.class.getDeclaredFields();
+ for (final Field field : fieldList) {
+ if (!field.getName().startsWith(WEIGHT_FIELD_NAME_PREFIX)) continue;
+ field.setAccessible(true);
+ sum += (float) field.get(null);
+ }
+ assertEquals(sum, NetworkAttributes.TOTAL_WEIGHT, EPSILON);
+
+ // Use directly the constructor with all attributes, and make sure that when compared
+ // to itself the score is a clean 1.0f.
+ final NetworkAttributes na =
+ new NetworkAttributes(
+ (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}),
+ "some hint",
+ Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}),
+ Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})),
+ 98);
+ assertEquals(1.0f, na.getNetworkGroupSamenessConfidence(na), EPSILON);
+ }
+}
diff --git a/tests/utils/SleepUtils/AlarmService/Android.mk b/tests/utils/SleepUtils/AlarmService/Android.mk
deleted file mode 100644
index 9022f03..0000000
--- a/tests/utils/SleepUtils/AlarmService/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright (C) 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += \
- src/com/android/testing/alarmservice/Alarm.aidl
-LOCAL_PACKAGE_NAME := SleepUtilsAlarmService
-LOCAL_SDK_VERSION := 7
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml b/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
deleted file mode 100644
index 1b6de39..0000000
--- a/tests/utils/SleepUtils/AlarmService/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the
- Apache License, Version 2.0 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software distributed
- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
- OR CONDITIONS OF ANY KIND, either express or implied. See the License for
- the specific language governing permissions and limitations under the License. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.testing.alarmservice" >
-
- <uses-sdk android:minSdkVersion="7" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
-
- <application android:label="Sleep Utils Alarm Service">
- <service android:name=".AlarmService"
- android:label="Sleep Utils Alarm Service"
- android:exported="true"
- android:enabled="true">
- <intent-filter>
- <action android:name="com.android.testing.ALARM_SERVICE" />
- </intent-filter>
- </service>
- <receiver android:name=".WakeUpCall">
- <intent-filter>
- <action android:name="com.android.testing.alarmservice.WAKEUP" />
- </intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
deleted file mode 100644
index 122d55d..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.testing.alarmservice;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.testing.alarmservice.Alarm.Stub;
-
-public class AlarmImpl extends Stub {
-
- private static final String LOG_TAG = AlarmImpl.class.getSimpleName();
-
- private Context mContext;
-
- public AlarmImpl(Context context) {
- super();
- mContext = context;
- }
-
- @Override
- public int prepare() throws RemoteException {
- WakeUpController.getController().getWakeLock().acquire();
- Log.d(LOG_TAG, "AlarmService prepared, wake lock acquired");
- return 0;
- }
-
- @Override
- public int setAlarmAndWait(long timeoutMills) throws RemoteException {
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + timeoutMills;
- AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL);
- PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, wakupIntent, 0);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- Log.d(LOG_TAG, String.format("Alarm set: %d, giving up wake lock", atTime));
- Object lock = WakeUpController.getController().getWakeSync();
- // release wakelock and wait for the lock to be poked from the broadcast receiver
- WakeUpController.getController().getWakeLock().release();
- // does not really matter if device enters suspend before we start waiting on lock
- synchronized (lock) {
- try {
- lock.wait();
- } catch (InterruptedException e) {
- }
- }
- Log.d(LOG_TAG, String.format("Alarm triggered, done waiting"));
- return 0;
- }
-
- @Override
- public int done() throws RemoteException {
- WakeUpController.getController().getWakeLock().release();
- return 0;
- }
-
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
deleted file mode 100644
index 576a1cf..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/AlarmService.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.testing.alarmservice;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class AlarmService extends Service {
-
- private AlarmImpl mAlarmImpl = null;
- static Context sContext;
-
- @Override
- public void onCreate() {
- super.onCreate();
- sContext = this;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return getAlarmImpl();
- }
-
- private AlarmImpl getAlarmImpl() {
- if (mAlarmImpl == null) {
- mAlarmImpl = new AlarmImpl(this);
- }
- return mAlarmImpl;
- }
-
- @Override
- public void onDestroy() {
- sContext = null;
- super.onDestroy();
- }
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
deleted file mode 100644
index f4bb4db..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpCall.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.testing.alarmservice;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * The receiver for the alarm we set
- *
- */
-public class WakeUpCall extends BroadcastReceiver {
-
- public static final String WAKEUP_CALL = "com.android.testing.alarmservice.WAKEUP";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // we acquire wakelock without release because user is supposed to manually release it
- WakeUpController.getController().getWakeLock().acquire();
- Object lock = WakeUpController.getController().getWakeSync();
- synchronized (lock) {
- // poke the lock so the service side can be woken from waiting on the lock
- lock.notifyAll();
- }
- }
-
-}
diff --git a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java b/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
deleted file mode 100644
index 478371f..0000000
--- a/tests/utils/SleepUtils/AlarmService/src/com/android/testing/alarmservice/WakeUpController.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.testing.alarmservice;
-
-import android.content.Context;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.util.Log;
-
-/**
- * A singleton used for controlling and sharing of states/wakelocks
- *
- */
-public class WakeUpController {
-
- private static final String LOG_TAG = WakeUpController.class.getName();
- private static WakeUpController mController = null;
- private WakeLock mWakeLock = null;
- private Object mWakeSync = new Object();
-
- private WakeUpController() {
- Log.i(LOG_TAG, "Created instance: 0x" + Integer.toHexString(this.hashCode()));
- }
-
- public static synchronized WakeUpController getController() {
- if (mController == null) {
- mController = new WakeUpController();
- }
- return mController;
- }
-
- public WakeLock getWakeLock() {
- if (mWakeLock == null) {
- PowerManager pm =
- (PowerManager) AlarmService.sContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "testing-alarmservice");
- Log.i(LOG_TAG, "Create wakelock: 0x" + Integer.toHexString(mWakeLock.hashCode()));
- }
- return mWakeLock;
- }
-
- public Object getWakeSync() {
- return mWakeSync;
- }
-}
diff --git a/tests/utils/SleepUtils/Android.mk b/tests/utils/SleepUtils/Android.mk
deleted file mode 100644
index 0e65e22..0000000
--- a/tests/utils/SleepUtils/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/tests/utils/SleepUtils/README b/tests/utils/SleepUtils/README
deleted file mode 100644
index bfe07da..0000000
--- a/tests/utils/SleepUtils/README
+++ /dev/null
@@ -1,23 +0,0 @@
-This folder contains utils to properly perform timed suspend and wakeup.
-
-AlarmService - a service that client can bind to and perform:
-1) holding wakelock (singleton to this service)
-2) setting alarm for a specified period and releasing the wakelock; service
- call will block until alarm has been triggered and the wakelock is held
-3) releasing the wakelock
-
-SleepHelper - a self instrumentation meant as a convenient way to trigger
-the service functions from command line. Corresponding to service function
-above, supported operations are:
-1) holding wakelock
-am instrument -w -e command prepare \
- com.android.testing.sleephelper/.SetAlarm
-
-2) setting alarm and wait til triggered
-am instrument -w -e command set_wait \
- -e param <time in ms> com.android.testing.sleephelper/.SetAlarm
-Note: for the function to work properly, "-w" parameter is required
-
-3) releasing wakelock
-am instrument -w -e command done \
- com.android.testing.sleephelper/.SetAlarm
diff --git a/tests/utils/SleepUtils/SleepHelper/Android.mk b/tests/utils/SleepUtils/SleepHelper/Android.mk
deleted file mode 100644
index f8267fd..0000000
--- a/tests/utils/SleepUtils/SleepHelper/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-# Only compile source java files in this apk.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SRC_FILES += \
- ../AlarmService/src/com/android/testing/alarmservice/Alarm.aidl
-LOCAL_SDK_VERSION := 7
-LOCAL_PACKAGE_NAME := SleepUtilsSleepHelper
-
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml b/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
deleted file mode 100644
index 0f1d491..0000000
--- a/tests/utils/SleepUtils/SleepHelper/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the
- Apache License, Version 2.0 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software distributed
- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
- OR CONDITIONS OF ANY KIND, either express or implied. See the License for
- the specific language governing permissions and limitations under the License. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.testing.sleephelper">
-
- <uses-sdk android:minSdkVersion="7" />
- <instrumentation android:label="Sleep Helper"
- android:name="com.android.testing.sleephelper.SetAlarm"
- android:targetPackage="com.android.testing.sleephelper" />
-
- <application android:label="Sleep Utils Sleep Helper">
- <uses-library android:name="android.test.runner" />
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java b/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
deleted file mode 100644
index b558741..0000000
--- a/tests/utils/SleepUtils/SleepHelper/src/com/android/testing/sleephelper/SetAlarm.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.testing.sleephelper;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.testing.alarmservice.Alarm;
-
-public class SetAlarm extends Instrumentation {
-
- private static final String COMMAND = "command";
- private static final String PARAM = "param";
- private static final String CMD_PREPARE = "prepare";
- private static final String CMD_SET = "set_wait";
- private static final String CMD_DONE = "done";
- private static final String SERVICE_ACTION = "com.android.testing.ALARM_SERVICE";
- private static final String SERVICE_PKG = "com.android.testing.alarmservice";
- private static final String LOG_TAG = SetAlarm.class.getSimpleName();
-
- private Alarm mAlarmService = null;
- private Bundle mArgs = null;
- private String mCommand = null;
- private Intent mServceIntent = new Intent(SERVICE_ACTION).setPackage(SERVICE_PKG);
-
- private ServiceConnection mConn = new ServiceConnection() {
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Log.d(LOG_TAG, "Service disconnected.");
- mAlarmService = null;
- errorFinish("service disconnected");
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.d(LOG_TAG, "Service connected.");
- mAlarmService = Alarm.Stub.asInterface(service);
- handleCommands();
- }
- };
-
-
- private void handleCommands() {
- if (CMD_PREPARE.equals(mCommand)) {
- callPrepare();
- } else if (CMD_SET.equals(mCommand)) {
- String paramString = mArgs.getString(PARAM);
- if (paramString == null) {
- errorFinish("argument expected for alarm time");
- }
- long timeout = -1;
- try {
- timeout = Long.parseLong(paramString);
- } catch (NumberFormatException nfe) {
- errorFinish("a number argument is expected");
- }
- callSetAndWait(timeout);
- } else if (CMD_DONE.equals(mCommand)) {
- callDone();
- } else {
- errorFinish("Unrecognized command: " + mCommand);
- }
- finish(Activity.RESULT_OK, new Bundle());
- }
-
- @Override
- public void onCreate(Bundle arguments) {
- super.onCreate(arguments);
- mCommand = arguments.getString(COMMAND);
- if ("true".equals(arguments.getString("debug"))) {
- Debug.waitForDebugger();
- }
- if (mCommand == null) {
- errorFinish("No command specified");
- }
- mArgs = arguments;
- connectToAlarmService();
- }
-
- private void errorFinish(String msg) {
- Bundle ret = new Bundle();
- ret.putString("error", msg);
- finish(Activity.RESULT_CANCELED, ret);
- }
-
- private void connectToAlarmService() {
- // start the service with an intent, this ensures the service keeps running after unbind
- ComponentName cn = getContext().startService(mServceIntent);
- if (cn == null) {
- errorFinish("failed to start service");
- }
- if (!getContext().bindService(mServceIntent, mConn, Context.BIND_AUTO_CREATE)) {
- errorFinish("failed to bind service");
- }
- }
-
- private void callPrepare() {
- try {
- mAlarmService.prepare();
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in prepare()");
- } finally {
- getContext().unbindService(mConn);
- }
- }
-
- private void callDone() {
- try {
- mAlarmService.done();
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in prepare()");
- } finally {
- getContext().unbindService(mConn);
- }
- // explicitly stop the service (started in prepare()) so that the service is now free
- // to be reclaimed
- getContext().stopService(mServceIntent);
- }
-
- private void callSetAndWait(long timeoutMills) {
- try {
- mAlarmService.setAlarmAndWait(timeoutMills);
- } catch (RemoteException e) {
- errorFinish("RemoteExeption in setAlarmAndWait()");
- } finally {
- getContext().unbindService(mConn);
- }
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/Android.mk b/tests/utils/SleepUtils/WakeLoopService/Android.mk
deleted file mode 100644
index a8a944b..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := WakeupLoopService
-LOCAL_SDK_VERSION := 7
-include $(BUILD_PACKAGE)
diff --git a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml b/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
deleted file mode 100644
index a7028c4..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project Licensed under the
- Apache License, Version 2.0 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software distributed
- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
- OR CONDITIONS OF ANY KIND, either express or implied. See the License for
- the specific language governing permissions and limitations under the License. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.test.wakeuploop" >
-
- <uses-sdk android:minSdkVersion="7" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
- <application android:label="Auto Wakeup Loop">
- <service android:name=".WakeLoopService"
- android:label="Wakup Loop Service"
- android:exported="true"
- android:enabled="true">
- <intent-filter>
- <action android:name="android.test.wakeuploop.WAKEUP_SERVICE" />
- </intent-filter>
- </service>
- <receiver android:name=".WakeUpCall">
- <intent-filter>
- <action android:name="android.test.wakeuploop.WAKEUP" />
- </intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
deleted file mode 100644
index c8b075b..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/FileUtil.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.wakeuploop;
-
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-public class FileUtil {
-
- private static FileUtil sInst = null;
- private static DateFormat sDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
-
- private FileUtil() {};
-
- public static FileUtil get() {
- if (sInst == null) {
- sInst = new FileUtil();
- }
- return sInst;
- }
-
- public void writeDateToFile(File file) {
- try {
- FileOutputStream fos = new FileOutputStream(file);
- fos.write(sDateFormat.format(new Date()).getBytes());
- fos.write('\n');
- fos.flush();
- fos.close();
- } catch (IOException ioe) {
- Log.e("FileUtil", "exception writing date to file", ioe);
- }
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
deleted file mode 100644
index 4f557b8..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeLoopService.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.wakeuploop;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.File;
-
-public class WakeLoopService extends Service {
-
- private static final String LOG_TAG = WakeLoopService.class.getSimpleName();
- static final String WAKEUP_INTERNAL = "WAKEUP_INTERVAL";
- static final String MAX_LOOP = "MAX_LOOP";
- static final String STOP_CALLBACK = "STOP_CALLBACK";
- static final String THIS_LOOP = "THIS_LOOP";
- static final int MSG_STOP_SERVICE = 0xd1ed1e;
-
- private final Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.what == MSG_STOP_SERVICE) {
- stopSelf();
- } else {
- super.handleMessage(msg);
- }
- };
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- // no binding, just start via intent
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // get wakeup interval from intent
- long wakeupInterval = intent.getLongExtra(WAKEUP_INTERNAL, 0);
- long maxLoop = intent.getLongExtra(MAX_LOOP, 0);
-
- if (wakeupInterval == 0) {
- // stop and error
- Log.e(LOG_TAG, "No wakeup interval specified, not starting the service");
- stopSelf();
- return START_NOT_STICKY;
- }
- FileUtil.get().writeDateToFile(new File(Environment.getExternalStorageDirectory(),
- "wakeup-loop-start.txt"));
- Log.d(LOG_TAG, String.format("WakeLoop: STARTED interval = %d, total loop = %d",
- wakeupInterval, maxLoop));
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
- AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
- Intent wakupIntent = new Intent(WakeUpCall.WAKEUP_CALL)
- .putExtra(WAKEUP_INTERNAL, wakeupInterval)
- .putExtra(MAX_LOOP, maxLoop)
- .putExtra(THIS_LOOP, 0L)
- .putExtra(STOP_CALLBACK, new Messenger(mHandler));
- PendingIntent pi = PendingIntent.getBroadcast(this, 0, wakupIntent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- return START_NOT_STICKY;
- }
-
- @Override
- public void onDestroy() {
- Log.d(LOG_TAG, "WakeLoop: STOPPED");
- // cancel alarms first
- Intent intent = new Intent(WakeUpCall.WAKEUP_CALL)
- .putExtra(WakeUpCall.CANCEL, "true");
- sendBroadcast(intent);
- }
-}
diff --git a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java b/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
deleted file mode 100644
index 8347bbf0..0000000
--- a/tests/utils/SleepUtils/WakeLoopService/src/android/test/wakeuploop/WakeUpCall.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test.wakeuploop;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.File;
-
-/**
- * The receiver for the alarm we set
- *
- */
-public class WakeUpCall extends BroadcastReceiver {
- private static final String LOG_TAG = WakeUpCall.class.getSimpleName();
- static final String WAKEUP_CALL = "android.test.wakeuploop.WAKEUP";
- static final String CANCEL = "CANCEL";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- boolean cancel = intent.hasExtra(CANCEL);
- if (!cancel) {
- long maxLoop = intent.getLongExtra(WakeLoopService.MAX_LOOP, 0);
- long wakeupInterval = intent.getLongExtra(WakeLoopService.WAKEUP_INTERNAL, 0);
- long thisLoop = intent.getLongExtra(WakeLoopService.THIS_LOOP, -1);
- Log.d(LOG_TAG, String.format("incoming: interval = %d, max loop = %d, this loop = %d",
- wakeupInterval, maxLoop, thisLoop));
- if (thisLoop == -1) {
- Log.e(LOG_TAG, "no valid loop count received, trying to stop service");
- stopService(intent);
- return;
- }
- if (wakeupInterval == 0) {
- Log.e(LOG_TAG, "no valid wakeup interval received, trying to stop service");
- stopService(intent);
- return;
- }
- thisLoop++;
- Log.d(LOG_TAG, String.format("WakeLoop - iteration %d of %d", thisLoop, maxLoop));
- if (thisLoop == maxLoop) {
- // when maxLoop is 0, we loop forever, so not checking that case
- // here
- Log.d(LOG_TAG, "reached max loop count, stopping service");
- stopService(intent);
- return;
- }
- screenOn(context);
- FileUtil.get().writeDateToFile(
- new File(Environment.getExternalStorageDirectory(), "wakeup-loop.txt"));
- // calculate when device should be waken up
- long atTime = SystemClock.elapsedRealtime() + wakeupInterval;
- intent.putExtra(WakeLoopService.THIS_LOOP, thisLoop);
- PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- // set alarm, which will be delivered in form of the wakeupIntent
- am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
- } else {
- // cancel alarms
- Log.d(LOG_TAG, "cancelling future alarms on request");
- am.cancel(PendingIntent.getBroadcast(context, 0, intent, 0));
- }
- }
-
- private void stopService(Intent i) {
- Messenger msgr = i.getParcelableExtra(WakeLoopService.STOP_CALLBACK);
- if (msgr == null) {
- Log.e(LOG_TAG, "no stop service callback found, cannot stop");
- } else {
- Message msg = new Message();
- msg.what = WakeLoopService.MSG_STOP_SERVICE;
- try {
- msgr.send(msg);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "ignored remoted exception while attempting to stop service", e);
- }
- }
- }
-
- private void screenOn(Context context) {
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- @SuppressWarnings("deprecation")
- WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ACQUIRE_CAUSES_WAKEUP, LOG_TAG);
- wl.acquire(500);
- }
-}
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 92beb4e..0512bdc 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -469,16 +469,12 @@
return false;
}
- // Read the file as a string
- char buffer_2[data->size()];
- memcpy(&buffer_2, data->data(), data->size());
- StringPiece content(buffer_2, data->size());
-
BigBuffer crunched_png_buffer(4096);
io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
// Ensure that we only keep the chunks we care about if we end up
// using the original PNG instead of the crunched one.
+ const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size());
PngChunkFilter png_chunk_filter(content);
std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
if (!image) {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 8c00ceb..1fd45e7 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2084,6 +2084,8 @@
public static final int WIFI_FEATURE_LOW_LATENCY = 0x40000000; // Low Latency modes
/** @hide */
public static final int WIFI_FEATURE_DPP = 0x80000000; // DPP (Easy-Connect)
+ /** @hide */
+ public static final long WIFI_FEATURE_P2P_RAND_MAC = 0x100000000L; // Random P2P MAC
private long getSupportedFeatures() {
try {
@@ -3717,10 +3719,8 @@
* @param SSID, in the format of WifiConfiguration's SSID.
* @hide
*/
- @SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_SETTINGS,
- android.Manifest.permission.NETWORK_SETUP_WIZARD,
android.Manifest.permission.NETWORK_STACK
})
public void disableEphemeralNetwork(String SSID) {
diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index aa1669e..52ee742 100644
--- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -28,6 +28,7 @@
import android.net.NetworkSpecifier;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.Objects;
@@ -50,12 +51,24 @@
*/
private final int mOriginalRequestorUid;
+ /**
+ * The package name of the app that requested a specific wifi network using
+ * {@link WifiNetworkSpecifier}.
+ *
+ * Will only be filled when the device connects to a wifi network as a result of a
+ * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device
+ * auto-connected to a wifi network.
+ */
+ private final String mOriginalRequestorPackageName;
+
public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration,
- int originalRequestorUid) {
+ int originalRequestorUid,
+ @Nullable String originalRequestorPackageName) {
checkNotNull(wifiConfiguration);
mWifiConfiguration = wifiConfiguration;
mOriginalRequestorUid = originalRequestorUid;
+ mOriginalRequestorPackageName = originalRequestorPackageName;
}
/**
@@ -67,7 +80,9 @@
public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) {
WifiConfiguration wifiConfiguration = in.readParcelable(null);
int originalRequestorUid = in.readInt();
- return new WifiNetworkAgentSpecifier(wifiConfiguration, originalRequestorUid);
+ String originalRequestorPackageName = in.readString();
+ return new WifiNetworkAgentSpecifier(
+ wifiConfiguration, originalRequestorUid, originalRequestorPackageName);
}
@Override
@@ -85,6 +100,7 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mWifiConfiguration, flags);
dest.writeInt(mOriginalRequestorUid);
+ dest.writeString(mOriginalRequestorPackageName);
}
@Override
@@ -137,6 +153,9 @@
if (ns.requestorUid != this.mOriginalRequestorUid) {
return false;
}
+ if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) {
+ return false;
+ }
return true;
}
@@ -146,7 +165,8 @@
mWifiConfiguration.SSID,
mWifiConfiguration.BSSID,
mWifiConfiguration.allowedKeyManagement,
- mOriginalRequestorUid);
+ mOriginalRequestorUid,
+ mOriginalRequestorPackageName);
}
@Override
@@ -162,7 +182,9 @@
&& Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID)
&& Objects.equals(this.mWifiConfiguration.allowedKeyManagement,
lhs.mWifiConfiguration.allowedKeyManagement)
- && mOriginalRequestorUid == lhs.mOriginalRequestorUid;
+ && mOriginalRequestorUid == lhs.mOriginalRequestorUid
+ && TextUtils.equals(mOriginalRequestorPackageName,
+ lhs.mOriginalRequestorPackageName);
}
@Override
@@ -172,6 +194,7 @@
.append(", SSID=").append(mWifiConfiguration.SSID)
.append(", BSSID=").append(mWifiConfiguration.BSSID)
.append(", mOriginalRequestorUid=").append(mOriginalRequestorUid)
+ .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName)
.append("]");
return sb.toString();
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
index ecee5ff..42d4393 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConfigBuilder.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.net.MacAddress;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
@@ -586,7 +587,8 @@
mSsidPatternMatcher,
mBssidPatternMatcher,
buildWifiConfiguration(),
- Process.myUid());
+ Process.myUid(),
+ ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
}
/**
@@ -648,7 +650,8 @@
buildWifiConfiguration(),
mIsAppInteractionRequired,
mIsUserInteractionRequired,
- Process.myUid());
+ Process.myUid(),
+ ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
}
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 6e4eeef..a5f4675 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -25,6 +25,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
+import android.text.TextUtils;
import android.util.Pair;
import java.util.Objects;
@@ -63,18 +64,25 @@
*/
public final int requestorUid;
+ /**
+ * The package name of the app initializing this network specifier.
+ */
+ public final String requestorPackageName;
+
public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher,
@NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher,
@NonNull WifiConfiguration wifiConfiguration,
- int requestorUid) {
+ int requestorUid, @NonNull String requestorPackageName) {
checkNotNull(ssidPatternMatcher);
checkNotNull(bssidPatternMatcher);
checkNotNull(wifiConfiguration);
+ checkNotNull(requestorPackageName);
this.ssidPatternMatcher = ssidPatternMatcher;
this.bssidPatternMatcher = bssidPatternMatcher;
this.wifiConfiguration = wifiConfiguration;
this.requestorUid = requestorUid;
+ this.requestorPackageName = requestorPackageName;
}
public static final Creator<WifiNetworkSpecifier> CREATOR =
@@ -88,8 +96,9 @@
Pair.create(baseAddress, mask);
WifiConfiguration wifiConfiguration = in.readParcelable(null);
int requestorUid = in.readInt();
+ String requestorPackageName = in.readString();
return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher,
- wifiConfiguration, requestorUid);
+ wifiConfiguration, requestorUid, requestorPackageName);
}
@Override
@@ -110,6 +119,7 @@
dest.writeParcelable(bssidPatternMatcher.second, flags);
dest.writeParcelable(wifiConfiguration, flags);
dest.writeInt(requestorUid);
+ dest.writeString(requestorPackageName);
}
@Override
@@ -136,7 +146,7 @@
ssidPatternMatcher.getType(),
bssidPatternMatcher,
wifiConfiguration.allowedKeyManagement,
- requestorUid);
+ requestorUid, requestorPackageName);
}
@Override
@@ -156,7 +166,8 @@
lhs.bssidPatternMatcher)
&& Objects.equals(this.wifiConfiguration.allowedKeyManagement,
lhs.wifiConfiguration.allowedKeyManagement)
- && requestorUid == lhs.requestorUid;
+ && requestorUid == lhs.requestorUid
+ && TextUtils.equals(requestorPackageName, lhs.requestorPackageName);
}
@Override
@@ -168,6 +179,7 @@
.append(", SSID=").append(wifiConfiguration.SSID)
.append(", BSSID=").append(wifiConfiguration.BSSID)
.append(", requestorUid=").append(requestorUid)
+ .append(", requestorPackageName=").append(requestorPackageName)
.append("]")
.toString();
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 3c90eb7..6b05dfc 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -18,8 +18,10 @@
import static com.android.internal.util.Preconditions.checkNotNull;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.List;
import java.util.Objects;
@@ -58,17 +60,25 @@
*/
public final int suggestorUid;
+ /**
+ * The package name of the process initializing this network suggestion.
+ * @hide
+ */
+ public final String suggestorPackageName;
+
/** @hide */
- public WifiNetworkSuggestion(WifiConfiguration wifiConfiguration,
+ public WifiNetworkSuggestion(@NonNull WifiConfiguration wifiConfiguration,
boolean isAppInteractionRequired,
boolean isUserInteractionRequired,
- int suggestorUid) {
+ int suggestorUid, @NonNull String suggestorPackageName) {
checkNotNull(wifiConfiguration);
+ checkNotNull(suggestorPackageName);
this.wifiConfiguration = wifiConfiguration;
this.isAppInteractionRequired = isAppInteractionRequired;
this.isUserInteractionRequired = isUserInteractionRequired;
this.suggestorUid = suggestorUid;
+ this.suggestorPackageName = suggestorPackageName;
}
public static final Creator<WifiNetworkSuggestion> CREATOR =
@@ -79,7 +89,8 @@
in.readParcelable(null), // wifiConfiguration
in.readBoolean(), // isAppInteractionRequired
in.readBoolean(), // isUserInteractionRequired
- in.readInt() // suggestorUid
+ in.readInt(), // suggestorUid
+ in.readString() // suggestorPackageName
);
}
@@ -100,12 +111,13 @@
dest.writeBoolean(isAppInteractionRequired);
dest.writeBoolean(isUserInteractionRequired);
dest.writeInt(suggestorUid);
+ dest.writeString(suggestorPackageName);
}
@Override
public int hashCode() {
return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
- wifiConfiguration.allowedKeyManagement, suggestorUid);
+ wifiConfiguration.allowedKeyManagement, suggestorUid, suggestorPackageName);
}
/**
@@ -124,7 +136,8 @@
&& Objects.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
&& Objects.equals(this.wifiConfiguration.allowedKeyManagement,
lhs.wifiConfiguration.allowedKeyManagement)
- && suggestorUid == lhs.suggestorUid;
+ && suggestorUid == lhs.suggestorUid
+ && TextUtils.equals(suggestorPackageName, lhs.suggestorPackageName);
}
@Override
@@ -135,6 +148,7 @@
.append(", isAppInteractionRequired=").append(isAppInteractionRequired)
.append(", isUserInteractionRequired=").append(isUserInteractionRequired)
.append(", suggestorUid=").append(suggestorUid)
+ .append(", suggestorPackageName=").append(suggestorPackageName)
.append("]");
return sb.toString();
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
index 2258e4d..e6eece8 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkAgentSpecifierTest.java
@@ -38,6 +38,8 @@
public class WifiNetworkAgentSpecifierTest {
private static final int TEST_UID = 5;
private static final int TEST_UID_1 = 8;
+ private static final String TEST_PACKAGE = "com.test";
+ private static final String TEST_PACKAGE_1 = "com.test.1";
private static final String TEST_SSID = "Test123";
private static final String TEST_SSID_PATTERN = "Test";
private static final String TEST_SSID_1 = "456test";
@@ -104,14 +106,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -128,14 +130,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.SSID = TEST_SSID_1;
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -152,14 +154,14 @@
WifiNetworkAgentSpecifier specifier1 =
new WifiNetworkAgentSpecifier(
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration(wifiConfiguration1);
wifiConfiguration2.BSSID = TEST_BSSID_1;
WifiNetworkAgentSpecifier specifier2 =
new WifiNetworkAgentSpecifier(
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(specifier2.equals(specifier1));
}
@@ -214,7 +216,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -243,7 +245,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -272,7 +274,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertTrue(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertTrue(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -292,7 +294,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -305,7 +307,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -325,7 +327,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB);
@@ -339,7 +341,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -359,7 +361,7 @@
WifiNetworkAgentSpecifier wifiNetworkAgentSpecifier =
new WifiNetworkAgentSpecifier(
wifiConfigurationNetworkAgent,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
PatternMatcher ssidPattern =
new PatternMatcher(TEST_SSID_PATTERN, PatternMatcher.PATTERN_PREFIX);
@@ -373,7 +375,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -401,7 +403,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -430,7 +432,7 @@
ssidPattern,
bssidPattern,
wificonfigurationNetworkSpecifier,
- TEST_UID_1);
+ TEST_UID_1, TEST_PACKAGE_1);
assertFalse(wifiNetworkSpecifier.satisfiedBy(wifiNetworkAgentSpecifier));
assertFalse(wifiNetworkAgentSpecifier.satisfiedBy(wifiNetworkSpecifier));
@@ -446,7 +448,8 @@
}
private WifiNetworkAgentSpecifier createDefaultNetworkAgentSpecifier() {
- return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID);
+ return new WifiNetworkAgentSpecifier(createDefaultWifiConfiguration(), TEST_UID,
+ TEST_PACKAGE);
}
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
index 2a8df8d..fce247f 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSpecifierTest.java
@@ -38,6 +38,7 @@
@SmallTest
public class WifiNetworkSpecifierTest {
private static final int TEST_UID = 5;
+ private static final String TEST_PACKAGE_NAME = "com.test";
private static final String TEST_SSID = "Test123";
private static final String TEST_BSSID_OUI_BASE_ADDRESS = "12:12:12:00:00:00";
private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00";
@@ -56,7 +57,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
Parcel parcelW = Parcel.obtain();
specifier.writeToParcel(parcelW, 0);
@@ -88,7 +89,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertTrue(specifier.satisfiedBy(null));
assertTrue(specifier.satisfiedBy(new MatchAllNetworkSpecifier()));
@@ -111,14 +112,14 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertTrue(specifier2.satisfiedBy(specifier1));
}
@@ -140,7 +141,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration1,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiConfiguration wifiConfiguration2 = new WifiConfiguration();
wifiConfiguration2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
@@ -149,7 +150,7 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration2,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertFalse(specifier2.satisfiedBy(specifier1));
}
@@ -171,14 +172,14 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
assertFalse(specifier2.satisfiedBy(specifier1));
}
@@ -200,13 +201,42 @@
Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
MacAddress.fromString(TEST_BSSID_OUI_MASK)),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
WifiNetworkSpecifier specifier2 =
new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS),
wifiConfiguration,
- TEST_UID);
+ TEST_UID, TEST_PACKAGE_NAME);
+
+ assertFalse(specifier2.satisfiedBy(specifier1));
+ }
+
+ /**
+ * Validate NetworkSpecifier matching.
+ * a) Create network specifier 1 for WPA_PSK network
+ * b) Create network specifier 2 with different package name .
+ * c) Ensure that the specifier 2 is not satisfied by specifier 1.
+ */
+ @Test
+ public void testWifiNetworkSpecifierDoesNotSatisfyWhenPackageNameDifferent() {
+ WifiConfiguration wifiConfiguration = new WifiConfiguration();
+ wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+ wifiConfiguration.preSharedKey = TEST_PRESHARED_KEY;
+
+ WifiNetworkSpecifier specifier1 =
+ new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+ Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+ MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+ wifiConfiguration,
+ TEST_UID, TEST_PACKAGE_NAME);
+
+ WifiNetworkSpecifier specifier2 =
+ new WifiNetworkSpecifier(new PatternMatcher(TEST_SSID, PATTERN_LITERAL),
+ Pair.create(MacAddress.fromString(TEST_BSSID_OUI_BASE_ADDRESS),
+ MacAddress.fromString(TEST_BSSID_OUI_MASK)),
+ wifiConfiguration,
+ TEST_UID, TEST_PACKAGE_NAME + "blah");
assertFalse(specifier2.satisfiedBy(specifier1));
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 31f501f..5f76055 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -29,6 +29,10 @@
*/
@SmallTest
public class WifiNetworkSuggestionTest {
+ private static final int TEST_UID = 45677;
+ private static final int TEST_UID_OTHER = 45673;
+ private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+ private static final String TEST_PACKAGE_NAME_OTHER = "com.test.packagenameother";
private static final String TEST_SSID = "\"Test123\"";
private static final String TEST_BSSID = "12:12:12:12:12:12";
private static final String TEST_SSID_1 = "\"Test1234\"";
@@ -43,7 +47,7 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, true, 0);
+ new WifiNetworkSuggestion(configuration, false, true, TEST_UID, TEST_PACKAGE_NAME);
Parcel parcelW = Parcel.obtain();
suggestion.writeToParcel(parcelW, 0);
@@ -77,14 +81,16 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, true, false, 0);
+ new WifiNetworkSuggestion(configuration, true, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.BSSID = TEST_BSSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, true, 0);
+ new WifiNetworkSuggestion(configuration1, false, true, TEST_UID,
+ TEST_PACKAGE_NAME);
assertEquals(suggestion, suggestion1);
}
@@ -99,13 +105,15 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID_1;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -121,13 +129,15 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -142,13 +152,15 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, false, false, 0);
+ new WifiNetworkSuggestion(configuration1, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
assertNotEquals(suggestion, suggestion1);
}
@@ -163,10 +175,31 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, false, false, 0);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration, false, false, 1);
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID_OTHER,
+ TEST_PACKAGE_NAME);
+
+ assertNotEquals(suggestion, suggestion1);
+ }
+
+ /**
+ * Check NetworkSuggestion equals returns {@code false} for 2 network suggestions with the same
+ * SSID, BSSID and key mgmt, but different package name.
+ */
+ @Test
+ public void testWifiNetworkSuggestionEqualsFailsWhenPackageNameIsDifferent() {
+ WifiConfiguration configuration = new WifiConfiguration();
+ configuration.SSID = TEST_SSID;
+ configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ WifiNetworkSuggestion suggestion =
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID, TEST_PACKAGE_NAME);
+
+ WifiNetworkSuggestion suggestion1 =
+ new WifiNetworkSuggestion(configuration, false, false, TEST_UID,
+ TEST_PACKAGE_NAME_OTHER);
assertNotEquals(suggestion, suggestion1);
}