diff options
162 files changed, 4199 insertions, 6199 deletions
@@ -348,12 +348,6 @@ LOCAL_SRC_FILES += \ media/java/android/media/projection/IMediaProjectionCallback.aidl \ media/java/android/media/projection/IMediaProjectionManager.aidl \ media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \ - media/java/android/media/routing/IMediaRouteService.aidl \ - media/java/android/media/routing/IMediaRouteClientCallback.aidl \ - media/java/android/media/routing/IMediaRouter.aidl \ - media/java/android/media/routing/IMediaRouterDelegate.aidl \ - media/java/android/media/routing/IMediaRouterRoutingCallback.aidl \ - media/java/android/media/routing/IMediaRouterStateCallback.aidl \ media/java/android/media/session/IActiveSessionsListener.aidl \ media/java/android/media/session/ISessionController.aidl \ media/java/android/media/session/ISessionControllerCallback.aidl \ @@ -460,7 +454,6 @@ aidl_files := \ frameworks/base/location/java/android/location/Criteria.aidl \ frameworks/base/media/java/android/media/MediaMetadata.aidl \ frameworks/base/media/java/android/media/MediaDescription.aidl \ - frameworks/base/media/java/android/media/routing/MediaRouteSelector.aidl \ frameworks/base/media/java/android/media/Rating.aidl \ frameworks/base/media/java/android/media/AudioAttributes.aidl \ frameworks/base/media/java/android/media/AudioFocusInfo.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index c812b6a..667ed02 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -233,6 +233,7 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libinputflingerhost.so $ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libinputflingerhost.so $(PRODUCT_OUT)/symbols/system/lib64/libinputflingerhost.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libinputflingerhost.so $(PRODUCT_OUT)/obj_arm/lib/libinputflingerhost.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinputflingerhost_intermediates $(PRODUCT_OUT)/obj_arm/SHARED_LIBRARIES/libinputflingerhost_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/target/common/obj/framework.aidl) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER diff --git a/api/current.txt b/api/current.txt index 375bee7..cdc2404 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28,7 +28,6 @@ package android { field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE"; field public static final java.lang.String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE"; field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD"; - field public static final java.lang.String BIND_MEDIA_ROUTE_SERVICE = "android.permission.BIND_MEDIA_ROUTE_SERVICE"; field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE"; field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"; field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE"; @@ -8282,6 +8281,7 @@ package android.content { field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT"; field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE"; field public static final deprecated java.lang.String EXTRA_ALLOW_REPLACE = "android.intent.extra.ALLOW_REPLACE"; + field public static final java.lang.String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS"; field public static final java.lang.String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT"; field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD"; field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE"; @@ -8293,6 +8293,7 @@ package android.content { field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list"; field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list"; field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list"; + field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; @@ -8325,6 +8326,7 @@ package android.content { field public static final java.lang.String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle"; field public static final java.lang.String EXTRA_RESTRICTIONS_INTENT = "android.intent.extra.restrictions_intent"; field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list"; + field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER"; field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT"; field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON"; field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE"; @@ -17424,248 +17426,11 @@ package android.media.projection { } -package android.media.routing { - - public final class MediaRouteSelector implements android.os.Parcelable { - method public boolean containsProtocol(java.lang.Class<?>); - method public boolean containsProtocol(java.lang.String); - method public int describeContents(); - method public android.os.Bundle getExtras(); - method public int getOptionalFeatures(); - method public java.util.List<java.lang.String> getOptionalProtocols(); - method public int getRequiredFeatures(); - method public java.util.List<java.lang.String> getRequiredProtocols(); - method public java.lang.String getServicePackageName(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.media.routing.MediaRouteSelector> CREATOR; - } - - public static final class MediaRouteSelector.Builder { - ctor public MediaRouteSelector.Builder(); - method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.Class<?>); - method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.String); - method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.Class<?>); - method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.String); - method public android.media.routing.MediaRouteSelector build(); - method public android.media.routing.MediaRouteSelector.Builder setExtras(android.os.Bundle); - method public android.media.routing.MediaRouteSelector.Builder setOptionalFeatures(int); - method public android.media.routing.MediaRouteSelector.Builder setRequiredFeatures(int); - method public android.media.routing.MediaRouteSelector.Builder setServicePackageName(java.lang.String); - } - - public abstract class MediaRouteService extends android.app.Service { - ctor public MediaRouteService(); - method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata(); - method public android.os.IBinder onBind(android.content.Intent); - method public abstract android.media.routing.MediaRouteService.ClientSession onCreateClientSession(android.media.routing.MediaRouteService.ClientInfo); - field public static final java.lang.String SERVICE_INTERFACE = "android.media.routing.MediaRouteService"; - } - - public static final class MediaRouteService.ClientInfo { - method public java.lang.String getPackageName(); - method public int getUid(); - } - - public static abstract class MediaRouteService.ClientSession { - ctor public MediaRouteService.ClientSession(); - method public abstract boolean onConnect(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouteService.ConnectionCallback); - method public abstract void onDisconnect(); - method public void onPauseStream(); - method public void onRelease(); - method public void onResumeStream(); - method public abstract boolean onStartDiscovery(android.media.routing.MediaRouter.DiscoveryRequest, android.media.routing.MediaRouteService.DiscoveryCallback); - method public abstract void onStopDiscovery(); - } - - public final class MediaRouteService.ConnectionCallback { - method public void onConnected(android.media.routing.MediaRouter.ConnectionInfo); - method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle); - method public void onDisconnected(); - } - - public final class MediaRouteService.DiscoveryCallback { - method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>); - method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo); - method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle); - } - - public final class MediaRouter { - ctor public MediaRouter(android.content.Context); - method public void addSelector(android.media.routing.MediaRouteSelector); - method public void clearSelectors(); - method public android.media.routing.MediaRouter.Delegate createDelegate(); - method public android.media.routing.MediaRouter.ConnectionInfo getConnection(); - method public int getConnectionState(); - method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations(); - method public java.util.List<android.media.routing.MediaRouter.RouteInfo> getDiscoveredRoutes(android.media.routing.MediaRouter.DestinationInfo); - method public int getDiscoveryState(); - method public android.media.AudioAttributes getPreferredAudioAttributes(); - method public android.view.Display getPreferredPresentationDisplay(); - method public android.media.VolumeProvider getPreferredVolumeProvider(); - method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination(); - method public android.media.routing.MediaRouter.RouteInfo getSelectedRoute(); - method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors(); - method public boolean isReleased(); - method public void pauseStream(); - method public void release(); - method public void removeSelector(android.media.routing.MediaRouteSelector); - method public void resumeStream(); - method public void setRoutingCallback(android.media.routing.MediaRouter.RoutingCallback, android.os.Handler); - field public static final int CONNECTION_ERROR_ABORTED = 1; // 0x1 - field public static final int CONNECTION_ERROR_BARGED = 7; // 0x7 - field public static final int CONNECTION_ERROR_BROKEN = 6; // 0x6 - field public static final int CONNECTION_ERROR_BUSY = 4; // 0x4 - field public static final int CONNECTION_ERROR_TIMEOUT = 5; // 0x5 - field public static final int CONNECTION_ERROR_UNAUTHORIZED = 2; // 0x2 - field public static final int CONNECTION_ERROR_UNKNOWN = 0; // 0x0 - field public static final int CONNECTION_ERROR_UNREACHABLE = 3; // 0x3 - field public static final int CONNECTION_FLAG_BARGE = 1; // 0x1 - field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2 - field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1 - field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0 - field public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0; // 0x0 - field public static final int DISCONNECTION_REASON_ERROR = 2; // 0x2 - field public static final int DISCONNECTION_REASON_USER_REQUEST = 1; // 0x1 - field public static final int DISCOVERY_ERROR_ABORTED = 1; // 0x1 - field public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2; // 0x2 - field public static final int DISCOVERY_ERROR_UNKNOWN = 0; // 0x0 - field public static final int DISCOVERY_FLAG_BACKGROUND = 1; // 0x1 - field public static final int DISCOVERY_STATE_STARTED = 1; // 0x1 - field public static final int DISCOVERY_STATE_STOPPED = 0; // 0x0 - field public static final int ROUTE_FEATURE_LIVE_AUDIO = 1; // 0x1 - field public static final int ROUTE_FEATURE_LIVE_VIDEO = 2; // 0x2 - } - - public static final class MediaRouter.ConnectionInfo { - method public android.media.AudioAttributes getAudioAttributes(); - method public android.os.Bundle getExtras(); - method public int getFeatures(); - method public android.view.Display getPresentationDisplay(); - method public android.os.IBinder getProtocolBinder(java.lang.String); - method public android.os.IBinder getProtocolBinder(int); - method public T getProtocolObject(java.lang.Class<T>); - method public java.util.List<java.lang.String> getProtocols(); - method public android.media.routing.MediaRouter.RouteInfo getRoute(); - method public android.media.VolumeProvider getVolumeProvider(); - } - - public static final class MediaRouter.ConnectionInfo.Builder { - ctor public MediaRouter.ConnectionInfo.Builder(android.media.routing.MediaRouter.RouteInfo); - method public android.media.routing.MediaRouter.ConnectionInfo build(); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setAudioAttributes(android.media.AudioAttributes); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setExtras(android.os.Bundle); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setPresentationDisplay(android.view.Display); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolBinder(java.lang.String, android.os.IBinder); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolStub(java.lang.Class<?>, android.os.IInterface); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setVolumeProvider(android.media.VolumeProvider); - } - - public static final class MediaRouter.ConnectionRequest { - method public android.os.Bundle getExtras(); - method public int getFlags(); - method public android.media.routing.MediaRouter.RouteInfo getRoute(); - method public void setExtras(android.os.Bundle); - method public void setFlags(int); - method public void setRoute(android.media.routing.MediaRouter.RouteInfo); - } - - public static final class MediaRouter.Delegate { - ctor public MediaRouter.Delegate(); - method public void addStateCallback(android.media.routing.MediaRouter.StateCallback, android.os.Handler); - method public void connect(android.media.routing.MediaRouter.DestinationInfo, int); - method public void disconnect(int); - method public int getConnectionState(); - method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations(); - method public int getDiscoveryState(); - method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination(); - method public boolean isReleased(); - method public void removeStateCallback(android.media.routing.MediaRouter.StateCallback); - method public void startDiscovery(int); - method public void stopDiscovery(); - } - - public static final class MediaRouter.DestinationInfo { - method public java.lang.CharSequence getDescription(); - method public android.os.Bundle getExtras(); - method public int getIconResourceId(); - method public java.lang.String getId(); - method public java.lang.CharSequence getName(); - method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata(); - method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); - } - - public static final class MediaRouter.DestinationInfo.Builder { - ctor public MediaRouter.DestinationInfo.Builder(java.lang.String, android.media.routing.MediaRouter.ServiceMetadata, java.lang.CharSequence); - method public android.media.routing.MediaRouter.DestinationInfo build(); - method public android.media.routing.MediaRouter.DestinationInfo.Builder setDescription(java.lang.CharSequence); - method public android.media.routing.MediaRouter.DestinationInfo.Builder setExtras(android.os.Bundle); - method public android.media.routing.MediaRouter.DestinationInfo.Builder setIconResourceId(int); - } - - public static final class MediaRouter.DiscoveryRequest { - method public int getFlags(); - method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors(); - method public void setFlags(int); - method public void setSelectors(java.util.List<android.media.routing.MediaRouteSelector>); - } - - public static final class MediaRouter.RouteInfo { - method public android.media.routing.MediaRouter.DestinationInfo getDestination(); - method public android.os.Bundle getExtras(); - method public int getFeatures(); - method public java.lang.String getId(); - method public java.util.List<java.lang.String> getProtocols(); - method public android.media.routing.MediaRouteSelector getSelector(); - } - - public static final class MediaRouter.RouteInfo.Builder { - ctor public MediaRouter.RouteInfo.Builder(java.lang.String, android.media.routing.MediaRouter.DestinationInfo, android.media.routing.MediaRouteSelector); - method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.Class<T>); - method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.String); - method public android.media.routing.MediaRouter.RouteInfo build(); - method public android.media.routing.MediaRouter.RouteInfo.Builder setExtras(android.os.Bundle); - method public android.media.routing.MediaRouter.RouteInfo.Builder setFeatures(int); - } - - public static abstract class MediaRouter.RoutingCallback extends android.media.routing.MediaRouter.StateCallback { - ctor public MediaRouter.RoutingCallback(); - method public boolean onPrepareConnectionRequest(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>); - method public boolean onPrepareDiscoveryRequest(android.media.routing.MediaRouter.DiscoveryRequest, java.util.List<android.media.routing.MediaRouteSelector>); - } - - public static final class MediaRouter.ServiceMetadata { - method public android.content.ComponentName getComponentName(); - method public android.graphics.drawable.Drawable getIcon(android.content.pm.PackageManager); - method public java.lang.CharSequence getLabel(android.content.pm.PackageManager); - method public java.lang.String getPackageName(); - method public android.content.pm.ServiceInfo getService(); - } - - public static abstract class MediaRouter.StateCallback { - ctor public MediaRouter.StateCallback(); - method public void onConnected(); - method public void onConnecting(); - method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle); - method public void onConnectionStateChanged(int); - method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo); - method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo); - method public void onDisconnected(); - method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle); - method public void onDiscoveryStarted(); - method public void onDiscoveryStateChanged(int); - method public void onDiscoveryStopped(); - method public void onReleased(); - method public void onSelectedDestinationChanged(android.media.routing.MediaRouter.DestinationInfo); - } - -} - package android.media.session { public final class MediaController { ctor public MediaController(android.content.Context, android.media.session.MediaSession.Token); method public void adjustVolume(int, int); - method public android.media.routing.MediaRouter.Delegate createMediaRouterDelegate(); method public boolean dispatchMediaButtonEvent(android.view.KeyEvent); method public android.os.Bundle getExtras(); method public long getFlags(); @@ -17739,7 +17504,6 @@ package android.media.session { method public void setExtras(android.os.Bundle); method public void setFlags(int); method public void setMediaButtonReceiver(android.app.PendingIntent); - method public void setMediaRouter(android.media.routing.MediaRouter); method public void setMetadata(android.media.MediaMetadata); method public void setPlaybackState(android.media.session.PlaybackState); method public void setPlaybackToLocal(android.media.AudioAttributes); @@ -28742,6 +28506,53 @@ package android.security { public abstract class KeyStoreKeyProperties { } + public static abstract class KeyStoreKeyProperties.Algorithm { + field public static final java.lang.String AES = "AES"; + field public static final java.lang.String EC = "EC"; + field public static final java.lang.String HMAC_SHA1 = "HmacSHA1"; + field public static final java.lang.String HMAC_SHA224 = "HmacSHA224"; + field public static final java.lang.String HMAC_SHA256 = "HmacSHA256"; + field public static final java.lang.String HMAC_SHA384 = "HmacSHA384"; + field public static final java.lang.String HMAC_SHA512 = "HmacSHA512"; + field public static final java.lang.String RSA = "RSA"; + } + + public static abstract class KeyStoreKeyProperties.AlgorithmEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.BlockMode { + field public static final java.lang.String CBC = "CBC"; + field public static final java.lang.String CTR = "CTR"; + field public static final java.lang.String ECB = "ECB"; + field public static final java.lang.String GCM = "GCM"; + } + + public static abstract class KeyStoreKeyProperties.BlockModeEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.Digest { + field public static final java.lang.String MD5 = "MD5"; + field public static final java.lang.String NONE = "NONE"; + field public static final java.lang.String SHA1 = "SHA-1"; + field public static final java.lang.String SHA224 = "SHA-224"; + field public static final java.lang.String SHA256 = "SHA-256"; + field public static final java.lang.String SHA384 = "SHA-384"; + field public static final java.lang.String SHA512 = "SHA-512"; + } + + public static abstract class KeyStoreKeyProperties.DigestEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.EncryptionPadding { + field public static final java.lang.String NONE = "NoPadding"; + field public static final java.lang.String PKCS7 = "PKCS7Padding"; + field public static final java.lang.String RSA_OAEP = "OAEPPadding"; + field public static final java.lang.String RSA_PKCS1 = "PKCS1Padding"; + } + + public static abstract class KeyStoreKeyProperties.EncryptionPaddingEnum implements java.lang.annotation.Annotation { + } + public static abstract class KeyStoreKeyProperties.Origin { field public static final int GENERATED = 1; // 0x1 field public static final int IMPORTED = 2; // 0x2 @@ -28761,6 +28572,14 @@ package android.security { public static abstract class KeyStoreKeyProperties.PurposeEnum implements java.lang.annotation.Annotation { } + public static abstract class KeyStoreKeyProperties.SignaturePadding { + field public static final java.lang.String RSA_PKCS1 = "PKCS1"; + field public static final java.lang.String RSA_PSS = "PSS"; + } + + public static abstract class KeyStoreKeyProperties.SignaturePaddingEnum implements java.lang.annotation.Annotation { + } + public class KeyStoreKeySpec implements java.security.spec.KeySpec { method public java.lang.String[] getBlockModes(); method public java.lang.String[] getDigests(); @@ -34187,14 +34006,6 @@ package android.util { } public deprecated class FloatMath { - method public static float ceil(float); - method public static float cos(float); - method public static float exp(float); - method public static float floor(float); - method public static float hypot(float, float); - method public static float pow(float, float); - method public static float sin(float); - method public static float sqrt(float); } public final class JsonReader implements java.io.Closeable { diff --git a/api/removed.txt b/api/removed.txt index 5e77e15..0046a70 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -108,6 +108,21 @@ package android.text.format { } +package android.util { + + public deprecated class FloatMath { + method public static float ceil(float); + method public static float cos(float); + method public static float exp(float); + method public static float floor(float); + method public static float hypot(float, float); + method public static float pow(float, float); + method public static float sin(float); + method public static float sqrt(float); + } + +} + package android.view { public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { diff --git a/api/system-current.txt b/api/system-current.txt index 63ba05f..7012773 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -39,7 +39,6 @@ package android { field public static final java.lang.String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE"; field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD"; field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET"; - field public static final java.lang.String BIND_MEDIA_ROUTE_SERVICE = "android.permission.BIND_MEDIA_ROUTE_SERVICE"; field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE"; field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"; field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE"; @@ -8508,6 +8507,7 @@ package android.content { field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT"; field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE"; field public static final deprecated java.lang.String EXTRA_ALLOW_REPLACE = "android.intent.extra.ALLOW_REPLACE"; + field public static final java.lang.String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS"; field public static final java.lang.String EXTRA_ASSIST_CONTEXT = "android.intent.extra.ASSIST_CONTEXT"; field public static final java.lang.String EXTRA_ASSIST_INPUT_HINT_KEYBOARD = "android.intent.extra.ASSIST_INPUT_HINT_KEYBOARD"; field public static final java.lang.String EXTRA_ASSIST_PACKAGE = "android.intent.extra.ASSIST_PACKAGE"; @@ -8519,6 +8519,7 @@ package android.content { field public static final java.lang.String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list"; field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list"; field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list"; + field public static final java.lang.String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT"; field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER"; @@ -8554,6 +8555,7 @@ package android.content { field public static final java.lang.String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle"; field public static final java.lang.String EXTRA_RESTRICTIONS_INTENT = "android.intent.extra.restrictions_intent"; field public static final java.lang.String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list"; + field public static final java.lang.String EXTRA_RESULT_RECEIVER = "android.intent.extra.RESULT_RECEIVER"; field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT"; field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON"; field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE"; @@ -18717,248 +18719,11 @@ package android.media.projection { } -package android.media.routing { - - public final class MediaRouteSelector implements android.os.Parcelable { - method public boolean containsProtocol(java.lang.Class<?>); - method public boolean containsProtocol(java.lang.String); - method public int describeContents(); - method public android.os.Bundle getExtras(); - method public int getOptionalFeatures(); - method public java.util.List<java.lang.String> getOptionalProtocols(); - method public int getRequiredFeatures(); - method public java.util.List<java.lang.String> getRequiredProtocols(); - method public java.lang.String getServicePackageName(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.media.routing.MediaRouteSelector> CREATOR; - } - - public static final class MediaRouteSelector.Builder { - ctor public MediaRouteSelector.Builder(); - method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.Class<?>); - method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.String); - method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.Class<?>); - method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.String); - method public android.media.routing.MediaRouteSelector build(); - method public android.media.routing.MediaRouteSelector.Builder setExtras(android.os.Bundle); - method public android.media.routing.MediaRouteSelector.Builder setOptionalFeatures(int); - method public android.media.routing.MediaRouteSelector.Builder setRequiredFeatures(int); - method public android.media.routing.MediaRouteSelector.Builder setServicePackageName(java.lang.String); - } - - public abstract class MediaRouteService extends android.app.Service { - ctor public MediaRouteService(); - method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata(); - method public android.os.IBinder onBind(android.content.Intent); - method public abstract android.media.routing.MediaRouteService.ClientSession onCreateClientSession(android.media.routing.MediaRouteService.ClientInfo); - field public static final java.lang.String SERVICE_INTERFACE = "android.media.routing.MediaRouteService"; - } - - public static final class MediaRouteService.ClientInfo { - method public java.lang.String getPackageName(); - method public int getUid(); - } - - public static abstract class MediaRouteService.ClientSession { - ctor public MediaRouteService.ClientSession(); - method public abstract boolean onConnect(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouteService.ConnectionCallback); - method public abstract void onDisconnect(); - method public void onPauseStream(); - method public void onRelease(); - method public void onResumeStream(); - method public abstract boolean onStartDiscovery(android.media.routing.MediaRouter.DiscoveryRequest, android.media.routing.MediaRouteService.DiscoveryCallback); - method public abstract void onStopDiscovery(); - } - - public final class MediaRouteService.ConnectionCallback { - method public void onConnected(android.media.routing.MediaRouter.ConnectionInfo); - method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle); - method public void onDisconnected(); - } - - public final class MediaRouteService.DiscoveryCallback { - method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>); - method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo); - method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle); - } - - public final class MediaRouter { - ctor public MediaRouter(android.content.Context); - method public void addSelector(android.media.routing.MediaRouteSelector); - method public void clearSelectors(); - method public android.media.routing.MediaRouter.Delegate createDelegate(); - method public android.media.routing.MediaRouter.ConnectionInfo getConnection(); - method public int getConnectionState(); - method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations(); - method public java.util.List<android.media.routing.MediaRouter.RouteInfo> getDiscoveredRoutes(android.media.routing.MediaRouter.DestinationInfo); - method public int getDiscoveryState(); - method public android.media.AudioAttributes getPreferredAudioAttributes(); - method public android.view.Display getPreferredPresentationDisplay(); - method public android.media.VolumeProvider getPreferredVolumeProvider(); - method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination(); - method public android.media.routing.MediaRouter.RouteInfo getSelectedRoute(); - method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors(); - method public boolean isReleased(); - method public void pauseStream(); - method public void release(); - method public void removeSelector(android.media.routing.MediaRouteSelector); - method public void resumeStream(); - method public void setRoutingCallback(android.media.routing.MediaRouter.RoutingCallback, android.os.Handler); - field public static final int CONNECTION_ERROR_ABORTED = 1; // 0x1 - field public static final int CONNECTION_ERROR_BARGED = 7; // 0x7 - field public static final int CONNECTION_ERROR_BROKEN = 6; // 0x6 - field public static final int CONNECTION_ERROR_BUSY = 4; // 0x4 - field public static final int CONNECTION_ERROR_TIMEOUT = 5; // 0x5 - field public static final int CONNECTION_ERROR_UNAUTHORIZED = 2; // 0x2 - field public static final int CONNECTION_ERROR_UNKNOWN = 0; // 0x0 - field public static final int CONNECTION_ERROR_UNREACHABLE = 3; // 0x3 - field public static final int CONNECTION_FLAG_BARGE = 1; // 0x1 - field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2 - field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1 - field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0 - field public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0; // 0x0 - field public static final int DISCONNECTION_REASON_ERROR = 2; // 0x2 - field public static final int DISCONNECTION_REASON_USER_REQUEST = 1; // 0x1 - field public static final int DISCOVERY_ERROR_ABORTED = 1; // 0x1 - field public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2; // 0x2 - field public static final int DISCOVERY_ERROR_UNKNOWN = 0; // 0x0 - field public static final int DISCOVERY_FLAG_BACKGROUND = 1; // 0x1 - field public static final int DISCOVERY_STATE_STARTED = 1; // 0x1 - field public static final int DISCOVERY_STATE_STOPPED = 0; // 0x0 - field public static final int ROUTE_FEATURE_LIVE_AUDIO = 1; // 0x1 - field public static final int ROUTE_FEATURE_LIVE_VIDEO = 2; // 0x2 - } - - public static final class MediaRouter.ConnectionInfo { - method public android.media.AudioAttributes getAudioAttributes(); - method public android.os.Bundle getExtras(); - method public int getFeatures(); - method public android.view.Display getPresentationDisplay(); - method public android.os.IBinder getProtocolBinder(java.lang.String); - method public android.os.IBinder getProtocolBinder(int); - method public T getProtocolObject(java.lang.Class<T>); - method public java.util.List<java.lang.String> getProtocols(); - method public android.media.routing.MediaRouter.RouteInfo getRoute(); - method public android.media.VolumeProvider getVolumeProvider(); - } - - public static final class MediaRouter.ConnectionInfo.Builder { - ctor public MediaRouter.ConnectionInfo.Builder(android.media.routing.MediaRouter.RouteInfo); - method public android.media.routing.MediaRouter.ConnectionInfo build(); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setAudioAttributes(android.media.AudioAttributes); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setExtras(android.os.Bundle); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setPresentationDisplay(android.view.Display); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolBinder(java.lang.String, android.os.IBinder); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolStub(java.lang.Class<?>, android.os.IInterface); - method public android.media.routing.MediaRouter.ConnectionInfo.Builder setVolumeProvider(android.media.VolumeProvider); - } - - public static final class MediaRouter.ConnectionRequest { - method public android.os.Bundle getExtras(); - method public int getFlags(); - method public android.media.routing.MediaRouter.RouteInfo getRoute(); - method public void setExtras(android.os.Bundle); - method public void setFlags(int); - method public void setRoute(android.media.routing.MediaRouter.RouteInfo); - } - - public static final class MediaRouter.Delegate { - ctor public MediaRouter.Delegate(); - method public void addStateCallback(android.media.routing.MediaRouter.StateCallback, android.os.Handler); - method public void connect(android.media.routing.MediaRouter.DestinationInfo, int); - method public void disconnect(int); - method public int getConnectionState(); - method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations(); - method public int getDiscoveryState(); - method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination(); - method public boolean isReleased(); - method public void removeStateCallback(android.media.routing.MediaRouter.StateCallback); - method public void startDiscovery(int); - method public void stopDiscovery(); - } - - public static final class MediaRouter.DestinationInfo { - method public java.lang.CharSequence getDescription(); - method public android.os.Bundle getExtras(); - method public int getIconResourceId(); - method public java.lang.String getId(); - method public java.lang.CharSequence getName(); - method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata(); - method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); - } - - public static final class MediaRouter.DestinationInfo.Builder { - ctor public MediaRouter.DestinationInfo.Builder(java.lang.String, android.media.routing.MediaRouter.ServiceMetadata, java.lang.CharSequence); - method public android.media.routing.MediaRouter.DestinationInfo build(); - method public android.media.routing.MediaRouter.DestinationInfo.Builder setDescription(java.lang.CharSequence); - method public android.media.routing.MediaRouter.DestinationInfo.Builder setExtras(android.os.Bundle); - method public android.media.routing.MediaRouter.DestinationInfo.Builder setIconResourceId(int); - } - - public static final class MediaRouter.DiscoveryRequest { - method public int getFlags(); - method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors(); - method public void setFlags(int); - method public void setSelectors(java.util.List<android.media.routing.MediaRouteSelector>); - } - - public static final class MediaRouter.RouteInfo { - method public android.media.routing.MediaRouter.DestinationInfo getDestination(); - method public android.os.Bundle getExtras(); - method public int getFeatures(); - method public java.lang.String getId(); - method public java.util.List<java.lang.String> getProtocols(); - method public android.media.routing.MediaRouteSelector getSelector(); - } - - public static final class MediaRouter.RouteInfo.Builder { - ctor public MediaRouter.RouteInfo.Builder(java.lang.String, android.media.routing.MediaRouter.DestinationInfo, android.media.routing.MediaRouteSelector); - method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.Class<T>); - method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.String); - method public android.media.routing.MediaRouter.RouteInfo build(); - method public android.media.routing.MediaRouter.RouteInfo.Builder setExtras(android.os.Bundle); - method public android.media.routing.MediaRouter.RouteInfo.Builder setFeatures(int); - } - - public static abstract class MediaRouter.RoutingCallback extends android.media.routing.MediaRouter.StateCallback { - ctor public MediaRouter.RoutingCallback(); - method public boolean onPrepareConnectionRequest(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>); - method public boolean onPrepareDiscoveryRequest(android.media.routing.MediaRouter.DiscoveryRequest, java.util.List<android.media.routing.MediaRouteSelector>); - } - - public static final class MediaRouter.ServiceMetadata { - method public android.content.ComponentName getComponentName(); - method public android.graphics.drawable.Drawable getIcon(android.content.pm.PackageManager); - method public java.lang.CharSequence getLabel(android.content.pm.PackageManager); - method public java.lang.String getPackageName(); - method public android.content.pm.ServiceInfo getService(); - } - - public static abstract class MediaRouter.StateCallback { - ctor public MediaRouter.StateCallback(); - method public void onConnected(); - method public void onConnecting(); - method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle); - method public void onConnectionStateChanged(int); - method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo); - method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo); - method public void onDisconnected(); - method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle); - method public void onDiscoveryStarted(); - method public void onDiscoveryStateChanged(int); - method public void onDiscoveryStopped(); - method public void onReleased(); - method public void onSelectedDestinationChanged(android.media.routing.MediaRouter.DestinationInfo); - } - -} - package android.media.session { public final class MediaController { ctor public MediaController(android.content.Context, android.media.session.MediaSession.Token); method public void adjustVolume(int, int); - method public android.media.routing.MediaRouter.Delegate createMediaRouterDelegate(); method public boolean dispatchMediaButtonEvent(android.view.KeyEvent); method public android.os.Bundle getExtras(); method public long getFlags(); @@ -19032,7 +18797,6 @@ package android.media.session { method public void setExtras(android.os.Bundle); method public void setFlags(int); method public void setMediaButtonReceiver(android.app.PendingIntent); - method public void setMediaRouter(android.media.routing.MediaRouter); method public void setMetadata(android.media.MediaMetadata); method public void setPlaybackState(android.media.session.PlaybackState); method public void setPlaybackToLocal(android.media.AudioAttributes); @@ -30756,6 +30520,53 @@ package android.security { public abstract class KeyStoreKeyProperties { } + public static abstract class KeyStoreKeyProperties.Algorithm { + field public static final java.lang.String AES = "AES"; + field public static final java.lang.String EC = "EC"; + field public static final java.lang.String HMAC_SHA1 = "HmacSHA1"; + field public static final java.lang.String HMAC_SHA224 = "HmacSHA224"; + field public static final java.lang.String HMAC_SHA256 = "HmacSHA256"; + field public static final java.lang.String HMAC_SHA384 = "HmacSHA384"; + field public static final java.lang.String HMAC_SHA512 = "HmacSHA512"; + field public static final java.lang.String RSA = "RSA"; + } + + public static abstract class KeyStoreKeyProperties.AlgorithmEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.BlockMode { + field public static final java.lang.String CBC = "CBC"; + field public static final java.lang.String CTR = "CTR"; + field public static final java.lang.String ECB = "ECB"; + field public static final java.lang.String GCM = "GCM"; + } + + public static abstract class KeyStoreKeyProperties.BlockModeEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.Digest { + field public static final java.lang.String MD5 = "MD5"; + field public static final java.lang.String NONE = "NONE"; + field public static final java.lang.String SHA1 = "SHA-1"; + field public static final java.lang.String SHA224 = "SHA-224"; + field public static final java.lang.String SHA256 = "SHA-256"; + field public static final java.lang.String SHA384 = "SHA-384"; + field public static final java.lang.String SHA512 = "SHA-512"; + } + + public static abstract class KeyStoreKeyProperties.DigestEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.EncryptionPadding { + field public static final java.lang.String NONE = "NoPadding"; + field public static final java.lang.String PKCS7 = "PKCS7Padding"; + field public static final java.lang.String RSA_OAEP = "OAEPPadding"; + field public static final java.lang.String RSA_PKCS1 = "PKCS1Padding"; + } + + public static abstract class KeyStoreKeyProperties.EncryptionPaddingEnum implements java.lang.annotation.Annotation { + } + public static abstract class KeyStoreKeyProperties.Origin { field public static final int GENERATED = 1; // 0x1 field public static final int IMPORTED = 2; // 0x2 @@ -30775,6 +30586,14 @@ package android.security { public static abstract class KeyStoreKeyProperties.PurposeEnum implements java.lang.annotation.Annotation { } + public static abstract class KeyStoreKeyProperties.SignaturePadding { + field public static final java.lang.String RSA_PKCS1 = "PKCS1"; + field public static final java.lang.String RSA_PSS = "PSS"; + } + + public static abstract class KeyStoreKeyProperties.SignaturePaddingEnum implements java.lang.annotation.Annotation { + } + public class KeyStoreKeySpec implements java.security.spec.KeySpec { method public java.lang.String[] getBlockModes(); method public java.lang.String[] getDigests(); @@ -36398,14 +36217,6 @@ package android.util { } public deprecated class FloatMath { - method public static float ceil(float); - method public static float cos(float); - method public static float exp(float); - method public static float floor(float); - method public static float hypot(float, float); - method public static float pow(float, float); - method public static float sin(float); - method public static float sqrt(float); } public final class JsonReader implements java.io.Closeable { diff --git a/api/system-removed.txt b/api/system-removed.txt index 5e77e15..0046a70 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -108,6 +108,21 @@ package android.text.format { } +package android.util { + + public deprecated class FloatMath { + method public static float ceil(float); + method public static float cos(float); + method public static float exp(float); + method public static float floor(float); + method public static float hypot(float, float); + method public static float pow(float, float); + method public static float sin(float); + method public static float sqrt(float); + } + +} + package android.view { public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index bb25ec6..21dc1e2 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -630,7 +630,10 @@ bool BootAnimation::movie() } glDisable(GL_SCISSOR_TEST); } - glDrawTexiOES(xc, yc, 0, animation.width, animation.height); + // specify the y center as ceiling((mHeight - animation.height) / 2) + // which is equivalent to mHeight - (yc + animation.height) + glDrawTexiOES(xc, mHeight - (yc + animation.height), + 0, animation.width, animation.height); eglSwapBuffers(mDisplay, mSurface); nsecs_t now = systemTime(); diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index 5790682..cdd72be 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * This class enables automatic animations on layout changes in ViewGroup objects. To enable @@ -757,7 +758,7 @@ public class LayoutTransition { // reset the inter-animation delay, in case we use it later staggerDelay = 0; - final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup + final ViewTreeObserver observer = parent.getViewTreeObserver(); if (!observer.isAlive()) { // If the observer's not in a good state, skip the transition return; @@ -790,21 +791,9 @@ public class LayoutTransition { // This is the cleanup step. When we get this rendering event, we know that all of // the appropriate animations have been set up and run. Now we can clear out the // layout listeners. - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - parent.getViewTreeObserver().removeOnPreDrawListener(this); - int count = layoutChangeListenerMap.size(); - if (count > 0) { - Collection<View> views = layoutChangeListenerMap.keySet(); - for (View view : views) { - View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); - view.removeOnLayoutChangeListener(listener); - } - } - layoutChangeListenerMap.clear(); - return true; - } - }); + CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent); + observer.addOnPreDrawListener(callback); + parent.addOnAttachStateChangeListener(callback); } /** @@ -1499,4 +1488,50 @@ public class LayoutTransition { View view, int transitionType); } + /** + * Utility class to clean up listeners after animations are setup. Cleanup happens + * when either the OnPreDrawListener method is called or when the parent is detached, + * whichever comes first. + */ + private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener, + View.OnAttachStateChangeListener { + + final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap; + final ViewGroup parent; + + CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) { + this.layoutChangeListenerMap = listenerMap; + this.parent = parent; + } + + private void cleanup() { + parent.getViewTreeObserver().removeOnPreDrawListener(this); + parent.removeOnAttachStateChangeListener(this); + int count = layoutChangeListenerMap.size(); + if (count > 0) { + Collection<View> views = layoutChangeListenerMap.keySet(); + for (View view : views) { + View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); + view.removeOnLayoutChangeListener(listener); + } + layoutChangeListenerMap.clear(); + } + } + + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + cleanup(); + } + + @Override + public boolean onPreDraw() { + cleanup(); + return true; + } + }; + } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 7260d10..9968dbb 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -89,7 +89,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 75e4bab..1174387 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -18,6 +18,7 @@ package android.app; import java.util.ArrayList; +import android.annotation.CallSuper; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.Context; @@ -89,6 +90,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { * service, or receiver in a process. * If you override this method, be sure to call super.onCreate(). */ + @CallSuper public void onCreate() { } @@ -98,9 +100,11 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { * removed by simply killing them; no user code (including this callback) * is executed when doing so. */ + @CallSuper public void onTerminate() { } + @CallSuper public void onConfigurationChanged(Configuration newConfig) { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { @@ -110,6 +114,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + @CallSuper public void onLowMemory() { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { @@ -119,6 +124,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + @CallSuper public void onTrimMemory(int level) { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index d049104..f6e0e1e 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -48,7 +48,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 46da025..391131a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -91,7 +91,6 @@ import android.os.IPowerManager; import android.os.IUserManager; import android.os.PowerManager; import android.os.Process; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemVibrator; import android.os.UserHandle; @@ -111,7 +110,7 @@ import android.telephony.TelephonyManager; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; -import android.view.PhoneLayoutInflater; +import com.android.internal.policy.PhoneLayoutInflater; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 54fe786..d0298cd 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -17,6 +17,7 @@ package android.content; import android.content.pm.ApplicationInfo; +import android.os.ResultReceiver; import android.provider.MediaStore; import android.util.ArraySet; @@ -3291,11 +3292,79 @@ public class Intent implements Parcelable, Cloneable { /** * An Intent describing the choices you would like shown with - * {@link #ACTION_PICK_ACTIVITY}. + * {@link #ACTION_PICK_ACTIVITY} or {@link #ACTION_CHOOSER}. */ public static final String EXTRA_INTENT = "android.intent.extra.INTENT"; /** + * An Intent[] describing additional, alternate choices you would like shown with + * {@link #ACTION_CHOOSER}. + * + * <p>An app may be capable of providing several different payload types to complete a + * user's intended action. For example, an app invoking {@link #ACTION_SEND} to share photos + * with another app may use EXTRA_ALTERNATE_INTENTS to have the chooser transparently offer + * several different supported sending mechanisms for sharing, such as the actual "image/*" + * photo data or a hosted link where the photos can be viewed.</p> + * + * <p>The intent present in {@link #EXTRA_INTENT} will be treated as the + * first/primary/preferred intent in the set. Additional intents specified in + * this extra are ordered; by default intents that appear earlier in the array will be + * preferred over intents that appear later in the array as matches for the same + * target component. To alter this preference, a calling app may also supply + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p> + */ + public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS"; + + /** + * An {@link IntentSender} for an Activity that will be invoked when the user makes a selection + * from the chooser activity presented by {@link #ACTION_CHOOSER}. + * + * <p>An app preparing an action for another app to complete may wish to allow the user to + * disambiguate between several options for completing the action based on the chosen target + * or otherwise refine the action before it is invoked. + * </p> + * + * <p>When sent, this IntentSender may be filled in with the following extras:</p> + * <ul> + * <li>{@link #EXTRA_INTENT} The first intent that matched the user's chosen target</li> + * <li>{@link #EXTRA_ALTERNATE_INTENTS} Any additional intents that also matched the user's + * chosen target beyond the first</li> + * <li>{@link #EXTRA_RESULT_RECEIVER} A {@link ResultReceiver} that the refinement activity + * should fill in and send once the disambiguation is complete</li> + * </ul> + */ + public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER + = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; + + /** + * A {@link ResultReceiver} used to return data back to the sender. + * + * <p>Used to complete an app-specific + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER refinement} for {@link #ACTION_CHOOSER}.</p> + * + * <p>If {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} is present in the intent + * used to start a {@link #ACTION_CHOOSER} activity this extra will be + * {@link #fillIn(Intent, int) filled in} to that {@link IntentSender} and sent + * when the user selects a target component from the chooser. It is up to the recipient + * to send a result to this ResultReceiver to signal that disambiguation is complete + * and that the chooser should invoke the user's choice.</p> + * + * <p>The disambiguator should provide a Bundle to the ResultReceiver with an intent + * assigned to the key {@link #EXTRA_INTENT}. This supplied intent will be used by the chooser + * to match and fill in the final Intent or ChooserTarget before starting it. + * The supplied intent must {@link #filterEquals(Intent) match} one of the intents from + * {@link #EXTRA_INTENT} or {@link #EXTRA_ALTERNATE_INTENTS} passed to + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} to be accepted.</p> + * + * <p>The result code passed to the ResultReceiver should be + * {@link android.app.Activity#RESULT_OK} if the refinement succeeded and the supplied intent's + * target in the chooser should be started, or {@link android.app.Activity#RESULT_CANCELED} if + * the chooser should finish without starting a target.</p> + */ + public static final String EXTRA_RESULT_RECEIVER + = "android.intent.extra.RESULT_RECEIVER"; + + /** * A CharSequence dialog title to provide to the user when used with a * {@link #ACTION_CHOOSER}. */ diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index a4d6be0..691798f 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -326,9 +326,6 @@ public class RequestThreadManager { } try { - startPreview(); // If preview is not running (i.e. after a JPEG capture), we need to - // explicitely start and stop preview before setting preview surface. - // null. stopPreview(); } catch (RuntimeException e) { Log.e(TAG, "Received device exception in configure call: ", e); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 293cf6f..a7af838 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -324,23 +324,7 @@ public final class Settings { "android.settings.BLUETOOTH_SETTINGS"; /** - * Activity Action: Show settings to allow configuration of Wifi Displays. - * <p> - * In some cases, a matching Activity may not exist, so ensure you - * safeguard against this. - * <p> - * Input: Nothing. - * <p> - * Output: Nothing. - * @hide - */ - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_WIFI_DISPLAY_SETTINGS = - "android.settings.WIFI_DISPLAY_SETTINGS"; - - /** - * Activity Action: Show settings to allow configuration of - * {@link android.media.routing.MediaRouteService media route providers}. + * Activity Action: Show settings to allow configuration of cast endpoints. * <p> * In some cases, a matching Activity may not exist, so ensure you * safeguard against this. @@ -5951,6 +5935,12 @@ public final class Settings { "wireless_charging_started_sound"; /** + * Whether to play a sound for charging events. + * @hide + */ + public static final String CHARGING_SOUNDS_ENABLED = "charging_sounds_enabled"; + + /** * Whether we keep the device on while the device is plugged in. * Supported values are: * <ul> diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 29aaf30..0171869 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -40,7 +40,7 @@ import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.ViewGroup; diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index cf29310..3eeb04a 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1481,14 +1481,8 @@ public class TextToSpeech { // interface). // Sanitize locale using isLanguageAvailable. - int result = service.isLanguageAvailable( language, country, variant); - if (result >= LANG_AVAILABLE){ - if (result < LANG_COUNTRY_VAR_AVAILABLE) { - variant = ""; - if (result < LANG_COUNTRY_AVAILABLE) { - country = ""; - } - } + int result = service.isLanguageAvailable(language, country, variant); + if (result >= LANG_AVAILABLE) { // Get the default voice for the locale. String voiceName = service.getDefaultVoiceNameFor(language, country, variant); if (TextUtils.isEmpty(voiceName)) { @@ -1502,10 +1496,28 @@ public class TextToSpeech { return LANG_NOT_SUPPORTED; } + // Set the language/country/variant of the voice, so #getLanguage will return + // the currently set voice locale when called. + Voice voice = getVoice(service, voiceName); + String voiceLanguage = ""; + try { + voiceLanguage = voice.getLocale().getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + + voice.getLocale(), e); + } + + String voiceCountry = ""; + try { + voiceCountry = voice.getLocale().getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + + voice.getLocale(), e); + } mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName); - mParams.putString(Engine.KEY_PARAM_LANGUAGE, language); - mParams.putString(Engine.KEY_PARAM_COUNTRY, country); - mParams.putString(Engine.KEY_PARAM_VARIANT, variant); + mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage); + mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry); + mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant()); } return result; } @@ -1654,20 +1666,32 @@ public class TextToSpeech { if (TextUtils.isEmpty(voiceName)) { return null; } - List<Voice> voices = service.getVoices(); - if (voices == null) { - return null; - } - for (Voice voice : voices) { - if (voice.getName().equals(voiceName)) { - return voice; - } - } - return null; + return getVoice(service, voiceName); } }, null, "getVoice"); } + + /** + * Returns a Voice instance of the voice with the given voice name. + * + * @return Voice instance with the given voice name, or {@code null} if not set or on error. + * + * @see Voice + */ + private Voice getVoice(ITextToSpeechService service, String voiceName) throws RemoteException { + List<Voice> voices = service.getVoices(); + if (voices == null) { + return null; + } + for (Voice voice : voices) { + if (voice.getName().equals(voiceName)) { + return voice; + } + } + return null; + } + /** * Returns a Voice instance that's the default voice for the default Text-to-speech language. * @return The default voice instance for the default language, or {@code null} if not set or @@ -1690,14 +1714,7 @@ public class TextToSpeech { // Sanitize the locale using isLanguageAvailable. int result = service.isLanguageAvailable(language, country, variant); - if (result >= LANG_AVAILABLE){ - if (result < LANG_COUNTRY_VAR_AVAILABLE) { - variant = ""; - if (result < LANG_COUNTRY_AVAILABLE) { - country = ""; - } - } - } else { + if (result < LANG_AVAILABLE) { // The default language is not supported. return null; } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 451abea..59c7c6d 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -91,6 +91,7 @@ public class StaticLayout extends Layout { b.mEllipsizedWidth = width; b.mEllipsize = null; b.mMaxLines = Integer.MAX_VALUE; + b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; b.mMeasuredText = MeasuredText.obtain(); return b; @@ -100,6 +101,8 @@ public class StaticLayout extends Layout { b.mPaint = null; b.mText = null; MeasuredText.recycle(b.mMeasuredText); + b.mMeasuredText = null; + nFinishBuilder(b.mNativePtr); sPool.release(b); } diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java index 8f488af..bb7d15f 100644 --- a/core/java/android/util/FloatMath.java +++ b/core/java/android/util/FloatMath.java @@ -25,6 +25,8 @@ package android.util; * {@link java.lang.Math}. {@link java.lang.Math} should be used in * preference. * + * <p>All methods were removed from the public API in version 23. + * * @deprecated Use {@link java.lang.Math} instead. */ @Deprecated @@ -39,6 +41,7 @@ public class FloatMath { * * @param value to be converted * @return the floor of value + * @removed */ public static float floor(float value) { return (float) Math.floor(value); @@ -50,6 +53,7 @@ public class FloatMath { * * @param value to be converted * @return the ceiling of value + * @removed */ public static float ceil(float value) { return (float) Math.ceil(value); @@ -60,6 +64,7 @@ public class FloatMath { * * @param angle to compute the cosine of, in radians * @return the sine of angle + * @removed */ public static float sin(float angle) { return (float) Math.sin(angle); @@ -70,6 +75,7 @@ public class FloatMath { * * @param angle to compute the cosine of, in radians * @return the cosine of angle + * @removed */ public static float cos(float angle) { return (float) Math.cos(angle); @@ -81,6 +87,7 @@ public class FloatMath { * * @param value to compute sqrt of * @return the square root of value + * @removed */ public static float sqrt(float value) { return (float) Math.sqrt(value); @@ -92,6 +99,7 @@ public class FloatMath { * * @param value to compute the exponential of * @return the exponential of value + * @removed */ public static float exp(float value) { return (float) Math.exp(value); @@ -104,6 +112,7 @@ public class FloatMath { * @param x the base of the operation. * @param y the exponent of the operation. * @return {@code x} to the power of {@code y}. + * @removed */ public static float pow(float x, float y) { return (float) Math.pow(x, y); @@ -116,6 +125,7 @@ public class FloatMath { * @param x a float number * @param y a float number * @return the hypotenuse + * @removed */ public static float hypot(float x, float y) { return (float) Math.hypot(x, y); diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java index bc38e1a..9f46f45 100644 --- a/core/java/android/view/GhostView.java +++ b/core/java/android/view/GhostView.java @@ -40,8 +40,7 @@ public class GhostView extends View { mView.mGhostView = this; final ViewGroup parent = (ViewGroup) mView.getParent(); setGhostedVisibility(View.INVISIBLE); - parent.mRecreateDisplayList = true; - parent.updateDisplayListIfDirty(); + parent.invalidate(); } @Override diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fda6e63..c9c2a82 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -76,6 +76,7 @@ import android.widget.Scroller; import com.android.internal.R; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.PhoneFallbackEventHandler; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 78604bf..040fd37 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -247,6 +247,13 @@ public final class InputMethodManager { /** @hide */ public static final int DISPATCH_HANDLED = 1; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_AUTO = 0; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES = 1; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES = 2; + final IInputMethodManager mService; final Looper mMainLooper; @@ -1890,9 +1897,28 @@ public final class InputMethodManager { } } + /** + * Shows the input method chooser dialog. + * + * @param showAuxiliarySubtypes Set true to show auxiliary input methods. + * @hide + */ + public void showInputMethodPicker(boolean showAuxiliarySubtypes) { + synchronized (mH) { + try { + final int mode = showAuxiliarySubtypes ? + SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES: + SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES; + mService.showInputMethodPickerFromClient(mClient, mode); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + private void showInputMethodPickerLocked() { try { - mService.showInputMethodPickerFromClient(mClient); + mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO); } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index 8d8b3a3..97348e30 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -28,7 +28,7 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.View; import android.view.ViewGroup; import android.view.Window; diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index e347faa..62ca1f0 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; @@ -34,6 +35,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Parcelable; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.UserHandle; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; @@ -53,11 +55,13 @@ public class ChooserActivity extends ResolverActivity { private static final boolean DEBUG = false; - private static final int QUERY_TARGET_LIMIT = 5; + private static final int QUERY_TARGET_SERVICE_LIMIT = 5; private static final int WATCHDOG_TIMEOUT_MILLIS = 5000; private Bundle mReplacementExtras; private IntentSender mChosenComponentSender; + private IntentSender mRefinementIntentSender; + private RefinementResultReceiver mRefinementResultReceiver; private ChooserTarget[] mCallerChooserTargets; @@ -113,6 +117,32 @@ public class ChooserActivity extends ResolverActivity { if (target != null) { modifyTargetIntent(target); } + Parcelable[] targetsParcelable + = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS); + if (targetsParcelable != null) { + final boolean offset = target == null; + Intent[] additionalTargets = + new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length]; + for (int i = 0; i < targetsParcelable.length; i++) { + if (!(targetsParcelable[i] instanceof Intent)) { + Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: " + + targetsParcelable[i]); + finish(); + super.onCreate(null); + return; + } + final Intent additionalTarget = (Intent) targetsParcelable[i]; + if (i == 0 && target == null) { + target = additionalTarget; + modifyTargetIntent(target); + } else { + additionalTargets[offset ? i - 1 : i] = additionalTarget; + modifyTargetIntent(additionalTarget); + } + } + setAdditionalTargets(additionalTargets); + } + mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); int defaultTitleRes = 0; @@ -125,7 +155,7 @@ public class ChooserActivity extends ResolverActivity { initialIntents = new Intent[pa.length]; for (int i=0; i<pa.length; i++) { if (!(pa[i] instanceof Intent)) { - Log.w("ChooserActivity", "Initial intent #" + i + " not an Intent: " + pa[i]); + Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]); finish(); super.onCreate(null); return; @@ -141,8 +171,7 @@ public class ChooserActivity extends ResolverActivity { final ChooserTarget[] targets = new ChooserTarget[pa.length]; for (int i = 0; i < pa.length; i++) { if (!(pa[i] instanceof ChooserTarget)) { - Log.w("ChooserActivity", "Chooser target #" + i + " is not a ChooserTarget: " + - pa[i]); + Log.w(TAG, "Chooser target #" + i + " is not a ChooserTarget: " + pa[i]); finish(); super.onCreate(null); return; @@ -153,12 +182,23 @@ public class ChooserActivity extends ResolverActivity { } mChosenComponentSender = intent.getParcelableExtra( Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER); + mRefinementIntentSender = intent.getParcelableExtra( + Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); setSafeForwardingMode(true); super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); } @Override + protected void onDestroy() { + super.onDestroy(); + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + } + + @Override public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { Intent result = defIntent; if (mReplacementExtras != null) { @@ -211,6 +251,37 @@ public class ChooserActivity extends ResolverActivity { } } + @Override + protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { + if (mRefinementIntentSender != null) { + final Intent fillIn = new Intent(); + final List<Intent> sourceIntents = target.getAllSourceIntents(); + if (!sourceIntents.isEmpty()) { + fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0)); + if (sourceIntents.size() > 1) { + final Intent[] alts = new Intent[sourceIntents.size() - 1]; + for (int i = 1, N = sourceIntents.size(); i < N; i++) { + alts[i - 1] = sourceIntents.get(i); + } + fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts); + } + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + } + mRefinementResultReceiver = new RefinementResultReceiver(this, target, null); + fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER, + mRefinementResultReceiver); + try { + mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null); + return false; + } catch (SendIntentException e) { + Log.e(TAG, "Refinement IntentSender failed to send", e); + } + } + } + return super.onTargetSelected(target, alwaysCheck); + } + void queryTargetServices(ChooserListAdapter adapter) { final PackageManager pm = getPackageManager(); int targetsToQuery = 0; @@ -258,8 +329,9 @@ public class ChooserActivity extends ResolverActivity { targetsToQuery++; } } - if (targetsToQuery >= QUERY_TARGET_LIMIT) { - if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + QUERY_TARGET_LIMIT); + if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) { + if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + + QUERY_TARGET_SERVICE_LIMIT); break; } } @@ -303,6 +375,43 @@ public class ChooserActivity extends ResolverActivity { mTargetResultHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); } + void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + + if (selectedTarget == null) { + Log.e(TAG, "Refinement result intent did not match any known targets; canceling"); + } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) { + Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget + + " cannot match refined source intent " + matchingIntent); + } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) { + finish(); + return; + } + onRefinementCanceled(); + } + + void onRefinementCanceled() { + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + finish(); + } + + boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) { + final List<Intent> targetIntents = target.getAllSourceIntents(); + for (int i = 0, N = targetIntents.size(); i < N; i++) { + final Intent targetIntent = targetIntents.get(i); + if (targetIntent.filterEquals(matchingIntent)) { + return true; + } + } + return false; + } + @Override ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { @@ -313,17 +422,19 @@ public class ChooserActivity extends ResolverActivity { return adapter; } - class ChooserTargetInfo implements TargetInfo { - private final TargetInfo mSourceInfo; + final class ChooserTargetInfo implements TargetInfo { + private final DisplayResolveInfo mSourceInfo; private final ResolveInfo mBackupResolveInfo; private final ChooserTarget mChooserTarget; private final Drawable mDisplayIcon; + private final Intent mFillInIntent; + private final int mFillInFlags; public ChooserTargetInfo(ChooserTarget target) { this(null, target); } - public ChooserTargetInfo(TargetInfo sourceInfo, ChooserTarget chooserTarget) { + public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget) { mSourceInfo = sourceInfo; mChooserTarget = chooserTarget; mDisplayIcon = new BitmapDrawable(getResources(), chooserTarget.getIcon()); @@ -333,6 +444,18 @@ public class ChooserActivity extends ResolverActivity { } else { mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0); } + + mFillInIntent = null; + mFillInFlags = 0; + } + + private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) { + mSourceInfo = other.mSourceInfo; + mBackupResolveInfo = other.mBackupResolveInfo; + mChooserTarget = other.mChooserTarget; + mDisplayIcon = other.mDisplayIcon; + mFillInIntent = fillInIntent; + mFillInFlags = flags; } @Override @@ -358,22 +481,42 @@ public class ChooserActivity extends ResolverActivity { } private Intent getFillInIntent() { - return mSourceInfo != null ? mSourceInfo.getResolvedIntent() : getTargetIntent(); + Intent result = mSourceInfo != null + ? mSourceInfo.getResolvedIntent() : getTargetIntent(); + if (result == null) { + Log.e(TAG, "ChooserTargetInfo#getFillInIntent: no fillIn intent available"); + } else if (mFillInIntent != null) { + result = new Intent(result); + result.fillIn(mFillInIntent, mFillInFlags); + } + return result; } @Override public boolean start(Activity activity, Bundle options) { - return mChooserTarget.sendIntent(activity, getFillInIntent()); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntent(activity, intent); } @Override public boolean startAsCaller(Activity activity, Bundle options, int userId) { - return mChooserTarget.sendIntentAsCaller(activity, getFillInIntent(), userId); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntentAsCaller(activity, intent, userId); } @Override public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { - return mChooserTarget.sendIntentAsUser(activity, getFillInIntent(), user); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntentAsUser(activity, intent, user); } @Override @@ -395,6 +538,21 @@ public class ChooserActivity extends ResolverActivity { public Drawable getDisplayIcon() { return mDisplayIcon; } + + @Override + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { + return new ChooserTargetInfo(this, fillInIntent, flags); + } + + @Override + public List<Intent> getAllSourceIntents() { + final List<Intent> results = new ArrayList<>(); + if (mSourceInfo != null) { + // We only queried the service for the first one in our sourceinfo. + results.add(mSourceInfo.getAllSourceIntents().get(0)); + } + return results; + } } public class ChooserListAdapter extends ResolveListAdapter { @@ -542,4 +700,53 @@ public class ChooserActivity extends ResolverActivity { connection = c; } } + + static class RefinementResultReceiver extends ResultReceiver { + private ChooserActivity mChooserActivity; + private TargetInfo mSelectedTarget; + + public RefinementResultReceiver(ChooserActivity host, TargetInfo target, + Handler handler) { + super(handler); + mChooserActivity = host; + mSelectedTarget = target; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (mChooserActivity == null) { + Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); + return; + } + if (resultData == null) { + Log.e(TAG, "RefinementResultReceiver received null resultData"); + return; + } + + switch (resultCode) { + case RESULT_CANCELED: + mChooserActivity.onRefinementCanceled(); + break; + case RESULT_OK: + Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); + if (intentParcelable instanceof Intent) { + mChooserActivity.onRefinementResult(mSelectedTarget, + (Intent) intentParcelable); + } else { + Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent" + + " in resultData with key Intent.EXTRA_INTENT"); + } + break; + default: + Log.w(TAG, "Unknown result code " + resultCode + + " sent to RefinementResultReceiver"); + break; + } + } + + public void destroy() { + mChooserActivity = null; + mSelectedTarget = null; + } + } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 8dd7836..2048664 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -102,7 +102,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private int mLastSelected = AbsListView.INVALID_POSITION; private boolean mResolvingHome = false; private int mProfileSwitchMessageId = -1; - private Intent mIntent; + private final ArrayList<Intent> mIntents = new ArrayList<>(); private UsageStatsManager mUsm; private Map<String, UsageStats> mStats; @@ -229,7 +229,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); mIconDpi = am.getLauncherLargeIconDensity(); - mIntent = new Intent(intent); + mIntents.add(0, new Intent(intent)); mAdapter = createAdapter(this, initialIntents, rList, mLaunchedFromUid, alwaysUseOption); final int layoutId; @@ -250,7 +250,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return; } - int count = mAdapter.mList.size(); + int count = mAdapter.mDisplayList.size(); if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) { setContentView(layoutId); mAdapterView = (AbsListView) findViewById(R.id.resolver_list); @@ -376,8 +376,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } + protected final void setAdditionalTargets(Intent[] intents) { + if (intents != null) { + for (Intent intent : intents) { + mIntents.add(intent); + } + } + } + public Intent getTargetIntent() { - return mIntent; + return mIntents.isEmpty() ? null : mIntents.get(0); } private String getReferrerPackageName() { @@ -630,8 +638,9 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } TargetInfo target = mAdapter.targetInfoForPosition(which, filtered); - onTargetSelected(target, always); - finish(); + if (onTargetSelected(target, always)) { + finish(); + } } /** @@ -641,7 +650,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return defIntent; } - protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) { + protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { final ResolveInfo ri = target.getResolveInfo(); final Intent intent = target != null ? target.getResolvedIntent() : null; @@ -728,7 +737,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic ComponentName[] set = new ComponentName[N]; int bestMatch = 0; for (int i=0; i<N; i++) { - ResolveInfo r = mAdapter.mOrigResolveList.get(i); + ResolveInfo r = mAdapter.mOrigResolveList.get(i).getResolveInfoAt(0); set[i] = new ComponentName(r.activityInfo.packageName, r.activityInfo.name); if (r.match > bestMatch) bestMatch = r.match; @@ -774,6 +783,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (target != null) { safelyStartActivity(target); } + return true; } void safelyStartActivity(TargetInfo cti) { @@ -837,15 +847,17 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private Drawable mDisplayIcon; private final CharSequence mExtendedInfo; private final Intent mResolvedIntent; + private final List<Intent> mSourceIntents = new ArrayList<>(); - DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel, + DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent) { + mSourceIntents.add(originalIntent); mResolveInfo = pri; mDisplayLabel = pLabel; mExtendedInfo = pInfo; final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent : - getReplacementIntent(pri.activityInfo, mIntent)); + getReplacementIntent(pri.activityInfo, getTargetIntent())); intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); final ActivityInfo ai = mResolveInfo.activityInfo; @@ -854,6 +866,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic mResolvedIntent = intent; } + private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) { + mSourceIntents.addAll(other.getAllSourceIntents()); + mResolveInfo = other.mResolveInfo; + mDisplayLabel = other.mDisplayLabel; + mDisplayIcon = other.mDisplayIcon; + mExtendedInfo = other.mExtendedInfo; + mResolvedIntent = new Intent(other.mResolvedIntent); + mResolvedIntent.fillIn(fillInIntent, flags); + } + public ResolveInfo getResolveInfo() { return mResolveInfo; } @@ -866,6 +888,20 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return mDisplayIcon; } + @Override + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { + return new DisplayResolveInfo(this, fillInIntent, flags); + } + + @Override + public List<Intent> getAllSourceIntents() { + return mSourceIntents; + } + + public void addAlternateSourceIntent(Intent alt) { + mSourceIntents.add(alt); + } + public void setDisplayIcon(Drawable icon) { mDisplayIcon = icon; } @@ -986,6 +1022,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic * @return The drawable that should be used to represent this target */ public Drawable getDisplayIcon(); + + /** + * Clone this target with the given fill-in information. + */ + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags); + + /** + * @return the list of supported source intents deduped against this single target + */ + public List<Intent> getAllSourceIntents(); } class ResolveListAdapter extends BaseAdapter { @@ -998,8 +1044,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic protected final LayoutInflater mInflater; - List<DisplayResolveInfo> mList; - List<ResolveInfo> mOrigResolveList; + List<DisplayResolveInfo> mDisplayList; + List<ResolvedComponentInfo> mOrigResolveList; private int mLastChosenPosition = -1; private boolean mFilterLastUsed; @@ -1010,7 +1056,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic mBaseResolveList = rList; mLaunchedFromUid = launchedFromUid; mInflater = LayoutInflater.from(context); - mList = new ArrayList<>(); + mDisplayList = new ArrayList<>(); mFilterLastUsed = filterLastUsed; rebuildList(); } @@ -1027,7 +1073,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic public DisplayResolveInfo getFilteredItem() { if (mFilterLastUsed && mLastChosenPosition >= 0) { // Not using getItem since it offsets to dodge this position for the list - return mList.get(mLastChosenPosition); + return mDisplayList.get(mLastChosenPosition); } return null; } @@ -1048,11 +1094,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } private void rebuildList() { - List<ResolveInfo> currentResolveList; + List<ResolvedComponentInfo> currentResolveList = null; try { + final Intent primaryIntent = getTargetIntent(); mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity( - mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()), + primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()), PackageManager.MATCH_DEFAULT_ONLY); } catch (RemoteException re) { Log.d(TAG, "Error calling setLastChosenActivity\n" + re); @@ -1060,15 +1107,27 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // Clear the value of mOtherProfile from previous call. mOtherProfile = null; - mList.clear(); + mDisplayList.clear(); if (mBaseResolveList != null) { - currentResolveList = mOrigResolveList = mBaseResolveList; + currentResolveList = mOrigResolveList = new ArrayList<>(); + addResolveListDedupe(currentResolveList, getTargetIntent(), mBaseResolveList); } else { - currentResolveList = mOrigResolveList = mPm.queryIntentActivities(mIntent, - PackageManager.MATCH_DEFAULT_ONLY - | (shouldGetResolvedFilter() ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata() ? PackageManager.GET_META_DATA : 0) - ); + final boolean shouldGetResolvedFilter = shouldGetResolvedFilter(); + final boolean shouldGetActivityMetadata = shouldGetActivityMetadata(); + for (int i = 0, N = mIntents.size(); i < N; i++) { + final Intent intent = mIntents.get(i); + final List<ResolveInfo> infos = mPm.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY + | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)); + if (infos != null) { + if (currentResolveList == null) { + currentResolveList = mOrigResolveList = new ArrayList<>(); + } + addResolveListDedupe(currentResolveList, intent, infos); + } + } + // Filter out any activities that the launched uid does not // have permission for. We don't do this when we have an explicit // list of resolved activities, because that only happens when @@ -1076,14 +1135,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // they gave us. if (currentResolveList != null) { for (int i=currentResolveList.size()-1; i >= 0; i--) { - ActivityInfo ai = currentResolveList.get(i).activityInfo; + ActivityInfo ai = currentResolveList.get(i) + .getResolveInfoAt(0).activityInfo; int granted = ActivityManager.checkComponentPermission( ai.permission, mLaunchedFromUid, ai.applicationInfo.uid, ai.exported); if (granted != PackageManager.PERMISSION_GRANTED) { // Access not allowed! if (mOrigResolveList == currentResolveList) { - mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList); + mOrigResolveList = new ArrayList<>(mOrigResolveList); } currentResolveList.remove(i); } @@ -1094,9 +1154,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) { // Only display the first matches that are either of equal // priority or have asked to be default options. - ResolveInfo r0 = currentResolveList.get(0); + ResolvedComponentInfo rci0 = currentResolveList.get(0); + ResolveInfo r0 = rci0.getResolveInfoAt(0); for (int i=1; i<N; i++) { - ResolveInfo ri = currentResolveList.get(i); + ResolveInfo ri = currentResolveList.get(i).getResolveInfoAt(0); if (DEBUG) Log.v( TAG, r0.activityInfo.name + "=" + @@ -1107,7 +1168,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic r0.isDefault != ri.isDefault) { while (i < N) { if (mOrigResolveList == currentResolveList) { - mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList); + mOrigResolveList = new ArrayList<>(mOrigResolveList); } currentResolveList.remove(i); N--; @@ -1115,9 +1176,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } if (N > 1) { - Comparator<ResolveInfo> rComparator = - new ResolverComparator(ResolverActivity.this, mIntent); - Collections.sort(currentResolveList, rComparator); + Collections.sort(currentResolveList, + new ResolverComparator(ResolverActivity.this, getTargetIntent())); } // First put the initial items at the top. if (mInitialIntents != null) { @@ -1146,14 +1206,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic ri.nonLocalizedLabel = li.getNonLocalizedLabel(); ri.icon = li.getIconResource(); } - addResolveInfo(new DisplayResolveInfo(ri, + addResolveInfo(new DisplayResolveInfo(ii, ri, ri.loadLabel(getPackageManager()), null, ii)); } } // Check for applications with same name and use application name or // package name if necessary - r0 = currentResolveList.get(0); + rci0 = currentResolveList.get(0); + r0 = rci0.getResolveInfoAt(0); int start = 0; CharSequence r0Label = r0.loadLabel(mPm); mHasExtendedInfo = false; @@ -1161,7 +1222,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (r0Label == null) { r0Label = r0.activityInfo.packageName; } - ResolveInfo ri = currentResolveList.get(i); + ResolvedComponentInfo rci = currentResolveList.get(i); + ResolveInfo ri = rci.getResolveInfoAt(0); CharSequence riLabel = ri.loadLabel(mPm); if (riLabel == null) { riLabel = ri.activityInfo.packageName; @@ -1169,13 +1231,14 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (riLabel.equals(r0Label)) { continue; } - processGroup(currentResolveList, start, (i-1), r0, r0Label); + processGroup(currentResolveList, start, (i-1), rci0, r0Label); + rci0 = rci; r0 = ri; r0Label = riLabel; start = i; } // Process last group - processGroup(currentResolveList, start, (N-1), r0, r0Label); + processGroup(currentResolveList, start, (N-1), rci0, r0Label); } // Layout doesn't handle both profile button and last chosen @@ -1188,6 +1251,36 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic onListRebuilt(); } + private void addResolveListDedupe(List<ResolvedComponentInfo> into, Intent intent, + List<ResolveInfo> from) { + final int fromCount = from.size(); + final int intoCount = into.size(); + for (int i = 0; i < fromCount; i++) { + final ResolveInfo newInfo = from.get(i); + boolean found = false; + // Only loop to the end of into as it was before we started; no dupes in from. + for (int j = 0; j < intoCount; j++) { + final ResolvedComponentInfo rci = into.get(i); + if (isSameResolvedComponent(newInfo, rci)) { + found = true; + rci.add(intent, newInfo); + break; + } + } + if (!found) { + into.add(new ResolvedComponentInfo(new ComponentName( + newInfo.activityInfo.packageName, newInfo.activityInfo.name), + intent, newInfo)); + } + } + } + + private boolean isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b) { + final ActivityInfo ai = a.activityInfo; + return ai.packageName.equals(b.name.getPackageName()) + && ai.name.equals(b.name.getClassName()); + } + public void onListRebuilt() { // This space for rent } @@ -1196,18 +1289,18 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return mFilterLastUsed; } - private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, - CharSequence roLabel) { + private void processGroup(List<ResolvedComponentInfo> rList, int start, int end, + ResolvedComponentInfo ro, CharSequence roLabel) { // Process labels from start to i int num = end - start+1; if (num == 1) { // No duplicate labels. Use label for entry at start - addResolveInfo(new DisplayResolveInfo(ro, roLabel, null, null)); - updateLastChosenPosition(ro); + addResolveInfoWithAlternates(ro, null, roLabel); } else { mHasExtendedInfo = true; boolean usePkg = false; - CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm); + CharSequence startApp = ro.getResolveInfoAt(0).activityInfo.applicationInfo + .loadLabel(mPm); if (startApp == null) { usePkg = true; } @@ -1217,7 +1310,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic new HashSet<CharSequence>(); duplicates.add(startApp); for (int j = start+1; j <= end ; j++) { - ResolveInfo jRi = rList.get(j); + ResolveInfo jRi = rList.get(j).getResolveInfoAt(0); CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); if ( (jApp == null) || (duplicates.contains(jApp))) { usePkg = true; @@ -1230,26 +1323,46 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic duplicates.clear(); } for (int k = start; k <= end; k++) { - ResolveInfo add = rList.get(k); + final ResolvedComponentInfo rci = rList.get(k); + final ResolveInfo add = rci.getResolveInfoAt(0); + final CharSequence extraInfo; if (usePkg) { - // Use application name for all entries from start to end-1 - addResolveInfo(new DisplayResolveInfo(add, roLabel, - add.activityInfo.packageName, null)); - } else { // Use package name for all entries from start to end-1 - addResolveInfo(new DisplayResolveInfo(add, roLabel, - add.activityInfo.applicationInfo.loadLabel(mPm), null)); + extraInfo = add.activityInfo.packageName; + } else { + // Use application name for all entries from start to end-1 + extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm); } - updateLastChosenPosition(add); + addResolveInfoWithAlternates(rci, extraInfo, roLabel); + } + } + } + + private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, + CharSequence extraInfo, CharSequence roLabel) { + final int count = rci.getCount(); + final Intent intent = rci.getIntentAt(0); + final ResolveInfo add = rci.getResolveInfoAt(0); + final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent); + final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel, + extraInfo, replaceIntent); + addResolveInfo(dri); + if (replaceIntent == intent) { + // Only add alternates if we didn't get a specific replacement from + // the caller. If we have one it trumps potential alternates. + for (int i = 1, N = count; i < N; i++) { + final Intent altIntent = rci.getIntentAt(i); + dri.addAlternateSourceIntent(altIntent); } } + updateLastChosenPosition(add); } private void updateLastChosenPosition(ResolveInfo info) { if (mLastChosen != null && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName) && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) { - mLastChosenPosition = mList.size() - 1; + mLastChosenPosition = mDisplayList.size() - 1; } } @@ -1259,20 +1372,21 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // The first one we see gets special treatment. mOtherProfile = dri; } else { - mList.add(dri); + mDisplayList.add(dri); } } public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { - return (filtered ? getItem(position) : mList.get(position)).getResolveInfo(); + return (filtered ? getItem(position) : mDisplayList.get(position)) + .getResolveInfo(); } public TargetInfo targetInfoForPosition(int position, boolean filtered) { - return filtered ? getItem(position) : mList.get(position); + return filtered ? getItem(position) : mDisplayList.get(position); } public int getCount() { - int result = mList.size(); + int result = mDisplayList.size(); if (mFilterLastUsed && mLastChosenPosition >= 0) { result--; } @@ -1283,7 +1397,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { position++; } - return mList.get(position); + return mDisplayList.get(position); } public long getItemId(int position) { @@ -1295,8 +1409,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } public boolean hasResolvedTarget(ResolveInfo info) { - for (int i = 0, N = mList.size(); i < N; i++) { - if (info.equals(mList.get(i).getResolveInfo())) { + for (int i = 0, N = mDisplayList.size(); i < N; i++) { + if (info.equals(mDisplayList.get(i).getResolveInfo())) { return true; } } @@ -1304,11 +1418,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } protected int getDisplayResolveInfoCount() { - return mList.size(); + return mDisplayList.size(); } protected DisplayResolveInfo getDisplayResolveInfo(int index) { - return mList.get(index); + // Used to query services. We only query services for primary targets, not alternates. + return mDisplayList.get(index); } public final View getView(int position, View convertView, ViewGroup parent) { @@ -1349,6 +1464,52 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } + static final class ResolvedComponentInfo { + public final ComponentName name; + private final List<Intent> mIntents = new ArrayList<>(); + private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); + + public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) { + this.name = name; + add(intent, info); + } + + public void add(Intent intent, ResolveInfo info) { + mIntents.add(intent); + mResolveInfos.add(info); + } + + public int getCount() { + return mIntents.size(); + } + + public Intent getIntentAt(int index) { + return index >= 0 ? mIntents.get(index) : null; + } + + public ResolveInfo getResolveInfoAt(int index) { + return index >= 0 ? mResolveInfos.get(index) : null; + } + + public int findIntent(Intent intent) { + for (int i = 0, N = mIntents.size(); i < N; i++) { + if (intent.equals(mIntents.get(i))) { + return i; + } + } + return -1; + } + + public int findResolveInfo(ResolveInfo info) { + for (int i = 0, N = mResolveInfos.size(); i < N; i++) { + if (info.equals(mResolveInfos.get(i))) { + return i; + } + } + return -1; + } + } + static class ViewHolder { public TextView text; public TextView text2; @@ -1435,7 +1596,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic && match <= IntentFilter.MATCH_CATEGORY_PATH; } - class ResolverComparator implements Comparator<ResolveInfo> { + class ResolverComparator implements Comparator<ResolvedComponentInfo> { private final Collator mCollator; private final boolean mHttp; @@ -1446,7 +1607,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } @Override - public int compare(ResolveInfo lhs, ResolveInfo rhs) { + public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) { + final ResolveInfo lhs = lhsp.getResolveInfoAt(0); + final ResolveInfo rhs = rhsp.getResolveInfoAt(0); + // We want to put the one targeted to another user at the end of the dialog. if (lhs.targetUserId != UserHandle.USER_CURRENT) { return 1; @@ -1487,7 +1651,6 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (stats != null) { return stats.getTotalTimeInForeground(); } - } return 0; } diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 52485dd..ce94727 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -196,7 +196,7 @@ public class InputMethodSubtypeSwitchingController { } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList( - boolean showSubtypes, boolean inputShown, boolean isScreenLocked) { + boolean showSubtypes, boolean includeAuxiliarySubtypes, boolean isScreenLocked) { final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>(); final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = @@ -205,6 +205,12 @@ public class InputMethodSubtypeSwitchingController { if (immis == null || immis.size() == 0) { return Collections.emptyList(); } + if (isScreenLocked && includeAuxiliarySubtypes) { + if (DEBUG) { + Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen."); + } + includeAuxiliarySubtypes = false; + } mSortedImmis.clear(); mSortedImmis.putAll(immis); for (InputMethodInfo imi : mSortedImmis.keySet()) { @@ -227,7 +233,7 @@ public class InputMethodSubtypeSwitchingController { final String subtypeHashCode = String.valueOf(subtype.hashCode()); // We show all enabled IMEs and subtypes when an IME is shown. if (enabledSubtypeSet.contains(subtypeHashCode) - && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { + && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) { final CharSequence subtypeLabel = subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(mContext, imi.getPackageName(), @@ -516,8 +522,8 @@ public class InputMethodSubtypeSwitchingController { } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, - boolean inputShown, boolean isScreenLocked) { + boolean includingAuxiliarySubtypes, boolean isScreenLocked) { return mSubtypeList.getSortedInputMethodAndSubtypeList( - showSubtypes, inputShown, isScreenLocked); + showSubtypes, includingAuxiliarySubtypes, isScreenLocked); } } diff --git a/core/java/android/view/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java index 350650d..2cb9c25 100644 --- a/core/java/android/view/PhoneFallbackEventHandler.java +++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import android.app.KeyguardManager; import android.app.SearchManager; @@ -28,10 +28,11 @@ import android.os.UserHandle; import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.Log; -import android.view.View; -import android.view.HapticFeedbackConstants; import android.view.FallbackEventHandler; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; +import android.view.View; +import com.android.internal.policy.PhoneWindow; /** * @hide diff --git a/core/java/android/view/PhoneLayoutInflater.java b/core/java/com/android/internal/policy/PhoneLayoutInflater.java index 7d89a0b..991b6bb 100644 --- a/core/java/android/view/PhoneLayoutInflater.java +++ b/core/java/com/android/internal/policy/PhoneLayoutInflater.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import android.content.Context; import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; /** * @hide diff --git a/core/java/android/view/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index a3e7a10..a578a6e 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; @@ -27,6 +27,34 @@ import android.app.ActivityManagerNative; import android.app.SearchManager; import android.os.UserHandle; +import android.view.ActionMode; +import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.Gravity; +import android.view.IRotationWatcher.Stub; +import android.view.IWindowManager; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputQueue; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SearchEvent; +import android.view.SurfaceHolder.Callback2; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewManager; +import android.view.ViewParent; +import android.view.ViewRootImpl; +import android.view.ViewStub; +import android.view.ViewTreeObserver.OnPreDrawListener; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; import com.android.internal.R; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.FloatingActionMode; @@ -67,7 +95,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.transition.Scene; import android.transition.Transition; import android.transition.TransitionInflater; @@ -140,7 +167,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ViewGroup mContentRoot; - SurfaceHolder.Callback2 mTakeSurfaceCallback; + Callback2 mTakeSurfaceCallback; InputQueue.Callback mTakeInputQueueCallback; @@ -427,7 +454,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override - public void takeSurface(SurfaceHolder.Callback2 callback) { + public void takeSurface(Callback2 callback) { mTakeSurfaceCallback = callback; } @@ -2181,7 +2208,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ActionBarContextView mPrimaryActionModeView; private PopupWindow mPrimaryActionModePopup; private Runnable mShowPrimaryActionModePopup; - private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; + private OnPreDrawListener mFloatingToolbarPreDrawListener; private View mFloatingActionModeOriginatingView; private FloatingToolbar mFloatingToolbar; @@ -3354,7 +3381,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mContext, callback, originatingView, mFloatingToolbar); mFloatingActionModeOriginatingView = originatingView; mFloatingToolbarPreDrawListener = - new ViewTreeObserver.OnPreDrawListener() { + new OnPreDrawListener() { @Override public boolean onPreDraw() { mode.updateViewLocationInWindow(); @@ -4718,7 +4745,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } - static class RotationWatcher extends IRotationWatcher.Stub { + static class RotationWatcher extends Stub { private Handler mHandler; private final Runnable mRotationChanged = new Runnable() { public void run() { diff --git a/core/java/com/android/internal/transition/EpicenterClipReveal.java b/core/java/com/android/internal/transition/EpicenterClipReveal.java deleted file mode 100644 index 1a6736a..0000000 --- a/core/java/com/android/internal/transition/EpicenterClipReveal.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.transition; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.RectEvaluator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.transition.TransitionValues; -import android.transition.Visibility; -import android.util.AttributeSet; -import android.util.Property; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.view.animation.PathInterpolator; - -import com.android.internal.R; - -/** - * EpicenterClipReveal captures the {@link View#getClipBounds()} before and - * after the scene change and animates between those and the epicenter bounds - * during a visibility transition. - */ -public class EpicenterClipReveal extends Visibility { - private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; - private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; - - private final TimeInterpolator mInterpolatorX; - private final TimeInterpolator mInterpolatorY; - private final boolean mCenterClipBounds; - - public EpicenterClipReveal() { - mInterpolatorX = null; - mInterpolatorY = null; - mCenterClipBounds = false; - } - - public EpicenterClipReveal(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.EpicenterClipReveal, 0, 0); - - mCenterClipBounds = a.getBoolean(R.styleable.EpicenterClipReveal_centerClipBounds, false); - - final int interpolatorX = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorX, 0); - if (interpolatorX != 0) { - mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); - } else { - mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; - } - - final int interpolatorY = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorY, 0); - if (interpolatorY != 0) { - mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); - } else { - mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; - } - - a.recycle(); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - super.captureStartValues(transitionValues); - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - super.captureEndValues(transitionValues); - captureValues(transitionValues); - } - - private void captureValues(TransitionValues values) { - final View view = values.view; - if (view.getVisibility() == View.GONE) { - return; - } - - final Rect clip = view.getClipBounds(); - values.values.put(PROPNAME_CLIP, clip); - - if (clip == null) { - final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); - values.values.put(PROPNAME_BOUNDS, bounds); - } - } - - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (endValues == null) { - return null; - } - - final Rect end = getBestRect(endValues); - final Rect start = getEpicenterOrCenter(end); - - // Prepare the view. - view.setClipBounds(start); - - return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); - } - - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (startValues == null) { - return null; - } - - final Rect start = getBestRect(startValues); - final Rect end = getEpicenterOrCenter(start); - - // Prepare the view. - view.setClipBounds(start); - - return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); - } - - private Rect getEpicenterOrCenter(Rect bestRect) { - final Rect epicenter = getEpicenter(); - if (epicenter != null) { - // Translate the clip bounds to be centered within the target bounds. - if (mCenterClipBounds) { - final int offsetX = bestRect.centerX() - epicenter.centerX(); - final int offsetY = bestRect.centerY() - epicenter.centerY(); - epicenter.offset(offsetX, offsetY); - } - return epicenter; - } - - final int centerX = bestRect.centerX(); - final int centerY = bestRect.centerY(); - return new Rect(centerX, centerY, centerX, centerY); - } - - private Rect getBestRect(TransitionValues values) { - final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); - if (clipRect == null) { - return (Rect) values.values.get(PROPNAME_BOUNDS); - } - return clipRect; - } - - private static Animator createRectAnimator(final View view, Rect start, Rect end, - TransitionValues endValues, TimeInterpolator interpolatorX, - TimeInterpolator interpolatorY) { - final RectEvaluator evaluator = new RectEvaluator(new Rect()); - final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); - - final ClipDimenProperty propX = new ClipDimenProperty(ClipDimenProperty.TARGET_X); - final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, start, end); - if (interpolatorX != null) { - animX.setInterpolator(interpolatorX); - } - - final ClipDimenProperty propY = new ClipDimenProperty(ClipDimenProperty.TARGET_Y); - final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, start, end); - if (interpolatorY != null) { - animY.setInterpolator(interpolatorY); - } - - final AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(animX, animY); - animSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setClipBounds(terminalClip); - } - }); - return animSet; - } - - private static class ClipDimenProperty extends Property<View, Rect> { - public static final char TARGET_X = 'x'; - public static final char TARGET_Y = 'y'; - - private final Rect mTempRect = new Rect(); - - private final int mTargetDimension; - - public ClipDimenProperty(char targetDimension) { - super(Rect.class, "clip_bounds_" + targetDimension); - - mTargetDimension = targetDimension; - } - - @Override - public Rect get(View object) { - final Rect tempRect = mTempRect; - if (!object.getClipBounds(tempRect)) { - tempRect.setEmpty(); - } - return tempRect; - } - - @Override - public void set(View object, Rect value) { - final Rect tempRect = mTempRect; - if (object.getClipBounds(tempRect)) { - if (mTargetDimension == TARGET_X) { - tempRect.left = value.left; - tempRect.right = value.right; - } else { - tempRect.top = value.top; - tempRect.bottom = value.bottom; - } - object.setClipBounds(tempRect); - } - } - } -} diff --git a/core/java/com/android/internal/transition/EpicenterTranslate.java b/core/java/com/android/internal/transition/EpicenterTranslate.java deleted file mode 100644 index eac3ff8..0000000 --- a/core/java/com/android/internal/transition/EpicenterTranslate.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.transition; - -import com.android.internal.R; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.transition.TransitionValues; -import android.transition.Visibility; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; - -/** - * EpicenterTranslate captures the {@link View#getTranslationX()} and - * {@link View#getTranslationY()} before and after the scene change and - * animates between those and the epicenter's center during a visibility - * transition. - */ -public class EpicenterTranslate extends Visibility { - private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; - private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX"; - private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY"; - private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ"; - private static final String PROPNAME_Z = "android:epicenterReveal:z"; - - private final TimeInterpolator mInterpolatorX; - private final TimeInterpolator mInterpolatorY; - private final TimeInterpolator mInterpolatorZ; - - public EpicenterTranslate() { - mInterpolatorX = null; - mInterpolatorY = null; - mInterpolatorZ = null; - } - - public EpicenterTranslate(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.EpicenterTranslate, 0, 0); - - final int interpolatorX = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorX, 0); - if (interpolatorX != 0) { - mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); - } else { - mInterpolatorX = TransitionConstants.FAST_OUT_SLOW_IN; - } - - final int interpolatorY = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorY, 0); - if (interpolatorY != 0) { - mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); - } else { - mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; - } - - final int interpolatorZ = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorZ, 0); - if (interpolatorZ != 0) { - mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ); - } else { - mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN; - } - - a.recycle(); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - super.captureStartValues(transitionValues); - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - super.captureEndValues(transitionValues); - captureValues(transitionValues); - } - - private void captureValues(TransitionValues values) { - final View view = values.view; - if (view.getVisibility() == View.GONE) { - return; - } - - final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); - values.values.put(PROPNAME_BOUNDS, bounds); - values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX()); - values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY()); - values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ()); - values.values.put(PROPNAME_Z, view.getZ()); - } - - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (endValues == null) { - return null; - } - - final Rect end = (Rect) endValues.values.get(PROPNAME_BOUNDS); - final Rect start = getEpicenterOrCenter(end); - final float startX = start.centerX() - end.centerX(); - final float startY = start.centerY() - end.centerY(); - final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z); - - // Translate the view to be centered on the epicenter. - view.setTranslationX(startX); - view.setTranslationY(startY); - view.setTranslationZ(startZ); - - final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); - final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); - final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); - return createAnimator(view, startX, startY, startZ, endX, endY, endZ, - mInterpolatorX, mInterpolatorY, mInterpolatorZ); - } - - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (startValues == null) { - return null; - } - - final Rect start = (Rect) endValues.values.get(PROPNAME_BOUNDS); - final Rect end = getEpicenterOrCenter(start); - final float endX = end.centerX() - start.centerX(); - final float endY = end.centerY() - start.centerY(); - final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z); - - final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); - final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); - final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); - return createAnimator(view, startX, startY, startZ, endX, endY, endZ, - mInterpolatorX, mInterpolatorY, mInterpolatorZ); - } - - private Rect getEpicenterOrCenter(Rect bestRect) { - final Rect epicenter = getEpicenter(); - if (epicenter != null) { - return epicenter; - } - - final int centerX = bestRect.centerX(); - final int centerY = bestRect.centerY(); - return new Rect(centerX, centerY, centerX, centerY); - } - - private static Animator createAnimator(final View view, float startX, float startY, - float startZ, float endX, float endY, float endZ, TimeInterpolator interpolatorX, - TimeInterpolator interpolatorY, TimeInterpolator interpolatorZ) { - final ObjectAnimator animX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startX, endX); - if (interpolatorX != null) { - animX.setInterpolator(interpolatorX); - } - - final ObjectAnimator animY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY); - if (interpolatorY != null) { - animY.setInterpolator(interpolatorY); - } - - final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ); - if (interpolatorZ != null) { - animZ.setInterpolator(interpolatorZ); - } - - final AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(animX, animY, animZ); - return animSet; - } -} diff --git a/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java new file mode 100644 index 0000000..9d62079 --- /dev/null +++ b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.TypeEvaluator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.transition.TransitionValues; +import android.transition.Visibility; +import android.util.AttributeSet; +import android.util.Property; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; + +import com.android.internal.R; + +/** + * EpicenterTranslateClipReveal captures the clip bounds and translation values + * before and after the scene change and animates between those and the + * epicenter bounds during a visibility transition. + */ +public class EpicenterTranslateClipReveal extends Visibility { + private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; + private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; + private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX"; + private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY"; + private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ"; + private static final String PROPNAME_Z = "android:epicenterReveal:z"; + + private final TimeInterpolator mInterpolatorX; + private final TimeInterpolator mInterpolatorY; + private final TimeInterpolator mInterpolatorZ; + + public EpicenterTranslateClipReveal() { + mInterpolatorX = null; + mInterpolatorY = null; + mInterpolatorZ = null; + } + + public EpicenterTranslateClipReveal(Context context, AttributeSet attrs) { + super(context, attrs); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.EpicenterTranslateClipReveal, 0, 0); + + final int interpolatorX = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorX, 0); + if (interpolatorX != 0) { + mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); + } else { + mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; + } + + final int interpolatorY = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorY, 0); + if (interpolatorY != 0) { + mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); + } else { + mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; + } + + final int interpolatorZ = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorZ, 0); + if (interpolatorZ != 0) { + mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ); + } else { + mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN; + } + + a.recycle(); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + super.captureStartValues(transitionValues); + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + super.captureEndValues(transitionValues); + captureValues(transitionValues); + } + + private void captureValues(TransitionValues values) { + final View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); + values.values.put(PROPNAME_BOUNDS, bounds); + values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX()); + values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY()); + values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ()); + values.values.put(PROPNAME_Z, view.getZ()); + + final Rect clip = view.getClipBounds(); + values.values.put(PROPNAME_CLIP, clip); + } + + @Override + public Animator onAppear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (endValues == null) { + return null; + } + + final Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + final Rect startBounds = getEpicenterOrCenter(endBounds); + final float startX = startBounds.centerX() - endBounds.centerX(); + final float startY = startBounds.centerY() - endBounds.centerY(); + final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z); + + // Translate the view to be centered on the epicenter. + view.setTranslationX(startX); + view.setTranslationY(startY); + view.setTranslationZ(startZ); + + final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); + final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); + final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); + + final Rect endClip = getBestRect(endValues); + final Rect startClip = getEpicenterOrCenter(endClip); + + // Prepare the view. + view.setClipBounds(startClip); + + final State startStateX = new State(startClip.left, startClip.right, startX); + final State endStateX = new State(endClip.left, endClip.right, endX); + final State startStateY = new State(startClip.top, startClip.bottom, startY); + final State endStateY = new State(endClip.top, endClip.bottom, endY); + + return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY, + endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ); + } + + @Override + public Animator onDisappear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (startValues == null) { + return null; + } + + final Rect startBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + final Rect endBounds = getEpicenterOrCenter(startBounds); + final float endX = endBounds.centerX() - startBounds.centerX(); + final float endY = endBounds.centerY() - startBounds.centerY(); + final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z); + + final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); + final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); + final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); + + final Rect startClip = getBestRect(startValues); + final Rect endClip = getEpicenterOrCenter(startClip); + + // Prepare the view. + view.setClipBounds(startClip); + + final State startStateX = new State(startClip.left, startClip.right, startX); + final State endStateX = new State(endClip.left, endClip.right, endX); + final State startStateY = new State(startClip.top, startClip.bottom, startY); + final State endStateY = new State(endClip.top, endClip.bottom, endY); + + return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY, + endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ); + } + + private Rect getEpicenterOrCenter(Rect bestRect) { + final Rect epicenter = getEpicenter(); + if (epicenter != null) { + /* + // Translate the clip bounds to be centered within the target bounds. + final int offsetX = bestRect.centerX() - epicenter.centerX(); + final int offsetY = bestRect.centerY() - epicenter.centerY(); + epicenter.offset(offsetX, offsetY); + */ + return epicenter; + } + + final int centerX = bestRect.centerX(); + final int centerY = bestRect.centerY(); + return new Rect(centerX, centerY, centerX, centerY); + } + + private Rect getBestRect(TransitionValues values) { + final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); + if (clipRect == null) { + return (Rect) values.values.get(PROPNAME_BOUNDS); + } + return clipRect; + } + + private static Animator createRectAnimator(final View view, State startX, State startY, + float startZ, State endX, State endY, float endZ, TransitionValues endValues, + TimeInterpolator interpolatorX, TimeInterpolator interpolatorY, + TimeInterpolator interpolatorZ) { + final StateEvaluator evaluator = new StateEvaluator(); + + final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ); + if (interpolatorZ != null) { + animZ.setInterpolator(interpolatorZ); + } + + final StateProperty propX = new StateProperty(StateProperty.TARGET_X); + final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, startX, endX); + if (interpolatorX != null) { + animX.setInterpolator(interpolatorX); + } + + final StateProperty propY = new StateProperty(StateProperty.TARGET_Y); + final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, startY, endY); + if (interpolatorY != null) { + animY.setInterpolator(interpolatorY); + } + + final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); + final AnimatorListenerAdapter animatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setClipBounds(terminalClip); + } + }; + + final AnimatorSet animSet = new AnimatorSet(); + animSet.playTogether(animX, animY, animZ); + animSet.addListener(animatorListener); + return animSet; + } + + private static class State { + int lower; + int upper; + float trans; + + public State() {} + + public State(int lower, int upper, float trans) { + this.lower = lower; + this.upper = upper; + this.trans = trans; + } + } + + private static class StateEvaluator implements TypeEvaluator<State> { + private final State mTemp = new State(); + + @Override + public State evaluate(float fraction, State startValue, State endValue) { + mTemp.upper = startValue.upper + (int) ((endValue.upper - startValue.upper) * fraction); + mTemp.lower = startValue.lower + (int) ((endValue.lower - startValue.lower) * fraction); + mTemp.trans = startValue.trans + (int) ((endValue.trans - startValue.trans) * fraction); + return mTemp; + } + } + + private static class StateProperty extends Property<View, State> { + public static final char TARGET_X = 'x'; + public static final char TARGET_Y = 'y'; + + private final Rect mTempRect = new Rect(); + private final State mTempState = new State(); + + private final int mTargetDimension; + + public StateProperty(char targetDimension) { + super(State.class, "state_" + targetDimension); + + mTargetDimension = targetDimension; + } + + @Override + public State get(View object) { + final Rect tempRect = mTempRect; + if (!object.getClipBounds(tempRect)) { + tempRect.setEmpty(); + } + final State tempState = mTempState; + if (mTargetDimension == TARGET_X) { + tempState.trans = object.getTranslationX(); + tempState.lower = tempRect.left + (int) tempState.trans; + tempState.upper = tempRect.right + (int) tempState.trans; + } else { + tempState.trans = object.getTranslationY(); + tempState.lower = tempRect.top + (int) tempState.trans; + tempState.upper = tempRect.bottom + (int) tempState.trans; + } + return tempState; + } + + @Override + public void set(View object, State value) { + final Rect tempRect = mTempRect; + if (object.getClipBounds(tempRect)) { + if (mTargetDimension == TARGET_X) { + tempRect.left = value.lower - (int) value.trans; + tempRect.right = value.upper - (int) value.trans; + } else { + tempRect.top = value.lower - (int) value.trans; + tempRect.bottom = value.upper - (int) value.trans; + } + object.setClipBounds(tempRect); + } + + if (mTargetDimension == TARGET_X) { + object.setTranslationX(value.trans); + } else { + object.setTranslationY(value.trans); + } + } + } +} diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java index 34f62ba..bd0e6ce 100644 --- a/core/java/com/android/internal/util/AsyncChannel.java +++ b/core/java/com/android/internal/util/AsyncChannel.java @@ -462,10 +462,8 @@ public class AsyncChannel { } catch(Exception e) { } // Tell source we're disconnected. - if (mSrcHandler != null) { - replyDisconnected(STATUS_SUCCESSFUL); - mSrcHandler = null; - } + replyDisconnected(STATUS_SUCCESSFUL); + mSrcHandler = null; // Unlink only when bindService isn't used if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) { mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0); @@ -871,6 +869,8 @@ public class AsyncChannel { * @param status to be stored in msg.arg1 */ private void replyDisconnected(int status) { + // Can't reply if already disconnected. Avoid NullPointerException. + if (mSrcHandler == null) return; Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED); msg.arg1 = status; msg.obj = this; diff --git a/core/java/com/android/internal/util/CallbackRegistry.java b/core/java/com/android/internal/util/CallbackRegistry.java new file mode 100644 index 0000000..0f228d4 --- /dev/null +++ b/core/java/com/android/internal/util/CallbackRegistry.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks callbacks for the event. This class supports reentrant modification + * of the callbacks during notification without adversely disrupting notifications. + * A common pattern for callbacks is to receive a notification and then remove + * themselves. This class handles this behavior with constant memory under + * most circumstances. + * + * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to + * the constructor to define how notifications should be called. That implementation + * does the actual notification on the listener.</p> + * + * <p>This class supports only callbacks with at most two parameters. + * Typically, these are the notification originator and a parameter, but these may + * be used as required. If more than two parameters are required or primitive types + * must be used, <code>A</code> should be some kind of containing structure that + * the subclass may reuse between notifications.</p> + * + * @param <C> The callback type. + * @param <T> The notification sender type. Typically this is the containing class. + * @param <A> Opaque argument used to pass additional data beyond an int. + */ +public class CallbackRegistry<C, T, A> implements Cloneable { + private static final String TAG = "CallbackRegistry"; + + /** An ordered collection of listeners waiting to be notified. */ + private List<C> mCallbacks = new ArrayList<C>(); + + /** + * A bit flag for the first 64 listeners that are removed during notification. + * The lowest significant bit corresponds to the 0th index into mCallbacks. + * For a small number of callbacks, no additional array of objects needs to + * be allocated. + */ + private long mFirst64Removed = 0x0; + + /** + * Bit flags for the remaining callbacks that are removed during notification. + * When there are more than 64 callbacks and one is marked for removal, a dynamic + * array of bits are allocated for the callbacks. + */ + private long[] mRemainderRemoved; + + /** + * The reentrancy level of the notification. When we notify a callback, it may cause + * further notifications. The reentrancy level must be tracked to let us clean up + * the callback state when all notifications have been processed. + */ + private int mNotificationLevel; + + /** The notification mechanism for notifying an event. */ + private final NotifierCallback<C, T, A> mNotifier; + + /** + * Creates an EventRegistry that notifies the event with notifier. + * @param notifier The class to use to notify events. + */ + public CallbackRegistry(NotifierCallback<C, T, A> notifier) { + mNotifier = notifier; + } + + /** + * Notify all callbacks. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + public synchronized void notifyCallbacks(T sender, int arg, A arg2) { + mNotificationLevel++; + notifyRecurseLocked(sender, arg, arg2); + mNotificationLevel--; + if (mNotificationLevel == 0) { + if (mRemainderRemoved != null) { + for (int i = mRemainderRemoved.length - 1; i >= 0; i--) { + final long removedBits = mRemainderRemoved[i]; + if (removedBits != 0) { + removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits); + mRemainderRemoved[i] = 0; + } + } + } + if (mFirst64Removed != 0) { + removeRemovedCallbacks(0, mFirst64Removed); + mFirst64Removed = 0; + } + } + } + + /** + * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + private void notifyFirst64Locked(T sender, int arg, A arg2) { + final int maxNotified = Math.min(Long.SIZE, mCallbacks.size()); + notifyCallbacksLocked(sender, arg, arg2, 0, maxNotified, mFirst64Removed); + } + + /** + * Notify all callbacks using a recursive algorithm to avoid allocating on the heap. + * This part captures the callbacks beyond Long.SIZE that have no bits allocated for + * removal before it recurses into {@link #notifyRemainderLocked(Object, int, A, int)}. + * <p> + * Recursion is used to avoid allocating temporary state on the heap. Each stack has one + * long (64 callbacks) worth of information of which has been removed. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + private void notifyRecurseLocked(T sender, int arg, A arg2) { + final int callbackCount = mCallbacks.size(); + final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1; + + // Now we've got all callbacks that have no mRemainderRemoved value, so notify the + // others. + notifyRemainderLocked(sender, arg, arg2, remainderIndex); + + // notifyRemainderLocked notifies all at maxIndex, so we'd normally start at maxIndex + 1 + // However, we must also keep track of those in mFirst64Removed, so we add 2 instead: + final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE; + + // The remaining have no bit set + notifyCallbacksLocked(sender, arg, arg2, startCallbackIndex, callbackCount, 0); + } + + /** + * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If + * remainderIndex is -1, the first 64 will be notified instead. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param remainderIndex The index into mRemainderRemoved that should be notified. + */ + private void notifyRemainderLocked(T sender, int arg, A arg2, int remainderIndex) { + if (remainderIndex < 0) { + notifyFirst64Locked(sender, arg, arg2); + } else { + final long bits = mRemainderRemoved[remainderIndex]; + final int startIndex = (remainderIndex + 1) * Long.SIZE; + final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE); + notifyRemainderLocked(sender, arg, arg2, remainderIndex - 1); + notifyCallbacksLocked(sender, arg, arg2, startIndex, endIndex, bits); + } + } + + /** + * Notify callbacks from startIndex to endIndex, using bits as the bit status + * for whether they have been removed or not. bits should be from mRemainderRemoved or + * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to + * endIndex should be notified. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param startIndex The index into the mCallbacks to start notifying. + * @param endIndex One past the last index into mCallbacks to notify. + * @param bits A bit field indicating which callbacks have been removed and shouldn't + * be notified. + */ + private void notifyCallbacksLocked(T sender, int arg, A arg2, final int startIndex, + final int endIndex, final long bits) { + long bitMask = 1; + for (int i = startIndex; i < endIndex; i++) { + if ((bits & bitMask) == 0) { + mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2); + } + bitMask <<= 1; + } + } + + /** + * Add a callback to be notified. If the callback is already in the list, another won't + * be added. This does not affect current notifications. + * @param callback The callback to add. + */ + public synchronized void add(C callback) { + int index = mCallbacks.lastIndexOf(callback); + if (index < 0 || isRemovedLocked(index)) { + mCallbacks.add(callback); + } + } + + /** + * Returns true if the callback at index has been marked for removal. + * + * @param index The index into mCallbacks to check. + * @return true if the callback at index has been marked for removal. + */ + private boolean isRemovedLocked(int index) { + if (index < Long.SIZE) { + // It is in the first 64 callbacks, just check the bit. + final long bitMask = 1L << index; + return (mFirst64Removed & bitMask) != 0; + } else if (mRemainderRemoved == null) { + // It is after the first 64 callbacks, but nothing else was marked for removal. + return false; + } else { + final int maskIndex = (index / Long.SIZE) - 1; + if (maskIndex >= mRemainderRemoved.length) { + // There are some items in mRemainderRemoved, but nothing at the given index. + return false; + } else { + // There is something marked for removal, so we have to check the bit. + final long bits = mRemainderRemoved[maskIndex]; + final long bitMask = 1L << (index % Long.SIZE); + return (bits & bitMask) != 0; + } + } + } + + /** + * Removes callbacks from startIndex to startIndex + Long.SIZE, based + * on the bits set in removed. + * @param startIndex The index into the mCallbacks to start removing callbacks. + * @param removed The bits indicating removal, where each bit is set for one callback + * to be removed. + */ + private void removeRemovedCallbacks(int startIndex, long removed) { + // The naive approach should be fine. There may be a better bit-twiddling approach. + final int endIndex = startIndex + Long.SIZE; + + long bitMask = 1L << (Long.SIZE - 1); + for (int i = endIndex - 1; i >= startIndex; i--) { + if ((removed & bitMask) != 0) { + mCallbacks.remove(i); + } + bitMask >>>= 1; + } + } + + /** + * Remove a callback. This callback won't be notified after this call completes. + * @param callback The callback to remove. + */ + public synchronized void remove(C callback) { + if (mNotificationLevel == 0) { + mCallbacks.remove(callback); + } else { + int index = mCallbacks.lastIndexOf(callback); + if (index >= 0) { + setRemovalBitLocked(index); + } + } + } + + private void setRemovalBitLocked(int index) { + if (index < Long.SIZE) { + // It is in the first 64 callbacks, just check the bit. + final long bitMask = 1L << index; + mFirst64Removed |= bitMask; + } else { + final int remainderIndex = (index / Long.SIZE) - 1; + if (mRemainderRemoved == null) { + mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE]; + } else if (mRemainderRemoved.length < remainderIndex) { + // need to make it bigger + long[] newRemainders = new long[mCallbacks.size() / Long.SIZE]; + System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length); + mRemainderRemoved = newRemainders; + } + final long bitMask = 1L << (index % Long.SIZE); + mRemainderRemoved[remainderIndex] |= bitMask; + } + } + + /** + * Makes a copy of the registered callbacks and returns it. + * + * @return a copy of the registered callbacks. + */ + public synchronized ArrayList<C> copyListeners() { + ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size()); + int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + callbacks.add(mCallbacks.get(i)); + } + } + return callbacks; + } + + /** + * Returns true if there are no registered callbacks or false otherwise. + * + * @return true if there are no registered callbacks or false otherwise. + */ + public synchronized boolean isEmpty() { + if (mCallbacks.isEmpty()) { + return true; + } else if (mNotificationLevel == 0) { + return false; + } else { + int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + return false; + } + } + return true; + } + } + + /** + * Removes all callbacks from the list. + */ + public synchronized void clear() { + if (mNotificationLevel == 0) { + mCallbacks.clear(); + } else if (!mCallbacks.isEmpty()) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + setRemovalBitLocked(i); + } + } + } + + public synchronized CallbackRegistry<C, T, A> clone() { + CallbackRegistry<C, T, A> clone = null; + try { + clone = (CallbackRegistry<C, T, A>) super.clone(); + clone.mFirst64Removed = 0; + clone.mRemainderRemoved = null; + clone.mNotificationLevel = 0; + clone.mCallbacks = new ArrayList<C>(); + final int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + clone.mCallbacks.add(mCallbacks.get(i)); + } + } + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return clone; + } + + /** + * Class used to notify events from CallbackRegistry. + * + * @param <C> The callback type. + * @param <T> The notification sender type. Typically this is the containing class. + * @param <A> An opaque argument to pass to the notifier + */ + public abstract static class NotifierCallback<C, T, A> { + /** + * Used to notify the callback. + * + * @param callback The callback to notify. + * @param sender The opaque sender object. + * @param arg The opaque notification parameter. + * @param arg2 An opaque argument passed in + * {@link CallbackRegistry#notifyCallbacks} + * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback) + */ + public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2); + } +} diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 6f104dd..60c5e42 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -59,7 +59,8 @@ interface IInputMethodManager { int controlFlags, int softInputMode, int windowFlags, in EditorInfo attribute, IInputContext inputContext); - void showInputMethodPickerFromClient(in IInputMethodClient client); + void showInputMethodPickerFromClient(in IInputMethodClient client, + int auxiliarySubtypeMode); void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId); void setInputMethod(in IBinder token, String id); void setInputMethodAndSubtype(in IBinder token, String id, in InputMethodSubtype subtype); diff --git a/core/jni/Android.mk b/core/jni/Android.mk index bbdd860..5448214 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -132,6 +132,7 @@ LOCAL_SRC_FILES:= \ android_media_AudioRecord.cpp \ android_media_AudioSystem.cpp \ android_media_AudioTrack.cpp \ + android_media_DeviceCallback.cpp \ android_media_JetPlayer.cpp \ android_media_RemoteDisplay.cpp \ android_media_ToneGenerator.cpp \ diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 8ae2e3b..9babe82 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -188,18 +188,43 @@ size_t Bitmap::rowBytes() const { return mPixelRef->rowBytes(); } -SkPixelRef* Bitmap::pixelRef() const { +SkPixelRef* Bitmap::peekAtPixelRef() const { assertValid(); return mPixelRef.get(); } +SkPixelRef* Bitmap::refPixelRef() { + assertValid(); + android::AutoMutex _lock(mLock); + return refPixelRefLocked(); +} + +SkPixelRef* Bitmap::refPixelRefLocked() { + mPixelRef->ref(); + if (mPixelRef->unique()) { + // We just restored this from 0, pin the pixels and inc the strong count + // Note that there *might be* an incoming onStrongRefDestroyed from whatever + // last unref'd + pinPixelsLocked(); + mPinnedRefCount++; + } + return mPixelRef.get(); +} + void Bitmap::reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) { + { + android::AutoMutex _lock(mLock); + if (mPinnedRefCount) { + ALOGW("Called reconfigure on a bitmap that is in use! This may" + " cause graphical corruption!"); + } + } mPixelRef->reconfigure(info, rowBytes, ctable); } void Bitmap::reconfigure(const SkImageInfo& info) { - mPixelRef->reconfigure(info, mPixelRef->rowBytes(), mPixelRef->colorTable()); + reconfigure(info, mPixelRef->rowBytes(), mPixelRef->colorTable()); } void Bitmap::detachFromJava() { @@ -287,18 +312,10 @@ void Bitmap::unpinPixelsLocked() { void Bitmap::getSkBitmap(SkBitmap* outBitmap) { assertValid(); android::AutoMutex _lock(mLock); - mPixelRef->ref(); - if (mPixelRef->unique()) { - // We just restored this from 0, pin the pixels and inc the strong count - // Note that there *might be* an incoming onStrongRefDestroyed from whatever - // last unref'd - pinPixelsLocked(); - mPinnedRefCount++; - } // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes() // would require locking the pixels first. outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes()); - outBitmap->setPixelRef(mPixelRef.get())->unref(); + outBitmap->setPixelRef(refPixelRefLocked())->unref(); outBitmap->setHasHardwareMipMap(hasHardwareMipMap()); } @@ -323,7 +340,7 @@ public: } void* pixels() { - return mBitmap->pixelRef()->pixels(); + return mBitmap->peekAtPixelRef()->pixels(); } bool valid() { @@ -780,7 +797,7 @@ static jint Bitmap_config(JNIEnv* env, jobject, jlong bitmapHandle) { static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); - return static_cast<jint>(bitmap->pixelRef()->getGenerationID()); + return static_cast<jint>(bitmap->peekAtPixelRef()->getGenerationID()); } static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) { @@ -800,10 +817,10 @@ static void Bitmap_setHasAlpha(JNIEnv* env, jobject, jlong bitmapHandle, jboolean hasAlpha, jboolean requestPremul) { LocalScopedBitmap bitmap(bitmapHandle); if (hasAlpha) { - bitmap->pixelRef()->changeAlphaType( + bitmap->peekAtPixelRef()->changeAlphaType( requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); } else { - bitmap->pixelRef()->changeAlphaType(kOpaque_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kOpaque_SkAlphaType); } } @@ -812,9 +829,9 @@ static void Bitmap_setPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle, LocalScopedBitmap bitmap(bitmapHandle); if (!bitmap->info().isOpaque()) { if (isPremul) { - bitmap->pixelRef()->changeAlphaType(kPremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kPremul_SkAlphaType); } else { - bitmap->pixelRef()->changeAlphaType(kUnpremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kUnpremul_SkAlphaType); } } } @@ -1164,7 +1181,7 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); - SkPixelRef* pixelRef = bitmap.valid() ? bitmap->pixelRef() : nullptr; + SkPixelRef* pixelRef = bitmap.valid() ? bitmap->peekAtPixelRef() : nullptr; SkSafeRef(pixelRef); return reinterpret_cast<jlong>(pixelRef); } diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h index d6e5c61..efeb898 100644 --- a/core/jni/android/graphics/Bitmap.h +++ b/core/jni/android/graphics/Bitmap.h @@ -62,7 +62,8 @@ public: int width() const { return info().width(); } int height() const { return info().height(); } size_t rowBytes() const; - SkPixelRef* pixelRef() const; + SkPixelRef* peekAtPixelRef() const; + SkPixelRef* refPixelRef(); bool valid() const { return mPixelStorageType != PixelStorageType::Invalid; } void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable); @@ -88,6 +89,7 @@ private: JNIEnv* jniEnv(); bool shouldDisposeSelfLocked(); void assertValid() const; + SkPixelRef* refPixelRefLocked(); android::Mutex mLock; int mPinnedRefCount = 0; diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index cdd397d..3ca4e72 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -184,7 +184,7 @@ public: } mBitmap->reconfigure(info, bitmap->rowBytes(), ctable); - bitmap->setPixelRef(mBitmap->pixelRef()); + bitmap->setPixelRef(mBitmap->refPixelRef())->unref(); // since we're already allocated, we lockPixels right away // HeapAllocator/JavaPixelAllocator behaves this way too @@ -258,7 +258,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding unsigned int existingBufferSize = 0; if (javaBitmap != NULL) { reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap); - if (reuseBitmap->pixelRef()->isImmutable()) { + if (reuseBitmap->peekAtPixelRef()->isImmutable()) { ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); javaBitmap = NULL; reuseBitmap = nullptr; diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 0deb8cc..1c6f7de 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -352,8 +352,8 @@ void GraphicsJNI::getSkBitmap(JNIEnv* env, jobject bitmap, SkBitmap* outBitmap) getBitmap(env, bitmap)->getSkBitmap(outBitmap); } -SkPixelRef* GraphicsJNI::getSkPixelRef(JNIEnv* env, jobject bitmap) { - return getBitmap(env, bitmap)->pixelRef(); +SkPixelRef* GraphicsJNI::refSkPixelRef(JNIEnv* env, jobject bitmap) { + return getBitmap(env, bitmap)->refPixelRef(); } SkColorType GraphicsJNI::getNativeBitmapColorType(JNIEnv* env, jobject jconfig) { diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index e748bac..ef9c2a9 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -52,7 +52,7 @@ public: static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas); static android::Bitmap* getBitmap(JNIEnv*, jobject bitmap); static void getSkBitmap(JNIEnv*, jobject bitmap, SkBitmap* outBitmap); - static SkPixelRef* getSkPixelRef(JNIEnv*, jobject bitmap); + static SkPixelRef* refSkPixelRef(JNIEnv*, jobject bitmap); static SkRegion* getNativeRegion(JNIEnv*, jobject region); // Given the 'native' long held by the Rasterizer.java object, return a diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 8b65fd1..87b81d5 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -30,6 +30,7 @@ #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" +#include "android_media_DeviceCallback.h" // ---------------------------------------------------------------------------- @@ -44,6 +45,7 @@ struct audio_record_fields_t { jmethodID postNativeEventInJava; //... event post callback method jfieldID nativeRecorderInJavaObj; // provides access to the C++ AudioRecord object jfieldID nativeCallbackCookie; // provides access to the AudioRecord callback data + jfieldID nativeDeviceCallback; // provides access to the JNIDeviceCallback instance }; struct audio_attributes_fields_t { jfieldID fieldRecSource; // AudioAttributes.mSource @@ -120,6 +122,33 @@ static void recorderCallback(int event, void* user, void *info) { } } +static sp<JNIDeviceCallback> getJniDeviceCallback(JNIEnv* env, jobject thiz) +{ + Mutex::Autolock l(sLock); + JNIDeviceCallback* const cb = + (JNIDeviceCallback*)env->GetLongField(thiz, + javaAudioRecordFields.nativeDeviceCallback); + return sp<JNIDeviceCallback>(cb); +} + +static sp<JNIDeviceCallback> setJniDeviceCallback(JNIEnv* env, + jobject thiz, + const sp<JNIDeviceCallback>& cb) +{ + Mutex::Autolock l(sLock); + sp<JNIDeviceCallback> old = + (JNIDeviceCallback*)env->GetLongField(thiz, + javaAudioRecordFields.nativeDeviceCallback); + if (cb.get()) { + cb->incStrong((void*)setJniDeviceCallback); + } + if (old != 0) { + old->decStrong((void*)setJniDeviceCallback); + } + env->SetLongField(thiz, javaAudioRecordFields.nativeDeviceCallback, (jlong)cb.get()); + return old; +} + // ---------------------------------------------------------------------------- static sp<AudioRecord> getAudioRecord(JNIEnv* env, jobject thiz) { @@ -593,9 +622,63 @@ static jboolean android_media_AudioRecord_setInputDevice( JNIEnv *env, jobject thiz, jint device_id) { sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == 0) { + return 0; + } return lpRecorder->setInputDevice(device_id) == NO_ERROR; } +static jint android_media_AudioRecord_getRoutedDeviceId( + JNIEnv *env, jobject thiz) { + + sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == 0) { + return 0; + } + return (jint)lpRecorder->getRoutedDeviceId(); +} + +static void android_media_AudioRecord_enableDeviceCallback( + JNIEnv *env, jobject thiz) { + + sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == 0) { + return; + } + sp<JNIDeviceCallback> cb = getJniDeviceCallback(env, thiz); + if (cb != 0) { + return; + } + audiorecord_callback_cookie *cookie = + (audiorecord_callback_cookie *)env->GetLongField(thiz, + javaAudioRecordFields.nativeCallbackCookie); + if (cookie == NULL) { + return; + } + + cb = new JNIDeviceCallback(env, thiz, cookie->audioRecord_ref, + javaAudioRecordFields.postNativeEventInJava); + status_t status = lpRecorder->addAudioDeviceCallback(cb); + if (status == NO_ERROR) { + setJniDeviceCallback(env, thiz, cb); + } +} + +static void android_media_AudioRecord_disableDeviceCallback( + JNIEnv *env, jobject thiz) { + + sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == 0) { + return; + } + sp<JNIDeviceCallback> cb = setJniDeviceCallback(env, thiz, 0); + if (cb != 0) { + lpRecorder->removeAudioDeviceCallback(cb); + } +} + + + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { @@ -628,12 +711,17 @@ static JNINativeMethod gMethods[] = { {"native_get_min_buff_size", "(III)I", (void *)android_media_AudioRecord_get_min_buff_size}, {"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice}, + {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId}, + {"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback}, + {"native_disableDeviceCallback", "()V", + (void *)android_media_AudioRecord_disableDeviceCallback}, }; // field names found in android/media/AudioRecord.java #define JAVA_POSTEVENT_CALLBACK_NAME "postEventFromNative" #define JAVA_NATIVERECORDERINJAVAOBJ_FIELD_NAME "mNativeRecorderInJavaObj" #define JAVA_NATIVECALLBACKINFO_FIELD_NAME "mNativeCallbackCookie" +#define JAVA_NATIVEDEVICECALLBACK_FIELD_NAME "mNativeDeviceCallback" // ---------------------------------------------------------------------------- int register_android_media_AudioRecord(JNIEnv *env) @@ -641,6 +729,7 @@ int register_android_media_AudioRecord(JNIEnv *env) javaAudioRecordFields.postNativeEventInJava = NULL; javaAudioRecordFields.nativeRecorderInJavaObj = NULL; javaAudioRecordFields.nativeCallbackCookie = NULL; + javaAudioRecordFields.nativeDeviceCallback = NULL; // Get the AudioRecord class @@ -658,6 +747,9 @@ int register_android_media_AudioRecord(JNIEnv *env) javaAudioRecordFields.nativeCallbackCookie = GetFieldIDOrDie(env, audioRecordClass, JAVA_NATIVECALLBACKINFO_FIELD_NAME, "J"); + javaAudioRecordFields.nativeDeviceCallback = GetFieldIDOrDie(env, + audioRecordClass, JAVA_NATIVEDEVICECALLBACK_FIELD_NAME, "J"); + // Get the AudioAttributes class and fields jclass audioAttrClass = FindClassOrDie(env, kAudioAttributesClassPathName); javaAudioAttrFields.fieldRecSource = GetFieldIDOrDie(env, audioAttrClass, "mSource", "I"); diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 3655adc..eab5668 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -937,7 +937,8 @@ static jint convertAudioPortFromNative(JNIEnv *env, } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) { ALOGV("convertAudioPortFromNative is a mix"); *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor, - jHandle, nAudioPort->role, jDeviceName, + jHandle, nAudioPort->ext.mix.handle, + nAudioPort->role, jDeviceName, jSamplingRates, jChannelMasks, jFormats, jGains); } else { @@ -1670,7 +1671,7 @@ int register_android_media_AudioSystem(JNIEnv *env) jclass audioMixPortClass = FindClassOrDie(env, "android/media/AudioMixPort"); gAudioMixPortClass = MakeGlobalRefOrDie(env, audioMixPortClass); gAudioMixPortCstor = GetMethodIDOrDie(env, audioMixPortClass, "<init>", - "(Landroid/media/AudioHandle;ILjava/lang/String;[I[I[I[Landroid/media/AudioGain;)V"); + "(Landroid/media/AudioHandle;IILjava/lang/String;[I[I[I[Landroid/media/AudioGain;)V"); jclass audioGainClass = FindClassOrDie(env, "android/media/AudioGain"); gAudioGainClass = MakeGlobalRefOrDie(env, audioGainClass); diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 26b82c5..662ecd3 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -36,6 +36,7 @@ #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" #include "android_media_PlaybackSettings.h" +#include "android_media_DeviceCallback.h" // ---------------------------------------------------------------------------- @@ -79,6 +80,7 @@ class AudioTrackJniStorage { sp<MemoryHeapBase> mMemHeap; sp<MemoryBase> mMemBase; audiotrack_callback_cookie mCallbackData; + sp<JNIDeviceCallback> mDeviceCallback; AudioTrackJniStorage() { mCallbackData.audioTrack_class = 0; @@ -977,6 +979,51 @@ static jboolean android_media_AudioTrack_setOutputDevice( return lpTrack->setOutputDevice(device_id) == NO_ERROR; } +static jint android_media_AudioTrack_getRoutedDeviceId( + JNIEnv *env, jobject thiz) { + + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + return 0; + } + return (jint)lpTrack->getRoutedDeviceId(); +} + +static void android_media_AudioTrack_enableDeviceCallback( + JNIEnv *env, jobject thiz) { + + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + return; + } + AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( + thiz, javaAudioTrackFields.jniData); + if (pJniStorage == NULL || pJniStorage->mDeviceCallback != 0) { + return; + } + pJniStorage->mDeviceCallback = + new JNIDeviceCallback(env, thiz, pJniStorage->mCallbackData.audioTrack_ref, + javaAudioTrackFields.postNativeEventInJava); + lpTrack->addAudioDeviceCallback(pJniStorage->mDeviceCallback); +} + +static void android_media_AudioTrack_disableDeviceCallback( + JNIEnv *env, jobject thiz) { + + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + return; + } + AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( + thiz, javaAudioTrackFields.jniData); + if (pJniStorage == NULL || pJniStorage->mDeviceCallback == 0) { + return; + } + lpTrack->removeAudioDeviceCallback(pJniStorage->mDeviceCallback); + pJniStorage->mDeviceCallback.clear(); +} + + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { @@ -1030,6 +1077,9 @@ static JNINativeMethod gMethods[] = { "(I)I", (void *)android_media_AudioTrack_attachAuxEffect}, {"native_setOutputDevice", "(I)Z", (void *)android_media_AudioTrack_setOutputDevice}, + {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId}, + {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback}, + {"native_disableDeviceCallback", "()V", (void *)android_media_AudioTrack_disableDeviceCallback}, }; diff --git a/core/jni/android_media_DeviceCallback.cpp b/core/jni/android_media_DeviceCallback.cpp new file mode 100644 index 0000000..e159373 --- /dev/null +++ b/core/jni/android_media_DeviceCallback.cpp @@ -0,0 +1,82 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 + +#define LOG_TAG "AudioDeviceCallback-JNI" + +#include <utils/Log.h> +#include <JNIHelp.h> +#include <JniConstants.h> +#include "core_jni_helpers.h" +#include <media/AudioSystem.h> + +#include "android_media_DeviceCallback.h" + + +// ---------------------------------------------------------------------------- + +using namespace android; + +JNIDeviceCallback::JNIDeviceCallback(JNIEnv* env, jobject thiz, jobject weak_thiz, + jmethodID postEventFromNative) +{ + + // Hold onto the AudioTrack/AudioRecord class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the AudioTrack/AudioRecord object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); + + mPostEventFromNative = postEventFromNative; +} + +JNIDeviceCallback::~JNIDeviceCallback() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + return; + } + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNIDeviceCallback::onAudioDeviceUpdate(audio_io_handle_t audioIo, + audio_port_handle_t deviceId) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + return; + } + + ALOGV("%s audioIo %d deviceId %d", __FUNCTION__, audioIo, deviceId); + env->CallStaticVoidMethod(mClass, + mPostEventFromNative, + mObject, + AUDIO_NATIVE_EVENT_ROUTING_CHANGE, deviceId, 0, NULL); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred while notifying an event."); + env->ExceptionClear(); + } +} + diff --git a/core/jni/android_media_DeviceCallback.h b/core/jni/android_media_DeviceCallback.h new file mode 100644 index 0000000..7ae788e --- /dev/null +++ b/core/jni/android_media_DeviceCallback.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef ANDROID_MEDIA_DEVICE_CALLBACK_H +#define ANDROID_MEDIA_DEVICE_CALLBACK_H + +#include <system/audio.h> +#include <media/AudioSystem.h> + +namespace android { + +// keep in sync with AudioSystem.java +#define AUDIO_NATIVE_EVENT_ROUTING_CHANGE 1000 + +class JNIDeviceCallback: public AudioSystem::AudioDeviceCallback +{ +public: + JNIDeviceCallback(JNIEnv* env, jobject thiz, jobject weak_thiz, jmethodID postEventFromNative); + ~JNIDeviceCallback(); + + virtual void onAudioDeviceUpdate(audio_io_handle_t audioIo, + audio_port_handle_t deviceId); + +private: + void sendEvent(int event); + + jclass mClass; // Reference to AudioTrack/AudioRecord class + jobject mObject; // Weak ref to AudioTrack/AudioRecord Java object to call on + jmethodID mPostEventFromNative; // postEventFromNative method ID. +}; + +}; // namespace android + +#endif // ANDROID_MEDIA_DEVICE_CALLBACK_H diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 1965cd3..77af341 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -180,7 +180,7 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, (void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot, screenshotInfo, rowBytes, nullptr); screenshot.detach(); - bitmap->pixelRef()->setImmutable(); + bitmap->peekAtPixelRef()->setImmutable(); return GraphicsJNI::createBitmap(env, bitmap, GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e3930cd..5669b91 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2352,11 +2352,6 @@ <permission android:name="android.permission.BIND_CONDITION_PROVIDER_SERVICE" android:protectionLevel="signature" /> - <!-- Must be required by a {@link android.media.routing.MediaRouteService}, - to ensure that only the system can bind to it. --> - <permission android:name="android.permission.BIND_MEDIA_ROUTE_SERVICE" - android:protectionLevel="signature" /> - <!-- Must be required by an {@link android.service.dreams.DreamService}, to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_DREAM_SERVICE" diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml index 89c3749..4b544d2 100644 --- a/core/res/res/layout-land/time_picker_material.xml +++ b/core/res/res/layout-land/time_picker_material.xml @@ -16,7 +16,6 @@ --> <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -27,8 +26,7 @@ android:layout_column="0" android:layout_row="0" android:layout_rowSpan="3" - android:layout_gravity="center|fill" - tools:background="@color/accent_material_light" /> + android:layout_gravity="center|fill" /> <RelativeLayout android:layout_width="wrap_content" @@ -56,20 +54,14 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" - tools:text="23" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="right" /> <TextView android:id="@+id/separator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" - tools:text=":" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:importantForAccessibility="no" /> <!-- The minutes should always be to the right of the separator, regardless of the current locale's layout direction. --> @@ -80,10 +72,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" - tools:text="59" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="left" /> </LinearLayout> <!-- The layout alignment of this view will switch between toRightOf @@ -106,10 +95,7 @@ android:paddingTop="@dimen/timepicker_am_top_padding" android:lines="1" android:ellipsize="none" - android:includeFontPadding="false" - tools:text="AM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:includeFontPadding="false" /> <CheckedTextView android:id="@+id/pm_label" @@ -121,10 +107,7 @@ android:paddingTop="@dimen/timepicker_pm_top_padding" android:lines="1" android:ellipsize="none" - android:includeFontPadding="false" - tools:text="PM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:includeFontPadding="false" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml index 2150341..a4388f6 100644 --- a/core/res/res/layout/date_picker_header_material.xml +++ b/core/res/res/layout/date_picker_header_material.xml @@ -16,17 +16,13 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/date_picker_header" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="18dp" android:paddingStart="?attr/dialogPreferredPadding" android:paddingEnd="?attr/dialogPreferredPadding" - android:orientation="vertical" - tools:background="@color/accent_material_light" - tools:paddingStart="24dp" - tools:paddingEnd="24dp"> + android:orientation="vertical"> <!-- Top padding should stay on this view so that the touch target is a bit larger. --> @@ -35,10 +31,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="16dp" - android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" - tools:text="2015" - tools:textSize="@dimen/date_picker_year_label_size" - tools:textColor="@color/white" /> + android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" /> <TextView android:id="@+id/date_picker_header_date" @@ -47,9 +40,6 @@ android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel" android:gravity="start" android:maxLines="2" - android:ellipsize="none" - tools:text="Thu, Sep 30" - tools:textSize="@dimen/date_picker_date_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> </LinearLayout> diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml index be9e443..3f5e300 100644 --- a/core/res/res/layout/time_picker_header_material.xml +++ b/core/res/res/layout/time_picker_header_material.xml @@ -18,13 +18,11 @@ <!-- This layout is duplicated in land/time_picker_material.xml, so any changes made here need to be manually copied over. --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/time_header" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" - android:padding="@dimen/timepicker_separator_padding" - tools:background="@color/accent_material_light"> + android:padding="@dimen/timepicker_separator_padding"> <!-- The hour should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -37,10 +35,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" - tools:text="23" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="right" /> <TextView android:id="@+id/separator" @@ -50,10 +45,7 @@ android:layout_marginRight="@dimen/timepicker_separator_padding" android:layout_centerInParent="true" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" - tools:text=":" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:importantForAccessibility="no" /> <!-- The minutes should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -66,10 +58,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" - tools:text="59" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="left" /> <!-- The layout alignment of this view will switch between toRightOf @id/minutes and toLeftOf @id/hours depending on the locale. --> @@ -90,10 +79,7 @@ android:paddingTop="@dimen/timepicker_am_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" - android:ellipsize="none" - tools:text="AM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> <CheckedTextView android:id="@+id/pm_label" android:layout_width="wrap_content" @@ -103,9 +89,6 @@ android:paddingTop="@dimen/timepicker_pm_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" - android:ellipsize="none" - tools:text="PM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/transition/popup_window_enter.xml b/core/res/res/transition/popup_window_enter.xml index 38c41f0..c4c8dac 100644 --- a/core/res/res/transition/popup_window_enter.xml +++ b/core/res/res/transition/popup_window_enter.xml @@ -16,17 +16,14 @@ <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="together"> - <!-- Start from location of epicenter, move to popup location. --> - <transition - class="com.android.internal.transition.EpicenterTranslate" - android:duration="300" /> - <!-- Start from size of epicenter, expand to full width/height. --> <transition - class="com.android.internal.transition.EpicenterClipReveal" - android:centerClipBounds="true" - android:duration="300" /> + class="com.android.internal.transition.EpicenterTranslateClipReveal" + android:duration="250" /> <!-- Quickly fade in. --> - <fade android:duration="100" /> + <fade + android:duration="100" + android:fromAlpha="0.1" + android:toAlpha="1.0" /> </transitionSet> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 1c4b5f7..eaa6278 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5877,16 +5877,9 @@ </declare-styleable> <!-- @hide For internal use only. Use only as directed. --> - <declare-styleable name="EpicenterClipReveal"> - <attr name="centerClipBounds" format="boolean" /> + <declare-styleable name="EpicenterTranslateClipReveal"> <attr name="interpolatorX" format="reference" /> <attr name="interpolatorY" format="reference" /> - </declare-styleable> - - <!-- @hide For internal use only. Use only as directed. --> - <declare-styleable name="EpicenterTranslate"> - <attr name="interpolatorX" /> - <attr name="interpolatorY" /> <attr name="interpolatorZ" format="reference" /> </declare-styleable> diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java deleted file mode 100644 index f479e2b..0000000 --- a/core/tests/coretests/src/android/util/FloatMathTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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 junit.framework.TestCase; -import android.test.suitebuilder.annotation.SmallTest; - -public class FloatMathTest extends TestCase { - - @SmallTest - public void testSqrt() { - assertEquals(7, FloatMath.sqrt(49), 0); - assertEquals(10, FloatMath.sqrt(100), 0); - assertEquals(0, FloatMath.sqrt(0), 0); - assertEquals(1, FloatMath.sqrt(1), 0); - } - - @SmallTest - public void testFloor() { - assertEquals(78, FloatMath.floor(78.89f), 0); - assertEquals(-79, FloatMath.floor(-78.89f), 0); - } - - @SmallTest - public void testCeil() { - assertEquals(79, FloatMath.ceil(78.89f), 0); - assertEquals(-78, FloatMath.ceil(-78.89f), 0); - } - - @SmallTest - public void testSin() { - assertEquals(0.0, FloatMath.sin(0), 0); - assertEquals(0.8414709848078965f, FloatMath.sin(1), 0); - } - - @SmallTest - public void testCos() { - assertEquals(1.0f, FloatMath.cos(0), 0); - assertEquals(0.5403023058681398f, FloatMath.cos(1), 0); - } -} diff --git a/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java new file mode 100644 index 0000000..c53f4cc --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.util; + +import junit.framework.TestCase; + +import org.junit.Test; + +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class CallbackRegistryTest extends TestCase { + + final Integer callback1 = 1; + final Integer callback2 = 2; + final Integer callback3 = 3; + CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry; + int notify1; + int notify2; + int notify3; + int[] deepNotifyCount = new int[300]; + Integer argValue; + + private void addNotifyCount(Integer callback) { + if (callback == callback1) { + notify1++; + } else if (callback == callback2) { + notify2++; + } else if (callback == callback3) { + notify3++; + } + deepNotifyCount[callback]++; + } + + public void testAddListener() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + Integer callback = 0; + + assertNotNull(registry.copyListeners()); + assertEquals(0, registry.copyListeners().size()); + + registry.add(callback); + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + + registry.add(callback); + callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + + Integer otherListener = 1; + registry.add(otherListener); + callbacks = registry.copyListeners(); + assertEquals(2, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + assertEquals(otherListener, callbacks.get(1)); + + registry.remove(callback); + registry.add(callback); + callbacks = registry.copyListeners(); + assertEquals(2, callbacks.size()); + assertEquals(callback, callbacks.get(1)); + assertEquals(otherListener, callbacks.get(0)); + } + + public void testSimpleNotify() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + assertEquals(arg1, (int) arg); + addNotifyCount(callback); + argValue = arg; + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback2); + Integer arg = 1; + registry.notifyCallbacks(this, arg, arg); + assertEquals(arg, argValue); + assertEquals(1, notify2); + } + + public void testRemoveWhileNotifying() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + if (callback == callback1) { + registry.remove(callback1); + registry.remove(callback2); + } + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + assertEquals(1, notify1); + assertEquals(1, notify2); + assertEquals(1, notify3); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback3, callbacks.get(0)); + } + + public void testDeepRemoveWhileNotifying() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.remove(callback); + registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + assertEquals(1, notify1); + assertEquals(2, notify2); + assertEquals(3, notify3); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(0, callbacks.size()); + } + + public void testAddRemovedListener() { + + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + if (callback == callback1) { + registry.remove(callback2); + } else if (callback == callback3) { + registry.add(callback2); + } + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(3, callbacks.size()); + assertEquals(callback1, callbacks.get(0)); + assertEquals(callback3, callbacks.get(1)); + assertEquals(callback2, callbacks.get(2)); + assertEquals(1, notify1); + assertEquals(1, notify2); + assertEquals(1, notify3); + } + + public void testVeryDeepRemoveWhileNotifying() { + final Integer[] callbacks = new Integer[deepNotifyCount.length]; + for (int i = 0; i < callbacks.length; i++) { + callbacks[i] = i; + } + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.remove(callback); + registry.remove(callbacks[callbacks.length - callback - 1]); + registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < callbacks.length; i++) { + registry.add(callbacks[i]); + } + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + int expectedCount = Math.min(i + 1, deepNotifyCount.length - i); + assertEquals(expectedCount, deepNotifyCount[i]); + } + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + } + + public void testClear() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < deepNotifyCount.length; i++) { + registry.add(i); + } + registry.clear(); + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + assertEquals(0, deepNotifyCount[i]); + } + } + + public void testNestedClear() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.clear(); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < deepNotifyCount.length; i++) { + registry.add(i); + } + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + assertEquals(1, deepNotifyCount[i]); + } + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + } + + public void testIsEmpty() throws Exception { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + Integer callback = 0; + + assertTrue(registry.isEmpty()); + registry.add(callback); + assertFalse(registry.isEmpty()); + } + + public void testClone() throws Exception { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + + assertTrue(registry.isEmpty()); + CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry2 = registry.clone(); + Integer callback = 0; + registry.add(callback); + assertFalse(registry.isEmpty()); + assertTrue(registry2.isEmpty()); + registry2 = registry.clone(); + assertFalse(registry2.isEmpty()); + } +} diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js index b28f978..8538671 100644 --- a/docs/html/jd_collections.js +++ b/docs/html/jd_collections.js @@ -1221,6 +1221,14 @@ var RESOURCE_COLLECTIONS = { "https://support.google.com/googleplay/answer/2651410" ] }, + "preview/landing/resources": { + "title": "", + "resources": [ + "preview/api-overview.html", + "preview/setup-sdk.html", + "preview/samples.html" + ] + }, "autolanding": { "title": "", "resources": [ diff --git a/docs/html/jd_tag_helpers.js b/docs/html/jd_tag_helpers.js index 7538e4d..f03b1d7 100644 --- a/docs/html/jd_tag_helpers.js +++ b/docs/html/jd_tag_helpers.js @@ -13,6 +13,7 @@ var ALL_RESOURCES = mergeArrays( GOOGLE_RESOURCES, GUIDE_RESOURCES, SAMPLES_RESOURCES, + PREVIEW_RESOURCES, TOOLS_RESOURCES, TRAINING_RESOURCES, YOUTUBE_RESOURCES, @@ -70,6 +71,7 @@ var ALL_RESOURCES_BY_TYPE = { 'google': GOOGLE_RESOURCES, 'guide': GUIDE_RESOURCES, 'samples': SAMPLES_RESOURCES, + 'preview': PREVIEW_RESOURCES, 'tools': TOOLS_RESOURCES, 'training': TRAINING_RESOURCES, 'youtube': YOUTUBE_RESOURCES, @@ -86,6 +88,7 @@ var ALL_RESOURCES_BY_TAG = mergeMaps( {map:GOOGLE_BY_TAG,arr:GOOGLE_RESOURCES}, {map:GUIDE_BY_TAG,arr:GUIDE_RESOURCES}, {map:SAMPLES_BY_TAG,arr:SAMPLES_RESOURCES}, + {map:PREVIEW_BY_TAG,arr:PREVIEW_RESOURCES}, {map:TOOLS_BY_TAG,arr:TOOLS_RESOURCES}, {map:TRAINING_BY_TAG,arr:TRAINING_RESOURCES}, {map:YOUTUBE_BY_TAG,arr:YOUTUBE_RESOURCES}, diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd index f72ffbb..dde3c7b 100644 --- a/docs/html/preview/api-overview.jd +++ b/docs/html/preview/api-overview.jd @@ -1,5 +1,5 @@ page.title=API Overview -excludeFromSuggestions=true +page.keywords=preview,sdk,compatibility sdk.platform.apiLevel=22 @jd:body diff --git a/docs/html/preview/index.jd b/docs/html/preview/index.jd new file mode 100644 index 0000000..a2d0b18 --- /dev/null +++ b/docs/html/preview/index.jd @@ -0,0 +1,60 @@ +page.title=M Developer Preview +page.tags=preview +meta.tags="preview" +fullpage=true +page.viewport_width=970 +section.landing=true +header.hide=1 +footer.hide=1 +@jd:body + +<section class="dac-expand dac-hero dac-light"> + <div class="wrap"> + <div class="cols dac-hero-content"> + <div class="col-1of2 col-push-1of2 dac-hero-figure"> + <img class="dac-hero-image" src="/design/media/hero-material-design.png"> + </div> + <div class="col-1of2 col-pull-1of2"> + <h1 class="dac-hero-title">M Developer Preview</h1> + <p class="dac-hero-description"> + Get ready for the next official release of the platform. Test your apps + and give us feedback! + </p> + <a class="dac-hero-cta" href="{@docRoot}preview/setup-sdk.html"> + <span class="dac-sprite dac-auto-chevron"></span> + Set up the Preview SDK + </a><br> + <a class="dac-hero-cta" href="{@docRoot}preview/api-overview.html"> + <span class="dac-sprite dac-auto-chevron"></span> + Review the API changes + </a><br> + <a class="dac-hero-cta" href="https://code.google.com/p/android-developer-preview/"> + <span class="dac-sprite dac-auto-chevron"></span> + Report issues + </a><br> + </div> + </div> + </div> +</section> + +<section class="dac-section dac-gray dac-small dac-invert"><div class="wrap"> + <h2 class="norule">Latest</h2> + <div class="resource-widget resource-flow-layout col-16" + data-query="collection:develop/landing/latest" + data-cardSizes="6x6" + data-maxResults="3"></div> +</div></section> + + +<section class="dac-section"><div class="wrap"> + <h1 class="dac-section-title">Resources</h1> + <div class="dac-section-subtitle"> + Check out these resources to help you get started with the M Developer Preview. + </div> + <div class="resource-widget resource-flow-layout col-16" + data-query="collection:preview/landing/resources" + data-cardSizes="6x6" + data-maxResults="6"></div> +</div></section> + + diff --git a/docs/html/preview/overview.jd b/docs/html/preview/overview.jd new file mode 100644 index 0000000..00f1cfe --- /dev/null +++ b/docs/html/preview/overview.jd @@ -0,0 +1,7 @@ +page.title=Preview Program Overview + +@jd:body + +<p> + This is an overview of the program. Bacon. +</p>
\ No newline at end of file diff --git a/docs/html/preview/preview_toc.cs b/docs/html/preview/preview_toc.cs index bea4914..fbf73f6 100644 --- a/docs/html/preview/preview_toc.cs +++ b/docs/html/preview/preview_toc.cs @@ -1,6 +1,11 @@ <ul id="nav"> <li class="nav-section"> + <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/overview.html"> + Program Overview</a></div> + </li> + + <li class="nav-section"> <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/setup-sdk.html"> Set up the SDK</a></div> </li> @@ -29,9 +34,4 @@ License Agreement</a></div> </li> - <li class="nav-section" style="margin: 20px 0 0 -10px;"> - <div class="nav-section-header empty"><a href="<?cs var:toroot ?>index.html" class="back-link"> - Developer Home</a></div> - </li> - </ul> diff --git a/docs/html/preview/reference.jd b/docs/html/preview/reference.jd index ee1f24d..2d30c62 100644 --- a/docs/html/preview/reference.jd +++ b/docs/html/preview/reference.jd @@ -9,7 +9,7 @@ page.title=Reference <ul> <li> - <a href="http://storage.googleapis.com/androiddevelopers/preview/l-developer-preview-reference.zip"> + <a href="http://storage.googleapis.com/androiddevelopers/preview/m-developer-preview-reference.zip"> M Developer Preview reference</a> </li> </ul>
\ No newline at end of file diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java index 3b25ba6..3f29c6a 100644 --- a/keystore/java/android/security/AndroidKeyPairGenerator.java +++ b/keystore/java/android/security/AndroidKeyPairGenerator.java @@ -54,13 +54,13 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { public static class RSA extends AndroidKeyPairGenerator { public RSA() { - super("RSA"); + super(KeyStoreKeyProperties.Algorithm.RSA); } } public static class EC extends AndroidKeyPairGenerator { public EC() { - super("EC"); + super(KeyStoreKeyProperties.Algorithm.EC); } } @@ -83,15 +83,15 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { private android.security.KeyStore mKeyStore; private KeyPairGeneratorSpec mSpec; - private String mKeyAlgorithm; + private @KeyStoreKeyProperties.AlgorithmEnum String mKeyAlgorithm; private int mKeyType; private int mKeySize; - protected AndroidKeyPairGenerator(String algorithm) { + protected AndroidKeyPairGenerator(@KeyStoreKeyProperties.AlgorithmEnum String algorithm) { mAlgorithm = algorithm; } - public String getAlgorithm() { + public @KeyStoreKeyProperties.AlgorithmEnum String getAlgorithm() { return mAlgorithm; } @@ -197,7 +197,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { return certGen.generate(privateKey); } - private String getKeyAlgorithm(KeyPairGeneratorSpec spec) { + private @KeyStoreKeyProperties.AlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { String result = spec.getKeyType(); if (result != null) { return result; @@ -248,10 +248,11 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } } - private static String getDefaultSignatureAlgorithmForKeyAlgorithm(String algorithm) { - if ("RSA".equalsIgnoreCase(algorithm)) { + private static String getDefaultSignatureAlgorithmForKeyAlgorithm( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { + if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(algorithm)) { return "sha256WithRSA"; - } else if ("EC".equalsIgnoreCase(algorithm)) { + } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(algorithm)) { return "sha256WithECDSA"; } else { throw new IllegalArgumentException("Unsupported key type " + algorithm); @@ -287,7 +288,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) params; - String keyAlgorithm = getKeyAlgorithm(spec); + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithm = getKeyAlgorithm(spec); int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); if (keyType == -1) { throw new InvalidAlgorithmParameterException( diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index 72cb062..e82ff6a 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -128,10 +128,11 @@ public class AndroidKeyStore extends KeyStoreSpi { keymasterDigest = keymasterDigests.get(0); } - String keyAlgorithmString; + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithmString; try { - keyAlgorithmString = KeymasterUtils.getJcaSecretKeyAlgorithm( - keymasterAlgorithm, keymasterDigest); + keyAlgorithmString = + KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + keymasterAlgorithm, keymasterDigest); } catch (IllegalArgumentException e) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Unsupported secret key type").initCause(e); @@ -451,10 +452,10 @@ public class AndroidKeyStore extends KeyStoreSpi { int keymasterAlgorithm; int keymasterDigest; try { - keymasterAlgorithm = KeymasterUtils.getKeymasterAlgorithmFromJcaSecretKeyAlgorithm( + keymasterAlgorithm = KeyStoreKeyProperties.Algorithm.toKeymasterSecretKeyAlgorithm( keyAlgorithmString); keymasterDigest = - KeymasterUtils.getKeymasterDigestfromJcaSecretKeyAlgorithm(keyAlgorithmString); + KeyStoreKeyProperties.Algorithm.toKeymasterDigest(keyAlgorithmString); } catch (IllegalArgumentException e) { throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); } @@ -465,8 +466,7 @@ public class AndroidKeyStore extends KeyStoreSpi { int[] keymasterDigests; if (params.isDigestsSpecified()) { // Digest(s) specified in parameters - keymasterDigests = - KeymasterUtils.getKeymasterDigestsFromJcaDigestAlgorithms(params.getDigests()); + keymasterDigests = KeyStoreKeyProperties.Digest.allToKeymaster(params.getDigests()); if (keymasterDigest != -1) { // Digest also specified in the JCA key algorithm name. if (!com.android.internal.util.ArrayUtils.contains( @@ -494,8 +494,8 @@ public class AndroidKeyStore extends KeyStoreSpi { } @KeyStoreKeyProperties.PurposeEnum int purposes = params.getPurposes(); - int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( - params.getBlockModes()); + int[] keymasterBlockModes = + KeyStoreKeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) && (params.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : keymasterBlockModes) { @@ -503,8 +503,7 @@ public class AndroidKeyStore extends KeyStoreSpi { throw new KeyStoreException( "Randomized encryption (IND-CPA) required but may be violated by block" + " mode: " - + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( - keymasterBlockMode) + + KeyStoreKeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + ". See KeyStoreParameter documentation."); } } @@ -513,11 +512,11 @@ public class AndroidKeyStore extends KeyStoreSpi { args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); } args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); - int[] keymasterPaddings = ArrayUtils.concat( - KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( - params.getEncryptionPaddings()), - KeymasterUtils.getKeymasterPaddingsFromJcaSignaturePaddings( - params.getSignaturePaddings())); + if (params.getSignaturePaddings().length > 0) { + throw new KeyStoreException("Signature paddings not supported for symmetric keys"); + } + int[] keymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + params.getEncryptionPaddings()); args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); KeymasterUtils.addUserAuthArgs(args, params.getContext(), diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index e9c24dd..8e27dc3 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -262,7 +262,8 @@ public final class KeyChain { * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - String[] keyTypes, Principal[] issuers, String host, int port, String alias) { + @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, + String host, int port, String alias) { choosePrivateKeyAlias(activity, response, keyTypes, issuers, host, port, null, alias); } @@ -306,9 +307,8 @@ public final class KeyChain { * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - String[] keyTypes, Principal[] issuers, - String host, int port, String url, - String alias) { + @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, + String host, int port, String url, String alias) { /* * TODO currently keyTypes, issuers are unused. They are meant * to follow the semantics and purpose of X509KeyManager @@ -431,9 +431,11 @@ public final class KeyChain { * specific {@code PrivateKey} type indicated by {@code algorithm} (e.g., * "RSA"). */ - public static boolean isKeyAlgorithmSupported(String algorithm) { + public static boolean isKeyAlgorithmSupported( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { final String algUpper = algorithm.toUpperCase(Locale.US); - return "EC".equals(algUpper) || "RSA".equals(algUpper); + return KeyStoreKeyProperties.Algorithm.EC.equals(algUpper) + || KeyStoreKeyProperties.Algorithm.RSA.equals(algUpper); } /** @@ -443,7 +445,8 @@ public final class KeyChain { * hardware support that can be used to bind keys to the device in a way * that makes it non-exportable. */ - public static boolean isBoundKeyAlgorithm(String algorithm) { + public static boolean isBoundKeyAlgorithm( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { if (!isKeyAlgorithmSupported(algorithm)) { return false; } diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java index 8f135a6..729646d 100644 --- a/keystore/java/android/security/KeyGeneratorSpec.java +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -48,8 +48,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; @@ -63,8 +63,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -160,14 +160,14 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -195,10 +195,12 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. + * + * @see #isUserAuthenticationRequired() */ public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; @@ -220,8 +222,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mEncryptionPaddings; - private String[] mBlockModes; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; @@ -346,7 +348,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for keys which are used for encryption/decryption. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -357,7 +360,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for encryption/decryption keys. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -425,7 +428,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the user needs to authenticate for every use of the key. * - * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index d6d3789..9dde386 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -85,13 +85,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mDigests; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private final String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; @@ -138,10 +138,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] digests, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] blockModes, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -246,7 +246,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Returns the key type (e.g., "EC", "RSA") specified by this parameter. */ - public String getKeyType() { + public @KeyStoreKeyProperties.AlgorithmEnum String getKeyType() { return mKeyType; } @@ -352,28 +352,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the set of digest algorithms with which the key can be used. */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of padding schemes with which the key can be used when signing/verifying. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -403,14 +403,14 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Gets the duration of time (seconds) for which the private key can be used after the user - * is successfully authenticated. + * Gets the duration of time (seconds) for which this key can be used after the user is + * successfully authenticated. This has effect only if user authentication is required. * * <p>This restriction applies only to private key operations. Public key operations are not * restricted. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * * @see #isUserAuthenticationRequired() */ @@ -468,13 +468,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mDigests; + private @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private String[] mSignaturePaddings; + private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private String[] mBlockModes; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; @@ -511,7 +511,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Sets the key type (e.g., EC, RSA) of the keypair to be created. */ - public Builder setKeyType(String keyType) throws NoSuchAlgorithmException { + public Builder setKeyType(@KeyStoreKeyProperties.AlgorithmEnum String keyType) + throws NoSuchAlgorithmException { if (keyType == null) { throw new NullPointerException("keyType == null"); } else { @@ -691,7 +692,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for keys which are used for signing/verification. */ - public Builder setDigests(String... digests) { + public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { mDigests = ArrayUtils.cloneIfNotEmpty(digests); return this; } @@ -703,7 +704,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for keys which are used for encryption/decryption. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -715,7 +717,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for RSA keys which are used for signing/verification. */ - public Builder setSignaturePaddings(String... paddings) { + public Builder setSignaturePaddings( + @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -726,7 +729,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for encryption/decryption keys. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -788,7 +791,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <p>This restriction applies only to private key operations. Public key operations are not * restricted. * - * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 82d328b..304d277 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -115,10 +115,10 @@ public class KeyStore { return mToken; } - static int getKeyTypeForAlgorithm(String keyType) { - if ("RSA".equalsIgnoreCase(keyType)) { + static int getKeyTypeForAlgorithm(@KeyStoreKeyProperties.AlgorithmEnum String keyType) { + if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_RSA; - } else if ("EC".equalsIgnoreCase(keyType)) { + } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_EC; } else { return -1; diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java index ed450b8..bd601bc 100644 --- a/keystore/java/android/security/KeyStoreCipherSpi.java +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -495,7 +495,8 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry } if ((mIv != null) && (mIv.length > 0)) { try { - AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + AlgorithmParameters params = + AlgorithmParameters.getInstance(KeyStoreKeyProperties.Algorithm.AES); params.init(new IvParameterSpec(mIv)); return params; } catch (NoSuchAlgorithmException e) { @@ -634,10 +635,9 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if ((mIv == null) && (mEncrypting)) { // IV was not provided by the caller and thus will be generated by keymaster. // Mix in some additional entropy from the provided SecureRandom. - if (mRng != null) { - mAdditionalEntropyForBegin = new byte[mBlockSizeBytes]; - mRng.nextBytes(mAdditionalEntropyForBegin); - } + mAdditionalEntropyForBegin = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, mBlockSizeBytes); } } } diff --git a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java index e5933ad..311278b 100644 --- a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java @@ -21,6 +21,7 @@ import android.security.keymaster.KeymasterDefs; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.SecureRandom; /** * Assorted utility methods for implementing crypto operations on top of KeyStore. @@ -28,6 +29,9 @@ import java.security.InvalidKeyException; * @hide */ abstract class KeyStoreCryptoOperationUtils { + + private static volatile SecureRandom sRng; + private KeyStoreCryptoOperationUtils() {} /** @@ -81,4 +85,28 @@ abstract class KeyStoreCryptoOperationUtils { // General cases return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode); } + + /** + * Returns the requested number of random bytes to mix into keystore/keymaster RNG. + * + * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default + * RNG. + */ + static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { + if (rng == null) { + rng = getRng(); + } + byte[] result = new byte[sizeBytes]; + rng.nextBytes(result); + return result; + } + + private static SecureRandom getRng() { + // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is + // required to be thread-safe. + if (sRng == null) { + sRng = new SecureRandom(); + } + return sRng; + } } diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java index 68b5751..c265c46 100644 --- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -129,8 +129,8 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { int keySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits); @KeyStoreKeyProperties.PurposeEnum int purposes = spec.getPurposes(); - int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( - spec.getBlockModes()); + int[] keymasterBlockModes = + KeyStoreKeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) && (spec.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : keymasterBlockModes) { @@ -138,8 +138,7 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { throw new IllegalStateException( "Randomized encryption (IND-CPA) required but may be violated by block" + " mode: " - + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( - keymasterBlockMode) + + KeyStoreKeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + ". See KeyGeneratorSpec documentation."); } } @@ -152,7 +151,7 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); args.addInts( KeymasterDefs.KM_TAG_PADDING, - KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( + KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( spec.getEncryptionPaddings())); KeymasterUtils.addUserAuthArgs(args, spec.getContext(), @@ -174,12 +173,9 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); } - byte[] additionalEntropy = null; - SecureRandom rng = mRng; - if (rng != null) { - additionalEntropy = new byte[(keySizeBits + 7) / 8]; - rng.nextBytes(additionalEntropy); - } + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (keySizeBits + 7) / 8); int flags = spec.getFlags(); String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); @@ -189,8 +185,9 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { throw new IllegalStateException( "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); } - String keyAlgorithmJCA = - KeymasterUtils.getJcaSecretKeyAlgorithm(mKeymasterAlgorithm, mKeymasterDigest); + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithmJCA = + KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); } diff --git a/keystore/java/android/security/KeyStoreKeyProperties.java b/keystore/java/android/security/KeyStoreKeyProperties.java index b85ec53..1c3e300 100644 --- a/keystore/java/android/security/KeyStoreKeyProperties.java +++ b/keystore/java/android/security/KeyStoreKeyProperties.java @@ -17,13 +17,23 @@ package android.security; import android.annotation.IntDef; +import android.annotation.StringDef; import android.security.keymaster.KeymasterDefs; import libcore.util.EmptyArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; import java.util.Collection; +import java.util.Locale; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKeyFactory; /** * Properties of {@code AndroidKeyStore} keys. @@ -37,7 +47,7 @@ public abstract class KeyStoreKeyProperties { public @interface PurposeEnum {} /** - * Purpose of key. + * Purposes of key. */ public static abstract class Purpose { private Purpose() {} @@ -122,6 +132,514 @@ public abstract class KeyStoreKeyProperties { } @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Algorithm.RSA, + Algorithm.EC, + Algorithm.AES, + Algorithm.HMAC_SHA1, + Algorithm.HMAC_SHA224, + Algorithm.HMAC_SHA256, + Algorithm.HMAC_SHA384, + Algorithm.HMAC_SHA512, + }) + public @interface AlgorithmEnum {} + + /** + * Key algorithms. + * + * <p>These are standard names which can be used to obtain instances of {@link KeyGenerator}, + * {@link KeyPairGenerator}, {@link Cipher} (as part of the transformation string), {@link Mac}, + * {@link KeyFactory}, {@link SecretKeyFactory}. These are also the names used by + * {@link Key#getAlgorithm()}. + */ + public static abstract class Algorithm { + private Algorithm() {} + + /** Rivest Shamir Adleman (RSA) key. */ + public static final String RSA = "RSA"; + + /** Elliptic Curve (EC) key. */ + public static final String EC = "EC"; + + /** Advanced Encryption Standard (AES) key. */ + public static final String AES = "AES"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */ + public static final String HMAC_SHA1 = "HmacSHA1"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */ + public static final String HMAC_SHA224 = "HmacSHA224"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */ + public static final String HMAC_SHA256 = "HmacSHA256"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */ + public static final String HMAC_SHA384 = "HmacSHA384"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */ + public static final String HMAC_SHA512 = "HmacSHA512"; + + /** + * @hide + */ + static int toKeymasterSecretKeyAlgorithm(@AlgorithmEnum String algorithm) { + if (AES.equalsIgnoreCase(algorithm)) { + return KeymasterDefs.KM_ALGORITHM_AES; + } else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) { + return KeymasterDefs.KM_ALGORITHM_HMAC; + } else { + throw new IllegalArgumentException( + "Unsupported secret key algorithm: " + algorithm); + } + } + + /** + * @hide + */ + static @AlgorithmEnum String fromKeymasterSecretKeyAlgorithm( + int keymasterAlgorithm, int keymasterDigest) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_AES: + if (keymasterDigest != -1) { + throw new IllegalArgumentException("Digest not supported for AES key: " + + Digest.fromKeymaster(keymasterDigest)); + } + return AES; + case KeymasterDefs.KM_ALGORITHM_HMAC: + switch (keymasterDigest) { + case KeymasterDefs.KM_DIGEST_SHA1: + return HMAC_SHA1; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return HMAC_SHA224; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return HMAC_SHA256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return HMAC_SHA384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return HMAC_SHA512; + default: + throw new IllegalArgumentException("Unsupported HMAC digest: " + + Digest.fromKeymaster(keymasterDigest)); + } + default: + throw new IllegalArgumentException( + "Unsupported algorithm: " + keymasterAlgorithm); + } + } + + /** + * @hide + * + * @return keymaster digest or {@code -1} if the algorithm does not involve a digest. + */ + static int toKeymasterDigest(@AlgorithmEnum String algorithm) { + String algorithmUpper = algorithm.toUpperCase(Locale.US); + if (algorithmUpper.startsWith("HMAC")) { + String digestUpper = algorithmUpper.substring("HMAC".length()); + switch (digestUpper) { + case "SHA1": + return KeymasterDefs.KM_DIGEST_SHA1; + case "SHA224": + return KeymasterDefs.KM_DIGEST_SHA_2_224; + case "SHA256": + return KeymasterDefs.KM_DIGEST_SHA_2_256; + case "SHA384": + return KeymasterDefs.KM_DIGEST_SHA_2_384; + case "SHA512": + return KeymasterDefs.KM_DIGEST_SHA_2_512; + default: + throw new IllegalArgumentException( + "Unsupported HMAC digest: " + digestUpper); + } + } else { + return -1; + } + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + BlockMode.ECB, + BlockMode.CBC, + BlockMode.CTR, + BlockMode.GCM, + }) + public @interface BlockModeEnum {} + + /** + * Block modes that can be used when encrypting/decrypting using a key. + */ + public static abstract class BlockMode { + private BlockMode() {} + + /** Electronic Codebook (ECB) block mode. */ + public static final String ECB = "ECB"; + + /** Cipher Block Chaining (CBC) block mode. */ + public static final String CBC = "CBC"; + + /** Counter (CTR) block mode. */ + public static final String CTR = "CTR"; + + /** Galois/Counter Mode (GCM) block mode. */ + public static final String GCM = "GCM"; + + /** + * @hide + */ + static int toKeymaster(@BlockModeEnum String blockMode) { + if (ECB.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_ECB; + } else if (CBC.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_CBC; + } else if (CTR.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_CTR; + } else if (GCM.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_GCM; + } else { + throw new IllegalArgumentException("Unsupported block mode: " + blockMode); + } + } + + /** + * @hide + */ + static @BlockModeEnum String fromKeymaster(int blockMode) { + switch (blockMode) { + case KeymasterDefs.KM_MODE_ECB: + return ECB; + case KeymasterDefs.KM_MODE_CBC: + return CBC; + case KeymasterDefs.KM_MODE_CTR: + return CTR; + case KeymasterDefs.KM_MODE_GCM: + return GCM; + default: + throw new IllegalArgumentException("Unsupported block mode: " + blockMode); + } + } + + /** + * @hide + */ + static @BlockModeEnum String[] allFromKeymaster(Collection<Integer> blockModes) { + if ((blockModes == null) || (blockModes.isEmpty())) { + return EmptyArray.STRING; + } + @BlockModeEnum String[] result = new String[blockModes.size()]; + int offset = 0; + for (int blockMode : blockModes) { + result[offset] = fromKeymaster(blockMode); + offset++; + } + return result; + } + + /** + * @hide + */ + static int[] allToKeymaster(@BlockModeEnum String[] blockModes) { + if ((blockModes == null) || (blockModes.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[blockModes.length]; + for (int i = 0; i < blockModes.length; i++) { + result[i] = toKeymaster(blockModes[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + EncryptionPadding.NONE, + EncryptionPadding.PKCS7, + EncryptionPadding.RSA_PKCS1, + EncryptionPadding.RSA_OAEP, + }) + public @interface EncryptionPaddingEnum {} + + /** + * Padding schemes for encryption/decryption. + */ + public static abstract class EncryptionPadding { + private EncryptionPadding() {} + + /** + * No padding. + */ + public static final String NONE = "NoPadding"; + + /** + * PKCS#7 padding. + */ + public static final String PKCS7 = "PKCS7Padding"; + + /** + * RSA PKCS#1 v1.5 padding for encryption/decryption. + */ + public static final String RSA_PKCS1 = "PKCS1Padding"; + + /** + * RSA Optimal Asymmetric Encryption Padding (OAEP). + */ + public static final String RSA_OAEP = "OAEPPadding"; + + /** + * @hide + */ + static int toKeymaster(@EncryptionPaddingEnum String padding) { + if (NONE.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_NONE; + } else if (PKCS7.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_PKCS7; + } else if (RSA_PKCS1.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; + } else if (RSA_OAEP.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_RSA_OAEP; + } else { + throw new IllegalArgumentException( + "Unsupported encryption padding scheme: " + padding); + } + } + + /** + * @hide + */ + static @EncryptionPaddingEnum String fromKeymaster(int padding) { + switch (padding) { + case KeymasterDefs.KM_PAD_NONE: + return NONE; + case KeymasterDefs.KM_PAD_PKCS7: + return PKCS7; + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: + return RSA_PKCS1; + case KeymasterDefs.KM_PAD_RSA_OAEP: + return RSA_OAEP; + default: + throw new IllegalArgumentException( + "Unsupported encryption padding: " + padding); + } + } + + /** + * @hide + */ + static int[] allToKeymaster(@EncryptionPaddingEnum String[] paddings) { + if ((paddings == null) || (paddings.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[paddings.length]; + for (int i = 0; i < paddings.length; i++) { + result[i] = toKeymaster(paddings[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + SignaturePadding.RSA_PKCS1, + SignaturePadding.RSA_PSS, + }) + public @interface SignaturePaddingEnum {} + + /** + * Padding schemes for signing/verification. + */ + public static abstract class SignaturePadding { + private SignaturePadding() {} + + /** + * RSA PKCS#1 v1.5 padding for signatures. + */ + public static final String RSA_PKCS1 = "PKCS1"; + + /** + * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding. + */ + public static final String RSA_PSS = "PSS"; + + /** + * @hide + */ + static int toKeymaster(@SignaturePaddingEnum String padding) { + switch (padding.toUpperCase(Locale.US)) { + case RSA_PKCS1: + return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; + case RSA_PSS: + return KeymasterDefs.KM_PAD_RSA_PSS; + default: + throw new IllegalArgumentException( + "Unsupported signature padding scheme: " + padding); + } + } + + /** + * @hide + */ + static @SignaturePaddingEnum String fromKeymaster(int padding) { + switch (padding) { + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: + return RSA_PKCS1; + case KeymasterDefs.KM_PAD_RSA_PSS: + return RSA_PSS; + default: + throw new IllegalArgumentException("Unsupported signature padding: " + padding); + } + } + + /** + * @hide + */ + static int[] allToKeymaster(@SignaturePaddingEnum String[] paddings) { + if ((paddings == null) || (paddings.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[paddings.length]; + for (int i = 0; i < paddings.length; i++) { + result[i] = toKeymaster(paddings[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Digest.NONE, + Digest.MD5, + Digest.SHA1, + Digest.SHA224, + Digest.SHA256, + Digest.SHA384, + Digest.SHA512, + }) + public @interface DigestEnum {} + + /** + * Digests that can be used with a key when signing or generating Message Authentication + * Codes (MACs). + */ + public static abstract class Digest { + private Digest() {} + + /** + * No digest: sign/authenticate the raw message. + */ + public static final String NONE = "NONE"; + + /** + * MD5 digest. + */ + public static final String MD5 = "MD5"; + + /** + * SHA-1 digest. + */ + public static final String SHA1 = "SHA-1"; + + /** + * SHA-2 224 (aka SHA-224) digest. + */ + public static final String SHA224 = "SHA-224"; + + /** + * SHA-2 256 (aka SHA-256) digest. + */ + public static final String SHA256 = "SHA-256"; + + /** + * SHA-2 384 (aka SHA-384) digest. + */ + public static final String SHA384 = "SHA-384"; + + /** + * SHA-2 512 (aka SHA-512) digest. + */ + public static final String SHA512 = "SHA-512"; + + /** + * @hide + */ + static int toKeymaster(@DigestEnum String digest) { + switch (digest.toUpperCase(Locale.US)) { + case SHA1: + return KeymasterDefs.KM_DIGEST_SHA1; + case SHA224: + return KeymasterDefs.KM_DIGEST_SHA_2_224; + case SHA256: + return KeymasterDefs.KM_DIGEST_SHA_2_256; + case SHA384: + return KeymasterDefs.KM_DIGEST_SHA_2_384; + case SHA512: + return KeymasterDefs.KM_DIGEST_SHA_2_512; + case NONE: + return KeymasterDefs.KM_DIGEST_NONE; + case MD5: + return KeymasterDefs.KM_DIGEST_MD5; + default: + throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); + } + } + + /** + * @hide + */ + static @DigestEnum String fromKeymaster(int digest) { + switch (digest) { + case KeymasterDefs.KM_DIGEST_NONE: + return NONE; + case KeymasterDefs.KM_DIGEST_MD5: + return MD5; + case KeymasterDefs.KM_DIGEST_SHA1: + return SHA1; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return SHA224; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return SHA256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return SHA384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return SHA512; + default: + throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); + } + } + + /** + * @hide + */ + static @DigestEnum String[] allFromKeymaster(Collection<Integer> digests) { + if (digests.isEmpty()) { + return EmptyArray.STRING; + } + String[] result = new String[digests.size()]; + int offset = 0; + for (int digest : digests) { + result[offset] = fromKeymaster(digest); + offset++; + } + return result; + } + + /** + * @hide + */ + static int[] allToKeymaster(@DigestEnum String[] digests) { + if ((digests == null) || (digests.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[digests.length]; + int offset = 0; + for (@DigestEnum String digest : digests) { + result[offset] = toKeymaster(digest); + offset++; + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) @IntDef({Origin.GENERATED, Origin.IMPORTED, Origin.UNKNOWN}) public @interface OriginEnum {} diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java index 96d58d8..7533bdc 100644 --- a/keystore/java/android/security/KeyStoreKeySpec.java +++ b/keystore/java/android/security/KeyStoreKeySpec.java @@ -32,10 +32,10 @@ public class KeyStoreKeySpec implements KeySpec { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; - private final String[] mDigests; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; private final boolean mUserAuthenticationRequirementTeeEnforced; @@ -51,10 +51,10 @@ public class KeyStoreKeySpec implements KeySpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] digests, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, boolean userAuthenticationRequirementTeeEnforced) { @@ -143,28 +143,28 @@ public class KeyStoreKeySpec implements KeySpec { /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } /** * Gets the set of padding modes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of padding modes with which the key can be used when signing/verifying. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** * Gets the set of digest algorithms with which the key can be used. */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } @@ -179,10 +179,10 @@ public class KeyStoreKeySpec implements KeySpec { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * * @see #isUserAuthenticationRequired() */ diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java index b4747e9..46d60f4 100644 --- a/keystore/java/android/security/KeyStoreParameter.java +++ b/keystore/java/android/security/KeyStoreParameter.java @@ -45,10 +45,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; - private final String[] mDigests; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; @@ -60,10 +60,10 @@ public final class KeyStoreParameter implements ProtectionParameter { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] digests, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -151,7 +151,7 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } @@ -159,7 +159,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * Gets the set of padding schemes with which the key can be used when signing or verifying * signatures. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } @@ -170,7 +170,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * * @see #isDigestsSpecified() */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { if (mDigests == null) { throw new IllegalStateException("Digests not specified"); } @@ -190,7 +190,7 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -218,10 +218,12 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. + * + * @see #isUserAuthenticationRequired() */ public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; @@ -251,10 +253,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mEncryptionPaddings; - private String[] mSignaturePaddings; - private String[] mDigests; - private String[] mBlockModes; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; @@ -356,7 +358,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>This must be specified for keys which are used for encryption/decryption. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -368,7 +371,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>This must be specified for RSA keys which are used for signing/verification. */ - public Builder setSignaturePaddings(String... paddings) { + public Builder setSignaturePaddings( + @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -381,7 +385,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * <p>For HMAC keys, the default is the digest specified in {@link Key#getAlgorithm()}. For * asymmetric signing keys this constraint must be specified. */ - public Builder setDigests(String... digests) { + public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { mDigests = ArrayUtils.cloneIfNotEmpty(digests); return this; } @@ -392,7 +396,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>This must be specified for encryption/decryption keys. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -462,7 +466,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the user needs to authenticate for every use of the key. * - * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java index bfe09e3..ff79b7a 100644 --- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java @@ -79,8 +79,8 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { int keySize; @KeyStoreKeyProperties.PurposeEnum int purposes; String[] encryptionPaddings; - String[] digests; - String[] blockModes; + @KeyStoreKeyProperties.DigestEnum String[] digests; + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes; int keymasterSwEnforcedUserAuthenticators; int keymasterHwEnforcedUserAuthenticators; try { @@ -105,10 +105,10 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { List<String> encryptionPaddingsList = new ArrayList<String>(); for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) { - String jcaPadding; + @KeyStoreKeyProperties.EncryptionPaddingEnum String jcaPadding; try { - jcaPadding = KeymasterUtils.getJcaEncryptionPaddingFromKeymasterPadding( - keymasterPadding); + jcaPadding = + KeyStoreKeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); } catch (IllegalArgumentException e) { throw new InvalidKeySpecException( "Unsupported encryption padding: " + keymasterPadding); @@ -118,9 +118,9 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { encryptionPaddings = encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); - digests = KeymasterUtils.getJcaDigestAlgorithmsFromKeymasterDigests( + digests = KeyStoreKeyProperties.Digest.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST)); - blockModes = KeymasterUtils.getJcaBlockModesFromKeymasterBlockModes( + blockModes = KeyStoreKeyProperties.BlockMode.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_BLOCK_MODE)); keymasterSwEnforcedUserAuthenticators = keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java index aa44ecd..df67ae7 100644 --- a/keystore/java/android/security/KeymasterUtils.java +++ b/keystore/java/android/security/KeymasterUtils.java @@ -21,11 +21,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; -import libcore.util.EmptyArray; - -import java.util.Collection; -import java.util.Locale; - /** * @hide */ @@ -33,152 +28,6 @@ public abstract class KeymasterUtils { private KeymasterUtils() {} - public static int getKeymasterAlgorithmFromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) { - if ("AES".equalsIgnoreCase(jcaKeyAlgorithm)) { - return KeymasterDefs.KM_ALGORITHM_AES; - } else if (jcaKeyAlgorithm.toUpperCase(Locale.US).startsWith("HMAC")) { - return KeymasterDefs.KM_ALGORITHM_HMAC; - } else { - throw new IllegalArgumentException( - "Unsupported secret key algorithm: " + jcaKeyAlgorithm); - } - } - - public static String getJcaSecretKeyAlgorithm(int keymasterAlgorithm, int keymasterDigest) { - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_AES: - if (keymasterDigest != -1) { - throw new IllegalArgumentException( - "Digest not supported for AES key: " + keymasterDigest); - } - return "AES"; - case KeymasterDefs.KM_ALGORITHM_HMAC: - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_SHA1: - return "HmacSHA1"; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return "HmacSHA224"; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return "HmacSHA256"; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return "HmacSHA384"; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return "HmacSHA512"; - default: - throw new IllegalArgumentException( - "Unsupported HMAC digest: " + keymasterDigest); - } - default: - throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm); - } - } - - public static String getJcaKeyPairAlgorithmFromKeymasterAlgorithm(int keymasterAlgorithm) { - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_RSA: - return "RSA"; - case KeymasterDefs.KM_ALGORITHM_EC: - return "EC"; - default: - throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm); - } - } - - public static int getKeymasterDigestfromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) { - String algorithmUpper = jcaKeyAlgorithm.toUpperCase(Locale.US); - if (algorithmUpper.startsWith("HMAC")) { - String digestUpper = algorithmUpper.substring("HMAC".length()); - switch (digestUpper) { - case "MD5": - return KeymasterDefs.KM_DIGEST_MD5; - case "SHA1": - return KeymasterDefs.KM_DIGEST_SHA1; - case "SHA224": - return KeymasterDefs.KM_DIGEST_SHA_2_224; - case "SHA256": - return KeymasterDefs.KM_DIGEST_SHA_2_256; - case "SHA384": - return KeymasterDefs.KM_DIGEST_SHA_2_384; - case "SHA512": - return KeymasterDefs.KM_DIGEST_SHA_2_512; - default: - throw new IllegalArgumentException("Unsupported HMAC digest: " + digestUpper); - } - } else { - return -1; - } - } - - public static int getKeymasterDigestFromJcaDigestAlgorithm(String jcaDigestAlgorithm) { - if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-1")) { - return KeymasterDefs.KM_DIGEST_SHA1; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-224")) { - return KeymasterDefs.KM_DIGEST_SHA_2_224; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-256")) { - return KeymasterDefs.KM_DIGEST_SHA_2_256; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-384")) { - return KeymasterDefs.KM_DIGEST_SHA_2_384; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-512")) { - return KeymasterDefs.KM_DIGEST_SHA_2_512; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("NONE")) { - return KeymasterDefs.KM_DIGEST_NONE; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("MD5")) { - return KeymasterDefs.KM_DIGEST_MD5; - } else { - throw new IllegalArgumentException( - "Unsupported digest algorithm: " + jcaDigestAlgorithm); - } - } - - public static String getJcaDigestAlgorithmFromKeymasterDigest(int keymasterDigest) { - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_NONE: - return "NONE"; - case KeymasterDefs.KM_DIGEST_MD5: - return "MD5"; - case KeymasterDefs.KM_DIGEST_SHA1: - return "SHA-1"; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return "SHA-224"; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return "SHA-256"; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return "SHA-384"; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return "SHA-512"; - default: - throw new IllegalArgumentException( - "Unsupported digest algorithm: " + keymasterDigest); - } - } - - public static String[] getJcaDigestAlgorithmsFromKeymasterDigests( - Collection<Integer> keymasterDigests) { - if (keymasterDigests.isEmpty()) { - return EmptyArray.STRING; - } - String[] result = new String[keymasterDigests.size()]; - int offset = 0; - for (int keymasterDigest : keymasterDigests) { - result[offset] = getJcaDigestAlgorithmFromKeymasterDigest(keymasterDigest); - offset++; - } - return result; - } - - public static int[] getKeymasterDigestsFromJcaDigestAlgorithms(String[] jcaDigestAlgorithms) { - if ((jcaDigestAlgorithms == null) || (jcaDigestAlgorithms.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaDigestAlgorithms.length]; - int offset = 0; - for (String jcaDigestAlgorithm : jcaDigestAlgorithms) { - result[offset] = getKeymasterDigestFromJcaDigestAlgorithm(jcaDigestAlgorithm); - offset++; - } - return result; - } - public static int getDigestOutputSizeBits(int keymasterDigest) { switch (keymasterDigest) { case KeymasterDefs.KM_DIGEST_NONE: @@ -200,60 +49,6 @@ public abstract class KeymasterUtils { } } - public static int getKeymasterBlockModeFromJcaBlockMode(String jcaBlockMode) { - if ("ECB".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_ECB; - } else if ("CBC".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_CBC; - } else if ("CTR".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_CTR; - } else if ("GCM".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_GCM; - } else { - throw new IllegalArgumentException("Unsupported block mode: " + jcaBlockMode); - } - } - - public static String getJcaBlockModeFromKeymasterBlockMode(int keymasterBlockMode) { - switch (keymasterBlockMode) { - case KeymasterDefs.KM_MODE_ECB: - return "ECB"; - case KeymasterDefs.KM_MODE_CBC: - return "CBC"; - case KeymasterDefs.KM_MODE_CTR: - return "CTR"; - case KeymasterDefs.KM_MODE_GCM: - return "GCM"; - default: - throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode); - } - } - - public static String[] getJcaBlockModesFromKeymasterBlockModes( - Collection<Integer> keymasterBlockModes) { - if ((keymasterBlockModes == null) || (keymasterBlockModes.isEmpty())) { - return EmptyArray.STRING; - } - String[] result = new String[keymasterBlockModes.size()]; - int offset = 0; - for (int keymasterBlockMode : keymasterBlockModes) { - result[offset] = getJcaBlockModeFromKeymasterBlockMode(keymasterBlockMode); - offset++; - } - return result; - } - - public static int[] getKeymasterBlockModesFromJcaBlockModes(String[] jcaBlockModes) { - if ((jcaBlockModes == null) || (jcaBlockModes.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaBlockModes.length]; - for (int i = 0; i < jcaBlockModes.length; i++) { - result[i] = getKeymasterBlockModeFromJcaBlockMode(jcaBlockModes[i]); - } - return result; - } - public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) { switch (keymasterBlockMode) { case KeymasterDefs.KM_MODE_ECB: @@ -267,82 +62,6 @@ public abstract class KeymasterUtils { } } - public static int getKeymasterPaddingFromJcaEncryptionPadding(String jcaPadding) { - if ("NoPadding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_NONE; - } else if ("PKCS7Padding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_PKCS7; - } else if ("PKCS1Padding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; - } else if ("OEAPPadding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_OAEP; - } else { - throw new IllegalArgumentException( - "Unsupported encryption padding scheme: " + jcaPadding); - } - } - - public static String getJcaEncryptionPaddingFromKeymasterPadding(int keymasterPadding) { - switch (keymasterPadding) { - case KeymasterDefs.KM_PAD_NONE: - return "NoPadding"; - case KeymasterDefs.KM_PAD_PKCS7: - return "PKCS7Padding"; - case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: - return "PKCS1Padding"; - case KeymasterDefs.KM_PAD_RSA_OAEP: - return "OEAPPadding"; - default: - throw new IllegalArgumentException( - "Unsupported encryption padding: " + keymasterPadding); - } - } - - public static int getKeymasterPaddingFromJcaSignaturePadding(String jcaPadding) { - if ("PKCS#1".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; - } if ("PSS".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PSS; - } else { - throw new IllegalArgumentException( - "Unsupported signature padding scheme: " + jcaPadding); - } - } - - public static String getJcaSignaturePaddingFromKeymasterPadding(int keymasterPadding) { - switch (keymasterPadding) { - case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: - return "PKCS#1"; - case KeymasterDefs.KM_PAD_RSA_PSS: - return "PSS"; - default: - throw new IllegalArgumentException( - "Unsupported signature padding: " + keymasterPadding); - } - } - - public static int[] getKeymasterPaddingsFromJcaEncryptionPaddings(String[] jcaPaddings) { - if ((jcaPaddings == null) || (jcaPaddings.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaPaddings.length]; - for (int i = 0; i < jcaPaddings.length; i++) { - result[i] = getKeymasterPaddingFromJcaEncryptionPadding(jcaPaddings[i]); - } - return result; - } - - public static int[] getKeymasterPaddingsFromJcaSignaturePaddings(String[] jcaPaddings) { - if ((jcaPaddings == null) || (jcaPaddings.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaPaddings.length]; - for (int i = 0; i < jcaPaddings.length; i++) { - result[i] = getKeymasterPaddingFromJcaSignaturePadding(jcaPaddings[i]); - } - return result; - } - /** * Adds keymaster arguments to express the key's authorization policy supported by user * authentication. diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp index aca7c52..2f79c58 100644 --- a/libs/hwui/tests/main.cpp +++ b/libs/hwui/tests/main.cpp @@ -58,13 +58,20 @@ static void endRecording(DisplayListCanvas* renderer, RenderNode* node) { class TreeContentAnimation { public: virtual ~TreeContentAnimation() {} - virtual int getFrameCount() { return 150; } + int frameCount = 150; + virtual int getFrameCount() { return frameCount; } + virtual void setFrameCount(int fc) { + if (fc > 0) { + frameCount = fc; + } + } virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0; virtual void doFrame(int frameNr) = 0; template <class T> - static void run() { + static void run(int frameCount) { T animation; + animation.setFrameCount(frameCount); TestContext testContext; @@ -137,9 +144,10 @@ public: renderer->insertReorderBarrier(false); } void doFrame(int frameNr) override { + int curFrame = frameNr % 150; for (size_t ci = 0; ci < cards.size(); ci++) { - cards[ci]->mutateStagingProperties().setTranslationX(frameNr); - cards[ci]->mutateStagingProperties().setTranslationY(frameNr); + cards[ci]->mutateStagingProperties().setTranslationX(curFrame); + cards[ci]->mutateStagingProperties().setTranslationY(curFrame); cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } } @@ -159,6 +167,47 @@ private: } }; +class ShadowGrid2Animation : public TreeContentAnimation { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, DisplayListCanvas* renderer) override { + renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + renderer->insertReorderBarrier(true); + + for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { + for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { + sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); + renderer->drawRenderNode(card.get()); + cards.push_back(card); + } + } + + renderer->insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < cards.size(); ci++) { + cards[ci]->mutateStagingProperties().setTranslationX(curFrame); + cards[ci]->mutateStagingProperties().setTranslationY(curFrame); + cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->mutateStagingProperties().setElevation(dp(16)); + node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); + node->mutateStagingProperties().mutableOutline().setShouldClip(true); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); + + DisplayListCanvas* renderer = startRecording(node.get()); + renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + endRecording(renderer, node.get()); + return node; + } +}; + class RectGridAnimation : public TreeContentAnimation { public: sp<RenderNode> card; @@ -172,8 +221,9 @@ public: renderer->insertReorderBarrier(false); } void doFrame(int frameNr) override { - card->mutateStagingProperties().setTranslationX(frameNr); - card->mutateStagingProperties().setTranslationY(frameNr); + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } private: @@ -220,8 +270,9 @@ public: } void doFrame(int frameNr) override { - card->mutateStagingProperties().setTranslationX(frameNr); - card->mutateStagingProperties().setTranslationY(frameNr); + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } private: @@ -248,10 +299,11 @@ struct cstr_cmp { } }; -typedef void (*testProc)(); +typedef void (*testProc)(int); std::map<const char*, testProc, cstr_cmp> gTestMap { {"shadowgrid", TreeContentAnimation::run<ShadowGridAnimation>}, + {"shadowgrid2", TreeContentAnimation::run<ShadowGrid2Animation>}, {"rectgrid", TreeContentAnimation::run<RectGridAnimation> }, {"oval", TreeContentAnimation::run<OvalAnimation> }, }; @@ -263,7 +315,28 @@ int main(int argc, char* argv[]) { printf("Error: couldn't find test %s\n", testName); return 1; } - proc(); + int loopCount = 1; + if (argc > 2) { + loopCount = atoi(argv[2]); + if (!loopCount) { + printf("Invalid loop count!\n"); + return 1; + } + } + int frameCount = 150; + if (argc > 3) { + frameCount = atoi(argv[3]); + if (frameCount < 1) { + printf("Invalid frame count!\n"); + return 1; + } + } + if (loopCount < 0) { + loopCount = INT_MAX; + } + for (int i = 0; i < loopCount; i++) { + proc(frameCount); + } printf("Success!\n"); return 0; } diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java index 82da27d..c078260 100644 --- a/media/java/android/media/AudioDevicePort.java +++ b/media/java/android/media/AudioDevicePort.java @@ -83,6 +83,16 @@ public class AudioDevicePort extends AudioPort { if (o == null || !(o instanceof AudioDevicePort)) { return false; } + AudioDevicePort other = (AudioDevicePort)o; + if (mType != other.type()) { + return false; + } + if (mAddress == null && other.address() != null) { + return false; + } + if (!mAddress.equals(other.address())) { + return false; + } return super.equals(o); } diff --git a/media/java/android/media/AudioDevicesManager.java b/media/java/android/media/AudioDevicesManager.java index ca238d7..8b83c17 100644 --- a/media/java/android/media/AudioDevicesManager.java +++ b/media/java/android/media/AudioDevicesManager.java @@ -96,7 +96,7 @@ public class AudioDevicesManager { * @param flags A set of bitflags specifying the criteria to test. * @see {@link LIST_DEVICES_OUTPUTS} and {@link LIST_DEVICES_INPUTS} **/ - private boolean checkFlags(AudioDevicePort port, int flags) { + private static boolean checkFlags(AudioDevicePort port, int flags) { return port.role() == AudioPort.ROLE_SINK && (flags & LIST_DEVICES_OUTPUTS) != 0 || port.role() == AudioPort.ROLE_SOURCE && (flags & LIST_DEVICES_INPUTS) != 0; } @@ -110,8 +110,21 @@ public class AudioDevicesManager { * @return A (possibly zero-length) array of AudioDeviceInfo objects. */ public AudioDeviceInfo[] listDevices(int flags) { + return listDevicesStatic(flags); + } + + /** + * Generates a list of AudioDeviceInfo objects corresponding to the audio devices currently + * connected to the system and meeting the criteria specified in the <code>flags</code> + * parameter. + * @param flags A set of bitflags specifying the criteria to test. + * @see {@link LIST_DEVICES_OUTPUTS}, {@link LIST_DEVICES_INPUTS} and {@link LIST_DEVICES_ALL}. + * @return A (possibly zero-length) array of AudioDeviceInfo objects. + * @hide + */ + public static AudioDeviceInfo[] listDevicesStatic(int flags) { ArrayList<AudioDevicePort> ports = new ArrayList<AudioDevicePort>(); - int status = mAudioManager.listAudioDevicePorts(ports); + int status = AudioManager.listAudioDevicePorts(ports); if (status != AudioManager.SUCCESS) { // fail and bail! return new AudioDeviceInfo[0]; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 6eaf812..19900d0 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -3401,7 +3401,7 @@ public class AudioManager { * @param ports An AudioPort ArrayList where the list will be returned. * @hide */ - public int listAudioPorts(ArrayList<AudioPort> ports) { + public static int listAudioPorts(ArrayList<AudioPort> ports) { return updateAudioPortCache(ports, null); } @@ -3410,7 +3410,7 @@ public class AudioManager { * @see listAudioPorts(ArrayList<AudioPort>) * @hide */ - public int listAudioDevicePorts(ArrayList<AudioDevicePort> devices) { + public static int listAudioDevicePorts(ArrayList<AudioDevicePort> devices) { ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); int status = updateAudioPortCache(ports, null); if (status == SUCCESS) { @@ -3447,7 +3447,7 @@ public class AudioManager { * patch[0] contains the newly created patch * @hide */ - public int createAudioPatch(AudioPatch[] patch, + public static int createAudioPatch(AudioPatch[] patch, AudioPortConfig[] sources, AudioPortConfig[] sinks) { return AudioSystem.createAudioPatch(patch, sources, sinks); @@ -3464,7 +3464,7 @@ public class AudioManager { * - {@link #ERROR} if patch cannot be released for any other reason. * @hide */ - public int releaseAudioPatch(AudioPatch patch) { + public static int releaseAudioPatch(AudioPatch patch) { return AudioSystem.releaseAudioPatch(patch); } @@ -3473,7 +3473,7 @@ public class AudioManager { * @param patches An AudioPatch array where the list will be returned. * @hide */ - public int listAudioPatches(ArrayList<AudioPatch> patches) { + public static int listAudioPatches(ArrayList<AudioPatch> patches) { return updateAudioPortCache(null, patches); } @@ -3482,7 +3482,7 @@ public class AudioManager { * AudioGain.buildConfig() * @hide */ - public int setAudioPortGain(AudioPort port, AudioGainConfig gain) { + public static int setAudioPortGain(AudioPort port, AudioGainConfig gain) { if (port == null || gain == null) { return ERROR_BAD_VALUE; } diff --git a/media/java/android/media/AudioMixPort.java b/media/java/android/media/AudioMixPort.java index 9fac8d1..ab55c8d 100644 --- a/media/java/android/media/AudioMixPort.java +++ b/media/java/android/media/AudioMixPort.java @@ -20,16 +20,21 @@ package android.media; * The AudioMixPort is a specialized type of AudioPort * describing an audio mix or stream at an input or output stream of the audio * framework. + * In addition to base audio port attributes, the mix descriptor contains: + * - the unique audio I/O handle assigned by AudioFlinger to this mix. * @see AudioPort * @hide */ public class AudioMixPort extends AudioPort { - AudioMixPort(AudioHandle handle, int role, String deviceName, + private final int mIoHandle; + + AudioMixPort(AudioHandle handle, int ioHandle, int role, String deviceName, int[] samplingRates, int[] channelMasks, int[] formats, AudioGain[] gains) { super(handle, role, deviceName, samplingRates, channelMasks, formats, gains); + mIoHandle = ioHandle; } /** @@ -41,11 +46,23 @@ public class AudioMixPort extends AudioPort { return new AudioMixPortConfig(this, samplingRate, channelMask, format, gain); } + /** + * Get the device type (e.g AudioManager.DEVICE_OUT_SPEAKER) + */ + public int ioHandle() { + return mIoHandle; + } + @Override public boolean equals(Object o) { if (o == null || !(o instanceof AudioMixPort)) { return false; } + AudioMixPort other = (AudioMixPort)o; + if (mIoHandle != other.ioHandle()) { + return false; + } + return super.equals(o); } diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java index 88e784a..7328d7a 100644 --- a/media/java/android/media/AudioPort.java +++ b/media/java/android/media/AudioPort.java @@ -93,6 +93,14 @@ public class AudioPort { } /** + * Get the system unique device ID. + */ + public int id() { + return mHandle.id(); + } + + + /** * Get the audio port role */ public int role() { diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 472da02..11671d8 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -116,11 +116,6 @@ public class AudioRecord */ private static final int NATIVE_EVENT_NEW_POS = 3; - /** - * Event id denotes when the routing changes. - */ - private final static int NATIVE_EVENT_ROUTING_CHANGE = 1000; - private final static String TAG = "android.media.AudioRecord"; /** @hide */ @@ -161,6 +156,12 @@ public class AudioRecord @SuppressWarnings("unused") private long mNativeCallbackCookie; + /** + * Accessed by native methods: provides access to the JNIDeviceCallback instance. + */ + @SuppressWarnings("unused") + private long mNativeDeviceCallback; + //--------------------------------------------------------- // Member variables @@ -1205,6 +1206,17 @@ public class AudioRecord * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioRecord. */ public AudioDeviceInfo getRoutedDevice() { + int deviceId = native_getRoutedDeviceId(); + if (deviceId == 0) { + return null; + } + AudioDeviceInfo[] devices = + AudioDevicesManager.listDevicesStatic(AudioDevicesManager.LIST_DEVICES_INPUTS); + for (int i = 0; i < devices.length; i++) { + if (devices[i].getId() == deviceId) { + return devices[i]; + } + } return null; } @@ -1224,6 +1236,9 @@ public class AudioRecord android.os.Handler handler) { if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { synchronized (mRoutingChangeListeners) { + if (mRoutingChangeListeners.size() == 0) { + native_enableDeviceCallback(); + } mRoutingChangeListeners.put( listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); } @@ -1238,6 +1253,9 @@ public class AudioRecord synchronized (mRoutingChangeListeners) { if (mRoutingChangeListeners.containsKey(listener)) { mRoutingChangeListeners.remove(listener); + if (mRoutingChangeListeners.size() == 0) { + native_disableDeviceCallback(); + } } } } @@ -1271,7 +1289,7 @@ public class AudioRecord return; } switch(msg.what) { - case NATIVE_EVENT_ROUTING_CHANGE: + case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE: if (listener != null) { listener.onAudioRecordRouting(record); } @@ -1299,10 +1317,11 @@ public class AudioRecord synchronized (mRoutingChangeListeners) { values = mRoutingChangeListeners.values(); } + AudioManager.resetAudioPortGeneration(); for(NativeRoutingEventHandlerDelegate delegate : values) { Handler handler = delegate.getHandler(); if (handler != null) { - handler.sendEmptyMessage(NATIVE_EVENT_ROUTING_CHANGE); + handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE); } } } @@ -1341,10 +1360,14 @@ public class AudioRecord return false; } - mPreferredDevice = deviceInfo; - int preferredDeviceId = mPreferredDevice != null ? deviceInfo.getId() : 0; - - return native_setInputDevice(preferredDeviceId); + int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0; + boolean status = native_setInputDevice(preferredDeviceId); + if (status == true) { + synchronized (this) { + mPreferredDevice = deviceInfo; + } + } + return status; } /** @@ -1352,7 +1375,9 @@ public class AudioRecord * is not guarenteed to correspond to the actual device being used for recording. */ public AudioDeviceInfo getPreferredInputDevice() { - return mPreferredDevice; + synchronized (this) { + return mPreferredDevice; + } } //--------------------------------------------------------- @@ -1435,6 +1460,11 @@ public class AudioRecord return; } + if (what == AudioSystem.NATIVE_EVENT_ROUTING_CHANGE) { + recorder.broadcastRoutingChange(); + return; + } + if (recorder.mEventHandler != null) { Message m = recorder.mEventHandler.obtainMessage(what, arg1, arg2, obj); @@ -1486,7 +1516,9 @@ public class AudioRecord int sampleRateInHz, int channelCount, int audioFormat); private native final boolean native_setInputDevice(int deviceId); - + private native final int native_getRoutedDeviceId(); + private native final void native_enableDeviceCallback(); + private native final void native_disableDeviceCallback(); //--------------------------------------------------------- // Utility methods diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 3dae543..ee12374 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -721,5 +721,11 @@ public class AudioSystem (1 << STREAM_RING) | (1 << STREAM_NOTIFICATION) | (1 << STREAM_SYSTEM); + + /** + * Event posted by AudioTrack and AudioRecord JNI (JNIDeviceCallback) when routing changes. + * Keep in sync with core/jni/android_media_DeviceCallback.h. + */ + final static int NATIVE_EVENT_ROUTING_CHANGE = 1000; } diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index cb05cc5..a66a1e5 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -178,12 +178,6 @@ public class AudioTrack */ private static final int NATIVE_EVENT_NEW_POS = 4; - /** - * Event id denotes when the routing changes. - */ - private final static int NATIVE_EVENT_ROUTING_CHANGE = 1000; - - private final static String TAG = "android.media.AudioTrack"; @@ -2057,11 +2051,14 @@ public class AudioTrack if (deviceInfo != null && !deviceInfo.isSink()) { return false; } - - mPreferredDevice = deviceInfo; - int preferredDeviceId = mPreferredDevice != null ? deviceInfo.getId() : 0; - - return native_setOutputDevice(preferredDeviceId); + int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0; + boolean status = native_setOutputDevice(preferredDeviceId); + if (status == true) { + synchronized (this) { + mPreferredDevice = deviceInfo; + } + } + return status; } /** @@ -2069,7 +2066,9 @@ public class AudioTrack * is not guaranteed to correspond to the actual device being used for playback. */ public AudioDeviceInfo getPreferredOutputDevice() { - return mPreferredDevice; + synchronized (this) { + return mPreferredDevice; + } } //-------------------------------------------------------------------------- @@ -2079,6 +2078,17 @@ public class AudioTrack * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioTrack. */ public AudioDeviceInfo getRoutedDevice() { + int deviceId = native_getRoutedDeviceId(); + if (deviceId == 0) { + return null; + } + AudioDeviceInfo[] devices = + AudioDevicesManager.listDevicesStatic(AudioDevicesManager.LIST_DEVICES_OUTPUTS); + for (int i = 0; i < devices.length; i++) { + if (devices[i].getId() == deviceId) { + return devices[i]; + } + } return null; } @@ -2098,6 +2108,9 @@ public class AudioTrack android.os.Handler handler) { if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { synchronized (mRoutingChangeListeners) { + if (mRoutingChangeListeners.size() == 0) { + native_enableDeviceCallback(); + } mRoutingChangeListeners.put( listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); } @@ -2113,6 +2126,9 @@ public class AudioTrack if (mRoutingChangeListeners.containsKey(listener)) { mRoutingChangeListeners.remove(listener); } + if (mRoutingChangeListeners.size() == 0) { + native_disableDeviceCallback(); + } } } @@ -2124,10 +2140,11 @@ public class AudioTrack synchronized (mRoutingChangeListeners) { values = mRoutingChangeListeners.values(); } + AudioManager.resetAudioPortGeneration(); for(NativeRoutingEventHandlerDelegate delegate : values) { Handler handler = delegate.getHandler(); if (handler != null) { - handler.sendEmptyMessage(NATIVE_EVENT_ROUTING_CHANGE); + handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE); } } } @@ -2240,7 +2257,7 @@ public class AudioTrack return; } switch(msg.what) { - case NATIVE_EVENT_ROUTING_CHANGE: + case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE: if (listener != null) { listener.onAudioTrackRouting(track); } @@ -2273,6 +2290,10 @@ public class AudioTrack return; } + if (what == AudioSystem.NATIVE_EVENT_ROUTING_CHANGE) { + track.broadcastRoutingChange(); + return; + } NativePositionEventHandlerDelegate delegate = track.mEventHandlerDelegate; if (delegate != null) { Handler handler = delegate.getHandler(); @@ -2281,7 +2302,6 @@ public class AudioTrack handler.sendMessage(m); } } - } @@ -2362,6 +2382,9 @@ public class AudioTrack private native final int native_setAuxEffectSendLevel(float level); private native final boolean native_setOutputDevice(int deviceId); + private native final int native_getRoutedDeviceId(); + private native final void native_enableDeviceCallback(); + private native final void native_disableDeviceCallback(); //--------------------------------------------------------- // Utility methods diff --git a/media/java/android/media/routing/IMediaRouteClientCallback.aidl b/media/java/android/media/routing/IMediaRouteClientCallback.aidl deleted file mode 100644 index d90ea3b..0000000 --- a/media/java/android/media/routing/IMediaRouteClientCallback.aidl +++ /dev/null @@ -1,41 +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.media.routing; - -import android.media.routing.MediaRouteSelector; -import android.media.routing.ParcelableConnectionInfo; -import android.media.routing.ParcelableDestinationInfo; -import android.media.routing.ParcelableRouteInfo; -import android.os.IBinder; -import android.os.Bundle; - -/** - * @hide - */ -oneway interface IMediaRouteClientCallback { - void onDestinationFound(int seq, in ParcelableDestinationInfo destination, - in ParcelableRouteInfo[] routes); - - void onDestinationLost(int seq, String id); - - void onDiscoveryFailed(int seq, int error, in CharSequence message, in Bundle extras); - - void onConnected(int seq, in ParcelableConnectionInfo connection); - - void onDisconnected(int seq); - - void onConnectionFailed(int seq, int error, in CharSequence message, in Bundle extras); -} diff --git a/media/java/android/media/routing/IMediaRouteService.aidl b/media/java/android/media/routing/IMediaRouteService.aidl deleted file mode 100644 index 493ab6d..0000000 --- a/media/java/android/media/routing/IMediaRouteService.aidl +++ /dev/null @@ -1,46 +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.media.routing; - -import android.media.routing.IMediaRouteClientCallback; -import android.media.routing.MediaRouteSelector; -import android.os.Bundle; - -/** - * Interface to an app's MediaRouteService. - * @hide - */ -oneway interface IMediaRouteService { - void registerClient(int clientUid, String clientPackageName, - in IMediaRouteClientCallback callback); - - void unregisterClient(in IMediaRouteClientCallback callback); - - void startDiscovery(in IMediaRouteClientCallback callback, int seq, - in List<MediaRouteSelector> selectors, int flags); - - void stopDiscovery(in IMediaRouteClientCallback callback); - - void connect(in IMediaRouteClientCallback callback, int seq, - String destinationId, String routeId, int flags, in Bundle extras); - - void disconnect(in IMediaRouteClientCallback callback); - - void pauseStream(in IMediaRouteClientCallback callback); - - void resumeStream(in IMediaRouteClientCallback callback); -} - diff --git a/media/java/android/media/routing/IMediaRouter.aidl b/media/java/android/media/routing/IMediaRouter.aidl deleted file mode 100644 index 0abb258..0000000 --- a/media/java/android/media/routing/IMediaRouter.aidl +++ /dev/null @@ -1,22 +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.media.routing; - -/** @hide */ -interface IMediaRouter { - -} - diff --git a/media/java/android/media/routing/IMediaRouterDelegate.aidl b/media/java/android/media/routing/IMediaRouterDelegate.aidl deleted file mode 100644 index 35f84c8..0000000 --- a/media/java/android/media/routing/IMediaRouterDelegate.aidl +++ /dev/null @@ -1,22 +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.media.routing; - -/** @hide */ -interface IMediaRouterDelegate { - -} - diff --git a/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl b/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl deleted file mode 100644 index 173ae55..0000000 --- a/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl +++ /dev/null @@ -1,22 +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.media.routing; - -/** @hide */ -interface IMediaRouterRoutingCallback { - -} - diff --git a/media/java/android/media/routing/IMediaRouterStateCallback.aidl b/media/java/android/media/routing/IMediaRouterStateCallback.aidl deleted file mode 100644 index 0299904..0000000 --- a/media/java/android/media/routing/IMediaRouterStateCallback.aidl +++ /dev/null @@ -1,22 +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.media.routing; - -/** @hide */ -interface IMediaRouterStateCallback { - -} - diff --git a/media/java/android/media/routing/MediaRouteSelector.aidl b/media/java/android/media/routing/MediaRouteSelector.aidl deleted file mode 100644 index 37bfa4a..0000000 --- a/media/java/android/media/routing/MediaRouteSelector.aidl +++ /dev/null @@ -1,18 +0,0 @@ -/* 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. -*/ - -package android.media.routing; - -parcelable MediaRouteSelector; diff --git a/media/java/android/media/routing/MediaRouteSelector.java b/media/java/android/media/routing/MediaRouteSelector.java deleted file mode 100644 index 0bfc796..0000000 --- a/media/java/android/media/routing/MediaRouteSelector.java +++ /dev/null @@ -1,356 +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.media.routing; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.media.routing.MediaRouter.RouteFeatures; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * A media route selector consists of a set of constraints that are used to select - * the routes to which an application would like to connect. The constraints consist - * of a set of required or optional features and protocols. The constraints may also - * require the use of a specific media route service package or additional characteristics - * that are described by a bundle of extra parameters. - * <p> - * The application will typically create several different selectors that express - * various combinations of characteristics that it would like to use together when - * it connects to a destination media device. For each destination that is discovered, - * media route services will publish some number of routes and include information - * about which selector each route matches. The application will then choose among - * these routes to determine which best satisfies its desired purpose and connect to it. - * </p> - */ -public final class MediaRouteSelector implements Parcelable { - private final int mRequiredFeatures; - private final int mOptionalFeatures; - private final List<String> mRequiredProtocols; - private final List<String> mOptionalProtocols; - private final String mServicePackageName; - private final Bundle mExtras; - - MediaRouteSelector(int requiredFeatures, int optionalFeatures, - List<String> requiredProtocols, List<String> optionalProtocols, - String servicePackageName, Bundle extras) { - mRequiredFeatures = requiredFeatures; - mOptionalFeatures = optionalFeatures; - mRequiredProtocols = requiredProtocols; - mOptionalProtocols = optionalProtocols; - mServicePackageName = servicePackageName; - mExtras = extras; - } - - /** - * Gets the set of required route features. - * - * @return A set of required route feature flags. - */ - public @RouteFeatures int getRequiredFeatures() { - return mRequiredFeatures; - } - - /** - * Gets the set of optional route features. - * - * @return A set of optional route feature flags. - */ - public @RouteFeatures int getOptionalFeatures() { - return mOptionalFeatures; - } - - /** - * Gets the list of route protocols that a route must support in order to be selected. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - * - * @return The list of fully qualified route protocol names. - */ - public @NonNull List<String> getRequiredProtocols() { - return mRequiredProtocols; - } - - /** - * Gets the list of optional route protocols that a client may use if they are available. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - * - * @return The list of optional fully qualified route protocol names. - */ - public @NonNull List<String> getOptionalProtocols() { - return mOptionalProtocols; - } - - /** - * Returns true if the selector includes a required or optional request for - * the specified protocol using its fully qualified class name. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - * - * @param clazz The protocol class. - * @return True if the protocol was requested. - */ - public boolean containsProtocol(@NonNull Class<?> clazz) { - return containsProtocol(clazz.getName()); - } - - /** - * Returns true if the selector includes a required or optional request for - * the specified protocol. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - * - * @param name The name of the protocol. - * @return True if the protocol was requested. - */ - public boolean containsProtocol(@NonNull String name) { - return mRequiredProtocols.contains(name) - || mOptionalProtocols.contains(name); - } - - /** - * Gets the package name of a specific media route service that this route selector - * requires. - * - * @return The required media route service package name, or null if none. - */ - public @Nullable String getServicePackageName() { - return mServicePackageName; - } - - /** - * Gets optional extras that may be used to select or configure routes for a - * particular purpose. Some extras may be used by media route services to apply - * additional constraints or parameters for the routes to be discovered. - * - * @return The optional extras, or null if none. - */ - public @Nullable Bundle getExtras() { - return mExtras; - } - - @Override - public String toString() { - return "MediaRouteSelector{ " - + ", requiredFeatures=0x" + Integer.toHexString(mRequiredFeatures) - + ", optionalFeatures=0x" + Integer.toHexString(mOptionalFeatures) - + ", requiredProtocols=" + mRequiredProtocols - + ", optionalProtocols=" + mOptionalProtocols - + ", servicePackageName=" + mServicePackageName - + ", extras=" + mExtras + " }"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRequiredFeatures); - dest.writeInt(mOptionalFeatures); - dest.writeStringList(mRequiredProtocols); - dest.writeStringList(mOptionalProtocols); - dest.writeString(mServicePackageName); - dest.writeBundle(mExtras); - } - - public static final Parcelable.Creator<MediaRouteSelector> CREATOR = - new Parcelable.Creator<MediaRouteSelector>() { - @Override - public MediaRouteSelector createFromParcel(Parcel source) { - int requiredFeatures = source.readInt(); - int optionalFeatures = source.readInt(); - ArrayList<String> requiredProtocols = new ArrayList<String>(); - ArrayList<String> optionalProtocols = new ArrayList<String>(); - source.readStringList(requiredProtocols); - source.readStringList(optionalProtocols); - return new MediaRouteSelector(requiredFeatures, optionalFeatures, - requiredProtocols, optionalProtocols, - source.readString(), source.readBundle()); - } - - @Override - public MediaRouteSelector[] newArray(int size) { - return new MediaRouteSelector[size]; - } - }; - - /** - * Builder for {@link MediaRouteSelector} objects. - */ - public static final class Builder { - private int mRequiredFeatures; - private int mOptionalFeatures; - private final ArrayList<String> mRequiredProtocols = new ArrayList<String>(); - private final ArrayList<String> mOptionalProtocols = new ArrayList<String>(); - private String mServicePackageName; - private Bundle mExtras; - - /** - * Creates an initially empty selector builder. - */ - public Builder() { - } - - /** - * Sets the set of required route features. - * - * @param features A set of required route feature flags. - */ - public @NonNull Builder setRequiredFeatures(@RouteFeatures int features) { - mRequiredFeatures = features; - return this; - } - - /** - * Sets the set of optional route features. - * - * @param features A set of optional route feature flags. - */ - public @NonNull Builder setOptionalFeatures(@RouteFeatures int features) { - mOptionalFeatures = features; - return this; - } - - /** - * Adds a route protocol that a route must support in order to be selected - * using its fully qualified class name. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - * - * @param clazz The protocol class. - * @return this - */ - public @NonNull Builder addRequiredProtocol(@NonNull Class<?> clazz) { - if (clazz == null) { - throw new IllegalArgumentException("clazz must not be null"); - } - return addRequiredProtocol(clazz.getName()); - } - - /** - * Adds a route protocol that a route must support in order to be selected. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - * - * @param name The fully qualified name of the required protocol. - * @return this - */ - public @NonNull Builder addRequiredProtocol(@NonNull String name) { - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must not be null or empty"); - } - mRequiredProtocols.add(name); - return this; - } - - /** - * Adds an optional route protocol that a client may use if available - * using its fully qualified class name. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - * - * @param clazz The protocol class. - * @return this - */ - public @NonNull Builder addOptionalProtocol(@NonNull Class<?> clazz) { - if (clazz == null) { - throw new IllegalArgumentException("clazz must not be null"); - } - return addOptionalProtocol(clazz.getName()); - } - - /** - * Adds an optional route protocol that a client may use if available. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - * - * @param name The fully qualified name of the optional protocol. - * @return this - */ - public @NonNull Builder addOptionalProtocol(@NonNull String name) { - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must not be null or empty"); - } - mOptionalProtocols.add(name); - return this; - } - - /** - * Sets the package name of the media route service to which this selector - * appertains. - * <p> - * If a package name is specified here then this selector will only be - * passed to media route services from that package. This has the effect - * of restricting the set of matching routes to just those that are offered - * by that package. - * </p> - * - * @param packageName The required service package name, or null if none. - * @return this - */ - public @NonNull Builder setServicePackageName(@Nullable String packageName) { - mServicePackageName = packageName; - return this; - } - - /** - * Sets optional extras that may be used to select or configure routes for a - * particular purpose. Some extras may be used by route services to specify - * additional constraints or parameters for the routes to be discovered. - * - * @param extras The optional extras, or null if none. - * @return this - */ - public @NonNull Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Builds the {@link MediaRouteSelector} object. - * - * @return The new media route selector instance. - */ - public @NonNull MediaRouteSelector build() { - return new MediaRouteSelector(mRequiredFeatures, mOptionalFeatures, - mRequiredProtocols, mOptionalProtocols, mServicePackageName, mExtras); - } - } -} diff --git a/media/java/android/media/routing/MediaRouteService.java b/media/java/android/media/routing/MediaRouteService.java deleted file mode 100644 index 4d5a8a9..0000000 --- a/media/java/android/media/routing/MediaRouteService.java +++ /dev/null @@ -1,1023 +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.media.routing; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SdkConstant; -import android.app.Service; -import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; -import android.media.routing.MediaRouter.ConnectionError; -import android.media.routing.MediaRouter.ConnectionInfo; -import android.media.routing.MediaRouter.ConnectionRequest; -import android.media.routing.MediaRouter.DestinationInfo; -import android.media.routing.MediaRouter.DiscoveryError; -import android.media.routing.MediaRouter.DiscoveryRequest; -import android.media.routing.MediaRouter.RouteInfo; -import android.media.routing.MediaRouter.ServiceMetadata; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * Media route services implement strategies for discovering - * and establishing connections to media devices and their routes. These services - * are also known as media route providers. - * <p> - * Each media route service subclass is responsible for enabling applications - * and the system to interact with media devices of some kind. - * For example, one particular media route service implementation might - * offer support for discovering nearby wireless display devices and streaming - * video contents to them; another media route service implementation might - * offer support for discovering nearby speakers and streaming media appliances - * and sending commands to play content on request. - * </p><p> - * Subclasses must override the {@link #onCreateClientSession} method to return - * a {@link ClientSession} object that implements the {@link ClientSession#onStartDiscovery}, - * {@link ClientSession#onStopDiscovery}, and {@link ClientSession#onConnect} methods - * to allow clients to discover and connect to media devices. - * </p><p> - * This object is not thread-safe. All callbacks are invoked on the main looper. - * </p> - * - * <h3>Clients</h3> - * <p> - * The clients of this API are media applications that would like to discover - * and connect to media devices. The client may also be the system, such as - * when the user initiates display mirroring via the Cast Screen function. - * </p><p> - * There may be multiple client sessions active at the same time. Each client - * session can request discovery and connect to routes independently of any - * other client. It is the responsibility of the media route service to maintain - * separate state for each client session and to ensure that clients cannot interfere - * with one another in harmful ways. - * </p><p> - * Notwithstanding the requirement to support any number of concurrent client - * sessions, the media route service may impose constraints on how many clients - * can connect to the same media device in a particular mode at the same time. - * In some cases, media devices may support connections from an arbitrary number - * of clients simultaneously but often it may be necessary to ensure that only - * one client is in control. When this happens, the media route service should - * report a connection error unless the connection request specifies that the - * client should take control of the media device (and forcibly disconnect other - * clients that may be using it). - * </p> - * - * <h3>Destinations</h3> - * <p> - * The media devices to which an application may send media content are referred - * to in the API as destinations. Each destination therefore represents a single - * independent device such as a speaker or TV set. Destinations are given meaningful - * names and descriptions to help the user associate them with devices in their - * environment. - * </p><p> - * Destinations may be local or remote and may be accessed through various means, - * often wirelessly. The user may install media route services to enable - * media applications to connect to a variety of destinations with different - * capabilities. - * </p> - * - * <h3>Routes</h3> - * <p> - * Routes represent possible usages or means of reaching and interacting with - * a destination. Since destinations may support many different features, they may - * each offer multiple routes for applications to choose from based on their needs. - * For example, one route might express the ability to stream locally rendered audio - * and video to the device; another route might express the ability to send a URL for - * the destination to download from the network and play all by itself. - * </p><p> - * Routes are discovered according to the set of capabilities that - * an application or the system is seeking to use at a particular time. For example, - * if an application wants to stream music to a destination then it will ask the - * {@link MediaRouter} to find routes to destinations can stream music and ignore - * all other destinations that cannot. - * </p><p> - * In general, the application will inspect the set of routes that have been - * offered then connect to the most appropriate route for its desired purpose. - * </p> - * - * <h3>Discovery</h3> - * <p> - * Discovery is the process of finding destinations based on a description of the - * kinds of routes that an application or the system would like to use. - * </p><p> - * Discovery begins when {@link ClientSession#onStartDiscovery} is called and ends when - * {@link ClientSession#onStopDiscovery} is called. There may be multiple simultaneous - * discovery requests in progress at the same time from different clients. It is up to - * the media route service to perform these requests in parallel or multiplex them - * as required. - * </p><p> - * Media route services are <em>strongly encouraged</em> to use the information - * in the discovery request to optimize discovery and avoid redundant work. - * In the case where no media device supported by the media route service - * could possibly offer the requested capabilities, the - * {@link ClientSession#onStartDiscovery} method should return <code>false</code> to - * let the system know that it can unbind from the media route service and - * release its resources. - * </p> - * - * <h3>Settings</h3> - * <p> - * Many kinds of devices can be discovered on demand simply by scanning the local network - * or using wireless protocols such as Bluetooth to find them. However, in some cases - * it may be necessary for the user to manually configure destinations before they - * can be used (or to adjust settings later). Actual user configuration of destinations - * is beyond the scope of this API but media route services may specify an activity - * in their manifest that the user can launch to perform these tasks. - * </p><p> - * Note that media route services that are installed from the store must be enabled - * by the user before they become available for applications to use. - * The {@link android.provider.Settings#ACTION_CAST_SETTINGS Settings.ACTION_CAST_SETTINGS} - * settings activity provides the ability for the user to configure media route services. - * </p> - * - * <h3>Manifest Declaration</h3> - * <p> - * Media route services must be declared in the manifest along with meta-data - * about the kinds of routes that they are capable of discovering. The system - * uses this information to optimize the set of services to which it binds in - * order to satisfy a particular discovery request. - * </p><p> - * To extend this class, you must declare the service in your manifest file with - * the {@link android.Manifest.permission#BIND_MEDIA_ROUTE_SERVICE} permission - * and include an intent filter with the {@link #SERVICE_INTERFACE} action. You must - * also add meta-data to describe the kinds of routes that your service is capable - * of discovering. - * </p><p> - * For example: - * </p><pre> - * <service android:name=".MediaRouteProvider" - * android:label="@string/service_name" - * android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE"> - * <intent-filter> - * <action android:name="android.media.routing.MediaRouteService" /> - * </intent-filter> - * - * TODO: INSERT METADATA DECLARATIONS HERE - * - * </service> - * </pre> - */ -public abstract class MediaRouteService extends Service { - private static final String TAG = "MediaRouteService"; - - private static final boolean DEBUG = true; - - private final Handler mHandler; - private final BinderService mService; - private final ArrayMap<IBinder, ClientRecord> mClientRecords = - new ArrayMap<IBinder, ClientRecord>(); - - private ServiceMetadata mMetadata; - - /** - * The {@link Intent} that must be declared as handled by the service. - */ - @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) - public static final String SERVICE_INTERFACE = - "android.media.routing.MediaRouteService"; - - /** - * Creates a media route service. - */ - public MediaRouteService() { - mHandler = new Handler(true); - mService = new BinderService(); - } - - @Override - public @Nullable IBinder onBind(Intent intent) { - if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mService; - } - return null; - } - - /** - * Creates a new client session on behalf of a client. - * <p> - * The implementation should return a {@link ClientSession} for the client - * to use. The media route service must take care to manage the state of - * each client session independently from any others that might also be - * in use at the same time. - * </p> - * - * @param client Information about the client. - * @return The client session object, or null if the client is not allowed - * to interact with this media route service. - */ - public abstract @Nullable ClientSession onCreateClientSession(@NonNull ClientInfo client); - - /** - * Gets metadata about this service. - * <p> - * Use this method to obtain a {@link ServiceMetadata} object to provide when creating - * a {@link android.media.routing.MediaRouter.DestinationInfo.Builder}. - * </p> - * - * @return Metadata about this service. - */ - public @NonNull ServiceMetadata getServiceMetadata() { - if (mMetadata == null) { - try { - mMetadata = new ServiceMetadata(this); - } catch (NameNotFoundException ex) { - Log.wtf(TAG, "Could not retrieve own service metadata!"); - } - } - return mMetadata; - } - - /** - * Enables a single client to access the functionality of the media route service. - */ - public static abstract class ClientSession { - /** - * Starts discovery. - * <p> - * If the media route service is capable of discovering routes that satisfy - * the request then this method should start discovery and return true. - * Otherwise, this method should return false. If false is returned, - * then the framework will not call {@link #onStopDiscovery} since discovery - * was never actually started. - * </p><p> - * There may already be other discovery requests in progress at the same time - * for other clients; the media route service must keep track of them all. - * </p> - * - * @param req The discovery request to start. - * @param callback A callback to receive discovery events related to this - * particular request. The events that the service sends to this callback - * will be sent to the client that initiated the discovery request. - * @return True if discovery has started. False if the media route service - * is unable to discover routes that satisfy the request. - */ - public abstract boolean onStartDiscovery(@NonNull DiscoveryRequest req, - @NonNull DiscoveryCallback callback); - - /** - * Stops discovery. - * <p> - * If {@link #onStartDiscovery} returned true, then this method will eventually - * be called when the framework no longer requires this discovery request - * to be performed. - * </p><p> - * There may still be other discovery requests in progress for other clients; - * they must keep working until they have each been stopped by their client. - * </p> - */ - public abstract void onStopDiscovery(); - - /** - * Starts connecting to a route. - * - * @param req The connection request. - * @param callback A callback to receive events connection events related - * to this particular request. The events that the service sends to this callback - * will be sent to the client that initiated the discovery request. - * @return True if the connection is in progress, or false if the client - * unable to connect to the requested route. - */ - public abstract boolean onConnect(@NonNull ConnectionRequest req, - @NonNull ConnectionCallback callback); - - /** - * Called when the client requests to disconnect from the route - * or abort a connection attempt in progress. - */ - public abstract void onDisconnect(); - - /** - * Called when the client requests to pause streaming of content to - * live audio/video routes such as when it goes into the background. - * <p> - * The default implementation does nothing. - * </p> - */ - public void onPauseStream() { } - - /** - * Called when the application requests to resume streaming of content to - * live audio/video routes such as when it returns to the foreground. - * <p> - * The default implementation does nothing. - * </p> - */ - public void onResumeStream() { } - - /** - * Called when the client is releasing the session. - * <p> - * The framework automatically takes care of stopping discovery and - * terminating the connection politely before calling this method to release - * the session. - * </p><p> - * The default implementation does nothing. - * </p> - */ - public void onRelease() { } - } - - /** - * Provides events in response to a discovery request. - */ - public final class DiscoveryCallback { - private final ClientRecord mRecord; - - DiscoveryCallback(ClientRecord record) { - mRecord = record; - } - - /** - * Called by the service when a destination is found that - * offers one or more routes that satisfy the discovery request. - * <p> - * This method should be called whenever the list of available routes - * at a destination changes or whenever the properties of the destination - * itself change. - * </p> - * - * @param destination The destination that was found. - * @param routes The list of that destination's routes that satisfy the - * discovery request. - */ - public void onDestinationFound(final @NonNull DestinationInfo destination, - final @NonNull List<RouteInfo> routes) { - if (destination == null) { - throw new IllegalArgumentException("destination must not be null"); - } - if (routes == null) { - throw new IllegalArgumentException("routes must not be null"); - } - for (int i = 0; i < routes.size(); i++) { - if (routes.get(i).getDestination() != destination) { - throw new IllegalArgumentException("routes must refer to the " - + "destination"); - } - } - - mHandler.post(new Runnable() { - @Override - public void run() { - mRecord.dispatchDestinationFound(DiscoveryCallback.this, - destination, routes); - } - }); - } - - /** - * Called by the service when a destination is no longer - * reachable or is no longer offering any routes that satisfy - * the discovery request. - * - * @param destination The destination that went away. - */ - public void onDestinationLost(final @NonNull DestinationInfo destination) { - if (destination == null) { - throw new IllegalArgumentException("destination must not be null"); - } - - mHandler.post(new Runnable() { - @Override - public void run() { - mRecord.dispatchDestinationLost(DiscoveryCallback.this, destination); - } - }); - } - - /** - * Called by the service when a discovery has failed in a non-recoverable manner. - * - * @param error The error code: one of - * {@link MediaRouter#DISCOVERY_ERROR_UNKNOWN}, - * {@link MediaRouter#DISCOVERY_ERROR_ABORTED}, - * or {@link MediaRouter#DISCOVERY_ERROR_NO_CONNECTIVITY}. - * @param message The localized error message, or null if none. This message - * may be shown to the user. - * @param extras Additional information about the error which a client - * may use, or null if none. - */ - public void onDiscoveryFailed(final @DiscoveryError int error, - final @Nullable CharSequence message, final @Nullable Bundle extras) { - mHandler.post(new Runnable() { - @Override - public void run() { - mRecord.dispatchDiscoveryFailed(DiscoveryCallback.this, - error, message, extras); - } - }); - } - } - - /** - * Provides events in response to a connection request. - */ - public final class ConnectionCallback { - private final ClientRecord mRecord; - - ConnectionCallback(ClientRecord record) { - mRecord = record; - } - - /** - * Called by the service when the connection succeeds. - * - * @param connection Immutable information about the connection. - */ - public void onConnected(final @NonNull ConnectionInfo connection) { - if (connection == null) { - throw new IllegalArgumentException("connection must not be null"); - } - - mHandler.post(new Runnable() { - @Override - public void run() { - mRecord.dispatchConnected(ConnectionCallback.this, connection); - } - }); - } - - /** - * Called by the service when the connection is terminated normally. - * <p> - * Abnormal termination is reported via {@link #onConnectionFailed}. - * </p> - */ - public void onDisconnected() { - mHandler.post(new Runnable() { - @Override - public void run() { - mRecord.dispatchDisconnected(ConnectionCallback.this); - } - }); - } - - /** - * Called by the service when a connection attempt or connection in - * progress has failed in a non-recoverable manner. - * - * @param error The error code: one of - * {@link MediaRouter#CONNECTION_ERROR_ABORTED}, - * {@link MediaRouter#CONNECTION_ERROR_UNAUTHORIZED}, - * {@link MediaRouter#CONNECTION_ERROR_UNREACHABLE}, - * {@link MediaRouter#CONNECTION_ERROR_BUSY}, - * {@link MediaRouter#CONNECTION_ERROR_TIMEOUT}, - * {@link MediaRouter#CONNECTION_ERROR_BROKEN}, - * or {@link MediaRouter#CONNECTION_ERROR_BARGED}. - * @param message The localized error message, or null if none. This message - * may be shown to the user. - * @param extras Additional information about the error which a client - * may use, or null if none. - */ - public void onConnectionFailed(final @ConnectionError int error, - final @Nullable CharSequence message, final @Nullable Bundle extras) { - mHandler.post(new Runnable() { - @Override - public void run() { - mRecord.dispatchConnectionFailed(ConnectionCallback.this, - error, message, extras); - } - }); - } - } - - /** - * Identifies a client of the media route service. - */ - public static final class ClientInfo { - private final int mUid; - private final String mPackageName; - - ClientInfo(int uid, String packageName) { - mUid = uid; - mPackageName = packageName; - } - - /** - * Gets the UID of the client application. - */ - public int getUid() { - return mUid; - } - - /** - * Gets the package name of the client application. - */ - public @NonNull String getPackageName() { - return mPackageName; - } - - @Override - public @NonNull String toString() { - return "ClientInfo{ uid=" + mUid + ", package=" + mPackageName + " }"; - } - } - - private final class BinderService extends IMediaRouteService.Stub { - @Override - public void registerClient(final int clientUid, final String clientPackageName, - final IMediaRouteClientCallback callback) { - mHandler.post(new Runnable() { - @Override - public void run() { - ClientInfo client = new ClientInfo(clientUid, clientPackageName); - if (DEBUG) { - Log.d(TAG, "registerClient: client=" + client); - } - - ClientSession session = onCreateClientSession(client); - if (session == null) { - // request refused by service - Log.w(TAG, "Media route service refused to create session for client: " - + "client=" + client); - return; - } - - ClientRecord record = new ClientRecord(callback, client, session); - try { - callback.asBinder().linkToDeath(record, 0); - } catch (RemoteException ex) { - // client died prematurely - Log.w(TAG, "Client died prematurely while creating session: " - + "client=" + client); - record.release(); - return; - } - - mClientRecords.put(callback.asBinder(), record); - } - }); - } - - @Override - public void unregisterClient(IMediaRouteClientCallback callback) { - unregisterClient(callback, false); - } - - void unregisterClient(final IMediaRouteClientCallback callback, - final boolean died) { - mHandler.post(new Runnable() { - @Override - public void run() { - ClientRecord record = mClientRecords.remove(callback.asBinder()); - if (record == null) { - return; // spurious - } - - if (DEBUG) { - Log.d(TAG, "unregisterClient: client=" + record.getClientInfo() - + ", died=" + died); - } - - record.release(); - callback.asBinder().unlinkToDeath(record, 0); - } - }); - } - - @Override - public void startDiscovery(final IMediaRouteClientCallback callback, - final int seq, final List<MediaRouteSelector> selectors, - final int flags) { - mHandler.post(new Runnable() { - @Override - public void run() { - ClientRecord record = mClientRecords.get(callback.asBinder()); - if (record == null) { - return; // spurious - } - - if (DEBUG) { - Log.d(TAG, "startDiscovery: client=" + record.getClientInfo() - + ", seq=" + seq + ", selectors=" + selectors - + ", flags=0x" + Integer.toHexString(flags)); - } - record.startDiscovery(seq, selectors, flags); - } - }); - } - - @Override - public void stopDiscovery(final IMediaRouteClientCallback callback) { - mHandler.post(new Runnable() { - @Override - public void run() { - ClientRecord record = mClientRecords.get(callback.asBinder()); - if (record == null) { - return; // spurious - } - - if (DEBUG) { - Log.d(TAG, "stopDiscovery: client=" + record.getClientInfo()); - } - record.stopDiscovery(); - } - }); - } - - @Override - public void connect(final IMediaRouteClientCallback callback, - final int seq, final String destinationId, final String routeId, - final int flags, final Bundle extras) { - mHandler.post(new Runnable() { - @Override - public void run() { - ClientRecord record = mClientRecords.get(callback.asBinder()); - if (record == null) { - return; // spurious - } - - if (DEBUG) { - Log.d(TAG, "connect: client=" + record.getClientInfo() - + ", seq=" + seq + ", destinationId=" + destinationId - + ", routeId=" + routeId - + ", flags=0x" + Integer.toHexString(flags) - + ", extras=" + extras); - } - record.connect(seq, destinationId, routeId, flags, extras); - } - }); - } - - @Override - public void disconnect(final IMediaRouteClientCallback callback) { - mHandler.post(new Runnable() { - @Override - public void run() { - ClientRecord record = mClientRecords.get(callback.asBinder()); - if (record == null) { - return; // spurious - } - - if (DEBUG) { - Log.d(TAG, "disconnect: client=" + record.getClientInfo()); - } - record.disconnect(); - } - }); - } - - @Override - public void pauseStream(final IMediaRouteClientCallback callback) { - mHandler.post(new Runnable() { - @Override - public void run() { - ClientRecord record = mClientRecords.get(callback.asBinder()); - if (record == null) { - return; // spurious - } - - if (DEBUG) { - Log.d(TAG, "pauseStream: client=" + record.getClientInfo()); - } - record.pauseStream(); - } - }); - } - - @Override - public void resumeStream(final IMediaRouteClientCallback callback) { - mHandler.post(new Runnable() { - @Override - public void run() { - ClientRecord record = mClientRecords.get(callback.asBinder()); - if (record == null) { - return; // spurious - } - - if (DEBUG) { - Log.d(TAG, "resumeStream: client=" + record.getClientInfo()); - } - record.resumeStream(); - } - }); - } - } - - // Must be accessed on handler - private final class ClientRecord implements IBinder.DeathRecipient { - private final IMediaRouteClientCallback mClientCallback; - private final ClientInfo mClient; - private final ClientSession mSession; - - private int mDiscoverySeq; - private DiscoveryRequest mDiscoveryRequest; - private DiscoveryCallback mDiscoveryCallback; - private final ArrayMap<String, DestinationRecord> mDestinations = - new ArrayMap<String, DestinationRecord>(); - - private int mConnectionSeq; - private ConnectionRequest mConnectionRequest; - private ConnectionCallback mConnectionCallback; - private ConnectionInfo mConnection; - private boolean mConnectionPaused; - - public ClientRecord(IMediaRouteClientCallback callback, - ClientInfo client, ClientSession session) { - mClientCallback = callback; - mClient = client; - mSession = session; - } - - // Invoked on binder thread unlike all other methods in this class. - @Override - public void binderDied() { - mService.unregisterClient(mClientCallback, true); - } - - public ClientInfo getClientInfo() { - return mClient; - } - - public void release() { - stopDiscovery(); - disconnect(); - } - - public void startDiscovery(int seq, List<MediaRouteSelector> selectors, - int flags) { - stopDiscovery(); - - mDiscoverySeq = seq; - mDiscoveryRequest = new DiscoveryRequest(selectors); - mDiscoveryRequest.setFlags(flags); - mDiscoveryCallback = new DiscoveryCallback(this); - boolean started = mSession.onStartDiscovery(mDiscoveryRequest, mDiscoveryCallback); - if (!started) { - dispatchDiscoveryFailed(mDiscoveryCallback, - MediaRouter.DISCOVERY_ERROR_ABORTED, null, null); - clearDiscovery(); - } - } - - public void stopDiscovery() { - if (mDiscoveryRequest != null) { - mSession.onStopDiscovery(); - clearDiscovery(); - } - } - - private void clearDiscovery() { - mDestinations.clear(); - mDiscoveryRequest = null; - mDiscoveryCallback = null; - } - - public void connect(int seq, String destinationId, String routeId, - int flags, Bundle extras) { - disconnect(); - - mConnectionSeq = seq; - mConnectionCallback = new ConnectionCallback(this); - - DestinationRecord destinationRecord = mDestinations.get(destinationId); - if (destinationRecord == null) { - Log.w(TAG, "Aborting connection to route since no matching destination " - + "was found in the list of known destinations: " - + "destinationId=" + destinationId); - dispatchConnectionFailed(mConnectionCallback, - MediaRouter.CONNECTION_ERROR_ABORTED, null, null); - clearConnection(); - return; - } - - RouteInfo route = destinationRecord.getRoute(routeId); - if (route == null) { - Log.w(TAG, "Aborting connection to route since no matching route " - + "was found in the list of known routes: " - + "destination=" + destinationRecord.destination - + ", routeId=" + routeId); - dispatchConnectionFailed(mConnectionCallback, - MediaRouter.CONNECTION_ERROR_ABORTED, null, null); - clearConnection(); - return; - } - - mConnectionRequest = new ConnectionRequest(route); - mConnectionRequest.setFlags(flags); - mConnectionRequest.setExtras(extras); - boolean started = mSession.onConnect(mConnectionRequest, mConnectionCallback); - if (!started) { - dispatchConnectionFailed(mConnectionCallback, - MediaRouter.CONNECTION_ERROR_ABORTED, null, null); - clearConnection(); - } - } - - public void disconnect() { - if (mConnectionRequest != null) { - mSession.onDisconnect(); - clearConnection(); - } - } - - private void clearConnection() { - mConnectionRequest = null; - mConnectionCallback = null; - if (mConnection != null) { - mConnection.close(); - mConnection = null; - } - mConnectionPaused = false; - } - - public void pauseStream() { - if (mConnectionRequest != null && !mConnectionPaused) { - mConnectionPaused = true; - mSession.onPauseStream(); - } - } - - public void resumeStream() { - if (mConnectionRequest != null && mConnectionPaused) { - mConnectionPaused = false; - mSession.onResumeStream(); - } - } - - public void dispatchDestinationFound(DiscoveryCallback callback, - DestinationInfo destination, List<RouteInfo> routes) { - if (callback == mDiscoveryCallback) { - if (DEBUG) { - Log.d(TAG, "destinationFound: destination=" + destination - + ", routes=" + routes); - } - mDestinations.put(destination.getId(), - new DestinationRecord(destination, routes)); - - ParcelableDestinationInfo pdi = new ParcelableDestinationInfo(); - pdi.id = destination.getId(); - pdi.name = destination.getName(); - pdi.description = destination.getDescription(); - pdi.iconResourceId = destination.getIconResourceId(); - pdi.extras = destination.getExtras(); - ArrayList<ParcelableRouteInfo> pris = new ArrayList<ParcelableRouteInfo>(); - for (RouteInfo route : routes) { - int selectorIndex = mDiscoveryRequest.getSelectors().indexOf( - route.getSelector()); - if (selectorIndex < 0) { - Log.w(TAG, "Ignoring route because the selector does not match " - + "any of those that were originally supplied by the " - + "client's discovery request: destination=" + destination - + ", route=" + route); - continue; - } - - ParcelableRouteInfo pri = new ParcelableRouteInfo(); - pri.id = route.getId(); - pri.selectorIndex = selectorIndex; - pri.features = route.getFeatures(); - pri.protocols = route.getProtocols().toArray( - new String[route.getProtocols().size()]); - pri.extras = route.getExtras(); - pris.add(pri); - } - try { - mClientCallback.onDestinationFound(mDiscoverySeq, pdi, - pris.toArray(new ParcelableRouteInfo[pris.size()])); - } catch (RemoteException ex) { - // binder death handled elsewhere - } - } - } - - public void dispatchDestinationLost(DiscoveryCallback callback, - DestinationInfo destination) { - if (callback == mDiscoveryCallback) { - if (DEBUG) { - Log.d(TAG, "destinationLost: destination=" + destination); - } - - if (mDestinations.get(destination.getId()).destination == destination) { - mDestinations.remove(destination.getId()); - try { - mClientCallback.onDestinationLost(mDiscoverySeq, destination.getId()); - } catch (RemoteException ex) { - // binder death handled elsewhere - } - } - } - } - - public void dispatchDiscoveryFailed(DiscoveryCallback callback, - int error, CharSequence message, Bundle extras) { - if (callback == mDiscoveryCallback) { - if (DEBUG) { - Log.d(TAG, "discoveryFailed: error=" + error + ", message=" + message - + ", extras=" + extras); - } - - try { - mClientCallback.onDiscoveryFailed(mDiscoverySeq, error, message, extras); - } catch (RemoteException ex) { - // binder death handled elsewhere - } - } - } - - public void dispatchConnected(ConnectionCallback callback, ConnectionInfo connection) { - if (callback == mConnectionCallback) { - if (DEBUG) { - Log.d(TAG, "connected: connection=" + connection); - } - if (mConnection == null) { - mConnection = connection; - - ParcelableConnectionInfo pci = new ParcelableConnectionInfo(); - pci.audioAttributes = connection.getAudioAttributes(); - pci.presentationDisplayId = connection.getPresentationDisplay() != null ? - connection.getPresentationDisplay().getDisplayId() : -1; - pci.protocolBinders = new IBinder[connection.getProtocols().size()]; - for (int i = 0; i < pci.protocolBinders.length; i++) { - pci.protocolBinders[i] = connection.getProtocolBinder(i); - } - pci.extras = connection.getExtras(); - try { - mClientCallback.onConnected(mConnectionSeq, pci); - } catch (RemoteException ex) { - // binder death handled elsewhere - } - } else { - Log.w(TAG, "Media route service called onConnected() while already " - + "connected."); - } - } - } - - public void dispatchDisconnected(ConnectionCallback callback) { - if (callback == mConnectionCallback) { - if (DEBUG) { - Log.d(TAG, "disconnected"); - } - - if (mConnection != null) { - mConnection.close(); - mConnection = null; - - try { - mClientCallback.onDisconnected(mConnectionSeq); - } catch (RemoteException ex) { - // binder death handled elsewhere - } - } - } - } - - public void dispatchConnectionFailed(ConnectionCallback callback, - int error, CharSequence message, Bundle extras) { - if (callback == mConnectionCallback) { - if (DEBUG) { - Log.d(TAG, "connectionFailed: error=" + error + ", message=" + message - + ", extras=" + extras); - } - - try { - mClientCallback.onConnectionFailed(mConnectionSeq, error, message, extras); - } catch (RemoteException ex) { - // binder death handled elsewhere - } - } - } - } - - private static final class DestinationRecord { - public final DestinationInfo destination; - public final List<RouteInfo> routes; - - public DestinationRecord(DestinationInfo destination, List<RouteInfo> routes) { - this.destination = destination; - this.routes = routes; - } - - public RouteInfo getRoute(String routeId) { - final int count = routes.size(); - for (int i = 0; i < count; i++) { - RouteInfo route = routes.get(i); - if (route.getId().equals(routeId)) { - return route; - } - } - return null; - } - } -} diff --git a/media/java/android/media/routing/MediaRouter.java b/media/java/android/media/routing/MediaRouter.java deleted file mode 100644 index 4f6d324..0000000 --- a/media/java/android/media/routing/MediaRouter.java +++ /dev/null @@ -1,1886 +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.media.routing; - -import android.annotation.DrawableRes; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.Presentation; -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ServiceInfo; -import android.graphics.drawable.Drawable; -import android.hardware.display.DisplayManager; -import android.media.AudioAttributes; -import android.media.AudioManager; -import android.media.AudioTrack; -import android.media.VolumeProvider; -import android.media.session.MediaController; -import android.media.session.MediaSession; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.IInterface; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.view.Display; - -import java.io.Closeable; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; - -/** - * Media router allows applications to discover, connect to, control, - * and send content to nearby media devices known as destinations. - * <p> - * There are generally two participants involved in media routing: an - * application that wants to send media content to a destination and a - * {@link MediaRouteService media route service} that provides the - * service of transporting that content where it needs to go on behalf of the - * application. - * </p><p> - * To send media content to a destination, the application must ask the system - * to discover available routes to destinations that provide certain capabilities, - * establish a connection to a route, then send messages through the connection to - * control the routing of audio and video streams, launch remote applications, - * and invoke other functions of the destination. - * </p><p> - * Media router objects are thread-safe. - * </p> - * - * <h3>Destinations</h3> - * <p> - * The media devices to which an application may send media content are referred - * to in the API as destinations. Each destination therefore represents a single - * independent device such as a speaker or TV set. Destinations are given meaningful - * names and descriptions to help the user associate them with devices in their - * environment. - * </p><p> - * Destinations may be local or remote and may be accessed through various means, - * often wirelessly. The user may install media route services to enable - * media applications to connect to a variety of destinations with different - * capabilities. - * </p> - * - * <h3>Routes</h3> - * <p> - * Routes represent possible usages or means of reaching and interacting with - * a destination. Since destinations may support many different features, they may - * each offer multiple routes for applications to choose from based on their needs. - * For example, one route might express the ability to stream locally rendered audio - * and video to the device; another route might express the ability to send a URL for - * the destination to download from the network and play all by itself. - * </p><p> - * Routes are discovered according to the set of capabilities that - * an application or the system is seeking to use at a particular time. For example, - * if an application wants to stream music to a destination then it will ask the - * {@link MediaRouter} to find routes to destinations can stream music and ignore - * all other destinations that cannot. - * </p><p> - * In general, the application will inspect the set of routes that have been - * offered then connect to the most appropriate route for its desired purpose. - * </p> - * - * <h3>Route Selection</h3> - * <p> - * When the user open the media route chooser activity, the system will display - * a list of nearby media destinations which have been discovered. After the - * choice is made the application may connect to one of the routes offered by - * this destination and begin communicating with the destination. - * </p><p> - * Destinations are located through a process called discovery. During discovery, - * the system will start installed {@link MediaRouteService media route services} - * to scan the network for nearby devices that offer the kinds of capabilities that the - * application is seeking to use. The application specifies the capabilities it requires by - * adding {@link MediaRouteSelector media route selectors} to the media router - * using the {@link #addSelector} method. Only destinations that provide routes - * which satisfy at least one of these media route selectors will be discovered. - * </p><p> - * Once the user has selected a destination, the application will be given a chance - * to choose one of the routes to which it would like to connect. The application - * may switch to a different route from the same destination at a later time but - * in order to connect to a new destination, the application must once again launch - * the media route chooser activity to ask the user to choose a destination. - * </p> - * - * <h3>Route Protocols</h3> - * <p> - * Route protocols express capabilities offered by routes. Each media route selector - * must specify at least one required protocol by which the routes will be selected. - * </p><p> - * The framework provides several predefined <code>MediaRouteProtocols</code> which are - * defined in the <code>android-support-media-protocols.jar</code> support library. - * Applications must statically link this library to make use of these protocols. - * </p><p> - * The static library approach is used to enable ongoing extension and refinement - * of protocols in the SDK and interoperability with the media router implementation - * for older platform versions which is offered by the framework support library. - * </p><p> - * Media route services may also define custom media route protocols of their own - * to enable applications to access specialized capabilities of certain destinations - * assuming they have linked in the required protocol code. - * </p><p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> for more information. - * </p> - * - * <h3>Connections</h3> - * <p> - * After connecting to a media route, the application can send commands to - * the route using any of the protocols that it requested. If the route supports live - * audio or video streaming then the application can create an {@link AudioTrack} or - * {@link Presentation} to route locally generated content to the destination. - * </p> - * - * <h3>Delegation</h3> - * <p> - * The creator of the media router is responsible for establishing the policy for - * discovering and connecting to destinations. UI components may observe the state - * of the media router by {@link #createDelegate creating} a {@link Delegate}. - * </p><p> - * The media router should also be attached to the {@link MediaSession media session} - * that is handling media playback lifecycle. This will allow - * authorized {@link MediaController media controllers}, possibly running in other - * processes, to provide UI to examine and change the media destination by - * {@link MediaController#createMediaRouterDelegate creating} a {@link Delegate} - * for the media router associated with the session. - * </p> - */ -public final class MediaRouter { - private final DisplayManager mDisplayManager; - - private final Object mLock = new Object(); - - private RoutingCallback mRoutingCallback; - private Handler mRoutingCallbackHandler; - - private boolean mReleased; - private int mDiscoveryState; - private int mConnectionState; - private final ArrayList<MediaRouteSelector> mSelectors = - new ArrayList<MediaRouteSelector>(); - private final ArrayMap<DestinationInfo, List<RouteInfo>> mDiscoveredDestinations = - new ArrayMap<DestinationInfo, List<RouteInfo>>(); - private RouteInfo mSelectedRoute; - private ConnectionInfo mConnection; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { DISCOVERY_STATE_STOPPED, DISCOVERY_STATE_STARTED }) - public @interface DiscoveryState { } - - /** - * Discovery state: Discovery is not currently in progress. - */ - public static final int DISCOVERY_STATE_STOPPED = 0; - - /** - * Discovery state: Discovery is being performed. - */ - public static final int DISCOVERY_STATE_STARTED = 1; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = { DISCOVERY_FLAG_BACKGROUND }) - public @interface DiscoveryFlags { } - - /** - * Discovery flag: Indicates that the client has requested passive discovery in - * the background. The media route service should try to use less power and rely - * more on its internal caches to minimize its impact. - */ - public static final int DISCOVERY_FLAG_BACKGROUND = 1 << 0; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { DISCOVERY_ERROR_UNKNOWN, DISCOVERY_ERROR_ABORTED, - DISCOVERY_ERROR_NO_CONNECTIVITY }) - public @interface DiscoveryError { } - - /** - * Discovery error: Unknown error; refer to the error message for details. - */ - public static final int DISCOVERY_ERROR_UNKNOWN = 0; - - /** - * Discovery error: The media router or media route service has decided not to - * handle the discovery request for some reason. - */ - public static final int DISCOVERY_ERROR_ABORTED = 1; - - /** - * Discovery error: The media route service is unable to perform discovery - * due to a lack of connectivity such as because the radio is disabled. - */ - public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING, - CONNECTION_STATE_CONNECTED }) - public @interface ConnectionState { } - - /** - * Connection state: No destination has been selected. Media content should - * be sent to the default output. - */ - public static final int CONNECTION_STATE_DISCONNECTED = 0; - - /** - * Connection state: The application is in the process of connecting to - * a route offered by the selected destination. - */ - public static final int CONNECTION_STATE_CONNECTING = 1; - - /** - * Connection state: The application has connected to a route offered by - * the selected destination. - */ - public static final int CONNECTION_STATE_CONNECTED = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = { CONNECTION_FLAG_BARGE }) - public @interface ConnectionFlags { } - - /** - * Connection flag: Indicates that the client has requested to barge in and evict - * other clients that might have already connected to the destination and that - * would otherwise prevent this client from connecting. When this flag is not - * set, the media route service should be polite and report - * {@link MediaRouter#CONNECTION_ERROR_BUSY} in case the destination is - * already occupied and cannot accept additional connections. - */ - public static final int CONNECTION_FLAG_BARGE = 1 << 0; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { CONNECTION_ERROR_UNKNOWN, CONNECTION_ERROR_ABORTED, - CONNECTION_ERROR_UNAUTHORIZED, CONNECTION_ERROR_UNAUTHORIZED, - CONNECTION_ERROR_BUSY, CONNECTION_ERROR_TIMEOUT, CONNECTION_ERROR_BROKEN }) - public @interface ConnectionError { } - - /** - * Connection error: Unknown error; refer to the error message for details. - */ - public static final int CONNECTION_ERROR_UNKNOWN = 0; - - /** - * Connection error: The media router or media route service has decided not to - * handle the connection request for some reason. - */ - public static final int CONNECTION_ERROR_ABORTED = 1; - - /** - * Connection error: The device has refused the connection from this client. - * This error should be avoided because the media route service should attempt - * to filter out devices that the client cannot access as it performs discovery - * on behalf of that client. - */ - public static final int CONNECTION_ERROR_UNAUTHORIZED = 2; - - /** - * Connection error: The device is unreachable over the network. - */ - public static final int CONNECTION_ERROR_UNREACHABLE = 3; - - /** - * Connection error: The device is already busy serving another client and - * the connection request did not ask to barge in. - */ - public static final int CONNECTION_ERROR_BUSY = 4; - - /** - * Connection error: A timeout occurred during connection. - */ - public static final int CONNECTION_ERROR_TIMEOUT = 5; - - /** - * Connection error: The connection to the device was severed unexpectedly. - */ - public static final int CONNECTION_ERROR_BROKEN = 6; - - /** - * Connection error: The connection was terminated because a different client barged - * in and took control of the destination. - */ - public static final int CONNECTION_ERROR_BARGED = 7; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { DISCONNECTION_REASON_APPLICATION_REQUEST, - DISCONNECTION_REASON_USER_REQUEST, DISCONNECTION_REASON_ERROR }) - public @interface DisconnectionReason { } - - /** - * Disconnection reason: The application requested disconnection itself. - */ - public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0; - - /** - * Disconnection reason: The user requested disconnection. - */ - public static final int DISCONNECTION_REASON_USER_REQUEST = 1; - - /** - * Disconnection reason: An error occurred. - */ - public static final int DISCONNECTION_REASON_ERROR = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = { ROUTE_FEATURE_LIVE_AUDIO, ROUTE_FEATURE_LIVE_VIDEO }) - public @interface RouteFeatures { } - - /** - * Route feature: Live audio. - * <p> - * A route that supports live audio streams audio rendered by the application - * to the destination. - * </p><p> - * To take advantage of live audio routing, the application must render its - * media using the audio attributes specified by {@link #getPreferredAudioAttributes}. - * </p> - * - * @see #getPreferredAudioAttributes - * @see android.media.AudioAttributes - */ - public static final int ROUTE_FEATURE_LIVE_AUDIO = 1 << 0; - - /** - * Route feature: Live video. - * <p> - * A route that supports live video streams video rendered by the application - * to the destination. - * </p><p> - * To take advantage of live video routing, the application must render its - * media to a {@link android.app.Presentation presentation window} on the - * display specified by {@link #getPreferredPresentationDisplay}. - * </p> - * - * @see #getPreferredPresentationDisplay - * @see android.app.Presentation - */ - public static final int ROUTE_FEATURE_LIVE_VIDEO = 1 << 1; - - /** - * Creates a media router. - * - * @param context The context with which the router is associated. - */ - public MediaRouter(@NonNull Context context) { - if (context == null) { - throw new IllegalArgumentException("context must not be null"); - } - - mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); - } - - /** @hide */ - public IMediaRouter getBinder() { - // todo - return null; - } - - /** - * Disconnects from the selected destination and releases the media router. - * <p> - * This method should be called by the application when it no longer requires - * the media router to ensure that all bound resources may be cleaned up. - * </p> - */ - public void release() { - synchronized (mLock) { - mReleased = true; - // todo - } - } - - /** - * Returns true if the media router has been released. - */ - public boolean isReleased() { - synchronized (mLock) { - return mReleased; - } - } - - /** - * Gets the current route discovery state. - * - * @return The current discovery state: one of {@link #DISCOVERY_STATE_STOPPED}, - * {@link #DISCOVERY_STATE_STARTED}. - */ - public @DiscoveryState int getDiscoveryState() { - synchronized (mLock) { - return mDiscoveryState; - } - } - - /** - * Gets the current route connection state. - * - * @return The current state: one of {@link #CONNECTION_STATE_DISCONNECTED}, - * {@link #CONNECTION_STATE_CONNECTING} or {@link #CONNECTION_STATE_CONNECTED}. - */ - public @ConnectionState int getConnectionState() { - synchronized (mLock) { - return mConnectionState; - } - } - - /** - * Creates a media router delegate through which the destination of the media - * router may be controlled. - * <p> - * This is the point of entry for UI code that initiates discovery and - * connection to routes. - * </p> - */ - public @NonNull Delegate createDelegate() { - return null; // todo - } - - /** - * Sets a callback to participate in route discovery, filtering, and connection - * establishment. - * - * @param callback The callback to set, or null if none. - * @param handler The handler to receive callbacks, or null to use the current thread. - */ - public void setRoutingCallback(@Nullable RoutingCallback callback, - @Nullable Handler handler) { - synchronized (mLock) { - if (callback == null) { - mRoutingCallback = null; - mRoutingCallbackHandler = null; - } else { - mRoutingCallback = callback; - mRoutingCallbackHandler = handler != null ? handler : new Handler(); - } - } - } - - /** - * Adds a media route selector to use to find destinations that have - * routes with the specified capabilities during route discovery. - */ - public void addSelector(@NonNull MediaRouteSelector selector) { - if (selector == null) { - throw new IllegalArgumentException("selector must not be null"); - } - - synchronized (mLock) { - if (!mSelectors.contains(selector)) { - mSelectors.add(selector); - // todo - } - } - } - - /** - * Removes a media route selector. - */ - public void removeSelector(@NonNull MediaRouteSelector selector) { - if (selector == null) { - throw new IllegalArgumentException("selector must not be null"); - } - - synchronized (mLock) { - if (mSelectors.remove(selector)) { - // todo - } - } - } - - /** - * Removes all media route selectors. - * <p> - * Note that at least one selector must be added in order to perform discovery. - * </p> - */ - public void clearSelectors() { - synchronized (mLock) { - if (!mSelectors.isEmpty()) { - mSelectors.clear(); - // todo - } - } - } - - /** - * Gets a list of all media route selectors to consider during discovery. - */ - public @NonNull List<MediaRouteSelector> getSelectors() { - synchronized (mLock) { - return new ArrayList<MediaRouteSelector>(mSelectors); - } - } - - /** - * Gets the connection to the currently selected route. - * - * @return The connection to the currently selected route, or null if not connected. - */ - public @NonNull ConnectionInfo getConnection() { - synchronized (mLock) { - return mConnection; - } - } - - /** - * Gets the list of discovered destinations. - * <p> - * This list is only valid while discovery is running and is null otherwise. - * </p> - * - * @return The list of discovered destinations, or null if discovery is not running. - */ - public @NonNull List<DestinationInfo> getDiscoveredDestinations() { - synchronized (mLock) { - if (mDiscoveryState == DISCOVERY_STATE_STARTED) { - return new ArrayList<DestinationInfo>(mDiscoveredDestinations.keySet()); - } - return null; - } - } - - /** - * Gets the list of discovered routes for a particular destination. - * <p> - * This list is only valid while discovery is running and is null otherwise. - * </p> - * - * @param destination The destination for which to get the list of discovered routes. - * @return The list of discovered routes for the destination, or null if discovery - * is not running. - */ - public @NonNull List<RouteInfo> getDiscoveredRoutes(@NonNull DestinationInfo destination) { - if (destination == null) { - throw new IllegalArgumentException("destination must not be null"); - } - synchronized (mLock) { - if (mDiscoveryState == DISCOVERY_STATE_STARTED) { - List<RouteInfo> routes = mDiscoveredDestinations.get(destination); - if (routes != null) { - return new ArrayList<RouteInfo>(routes); - } - } - return null; - } - } - - /** - * Gets the destination that has been selected. - * - * @return The selected destination, or null if disconnected. - */ - public @Nullable DestinationInfo getSelectedDestination() { - synchronized (mLock) { - return mSelectedRoute != null ? mSelectedRoute.getDestination() : null; - } - } - - /** - * Gets the route that has been selected. - * - * @return The selected destination, or null if disconnected. - */ - public @Nullable RouteInfo getSelectedRoute() { - synchronized (mLock) { - return mSelectedRoute; - } - } - - /** - * Gets the preferred audio attributes that should be used to stream live audio content - * based on the connected route. - * <p> - * Use an {@link AudioTrack} to send audio content to the destination with these - * audio attributes. - * </p><p> - * The preferred audio attributes may change when a connection is established but it - * will remain constant until disconnected. - * </p> - * - * @return The preferred audio attributes to use. When connected, returns the - * route's audio attributes or null if it does not support live audio streaming. - * Otherwise returns audio attributes associated with {@link AudioAttributes#USAGE_MEDIA}. - */ - public @Nullable AudioAttributes getPreferredAudioAttributes() { - synchronized (mLock) { - if (mConnection != null) { - return mConnection.getAudioAttributes(); - } - return new AudioAttributes.Builder() - .setLegacyStreamType(AudioManager.STREAM_MUSIC) - .build(); - } - } - - /** - * Gets the preferred presentation display that should be used to stream live video content - * based on the connected route. - * <p> - * Use a {@link Presentation} to send video content to the destination with this display. - * </p><p> - * The preferred presentation display may change when a connection is established but it - * will remain constant until disconnected. - * </p> - * - * @return The preferred presentation display to use. When connected, returns - * the route's presentation display or null if it does not support live video - * streaming. Otherwise returns the first available - * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION presentation display}, - * such as a mirrored wireless or HDMI display or null if none. - */ - public @Nullable Display getPreferredPresentationDisplay() { - synchronized (mLock) { - if (mConnection != null) { - return mConnection.getPresentationDisplay(); - } - Display[] displays = mDisplayManager.getDisplays( - DisplayManager.DISPLAY_CATEGORY_PRESENTATION); - return displays.length != 0 ? displays[0] : null; - } - } - - /** - * Gets the preferred volume provider that should be used to control the volume - * of content rendered on the currently selected route. - * <p> - * The preferred volume provider may change when a connection is established but it - * will remain the same until disconnected. - * </p> - * - * @return The preferred volume provider to use, or null if the currently - * selected route does not support remote volume adjustment or if the connection - * is not yet established. If no route is selected, returns null to indicate - * that system volume control should be used. - */ - public @Nullable VolumeProvider getPreferredVolumeProvider() { - synchronized (mLock) { - if (mConnection != null) { - return mConnection.getVolumeProvider(); - } - return null; - } - } - - /** - * Requests to pause streaming of live audio or video routes. - * Should be called when the application is going into the background and is - * no longer rendering content locally. - * <p> - * This method does nothing unless a connection has been established. - * </p> - */ - public void pauseStream() { - // todo - } - - /** - * Requests to resume streaming of live audio or video routes. - * May be called when the application is returning to the foreground and is - * about to resume rendering content locally. - * <p> - * This method does nothing unless a connection has been established. - * </p> - */ - public void resumeStream() { - // todo - } - - /** - * This class is used by UI components to let the user discover and - * select a destination to which the media router should connect. - * <p> - * This API has somewhat more limited functionality than the {@link MediaRouter} - * itself because it is designed to allow applications to control - * the destination of media router instances that belong to other processes. - * </p><p> - * To control the destination of your own media router, call - * {@link #createDelegate} to obtain a local delegate object. - * </p><p> - * To control the destination of a media router that belongs to another process, - * first obtain a {@link MediaController} that is associated with the media playback - * that is occurring in that process, then call - * {@link MediaController#createMediaRouterDelegate} to obtain an instance of - * its destination controls. Note that special permissions may be required to - * obtain the {@link MediaController} instance in the first place. - * </p> - */ - public static final class Delegate { - /** - * Returns true if the media router has been released. - */ - public boolean isReleased() { - // todo - return false; - } - - /** - * Gets the current route discovery state. - * - * @return The current discovery state: one of {@link #DISCOVERY_STATE_STOPPED}, - * {@link #DISCOVERY_STATE_STARTED}. - */ - public @DiscoveryState int getDiscoveryState() { - // todo - return -1; - } - - /** - * Gets the current route connection state. - * - * @return The current state: one of {@link #CONNECTION_STATE_DISCONNECTED}, - * {@link #CONNECTION_STATE_CONNECTING} or {@link #CONNECTION_STATE_CONNECTED}. - */ - public @ConnectionState int getConnectionState() { - // todo - return -1; - } - - /** - * Gets the currently selected destination. - * - * @return The destination information, or null if none. - */ - public @Nullable DestinationInfo getSelectedDestination() { - return null; - } - - /** - * Gets the list of discovered destinations. - * <p> - * This list is only valid while discovery is running and is null otherwise. - * </p> - * - * @return The list of discovered destinations, or null if discovery is not running. - */ - public @NonNull List<DestinationInfo> getDiscoveredDestinations() { - return null; - } - - /** - * Adds a callback to receive state changes. - * - * @param callback The callback to set, or null if none. - * @param handler The handler to receive callbacks, or null to use the current thread. - */ - public void addStateCallback(@Nullable StateCallback callback, - @Nullable Handler handler) { - if (callback == null) { - throw new IllegalArgumentException("callback must not be null"); - } - if (handler == null) { - handler = new Handler(); - } - // todo - } - - /** - * Removes a callback for state changes. - * - * @param callback The callback to set, or null if none. - */ - public void removeStateCallback(@Nullable StateCallback callback) { - // todo - } - - /** - * Starts performing discovery. - * <p> - * Performing discovery is expensive. Make sure to call {@link #stopDiscovery} - * as soon as possible once a new destination has been selected to allow the system - * to stop services associated with discovery. - * </p> - * - * @param flags The discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}. - */ - public void startDiscovery(@DiscoveryFlags int flags) { - // todo - } - - /** - * Stops performing discovery. - */ - public void stopDiscovery() { - // todo - } - - /** - * Connects to a destination during route discovery. - * <p> - * This method may only be called while route discovery is active and the - * destination appears in the - * {@link #getDiscoveredDestinations list of discovered destinations}. - * If the media router is already connected to a route then it will first disconnect - * from the current route then connect to the new route. - * </p> - * - * @param destination The destination to which the media router should connect. - * @param flags The connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}. - */ - public void connect(@NonNull DestinationInfo destination, @DiscoveryFlags int flags) { - // todo - } - - /** - * Disconnects from the currently selected destination. - * <p> - * Does nothing if not currently connected. - * </p> - * - * @param reason The reason for the disconnection: one of - * {@link #DISCONNECTION_REASON_APPLICATION_REQUEST}, - * {@link #DISCONNECTION_REASON_USER_REQUEST}, or {@link #DISCONNECTION_REASON_ERROR}. - */ - public void disconnect(@DisconnectionReason int reason) { - // todo - } - } - - /** - * Describes immutable properties of a connection to a route. - */ - public static final class ConnectionInfo { - private final RouteInfo mRoute; - private final AudioAttributes mAudioAttributes; - private final Display mPresentationDisplay; - private final VolumeProvider mVolumeProvider; - private final IBinder[] mProtocolBinders; - private final Object[] mProtocolInstances; - private final Bundle mExtras; - private final ArrayList<Closeable> mCloseables; - - private static final Class<?>[] MEDIA_ROUTE_PROTOCOL_CTOR_PARAMETERS = - new Class<?>[] { IBinder.class }; - - ConnectionInfo(RouteInfo route, - AudioAttributes audioAttributes, Display display, - VolumeProvider volumeProvider, IBinder[] protocolBinders, - Bundle extras, ArrayList<Closeable> closeables) { - mRoute = route; - mAudioAttributes = audioAttributes; - mPresentationDisplay = display; - mVolumeProvider = volumeProvider; - mProtocolBinders = protocolBinders; - mProtocolInstances = new Object[mProtocolBinders.length]; - mExtras = extras; - mCloseables = closeables; - } - - /** - * Gets the route that is connected. - */ - public @NonNull RouteInfo getRoute() { - return mRoute; - } - - /** - * Gets the audio attributes which the client should use to stream audio - * to the destination, or null if the route does not support live audio streaming. - */ - public @Nullable AudioAttributes getAudioAttributes() { - return mAudioAttributes; - } - - /** - * Gets the display which the client should use to stream video to the - * destination using a {@link Presentation}, or null if the route does not - * support live video streaming. - */ - public @Nullable Display getPresentationDisplay() { - return mPresentationDisplay; - } - - /** - * Gets the route's volume provider, or null if none. - */ - public @Nullable VolumeProvider getVolumeProvider() { - return mVolumeProvider; - } - - /** - * Gets the set of supported route features. - */ - public @RouteFeatures int getFeatures() { - return mRoute.getFeatures(); - } - - /** - * Gets the list of supported route protocols. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - */ - public @NonNull List<String> getProtocols() { - return mRoute.getProtocols(); - } - - /** - * Gets an instance of a route protocol object that wraps the protocol binder - * and provides easy access to the protocol's functionality. - * <p> - * This is a convenience method which invokes {@link #getProtocolBinder(String)} - * using the name of the provided class then passes the resulting {@link IBinder} - * to a single-argument constructor of that class. - * </p><p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - */ - @SuppressWarnings("unchecked") - public @Nullable <T> T getProtocolObject(Class<T> clazz) { - int index = getProtocols().indexOf(clazz.getName()); - if (index < 0) { - return null; - } - if (mProtocolInstances[index] == null && mProtocolBinders[index] != null) { - final Constructor<T> ctor; - try { - ctor = clazz.getConstructor(MEDIA_ROUTE_PROTOCOL_CTOR_PARAMETERS); - } catch (NoSuchMethodException ex) { - throw new RuntimeException("Could not find public constructor " - + "with IBinder argument in protocol class: " + clazz.getName(), ex); - } - try { - mProtocolInstances[index] = ctor.newInstance(mProtocolBinders[index]); - } catch (InstantiationException | IllegalAccessException - | InvocationTargetException ex) { - throw new RuntimeException("Could create instance of protocol class: " - + clazz.getName(), ex); - } - } - return (T)mProtocolInstances[index]; - } - - /** - * Gets the {@link IBinder} that provides access to the specified route protocol - * or null if the protocol is not supported. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - */ - public @Nullable IBinder getProtocolBinder(@NonNull String name) { - int index = getProtocols().indexOf(name); - return index >= 0 ? mProtocolBinders[index] : null; - } - - /** - * Gets the {@link IBinder} that provides access to the specified route protocol - * at the given index in the protocol list or null if the protocol is not supported. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - */ - public @Nullable IBinder getProtocolBinder(int index) { - return mProtocolBinders[index]; - } - - /** - * Gets optional extra media route service or protocol specific information about - * the connection. Use the service or protocol name as the prefix for - * any extras to avoid namespace collisions. - */ - public @Nullable Bundle getExtras() { - return mExtras; - } - - /** - * Closes all closeables associated with the connection when the connection - * is being torn down. - */ - void close() { - final int count = mCloseables.size(); - for (int i = 0; i < count; i++) { - try { - mCloseables.get(i).close(); - } catch (IOException ex) { - } - } - } - - @Override - public @NonNull String toString() { - return "ConnectionInfo{ route=" + mRoute - + ", audioAttributes=" + mAudioAttributes - + ", presentationDisplay=" + mPresentationDisplay - + ", volumeProvider=" + mVolumeProvider - + ", protocolBinders=" + mProtocolBinders + " }"; - } - - /** - * Builds {@link ConnectionInfo} objects. - */ - public static final class Builder { - private final RouteInfo mRoute; - private AudioAttributes mAudioAttributes; - private Display mPresentationDisplay; - private VolumeProvider mVolumeProvider; - private final IBinder[] mProtocols; - private Bundle mExtras; - private final ArrayList<Closeable> mCloseables = new ArrayList<Closeable>(); - - /** - * Creates a builder for connection information. - * - * @param route The route that is connected. - */ - public Builder(@NonNull RouteInfo route) { - if (route == null) { - throw new IllegalArgumentException("route"); - } - mRoute = route; - mProtocols = new IBinder[route.getProtocols().size()]; - } - - /** - * Sets the audio attributes which the client should use to stream audio - * to the destination, or null if the route does not support live audio streaming. - */ - public @NonNull Builder setAudioAttributes( - @Nullable AudioAttributes audioAttributes) { - mAudioAttributes = audioAttributes; - return this; - } - - /** - * Sets the display which the client should use to stream video to the - * destination using a {@link Presentation}, or null if the route does not - * support live video streaming. - */ - public @NonNull Builder setPresentationDisplay(@Nullable Display display) { - mPresentationDisplay = display; - return this; - } - - /** - * Sets the route's volume provider, or null if none. - */ - public @NonNull Builder setVolumeProvider(@Nullable VolumeProvider provider) { - mVolumeProvider = provider; - return this; - } - - /** - * Sets the binder stub of a supported route protocol using - * the protocol's fully qualified class name. The protocol must be one - * of those that was indicated as being supported by the route. - * <p> - * If the stub implements {@link Closeable} then it will automatically - * be closed when the client disconnects from the route. - * </p><p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - */ - public @NonNull Builder setProtocolStub(@NonNull Class<?> clazz, - @NonNull IInterface stub) { - if (clazz == null) { - throw new IllegalArgumentException("clazz must not be null"); - } - if (stub == null) { - throw new IllegalArgumentException("stub must not be null"); - } - if (stub instanceof Closeable) { - mCloseables.add((Closeable)stub); - } - return setProtocolBinder(clazz.getName(), stub.asBinder()); - } - - /** - * Sets the binder interface of a supported route protocol by name. - * The protocol must be one of those that was indicated as being supported - * by the route. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - */ - public @NonNull Builder setProtocolBinder(@NonNull String name, - @NonNull IBinder binder) { - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must not be null or empty"); - } - if (binder == null) { - throw new IllegalArgumentException("binder must not be null"); - } - int index = mRoute.getProtocols().indexOf(name); - if (index < 0) { - throw new IllegalArgumentException("name must specify a protocol that " - + "the route actually declared that it supports: " - + "name=" + name + ", protocols=" + mRoute.getProtocols()); - } - mProtocols[index] = binder; - return this; - } - - /** - * Sets optional extra media route service or protocol specific information about - * the connection. Use the service or protocol name as the prefix for - * any extras to avoid namespace collisions. - */ - public @NonNull Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Builds the {@link ConnectionInfo} object. - */ - public @NonNull ConnectionInfo build() { - return new ConnectionInfo(mRoute, - mAudioAttributes, mPresentationDisplay, - mVolumeProvider, mProtocols, mExtras, mCloseables); - } - } - } - - /** - * Describes one particular way of routing media content to a destination - * according to the capabilities specified by a media route selector on behalf - * of an application. - */ - public static final class RouteInfo { - private final String mId; - private final DestinationInfo mDestination; - private final MediaRouteSelector mSelector; - private final int mFeatures; - private final ArrayList<String> mProtocols; - private final Bundle mExtras; - - RouteInfo(String id, DestinationInfo destination, MediaRouteSelector selector, - int features, ArrayList<String> protocols, Bundle extras) { - mId = id; - mDestination = destination; - mSelector = selector; - mFeatures = features; - mProtocols = protocols; - mExtras = extras; - } - - /** - * Gets the route's stable identifier. - * <p> - * The id is intended to uniquely identify the route among all routes that - * are offered by a particular destination in such a way that the client can - * refer to it at a later time. - * </p> - */ - public @NonNull String getId() { - return mId; - } - - /** - * Gets the destination that is offering this route. - */ - public @NonNull DestinationInfo getDestination() { - return mDestination; - } - - /** - * Gets the media route selector provided by the client for which this - * route was created. - * <p> - * It is implied that this route supports all of the required capabilities - * that were expressed in the selector. - * </p> - */ - public @NonNull MediaRouteSelector getSelector() { - return mSelector; - } - - /** - * Gets the set of supported route features. - */ - public @RouteFeatures int getFeatures() { - return mFeatures; - } - - /** - * Gets the list of supported route protocols. - * <p> - * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> - * for more information. - * </p> - */ - public @NonNull List<String> getProtocols() { - return mProtocols; - } - - /** - * Gets optional extra information about the route, or null if none. - */ - public @Nullable Bundle getExtras() { - return mExtras; - } - - @Override - public @NonNull String toString() { - return "RouteInfo{ id=" + mId + ", destination=" + mDestination - + ", features=0x" + Integer.toHexString(mFeatures) - + ", selector=" + mSelector + ", protocols=" + mProtocols - + ", extras=" + mExtras + " }"; - } - - /** - * Builds {@link RouteInfo} objects. - */ - public static final class Builder { - private final DestinationInfo mDestination; - private final String mId; - private final MediaRouteSelector mSelector; - private int mFeatures; - private final ArrayList<String> mProtocols = new ArrayList<String>(); - private Bundle mExtras; - - /** - * Creates a builder for route information. - * - * @param id The route's stable identifier. - * @param destination The destination of this route. - * @param selector The media route selector provided by the client for which - * this route was created. This must be one of the selectors that was - * included in the discovery request. - */ - public Builder(@NonNull String id, @NonNull DestinationInfo destination, - @NonNull MediaRouteSelector selector) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("id must not be null or empty"); - } - if (destination == null) { - throw new IllegalArgumentException("destination must not be null"); - } - if (selector == null) { - throw new IllegalArgumentException("selector must not be null"); - } - mDestination = destination; - mId = id; - mSelector = selector; - } - - /** - * Sets the set of supported route features. - */ - public @NonNull Builder setFeatures(@RouteFeatures int features) { - mFeatures = features; - return this; - } - - /** - * Adds a supported route protocol using its fully qualified class name. - * <p> - * If the protocol was not requested by the client in its selector - * then it will be silently discarded. - * </p> - */ - public @NonNull <T extends IInterface> Builder addProtocol(@NonNull Class<T> clazz) { - if (clazz == null) { - throw new IllegalArgumentException("clazz must not be null"); - } - return addProtocol(clazz.getName()); - } - - /** - * Adds a supported route protocol by name. - * <p> - * If the protocol was not requested by the client in its selector - * then it will be silently discarded. - * </p> - */ - public @NonNull Builder addProtocol(@NonNull String name) { - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must not be null"); - } - if (mSelector.containsProtocol(name)) { - mProtocols.add(name); - } - return this; - } - - /** - * Sets optional extra information about the route, or null if none. - */ - public @NonNull Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Builds the {@link RouteInfo} object. - * <p> - * Ensures that all required protocols have been supplied. - * </p> - */ - public @NonNull RouteInfo build() { - int missingFeatures = mSelector.getRequiredFeatures() & ~mFeatures; - if (missingFeatures != 0) { - throw new IllegalStateException("The media route selector " - + "specified required features which this route does " - + "not appear to support so it should not have been published: " - + "missing 0x" + Integer.toHexString(missingFeatures)); - } - for (String protocol : mSelector.getRequiredProtocols()) { - if (!mProtocols.contains(protocol)) { - throw new IllegalStateException("The media route selector " - + "specified required protocols which this route " - + "does not appear to support so it should not have " - + "been published: missing " + protocol); - } - } - return new RouteInfo(mId, mDestination, mSelector, - mFeatures, mProtocols, mExtras); - } - } - } - - /** - * Describes a destination for media content such as a device, - * an individual port on a device, or a group of devices. - */ - public static final class DestinationInfo { - private final String mId; - private final ServiceMetadata mService; - private final CharSequence mName; - private final CharSequence mDescription; - private final int mIconResourceId; - private final Bundle mExtras; - - DestinationInfo(String id, ServiceMetadata service, - CharSequence name, CharSequence description, - int iconResourceId, Bundle extras) { - mId = id; - mService = service; - mName = name; - mDescription = description; - mIconResourceId = iconResourceId; - mExtras = extras; - } - - /** - * Gets the destination's stable identifier. - * <p> - * The id is intended to uniquely identify the destination among all destinations - * provided by the media route service in such a way that the client can - * refer to it at a later time. Ideally, the id should be resilient to - * user-initiated actions such as changes to the name or description - * of the destination. - * </p> - */ - public @NonNull String getId() { - return mId; - } - - /** - * Gets metadata about the service that is providing access to this destination. - */ - public @NonNull ServiceMetadata getServiceMetadata() { - return mService; - } - - /** - * Gets the destination's name for display to the user. - */ - public @NonNull CharSequence getName() { - return mName; - } - - /** - * Gets the destination's description for display to the user, or null if none. - */ - public @Nullable CharSequence getDescription() { - return mDescription; - } - - /** - * Gets an icon resource from the service's package which is used - * to identify the destination, or -1 if none. - */ - public @DrawableRes int getIconResourceId() { - return mIconResourceId; - } - - /** - * Loads the icon drawable, or null if none. - */ - public @Nullable Drawable loadIcon(@NonNull PackageManager pm) { - return mIconResourceId >= 0 ? mService.getDrawable(pm, mIconResourceId) : null; - } - - /** - * Gets optional extra information about the destination, or null if none. - */ - public @Nullable Bundle getExtras() { - return mExtras; - } - - @Override - public @NonNull String toString() { - return "DestinationInfo{ id=" + mId + ", service=" + mService + ", name=" + mName - + ", description=" + mDescription + ", iconResourceId=" + mIconResourceId - + ", extras=" + mExtras + " }"; - } - - /** - * Builds {@link DestinationInfo} objects. - */ - public static final class Builder { - private final String mId; - private final ServiceMetadata mService; - private final CharSequence mName; - private CharSequence mDescription; - private int mIconResourceId = -1; - private Bundle mExtras; - - /** - * Creates a builder for destination information. - * - * @param id The destination's stable identifier. - * @param service Metatada about the service that is providing access to - * this destination. - * @param name The destination's name for display to the user. - */ - public Builder(@NonNull String id, @NonNull ServiceMetadata service, - @NonNull CharSequence name) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("id must not be null or empty"); - } - if (service == null) { - throw new IllegalArgumentException("service must not be null"); - } - if (TextUtils.isEmpty(name)) { - throw new IllegalArgumentException("name must not be null or empty"); - } - mId = id; - mService = service; - mName = name; - } - - /** - * Sets the destination's description for display to the user, or null if none. - */ - public @NonNull Builder setDescription(@Nullable CharSequence description) { - mDescription = description; - return this; - } - - /** - * Sets an icon resource from this package used to identify the destination, - * or -1 if none. - */ - public @NonNull Builder setIconResourceId(@DrawableRes int resid) { - mIconResourceId = resid; - return this; - } - - /** - * Gets optional extra information about the destination, or null if none. - */ - public @NonNull Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Builds the {@link DestinationInfo} object. - */ - public @NonNull DestinationInfo build() { - return new DestinationInfo(mId, mService, mName, mDescription, - mIconResourceId, mExtras); - } - } - } - - /** - * Describes metadata about a {@link MediaRouteService} which is providing - * access to certain kinds of destinations. - */ - public static final class ServiceMetadata { - private final ServiceInfo mService; - private CharSequence mLabel; - private Drawable mIcon; - - ServiceMetadata(Service service) throws NameNotFoundException { - mService = service.getPackageManager().getServiceInfo( - new ComponentName(service, service.getClass()), - PackageManager.GET_META_DATA); - } - - ServiceMetadata(ServiceInfo service) { - mService = service; - } - - /** - * Gets the service's component information including it name, label and icon. - */ - public @NonNull ServiceInfo getService() { - return mService; - } - - /** - * Gets the service's component name. - */ - public @NonNull ComponentName getComponentName() { - return new ComponentName(mService.packageName, mService.name); - } - - /** - * Gets the service's package name. - */ - public @NonNull String getPackageName() { - return mService.packageName; - } - - /** - * Gets the service's name for display to the user, or null if none. - */ - public @NonNull CharSequence getLabel(@NonNull PackageManager pm) { - if (mLabel == null) { - mLabel = mService.loadLabel(pm); - } - return mLabel; - } - - /** - * Gets the icon drawable, or null if none. - */ - public @Nullable Drawable getIcon(@NonNull PackageManager pm) { - if (mIcon == null) { - mIcon = mService.loadIcon(pm); - } - return mIcon; - } - - // TODO: add service metadata - - Drawable getDrawable(PackageManager pm, int resid) { - return pm.getDrawable(getPackageName(), resid, mService.applicationInfo); - } - - @Override - public @NonNull String toString() { - return "ServiceInfo{ service=" + getComponentName().toShortString() + " }"; - } - } - - /** - * Describes a request to discover routes on behalf of an application. - */ - public static final class DiscoveryRequest { - private final ArrayList<MediaRouteSelector> mSelectors = - new ArrayList<MediaRouteSelector>(); - private int mFlags; - - DiscoveryRequest(@NonNull List<MediaRouteSelector> selectors) { - setSelectors(selectors); - } - - /** - * Sets the list of media route selectors to consider during discovery. - */ - public void setSelectors(@NonNull List<MediaRouteSelector> selectors) { - if (selectors == null) { - throw new IllegalArgumentException("selectors"); - } - mSelectors.clear(); - mSelectors.addAll(selectors); - } - - /** - * Gets the list of media route selectors to consider during discovery. - */ - public @NonNull List<MediaRouteSelector> getSelectors() { - return mSelectors; - } - - /** - * Gets discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}. - */ - public @DiscoveryFlags int getFlags() { - return mFlags; - } - - /** - * Sets discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}. - */ - public void setFlags(@DiscoveryFlags int flags) { - mFlags = flags; - } - - @Override - public @NonNull String toString() { - return "DiscoveryRequest{ selectors=" + mSelectors - + ", flags=0x" + Integer.toHexString(mFlags) - + " }"; - } - } - - /** - * Describes a request to connect to a previously discovered route on - * behalf of an application. - */ - public static final class ConnectionRequest { - private RouteInfo mRoute; - private int mFlags; - private Bundle mExtras; - - ConnectionRequest(@NonNull RouteInfo route) { - setRoute(route); - } - - /** - * Gets the route to which to connect. - */ - public @NonNull RouteInfo getRoute() { - return mRoute; - } - - /** - * Sets the route to which to connect. - */ - public void setRoute(@NonNull RouteInfo route) { - if (route == null) { - throw new IllegalArgumentException("route must not be null"); - } - mRoute = route; - } - - /** - * Gets connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}. - */ - public @ConnectionFlags int getFlags() { - return mFlags; - } - - /** - * Sets connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}. - */ - public void setFlags(@ConnectionFlags int flags) { - mFlags = flags; - } - - /** - * Gets optional extras supplied by the application as part of the call to - * connect, or null if none. The media route service may use this - * information to configure the route during connection. - */ - public @Nullable Bundle getExtras() { - return mExtras; - } - - /** - * Sets optional extras supplied by the application as part of the call to - * connect, or null if none. The media route service may use this - * information to configure the route during connection. - */ - public void setExtras(@Nullable Bundle extras) { - mExtras = extras; - } - - @Override - public @NonNull String toString() { - return "ConnectionRequest{ route=" + mRoute - + ", flags=0x" + Integer.toHexString(mFlags) - + ", extras=" + mExtras + " }"; - } - } - - /** - * Callback interface to specify policy for route discovery, filtering, - * and connection establishment as well as observe media router state changes. - */ - public static abstract class RoutingCallback extends StateCallback { - /** - * Called to prepare a discovery request object to specify the desired - * media route selectors when the media router has been asked to start discovery. - * <p> - * By default, the discovery request contains all of the selectors which - * have been added to the media router. Subclasses may override the list of - * selectors by modifying the discovery request object before returning. - * </p> - * - * @param request The discovery request object which may be modified by - * this method to alter how discovery will be performed. - * @param selectors The immutable list of media route selectors which were - * added to the media router. - * @return True to allow discovery to proceed or false to abort it. - * By default, this methods returns true. - */ - public boolean onPrepareDiscoveryRequest(@NonNull DiscoveryRequest request, - @NonNull List<MediaRouteSelector> selectors) { - return true; - } - - /** - * Called to prepare a connection request object to specify the desired - * route and connection parameters when the media router has been asked to - * connect to a particular destination. - * <p> - * By default, the connection request specifies the first available route - * to the destination. Subclasses may override the route and destination - * or set additional connection parameters by modifying the connection request - * object before returning. - * </p> - * - * @param request The connection request object which may be modified by - * this method to alter how the connection will be established. - * @param destination The destination to which the media router was asked - * to connect. - * @param routes The list of routes that belong to that destination sorted - * in the same order as their matching media route selectors which were - * used during discovery. - * @return True to allow the connection to proceed or false to abort it. - * By default, this methods returns true. - */ - public boolean onPrepareConnectionRequest( - @NonNull ConnectionRequest request, - @NonNull DestinationInfo destination, @NonNull List<RouteInfo> routes) { - return true; - } - } - - /** - * Callback class to receive events from a {@link MediaRouter.Delegate}. - */ - public static abstract class StateCallback { - /** - * Called when the media router has been released. - */ - public void onReleased() { } - - /** - * Called when the discovery state has changed. - * - * @param state The new discovery state: one of - * {@link #DISCOVERY_STATE_STOPPED} or {@link #DISCOVERY_STATE_STARTED}. - */ - public void onDiscoveryStateChanged(@DiscoveryState int state) { } - - /** - * Called when the connection state has changed. - * - * @param state The new connection state: one of - * {@link #CONNECTION_STATE_DISCONNECTED}, {@link #CONNECTION_STATE_CONNECTING} - * or {@link #CONNECTION_STATE_CONNECTED}. - */ - public void onConnectionStateChanged(@ConnectionState int state) { } - - /** - * Called when the selected destination has changed. - * - * @param destination The new selected destination, or null if none. - */ - public void onSelectedDestinationChanged(@Nullable DestinationInfo destination) { } - - /** - * Called when route discovery has started. - */ - public void onDiscoveryStarted() { } - - /** - * Called when route discovery has stopped normally. - * <p> - * Abnormal termination is reported via {@link #onDiscoveryFailed}. - * </p> - */ - public void onDiscoveryStopped() { } - - /** - * Called when discovery has failed in a non-recoverable manner. - * - * @param error The error code: one of - * {@link MediaRouter#DISCOVERY_ERROR_UNKNOWN}, - * {@link MediaRouter#DISCOVERY_ERROR_ABORTED}, - * or {@link MediaRouter#DISCOVERY_ERROR_NO_CONNECTIVITY}. - * @param message The localized error message, or null if none. This message - * may be shown to the user. - * @param extras Additional information about the error which a client - * may use, or null if none. - */ - public void onDiscoveryFailed(@DiscoveryError int error, @Nullable CharSequence message, - @Nullable Bundle extras) { } - - /** - * Called when a new destination is found or has changed during discovery. - * <p> - * Certain destinations may be omitted because they have been filtered - * out by the media router's routing callback. - * </p> - * - * @param destination The destination that was found. - */ - public void onDestinationFound(@NonNull DestinationInfo destination) { } - - /** - * Called when a destination is no longer reachable or is no longer - * offering any routes that satisfy the discovery request. - * - * @param destination The destination that went away. - */ - public void onDestinationLost(@NonNull DestinationInfo destination) { } - - /** - * Called when a connection attempt begins. - */ - public void onConnecting() { } - - /** - * Called when the connection succeeds. - */ - public void onConnected() { } - - /** - * Called when the connection is terminated normally. - * <p> - * Abnormal termination is reported via {@link #onConnectionFailed}. - * </p> - */ - public void onDisconnected() { } - - /** - * Called when a connection attempt or connection in - * progress has failed in a non-recoverable manner. - * - * @param error The error code: one of - * {@link MediaRouter#CONNECTION_ERROR_ABORTED}, - * {@link MediaRouter#CONNECTION_ERROR_UNAUTHORIZED}, - * {@link MediaRouter#CONNECTION_ERROR_UNREACHABLE}, - * {@link MediaRouter#CONNECTION_ERROR_BUSY}, - * {@link MediaRouter#CONNECTION_ERROR_TIMEOUT}, - * {@link MediaRouter#CONNECTION_ERROR_BROKEN}, - * or {@link MediaRouter#CONNECTION_ERROR_BARGED}. - * @param message The localized error message, or null if none. This message - * may be shown to the user. - * @param extras Additional information about the error which a client - * may use, or null if none. - */ - public void onConnectionFailed(@ConnectionError int error, - @Nullable CharSequence message, @Nullable Bundle extras) { } - } -} diff --git a/media/java/android/media/routing/ParcelableConnectionInfo.aidl b/media/java/android/media/routing/ParcelableConnectionInfo.aidl deleted file mode 100644 index 4a9ec94..0000000 --- a/media/java/android/media/routing/ParcelableConnectionInfo.aidl +++ /dev/null @@ -1,18 +0,0 @@ -/* 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. -*/ - -package android.media.routing; - -parcelable ParcelableConnectionInfo; diff --git a/media/java/android/media/routing/ParcelableConnectionInfo.java b/media/java/android/media/routing/ParcelableConnectionInfo.java deleted file mode 100644 index 45cfe9f..0000000 --- a/media/java/android/media/routing/ParcelableConnectionInfo.java +++ /dev/null @@ -1,71 +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.media.routing; - -import android.media.AudioAttributes; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Internal parcelable representation of a media route connection. - */ -class ParcelableConnectionInfo implements Parcelable { - public AudioAttributes audioAttributes; - public int presentationDisplayId = -1; - // todo: volume - public IBinder[] protocolBinders; - public Bundle extras; - - public static final Parcelable.Creator<ParcelableConnectionInfo> CREATOR = - new Parcelable.Creator<ParcelableConnectionInfo>() { - @Override - public ParcelableConnectionInfo createFromParcel(Parcel source) { - ParcelableConnectionInfo info = new ParcelableConnectionInfo(); - if (source.readInt() != 0) { - info.audioAttributes = AudioAttributes.CREATOR.createFromParcel(source); - } - info.presentationDisplayId = source.readInt(); - info.protocolBinders = source.createBinderArray(); - info.extras = source.readBundle(); - return info; - } - - @Override - public ParcelableConnectionInfo[] newArray(int size) { - return new ParcelableConnectionInfo[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - if (audioAttributes != null) { - dest.writeInt(1); - audioAttributes.writeToParcel(dest, flags); - } else { - dest.writeInt(0); - } - dest.writeInt(presentationDisplayId); - dest.writeBinderArray(protocolBinders); - dest.writeBundle(extras); - } -} diff --git a/media/java/android/media/routing/ParcelableDestinationInfo.aidl b/media/java/android/media/routing/ParcelableDestinationInfo.aidl deleted file mode 100644 index bf1c198..0000000 --- a/media/java/android/media/routing/ParcelableDestinationInfo.aidl +++ /dev/null @@ -1,18 +0,0 @@ -/* 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. -*/ - -package android.media.routing; - -parcelable ParcelableDestinationInfo; diff --git a/media/java/android/media/routing/ParcelableDestinationInfo.java b/media/java/android/media/routing/ParcelableDestinationInfo.java deleted file mode 100644 index eca5eec..0000000 --- a/media/java/android/media/routing/ParcelableDestinationInfo.java +++ /dev/null @@ -1,65 +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.media.routing; - -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; - -/** - * Internal parcelable representation of a media destination. - */ -class ParcelableDestinationInfo implements Parcelable { - public String id; - public CharSequence name; - public CharSequence description; - public int iconResourceId; - public Bundle extras; - - public static final Parcelable.Creator<ParcelableDestinationInfo> CREATOR = - new Parcelable.Creator<ParcelableDestinationInfo>() { - @Override - public ParcelableDestinationInfo createFromParcel(Parcel source) { - ParcelableDestinationInfo info = new ParcelableDestinationInfo(); - info.id = source.readString(); - info.name = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - info.description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); - info.iconResourceId = source.readInt(); - info.extras = source.readBundle(); - return info; - } - - @Override - public ParcelableDestinationInfo[] newArray(int size) { - return new ParcelableDestinationInfo[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(id); - TextUtils.writeToParcel(name, dest, flags); - TextUtils.writeToParcel(description, dest, flags); - dest.writeInt(iconResourceId); - dest.writeBundle(extras); - } -} diff --git a/media/java/android/media/routing/ParcelableRouteInfo.aidl b/media/java/android/media/routing/ParcelableRouteInfo.aidl deleted file mode 100644 index 126afaa..0000000 --- a/media/java/android/media/routing/ParcelableRouteInfo.aidl +++ /dev/null @@ -1,18 +0,0 @@ -/* 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. -*/ - -package android.media.routing; - -parcelable ParcelableRouteInfo; diff --git a/media/java/android/media/routing/ParcelableRouteInfo.java b/media/java/android/media/routing/ParcelableRouteInfo.java deleted file mode 100644 index fb1a547..0000000 --- a/media/java/android/media/routing/ParcelableRouteInfo.java +++ /dev/null @@ -1,64 +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.media.routing; - -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Internal parcelable representation of a media route. - */ -class ParcelableRouteInfo implements Parcelable { - public String id; - public int selectorIndex; // index of selector within list used for discovery - public int features; - public String[] protocols; - public Bundle extras; - - public static final Parcelable.Creator<ParcelableRouteInfo> CREATOR = - new Parcelable.Creator<ParcelableRouteInfo>() { - @Override - public ParcelableRouteInfo createFromParcel(Parcel source) { - ParcelableRouteInfo info = new ParcelableRouteInfo(); - info.id = source.readString(); - info.selectorIndex = source.readInt(); - info.features = source.readInt(); - info.protocols = source.createStringArray(); - info.extras = source.readBundle(); - return info; - } - - @Override - public ParcelableRouteInfo[] newArray(int size) { - return new ParcelableRouteInfo[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(id); - dest.writeInt(selectorIndex); - dest.writeInt(features); - dest.writeStringArray(protocols); - dest.writeBundle(extras); - } -} diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index af3b72e..bd0019f 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -19,7 +19,6 @@ import android.app.PendingIntent; import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; import android.media.MediaMetadata; -import android.media.routing.IMediaRouter; import android.media.session.ISessionController; import android.media.session.PlaybackState; import android.media.session.MediaSession; @@ -35,7 +34,6 @@ interface ISession { ISessionController getController(); void setFlags(int flags); void setActive(boolean active); - void setMediaRouter(in IMediaRouter router); void setMediaButtonReceiver(in PendingIntent mbr); void setLaunchPendingIntent(in PendingIntent pi); void destroy(); diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index 8d58a60..285e5f7 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -20,8 +20,6 @@ import android.content.Intent; import android.content.pm.ParceledListSlice; import android.media.MediaMetadata; import android.media.Rating; -import android.media.routing.IMediaRouterDelegate; -import android.media.routing.IMediaRouterStateCallback; import android.media.session.ISessionControllerCallback; import android.media.session.MediaSession; import android.media.session.ParcelableVolumeInfo; @@ -51,8 +49,6 @@ interface ISessionController { void adjustVolume(int direction, int flags, String packageName); void setVolumeTo(int value, int flags, String packageName); - IMediaRouterDelegate createMediaRouterDelegate(IMediaRouterStateCallback callback); - // These commands are for the TransportControls void play(); void playFromMediaId(String mediaId, in Bundle extras); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index dd81a22..2acee04 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -26,7 +26,6 @@ import android.media.AudioManager; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; -import android.media.routing.MediaRouter; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -120,17 +119,6 @@ public final class MediaController { } /** - * Creates a media router delegate through which the destination of the media - * router may be observed and controlled. - * - * @return The media router delegate, or null if the media session does - * not support media routing. - */ - public @Nullable MediaRouter.Delegate createMediaRouterDelegate() { - return new MediaRouter.Delegate(); - } - - /** * Send the specified media button event to the session. Only media keys can * be sent by this method, other keys will be ignored. * diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index cee82b4..e1e9b79 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -29,7 +29,6 @@ import android.media.MediaDescription; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; -import android.media.routing.MediaRouter; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -225,23 +224,6 @@ public final class MediaSession { } /** - * Associates a {@link MediaRouter} with this session to control the destination - * of media content. - * <p> - * A media router may only be associated with at most one session at a time. - * </p> - * - * @param router The media router, or null to remove the current association. - */ - public void setMediaRouter(@Nullable MediaRouter router) { - try { - mBinder.setMediaRouter(router != null ? router.getBinder() : null); - } catch (RemoteException e) { - Log.wtf(TAG, "Failure in setMediaButtonReceiver.", 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 diff --git a/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplayProvider.java b/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplayProvider.java index e2df77c..4d3edb8 100644 --- a/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplayProvider.java +++ b/media/lib/remotedisplay/java/com/android/media/remotedisplay/RemoteDisplayProvider.java @@ -287,7 +287,7 @@ public abstract class RemoteDisplayProvider { */ public PendingIntent getSettingsPendingIntent() { if (mSettingsPendingIntent == null) { - Intent settingsIntent = new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS); + Intent settingsIntent = new Intent(Settings.ACTION_CAST_SETTINGS); settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp index 0521833..6d2de98 100644 --- a/native/graphics/jni/bitmap.cpp +++ b/native/graphics/jni/bitmap.cpp @@ -62,7 +62,7 @@ int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; } - SkPixelRef* pixelRef = GraphicsJNI::getSkPixelRef(env, jbitmap); + SkPixelRef* pixelRef = GraphicsJNI::refSkPixelRef(env, jbitmap); if (!pixelRef) { return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; } @@ -71,9 +71,9 @@ int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) { void* addr = pixelRef->pixels(); if (NULL == addr) { pixelRef->unlockPixels(); + pixelRef->unref(); return ANDROID_BITMAP_RESULT_ALLOCATION_FAILED; } - pixelRef->ref(); if (addrPtr) { *addrPtr = addr; @@ -86,7 +86,7 @@ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; } - SkPixelRef* pixelRef = GraphicsJNI::getSkPixelRef(env, jbitmap); + SkPixelRef* pixelRef = GraphicsJNI::refSkPixelRef(env, jbitmap); if (!pixelRef) { return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; } @@ -98,6 +98,12 @@ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) { pixelRef->notifyPixelsChanged(); pixelRef->unlockPixels(); + // Awkward in that we need to double-unref as the call to get the SkPixelRef + // did a ref(), so we need to unref() for the local ref and for the previous + // AndroidBitmap_lockPixels(). However this keeps GraphicsJNI a bit safer + // if others start using it without knowing about android::Bitmap's "fun" + // ref counting mechanism(s). + pixelRef->unref(); pixelRef->unref(); return ANDROID_BITMAP_RESULT_SUCCESS; diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java index 845d53a..929258d 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java @@ -162,7 +162,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView switchImeButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mCallback.userActivity(); // Leave the screen on a bit longer - mImm.showInputMethodPicker(); + // Do not show auxiliary subtypes in password lock screen. + mImm.showInputMethodPicker(false /* showAuxiliarySubtypes */); } }); } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 4082bf5..b702e35 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -64,6 +64,8 @@ <string name="wifi_not_in_range">Not in range</string> <!-- Summary for the remembered network but no internet connection was detected. --> <string name="wifi_no_internet">No Internet Access Detected, won\'t automatically reconnect.</string> + <!-- Summary for saved networks --> + <string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string> <!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] --> <string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string> @@ -71,6 +73,10 @@ <string name="connected_via_passpoint">Connected via %1$s</string> <!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] --> <string name="available_via_passpoint">Available via %1$s</string> + <!-- Package name for Settings app--> + <string name="settings_package" translatable="false">com.android.settings</string> + <!-- Package name for Certinstaller app--> + <string name="certinstaller_package" translatable="false">com.android.certinstaller</string> <!-- Summary for Connected wifi network without internet --> <string name="wifi_connected_no_internet">Connected, no Internet</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 53e69e3..7eaa728 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -35,6 +35,12 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.util.LruCache; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.os.RemoteException; +import android.app.AppGlobals; import com.android.settingslib.R; @@ -288,12 +294,28 @@ public class AccessPoint implements Comparable<AccessPoint> { } public String getSavedNetworkSummary() { - // Update to new summary - if (mConfig != null && mConfig.isPasspoint()) { - return ""; - } else { - return getSettingsSummary(); + if (mConfig != null) { + PackageManager pm = mContext.getPackageManager(); + String systemName = pm.getNameForUid(android.os.Process.SYSTEM_UID); + int userId = UserHandle.getUserId(mConfig.creatorUid); + ApplicationInfo appInfo = null; + if (mConfig.creatorName != null && mConfig.creatorName.equals(systemName)) { + appInfo = mContext.getApplicationInfo(); + } else { + try { + IPackageManager ipm = AppGlobals.getPackageManager(); + appInfo = ipm.getApplicationInfo(mConfig.creatorName, 0 /* flags */, userId); + } catch (RemoteException rex) { + } + } + if (appInfo != null && + !appInfo.packageName.equals(mContext.getString(R.string.settings_package)) && + !appInfo.packageName.equals( + mContext.getString(R.string.certinstaller_package))) { + return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm)); + } } + return ""; } public String getSummary() { diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml index 595c9ed..0264f3d 100644 --- a/packages/SystemUI/res/layout/zen_mode_panel.xml +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -52,7 +52,7 @@ android:layout_alignParentEnd="true" android:background="@drawable/btn_borderless_rect" android:clickable="true" - android:contentDescription="@string/accessibility_desc_confirm" + android:contentDescription="@string/accessibility_desc_close" android:scaleType="center" android:src="@drawable/ic_close" android:tint="@android:color/white" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8466a5a..8606a59 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -424,8 +424,8 @@ <string name="accessibility_desc_settings">Settings</string> <!-- Content description for the recent apps panel (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_desc_recent_apps">Overview.</string> - <!-- Content description for the confirm button in the zen mode panel introduction message. [CHAR LIMIT=NONE] --> - <string name="accessibility_desc_confirm">Confirm</string> + <!-- Content description for the close button in the zen mode panel introduction message. [CHAR LIMIT=NONE] --> + <string name="accessibility_desc_close">Close</string> <!-- Content description of the user tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_user">User <xliff:g id="user" example="John Doe">%s</xliff:g>.</string> diff --git a/packages/SystemUI/src/com/android/systemui/EventLogConstants.java b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java index c8af2d4..43a1be1 100644 --- a/packages/SystemUI/src/com/android/systemui/EventLogConstants.java +++ b/packages/SystemUI/src/com/android/systemui/EventLogConstants.java @@ -34,4 +34,10 @@ public class EventLogConstants { public static final int SYSUI_LOCKSCREEN_GESTURE_TAP_LOCK = 6; /** The user tapped a notification, needs to tap again to launch. */ public static final int SYSUI_LOCKSCREEN_GESTURE_TAP_NOTIFICATION_ACTIVATE = 7; + /** The user swiped down to open quick settings, from keyguard. */ + public static final int SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS = 8; + /** The user swiped down to open quick settings, from shade. */ + public static final int SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS = 9; + /** The user tapped on the status bar to open quick settings, from shade. */ + public static final int SYSUI_TAP_TO_OPEN_QS = 10; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index c62ad66..7077a17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -144,7 +144,7 @@ public class NavigationBarView extends LinearLayout { @Override public void onClick(View view) { ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE)) - .showInputMethodPicker(); + .showInputMethodPicker(true /* showAuxiliarySubtypes */); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index c3ede75..f77ac4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -39,6 +39,7 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.TextView; +import com.android.internal.logging.MetricsLogger; import com.android.keyguard.KeyguardStatusView; import com.android.systemui.EventLogConstants; import com.android.systemui.EventLogTags; @@ -73,6 +74,10 @@ public class NotificationPanelView extends PanelView implements private static final float HEADER_RUBBERBAND_FACTOR = 2.05f; private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f; + private static final String COUNTER_PANEL_OPEN = "panel_open"; + private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; + private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek"; + public static final long DOZE_ANIMATION_DURATION = 700; private KeyguardAffordanceHelper mAfforanceHelper; @@ -541,6 +546,8 @@ public class NotificationPanelView extends PanelView implements initDownStates(event); if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { mIsExpansionFromHeadsUp = true; + MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1); + MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1); return true; } if (!isFullyCollapsed() && onQsIntercept(event)) { @@ -617,7 +624,7 @@ public class NotificationPanelView extends PanelView implements case MotionEvent.ACTION_UP: trackMovement(event); if (mQsTracking) { - flingQsWithCurrentVelocity( + flingQsWithCurrentVelocity(y, event.getActionMasked() == MotionEvent.ACTION_CANCEL); mQsTracking = false; } @@ -655,9 +662,24 @@ public class NotificationPanelView extends PanelView implements super.requestDisallowInterceptTouchEvent(disallowIntercept); } - private void flingQsWithCurrentVelocity(boolean isCancelMotionEvent) { + private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) { + float vel = getCurrentVelocity(); + final boolean expandsQs = flingExpandsQs(vel); + if (expandsQs) { + logQsSwipeDown(y); + } + flingSettings(vel, expandsQs && !isCancelMotionEvent); + } + + private void logQsSwipeDown(float y) { float vel = getCurrentVelocity(); - flingSettings(vel, flingExpandsQs(vel) && !isCancelMotionEvent); + final int gesture = mStatusBarState == StatusBarState.KEYGUARD + ? EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS + : EventLogConstants.SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS; + EventLogTags.writeSysuiLockscreenGesture( + gesture, + (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()), + (int) (vel / mStatusBar.getDisplayDensity())); } private boolean flingExpandsQs(float vel) { @@ -699,6 +721,7 @@ public class NotificationPanelView extends PanelView implements return true; } if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { + MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1); updateVerticalPanelPosition(event.getX()); } super.onTouchEvent(event); @@ -738,6 +761,7 @@ public class NotificationPanelView extends PanelView implements if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN && event.getPointerCount() == 2 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { + MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1); mQsExpandImmediate = true; requestPanelHeightUpdate(); @@ -799,6 +823,7 @@ public class NotificationPanelView extends PanelView implements } final float y = event.getY(pointerIndex); final float x = event.getX(pointerIndex); + final float h = y - mInitialTouchY; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: @@ -826,7 +851,6 @@ public class NotificationPanelView extends PanelView implements break; case MotionEvent.ACTION_MOVE: - final float h = y - mInitialTouchY; setQsExpansion(h + mInitialHeightOnTouch); if (h >= getFalsingThreshold()) { mQsTouchAboveFalsingThreshold = true; @@ -842,9 +866,10 @@ public class NotificationPanelView extends PanelView implements float fraction = getQsExpansionFraction(); if ((fraction != 0f || y >= mInitialTouchY) && (fraction != 1f || y <= mInitialTouchY)) { - flingQsWithCurrentVelocity( + flingQsWithCurrentVelocity(y, event.getActionMasked() == MotionEvent.ACTION_CANCEL); } else { + logQsSwipeDown(y); mScrollYOverride = -1; } if (mVelocityTracker != null) { @@ -1819,6 +1844,9 @@ public class NotificationPanelView extends PanelView implements if (mQsExpanded) { flingSettings(0 /* vel */, false /* expand */); } else if (mQsExpansionEnabled) { + EventLogTags.writeSysuiLockscreenGesture( + EventLogConstants.SYSUI_TAP_TO_OPEN_QS, + 0, 0); flingSettings(0 /* vel */, true /* expand */); } } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java index 23a65e8..ca32567 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java @@ -93,7 +93,7 @@ public class UsbResolverActivity extends ResolverActivity { } @Override - protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) { + protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { final ResolveInfo ri = target.getResolveInfo(); try { IBinder b = ServiceManager.getService(USB_SERVICE); @@ -129,5 +129,6 @@ public class UsbResolverActivity extends ResolverActivity { } catch (RemoteException e) { Log.e(TAG, "onIntentSelected failed", e); } + return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java index 7115897..1e34663 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -788,6 +788,7 @@ public class VolumeDialog { public void onConfigurationChanged() { updateWindowWidthH(); mSpTexts.update(); + mZenFooter.onConfigurationChanged(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java index 8aded45..ccb2b5a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java @@ -36,10 +36,11 @@ public class ZenFooter extends LinearLayout { private static final String TAG = Util.logTag(ZenFooter.class); private final Context mContext; + private final SpTexts mSpTexts; private TextView mSummaryLine1; private TextView mSummaryLine2; - private View mEndNowButton; + private TextView mEndNowButton; private int mZen = -1; private ZenModeConfig mConfig; private ZenModeController mController; @@ -47,6 +48,7 @@ public class ZenFooter extends LinearLayout { public ZenFooter(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; + mSpTexts = new SpTexts(mContext); setLayoutTransition(new LayoutTransition()); } @@ -55,7 +57,10 @@ public class ZenFooter extends LinearLayout { super.onFinishInflate(); mSummaryLine1 = (TextView) findViewById(R.id.volume_zen_summary_line_1); mSummaryLine2 = (TextView) findViewById(R.id.volume_zen_summary_line_2); - mEndNowButton = findViewById(R.id.volume_zen_end_now); + mEndNowButton = (TextView) findViewById(R.id.volume_zen_end_now); + mSpTexts.add(mSummaryLine1); + mSpTexts.add(mSummaryLine2); + mSpTexts.add(mEndNowButton); } public void init(final ZenModeController controller) { @@ -122,4 +127,8 @@ public class ZenFooter extends LinearLayout { Util.setText(mSummaryLine2, line2); } + public void onConfigurationChanged() { + mSpTexts.update(); + } + } diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java index 6a1efee..7cd6d09 100644 --- a/rs/java/android/renderscript/Script.java +++ b/rs/java/android/renderscript/Script.java @@ -182,9 +182,9 @@ public class Script extends BaseObj { mRS.validateObject(ain); mRS.validateObject(aout); - if (ain == null && aout == null) { + if (ain == null && aout == null && sc == null) { throw new RSIllegalArgumentException( - "At least one of ain or aout is required to be non-null."); + "At least one of input allocation, output allocation, or LaunchOptions is required to be non-null."); } long[] in_ids = null; diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index 80d6515..58d0fce 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "libRS_jni" +#define LOG_TAG "RenderScript_jni" #include <stdlib.h> #include <stdio.h> @@ -1857,7 +1857,7 @@ nScriptForEach(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot, jintArray limits) { if (kLogApi) { - ALOGD("nScriptForEach, con(%p), s(%p), slot(%i)", (RsContext)con, (void *)script, slot); + ALOGD("nScriptForEach, con(%p), s(%p), slot(%i) ains(%p) aout(%lli)", (RsContext)con, (void *)script, slot, ains, aout); } jint in_len = 0; diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index bfe8b5c..6c1023c 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -9368,44 +9368,47 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF throw new SecurityException("No permission to restore other packages"); } - // So far so good; we're allowed to try to restore this package. Now - // check whether there is data for it in the current dataset, falling back - // to the ancestral dataset if not. - long token = getAvailableRestoreToken(packageName); - if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName - + " token=" + Long.toHexString(token)); - - // If we didn't come up with a place to look -- no ancestral dataset and - // the app has never been backed up from this device -- there's nothing - // to do but return failure. - if (token == 0) { - if (DEBUG) Slog.w(TAG, "No data available for this package; not restoring"); - return -1; - } - - String dirName; + // So far so good; we're allowed to try to restore this package. + long oldId = Binder.clearCallingIdentity(); try { - dirName = mRestoreTransport.transportDirName(); - } catch (RemoteException e) { - // Transport went AWOL; fail. - Slog.e(TAG, "Unable to contact transport for restore"); - return -1; - } + // Check whether there is data for it in the current dataset, falling back + // to the ancestral dataset if not. + long token = getAvailableRestoreToken(packageName); + if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName + + " token=" + Long.toHexString(token)); + + // If we didn't come up with a place to look -- no ancestral dataset and + // the app has never been backed up from this device -- there's nothing + // to do but return failure. + if (token == 0) { + if (DEBUG) Slog.w(TAG, "No data available for this package; not restoring"); + return -1; + } - // Stop the session timeout until we finalize the restore - mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); + String dirName; + try { + dirName = mRestoreTransport.transportDirName(); + } catch (RemoteException e) { + // Transport went AWOL; fail. + Slog.e(TAG, "Unable to contact transport for restore"); + return -1; + } - // Ready to go: enqueue the restore request and claim success - long oldId = Binder.clearCallingIdentity(); - mWakelock.acquire(); - if (MORE_DEBUG) { - Slog.d(TAG, "restorePackage() : " + packageName); + // Stop the session timeout until we finalize the restore + mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); + + // Ready to go: enqueue the restore request and claim success + mWakelock.acquire(); + if (MORE_DEBUG) { + Slog.d(TAG, "restorePackage() : " + packageName); + } + Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); + msg.obj = new RestoreParams(mRestoreTransport, dirName, + observer, token, app, 0); + mBackupHandler.sendMessage(msg); + } finally { + Binder.restoreCallingIdentity(oldId); } - Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(mRestoreTransport, dirName, - observer, token, app, 0); - mBackupHandler.sendMessage(msg); - Binder.restoreCallingIdentity(oldId); return 0; } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 1019faa..32b91d2 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -512,7 +512,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private void onBluetoothGattServiceUp() { if (DBG) Log.d(TAG,"BluetoothGatt Service is Up"); try{ - if (isBleAppPresent() == false && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { + if (isBleAppPresent() == false && mBluetooth != null + && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { mBluetooth.onLeServiceUp(); // waive WRITE_SECURE_SETTINGS permission check @@ -531,32 +532,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub { */ private void sendBrEdrDownCallback() { if (DBG) Log.d(TAG,"Calling sendBrEdrDownCallback callbacks"); - int n = mCallbacks.beginBroadcast(); + + if(mBluetooth == null) { + Log.w(TAG, "Bluetooth handle is null"); + return; + } if (isBleAppPresent() == false) { try { mBluetooth.onBrEdrDown(); } catch(RemoteException e) { - Log.e(TAG,"Unable to call onBrEdrDown", e); + Log.e(TAG, "Call to onBrEdrDown() failed.", e); } - } - else{//need to stay at BLE ON. disconnect all Gatt connections + } else { + // Need to stay at BLE ON. Disconnect all Gatt connections try{ - mBluetoothGatt.unregAll();//disconnectAll(); + mBluetoothGatt.unregAll(); } catch(RemoteException e) { - Log.e(TAG,"Unable to disconn all", e); - } - } - - Log.d(TAG,"Broadcasting onBrEdrDown() to " + n + " receivers."); - for (int i=0; i <n; i++) { - try { - mCallbacks.getBroadcastItem(i).onBrEdrDown(); - } catch (RemoteException e) { - Log.e(TAG, "Unable to call sendBrEdrDownCallback() on callback #" + i, e); + Log.e(TAG, "Unable to disconnect all apps.", e); } } - mCallbacks.finishBroadcast(); } /** @hide*/ diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 45909db..9511f54 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -2038,11 +2038,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!mIWindowManager.inputMethodClientHasFocus(client)) { if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client); - setImeWindowVisibilityStatusHiddenLocked(); return false; } } catch (RemoteException e) { - setImeWindowVisibilityStatusHiddenLocked(); return false; } } @@ -2238,7 +2236,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void showInputMethodPickerFromClient(IInputMethodClient client) { + public void showInputMethodPickerFromClient( + IInputMethodClient client, int auxiliarySubtypeMode) { if (!calledFromValidUser()) { return; } @@ -2251,7 +2250,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Always call subtype picker, because subtype picker is a superset of input method // picker. - mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); + mHandler.sendMessage(mCaller.obtainMessageI( + MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode)); } } @@ -2597,7 +2597,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub SomeArgs args; switch (msg.what) { case MSG_SHOW_IM_SUBTYPE_PICKER: - showInputMethodMenu(); + final boolean showAuxSubtypes; + switch (msg.arg1) { + case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO: + // This is undocumented so far, but IMM#showInputMethodPicker() has been + // implemented so that auxiliary subtypes will be excluded when the soft + // keyboard is invisible. + showAuxSubtypes = mInputShown; + break; + case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES: + showAuxSubtypes = true; + break; + case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES: + showAuxSubtypes = false; + break; + default: + Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1); + return false; + } + showInputMethodMenu(showAuxSubtypes); return true; case MSG_SHOW_IM_SUBTYPE_ENABLER: @@ -2880,8 +2898,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); } - private void showInputMethodMenu() { - if (DEBUG) Slog.v(TAG, "Show switching menu"); + private void showInputMethodMenu(boolean showAuxSubtypes) { + if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes); final Context context = mContext; final boolean isScreenLocked = isScreenLocked(); @@ -2902,7 +2920,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final List<ImeSubtypeListItem> imList = mSwitchingController.getSortedInputMethodAndSubtypeListLocked( - true /* showSubtypes */, mInputShown, isScreenLocked); + true /* showSubtypes */, showAuxSubtypes, isScreenLocked); if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked(); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index a07591c9..ac55292 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -488,7 +488,8 @@ public class Vpn { try { // Restricted users are not allowed to create VPNs, they are tied to Owner UserInfo user = mgr.getUserInfo(mUserHandle); - if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { + if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, + new UserHandle(mUserHandle))) { throw new SecurityException("Restricted users cannot establish VPNs"); } @@ -896,7 +897,8 @@ public class Vpn { } UserManager mgr = UserManager.get(mContext); UserInfo user = mgr.getUserInfo(mUserHandle); - if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { + if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN, + new UserHandle(mUserHandle))) { throw new SecurityException("Restricted users cannot establish VPNs"); } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 7cccef2..3dc282b 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -52,6 +52,7 @@ import android.content.pm.RegisteredServicesCache; import android.content.pm.RegisteredServicesCacheListener; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.database.ContentObserver; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.BatteryStats; @@ -99,6 +100,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; @@ -157,7 +159,19 @@ public class SyncManager { /** * How long to wait before considering an active sync to have timed-out, and cancelling it. */ - private static final long ACTIVE_SYNC_TIMEOUT_MILLIS = 30L * 60 * 1000; // 30 mins. + private static final long ACTIVE_SYNC_TIMEOUT_MILLIS = 30L * 60 * 1000; // 30 mins + + /** + * How long to delay each queued {@link SyncHandler} message that may have occurred before boot + * or befor the device became provisioned. + */ + private static final long PER_SYNC_BOOT_DELAY_MILLIS = 3000L; // 3 seconds + + /** + * The maximum amount of time we're willing to delay syncs out of boot, after device has been + * provisioned, etc. + */ + private static final long MAX_SYNC_BOOT_DELAY_MILLIS = 120000L; // 2 minutes private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*/"; private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; @@ -198,6 +212,9 @@ public class SyncManager { // its accessor, getConnManager(). private ConnectivityManager mConnManagerDoNotUseDirectly; + /** Track whether the device has already been provisioned. */ + private boolean mProvisioned; + protected SyncAdaptersCache mSyncAdapters; private final AppIdleMonitor mAppIdleMonitor; @@ -242,6 +259,7 @@ public class SyncManager { private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + mBootCompleted = true; mSyncHandler.onBootCompleted(); } }; @@ -491,12 +509,41 @@ public class SyncManager { mSyncStorageEngine.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() { - @Override - public void onStatusChanged(int which) { - // force the sync loop to run if the settings change - sendCheckAlarmsMessage(); + @Override + public void onStatusChanged(int which) { + // force the sync loop to run if the settings change + sendCheckAlarmsMessage(); + } + }); + + mProvisioned = isDeviceProvisioned(); + if (!mProvisioned) { + final ContentResolver resolver = context.getContentResolver(); + ContentObserver provisionedObserver = + new ContentObserver(null /* current thread */) { + public void onChange(boolean selfChange) { + mProvisioned |= isDeviceProvisioned(); + if (mProvisioned) { + mSyncHandler.onDeviceProvisioned(); + resolver.unregisterContentObserver(this); + } + } + }; + + synchronized (mSyncHandler) { + resolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false /* notifyForDescendents */, + provisionedObserver); + + // The device *may* have been provisioned while we were registering above observer. + // Check again to make sure. + mProvisioned |= isDeviceProvisioned(); + if (mProvisioned) { + resolver.unregisterContentObserver(provisionedObserver); + } } - }); + } if (!factoryTest) { // Register for account list updates for all users @@ -510,6 +557,10 @@ public class SyncManager { mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000; } + private boolean isDeviceProvisioned() { + final ContentResolver resolver = mContext.getContentResolver(); + return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0); + } /** * Return a random value v that satisfies minValue <= v < maxValue. The difference between * maxValue and minValue must be less than Integer.MAX_VALUE. @@ -2000,20 +2051,36 @@ public class SyncManager { public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker(); private final HashMap<String, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap(); - private List<Message> mBootQueue = new ArrayList<Message>(); + private List<Message> mUnreadyQueue = new ArrayList<Message>(); - public void onBootCompleted() { + void onBootCompleted() { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Boot completed, clearing boot queue."); } doDatabaseCleanup(); synchronized(this) { // Dispatch any stashed messages. - for (Message message : mBootQueue) { - sendMessage(message); + maybeEmptyUnreadyQueueLocked(); + } + } + + void onDeviceProvisioned() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "mProvisioned=" + mProvisioned); + } + synchronized (this) { + maybeEmptyUnreadyQueueLocked(); + } + } + + private void maybeEmptyUnreadyQueueLocked() { + if (mProvisioned && mBootCompleted) { + // Dispatch any stashed messages. + for (int i=0; i<mUnreadyQueue.size(); i++) { + sendMessageDelayed(mUnreadyQueue.get(i), + Math.max(PER_SYNC_BOOT_DELAY_MILLIS * i, MAX_SYNC_BOOT_DELAY_MILLIS)); } - mBootQueue = null; - mBootCompleted = true; + mUnreadyQueue = null; } } @@ -2030,20 +2097,23 @@ public class SyncManager { } /** - * Stash any messages that come to the handler before boot is complete. - * {@link #onBootCompleted()} will disable this and dispatch all the messages collected. + * Stash any messages that come to the handler before boot is complete or before the device + * is properly provisioned (i.e. out of set-up wizard). + * {@link #onBootCompleted()} and {@link #onDeviceProvisioned(boolean)} both need to come + * in before we start syncing. * @param msg Message to dispatch at a later point. * @return true if a message was enqueued, false otherwise. This is to avoid losing the * message if we manage to acquire the lock but by the time we do boot has completed. */ private boolean tryEnqueueMessageUntilReadyToRun(Message msg) { synchronized (this) { - if (!mBootCompleted) { + if (!mBootCompleted || !mProvisioned) { // Need to copy the message bc looper will recycle it. - mBootQueue.add(Message.obtain(msg)); + mUnreadyQueue.add(Message.obtain(msg)); return true; + } else { + return false; } - return false; } } @@ -2100,7 +2170,7 @@ public class SyncManager { } cancelActiveSync(expiredContext.mSyncOperation.target, expiredContext.mSyncOperation.extras); - nextPendingSyncTime = maybeStartNextSyncLocked(); + nextPendingSyncTime = maybeStartNextSyncH(); break; case SyncHandler.MESSAGE_CANCEL: { @@ -2111,7 +2181,7 @@ public class SyncManager { + payload + " bundle: " + extras); } cancelActiveSyncLocked(payload, extras); - nextPendingSyncTime = maybeStartNextSyncLocked(); + nextPendingSyncTime = maybeStartNextSyncH(); break; } @@ -2120,17 +2190,17 @@ public class SyncManager { Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED"); } SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload) msg.obj; - if (!isSyncStillActive(payload.activeSyncContext)) { + if (!isSyncStillActiveH(payload.activeSyncContext)) { Log.d(TAG, "handleSyncHandlerMessage: dropping since the " + "sync is no longer active: " + payload.activeSyncContext); break; } - runSyncFinishedOrCanceledLocked(payload.syncResult, + runSyncFinishedOrCanceledH(payload.syncResult, payload.activeSyncContext); // since a sync just finished check if it is time to start a new sync - nextPendingSyncTime = maybeStartNextSyncLocked(); + nextPendingSyncTime = maybeStartNextSyncH(); break; case SyncHandler.MESSAGE_SERVICE_CONNECTED: { @@ -2140,7 +2210,7 @@ public class SyncManager { + msgData.activeSyncContext); } // check that this isn't an old message - if (isSyncStillActive(msgData.activeSyncContext)) { + if (isSyncStillActiveH(msgData.activeSyncContext)) { runBoundToAdapter( msgData.activeSyncContext, msgData.adapter); @@ -2156,7 +2226,7 @@ public class SyncManager { + currentSyncContext); } // check that this isn't an old message - if (isSyncStillActive(currentSyncContext)) { + if (isSyncStillActiveH(currentSyncContext)) { // cancel the sync if we have a syncadapter, which means one is // outstanding try { @@ -2174,10 +2244,10 @@ public class SyncManager { // which is a soft error SyncResult syncResult = new SyncResult(); syncResult.stats.numIoExceptions++; - runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext); + runSyncFinishedOrCanceledH(syncResult, currentSyncContext); // since a sync just finished check if it is time to start a new sync - nextPendingSyncTime = maybeStartNextSyncLocked(); + nextPendingSyncTime = maybeStartNextSyncH(); } break; @@ -2190,7 +2260,7 @@ public class SyncManager { } mAlarmScheduleTime = null; try { - nextPendingSyncTime = maybeStartNextSyncLocked(); + nextPendingSyncTime = maybeStartNextSyncH(); } finally { mHandleAlarmWakeLock.release(); } @@ -2201,7 +2271,7 @@ public class SyncManager { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS"); } - nextPendingSyncTime = maybeStartNextSyncLocked(); + nextPendingSyncTime = maybeStartNextSyncH(); break; } } finally { @@ -2393,7 +2463,7 @@ public class SyncManager { 0 : (earliestFuturePollTime - nowAbsolute)); } - private long maybeStartNextSyncLocked() { + private long maybeStartNextSyncH() { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); if (isLoggable) Log.v(TAG, "maybeStartNextSync"); @@ -2612,7 +2682,7 @@ public class SyncManager { } if (toReschedule != null) { - runSyncFinishedOrCanceledLocked(null, toReschedule); + runSyncFinishedOrCanceledH(null, toReschedule); scheduleSyncOperation(toReschedule.mSyncOperation); } synchronized (mSyncQueue) { @@ -2845,14 +2915,14 @@ public class SyncManager { false /* no config settings */)) { continue; } - runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */, + runSyncFinishedOrCanceledH(null /* no result since this is a cancel */, activeSyncContext); } } } - private void runSyncFinishedOrCanceledLocked(SyncResult syncResult, - ActiveSyncContext activeSyncContext) { + private void runSyncFinishedOrCanceledH(SyncResult syncResult, + ActiveSyncContext activeSyncContext) { boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); final SyncOperation syncOperation = activeSyncContext.mSyncOperation; @@ -3257,7 +3327,7 @@ public class SyncManager { } } - private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) { + private boolean isSyncStillActiveH(ActiveSyncContext activeSyncContext) { for (ActiveSyncContext sync : mActiveSyncContexts) { if (sync == activeSyncContext) { return true; diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index e434f39..8c12060 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -214,6 +214,10 @@ final class Constants { // values which denotes the device type in HDMI Spec 1.4. static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type"; + // TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event. + // True by default. + static final String PROPERTY_WAKE_ON_HOTPLUG = "ro.hdmi.wake_on_hotplug"; + // Set to false to allow playback device to go to suspend mode even // when it's an active source. True by default. static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake"; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 89ffe45..fd3364a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -34,6 +34,9 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { private static final String TAG = "HdmiCecLocalDevicePlayback"; + private static final boolean WAKE_ON_HOTPLUG = + SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true); + private boolean mIsActiveSource = false; // Used to keep the device awake while it is the active source. For devices that @@ -130,7 +133,7 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { assertRunOnServiceThread(); mCecMessageCache.flushAll(); // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3. - if (connected && mService.isPowerStandbyOrTransient()) { + if (WAKE_ON_HOTPLUG && connected && mService.isPowerStandbyOrTransient()) { mService.wakeUp(); } if (!connected) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 5ac027d..e650456 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -486,6 +486,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId()); mDelayedMessageBuffer.add(message); } else { + updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON); ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType()); } @@ -1613,6 +1614,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { super.disableDevice(initiatedByCec, callback); clearDeviceInfoList(); + getActiveSource().invalidate(); + setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); checkIfPendingActionsCleared(); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 09d0501..dca762c 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -27,9 +27,6 @@ import android.media.MediaDescription; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; -import android.media.routing.IMediaRouter; -import android.media.routing.IMediaRouterDelegate; -import android.media.routing.IMediaRouterStateCallback; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionController; @@ -718,11 +715,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void setMediaRouter(IMediaRouter router) { - mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); - } - - @Override public void setMediaButtonReceiver(PendingIntent pi) { mMediaButtonReceiver = pi; } @@ -1209,13 +1201,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { public boolean isTransportControlEnabled() { return MediaSessionRecord.this.isTransportControlEnabled(); } - - @Override - public IMediaRouterDelegate createMediaRouterDelegate( - IMediaRouterStateCallback callback) { - // todo - return null; - } } private class MessageHandler extends Handler { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 51503ec..dbd3676 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -93,7 +93,7 @@ import android.view.KeyCharacterMap; import android.view.KeyCharacterMap.FallbackAction; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.Surface; import android.view.View; import android.view.ViewConfiguration; diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index c48367e..fd98010 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -539,9 +539,11 @@ final class Notifier { }; private void playWirelessChargingStartedSound() { + final boolean enabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.CHARGING_SOUNDS_ENABLED, 1) != 0; final String soundPath = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.WIRELESS_CHARGING_STARTED_SOUND); - if (soundPath != null) { + if (enabled && soundPath != null) { final Uri soundUri = Uri.parse("file://" + soundPath); if (soundUri != null) { final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java index fd0c06d..d3df151 100644 --- a/telecomm/java/android/telecom/DefaultDialerManager.java +++ b/telecomm/java/android/telecom/DefaultDialerManager.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.provider.Settings; import android.text.TextUtils; @@ -151,14 +152,14 @@ public class DefaultDialerManager { for (ResolveInfo resolveInfo : resolveInfoList) { final ActivityInfo activityInfo = resolveInfo.activityInfo; - if (activityInfo == null) { - continue; + if (activityInfo != null && !packageNames.contains(activityInfo.packageName)) { + packageNames.add(activityInfo.packageName); } - packageNames.add(activityInfo.packageName); } - // TODO: Filter for apps that don't handle DIAL intent with tel scheme - return packageNames; + final Intent dialIntentWithTelScheme = new Intent(Intent.ACTION_DIAL); + dialIntentWithTelScheme.setData(Uri.fromParts(PhoneAccount.SCHEME_TEL, "", null)); + return filterByIntent(context, packageNames, dialIntentWithTelScheme); } /** @@ -182,6 +183,36 @@ public class DefaultDialerManager { || packageName.equals(tm.getSystemDialerPackage()); } + /** + * Filter a given list of package names for those packages that contain an activity that has + * an intent filter for a given intent. + * + * @param context A valid context + * @param packageNames List of package names to filter. + * @return The filtered list. + */ + private static List<String> filterByIntent(Context context, List<String> packageNames, + Intent intent) { + if (packageNames == null || packageNames.isEmpty()) { + return new ArrayList<>(); + } + + final List<String> result = new ArrayList<>(); + final List<ResolveInfo> resolveInfoList = + context.getPackageManager().queryIntentActivities(intent, 0); + final int length = resolveInfoList.size(); + for (int i = 0; i < length; i++) { + final ActivityInfo info = resolveInfoList.get(i).activityInfo; + if (info != null && packageNames.contains(info.packageName) + && !result.contains(info.packageName)) { + result.add(info.packageName); + } + } + + return result; + } + + private static TelecomManager getTelecomManager(Context context) { return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 1431eb8..ebd3f12 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1143,8 +1143,12 @@ public class TelecomManager { public void placeCall(Uri address, Bundle extras) { ITelecomService service = getTelecomService(); if (service != null) { + if (address == null) { + Log.w(TAG, "Cannot place call to empty address."); + } try { - service.placeCall(address, extras, mContext.getOpPackageName()); + service.placeCall(address, extras == null ? new Bundle() : extras, + mContext.getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#placeCall", e); } diff --git a/tests/OneMedia/Android.mk b/tests/OneMedia/Android.mk index b7d7f98..9fc6403 100644 --- a/tests/OneMedia/Android.mk +++ b/tests/OneMedia/Android.mk @@ -9,9 +9,6 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) \ LOCAL_PACKAGE_NAME := OneMedia LOCAL_CERTIFICATE := platform -LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-media-protocols - LOCAL_JAVA_LIBRARIES += org.apache.http.legacy LOCAL_PROGUARD_ENABLED := disabled diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml index ef3fad5..c6824ec 100644 --- a/tests/OneMedia/AndroidManifest.xml +++ b/tests/OneMedia/AndroidManifest.xml @@ -27,15 +27,6 @@ android:name="com.android.onemedia.OnePlayerService" android:exported="true" android:process="com.android.onemedia.service" /> - <service - android:name=".provider.OneMediaRouteProvider" - android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE" - android:exported="true" - android:process="com.android.onemedia.provider"> - <intent-filter> - <action android:name="android.media.routing.MediaRouteService" /> - </intent-filter> - </service> </application> </manifest> diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 141a209..2455c9c 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -19,25 +19,17 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.media.MediaMetadata; -import android.media.routing.MediaRouteSelector; -import android.media.routing.MediaRouter; -import android.media.routing.MediaRouter.ConnectionRequest; -import android.media.routing.MediaRouter.DestinationInfo; -import android.media.routing.MediaRouter.RouteInfo; import android.media.session.MediaSession; import android.media.session.MediaSession.QueueItem; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Bundle; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaStatus; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.view.KeyEvent; import com.android.onemedia.playback.LocalRenderer; -import com.android.onemedia.playback.OneMRPRenderer; import com.android.onemedia.playback.Renderer; import com.android.onemedia.playback.RequestUtils; @@ -48,7 +40,6 @@ public class PlayerSession { private static final String TAG = "PlayerSession"; protected MediaSession mSession; - protected MediaRouter mRouter; protected Context mContext; protected Renderer mRenderer; protected MediaSession.Callback mCallback; @@ -84,22 +75,11 @@ public class PlayerSession { .getSystemService(Context.MEDIA_SESSION_SERVICE); Log.d(TAG, "Creating session for package " + mContext.getBasePackageName()); - mRouter = new MediaRouter(mContext); - mRouter.addSelector(new MediaRouteSelector.Builder() - .addRequiredProtocol(MediaPlayerProtocol.class) - .build()); - mRouter.addSelector(new MediaRouteSelector.Builder() - .setRequiredFeatures(MediaRouter.ROUTE_FEATURE_LIVE_AUDIO) - .setOptionalFeatures(MediaRouter.ROUTE_FEATURE_LIVE_VIDEO) - .build()); - mRouter.setRoutingCallback(new RoutingCallback(), null); - mSession = new MediaSession(mContext, "OneMedia"); mSession.setCallback(mCallback); mSession.setPlaybackState(mPlaybackState); mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); - mSession.setMediaRouter(mRouter); mSession.setActive(true); updateMetadata(); } @@ -117,10 +97,6 @@ public class PlayerSession { mSession.release(); mSession = null; } - if (mRouter != null) { - mRouter.release(); - mRouter = null; - } } public void setListener(Listener listener) { @@ -278,63 +254,4 @@ public class PlayerSession { mRenderer.onPause(); } } - - private class RoutingCallback extends MediaRouter.RoutingCallback { - @Override - public void onConnectionStateChanged(int state) { - if (state == MediaRouter.CONNECTION_STATE_CONNECTING) { - if (mRenderer != null) { - mRenderer.onStop(); - } - mRenderer = null; - updateState(PlaybackState.STATE_CONNECTING); - return; - } - - MediaRouter.ConnectionInfo connection = mRouter.getConnection(); - if (connection != null) { - MediaPlayerProtocol protocol = - connection.getProtocolObject(MediaPlayerProtocol.class); - if (protocol != null) { - Log.d(TAG, "Connected to route using media player protocol"); - - protocol.setCallback(new PlayerCallback(), null); - mRenderer = new OneMRPRenderer(protocol); - updateState(PlaybackState.STATE_NONE); - return; - } - } - - // Use local route - mRenderer = new LocalRenderer(mContext, null); - mRenderer.registerListener(mRenderListener); - updateState(PlaybackState.STATE_NONE); - } - } - - private class PlayerCallback extends MediaPlayerProtocol.Callback { - @Override - public void onStatusUpdated(MediaStatus status, Bundle extras) { - if (status != null) { - Log.d(TAG, "Received status update: " + status.toBundle()); - switch (status.getPlayerState()) { - case MediaStatus.PLAYER_STATE_BUFFERING: - updateState(PlaybackState.STATE_BUFFERING); - break; - case MediaStatus.PLAYER_STATE_IDLE: - updateState(PlaybackState.STATE_STOPPED); - break; - case MediaStatus.PLAYER_STATE_PAUSED: - updateState(PlaybackState.STATE_PAUSED); - break; - case MediaStatus.PLAYER_STATE_PLAYING: - updateState(PlaybackState.STATE_PLAYING); - break; - case MediaStatus.PLAYER_STATE_UNKNOWN: - updateState(PlaybackState.STATE_NONE); - break; - } - } - } - } } diff --git a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java deleted file mode 100644 index 55eb92c..0000000 --- a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.android.onemedia.playback; - -import android.os.Bundle; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaInfo; - -/** - * Renderer for communicating with the OneMRP route - */ -public class OneMRPRenderer extends Renderer { - private final MediaPlayerProtocol mProtocol; - - public OneMRPRenderer(MediaPlayerProtocol protocol) { - super(null, null); - mProtocol = protocol; - } - - @Override - public void setContent(Bundle request) { - MediaInfo mediaInfo = new MediaInfo(request.getString(RequestUtils.EXTRA_KEY_SOURCE), - MediaInfo.STREAM_TYPE_BUFFERED, "audio/mp3"); - mProtocol.load(mediaInfo, true, 0, null); - } - - @Override - public boolean onStop() { - mProtocol.stop(null); - return true; - } - - @Override - public boolean onPlay() { - mProtocol.play(null); - return true; - } - - @Override - public boolean onPause() { - mProtocol.pause(null); - return true; - } - - @Override - public long getSeekPosition() { - return -1; - } -} diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java deleted file mode 100644 index 5845e48..0000000 --- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java +++ /dev/null @@ -1,270 +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 com.android.onemedia.provider; - -import android.media.routing.MediaRouteSelector; -import android.media.routing.MediaRouteService; -import android.media.routing.MediaRouter.ConnectionInfo; -import android.media.routing.MediaRouter.ConnectionRequest; -import android.media.routing.MediaRouter.DestinationInfo; -import android.media.routing.MediaRouter.DiscoveryRequest; -import android.media.routing.MediaRouter.RouteInfo; -import android.media.session.PlaybackState; -import android.os.Bundle; -import android.os.Handler; -import android.os.Process; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaInfo; -import android.support.media.protocols.MediaPlayerProtocol.MediaStatus; -import android.os.Looper; -import android.os.ResultReceiver; -import android.os.SystemClock; -import android.util.Log; - -import com.android.onemedia.playback.LocalRenderer; -import com.android.onemedia.playback.Renderer; -import com.android.onemedia.playback.RequestUtils; - -import java.util.ArrayList; - -/** - * Test of MediaRouteProvider. Show a dummy provider with a simple interface for - * playing music. - */ -public class OneMediaRouteProvider extends MediaRouteService { - private static final String TAG = "OneMRP"; - private static final boolean DEBUG = true; - - private static final String TEST_DESTINATION_ID = "testDestination"; - private static final String TEST_ROUTE_ID = "testRoute"; - - private Renderer mRenderer; - private RenderListener mRenderListener; - private PlaybackState mPlaybackState; - private Handler mHandler; - - private OneStub mStub; - - @Override - public void onCreate() { - mHandler = new Handler(); - mRenderer = new LocalRenderer(this, null); - mRenderListener = new RenderListener(); - PlaybackState.Builder bob = new PlaybackState.Builder(); - bob.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY); - mPlaybackState = bob.build(); - - mRenderer.registerListener(mRenderListener); - } - - @Override - public ClientSession onCreateClientSession(ClientInfo client) { - if (client.getUid() != Process.myUid()) { - // for testing purposes, only allow connections from this application - // since this provider is not fully featured - return null; - } - return new OneSession(client); - } - - private final class OneSession extends ClientSession { - private final ClientInfo mClient; - - public OneSession(ClientInfo client) { - mClient = client; - } - - @Override - public boolean onStartDiscovery(DiscoveryRequest req, DiscoveryCallback callback) { - for (MediaRouteSelector selector : req.getSelectors()) { - if (isMatch(selector)) { - DestinationInfo destination = new DestinationInfo.Builder( - TEST_DESTINATION_ID, getServiceMetadata(), "OneMedia") - .setDescription("Test route from OneMedia app.") - .build(); - ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>(); - routes.add(new RouteInfo.Builder( - TEST_ROUTE_ID, destination, selector).build()); - callback.onDestinationFound(destination, routes); - return true; - } - } - return false; - } - - @Override - public void onStopDiscovery() { - } - - @Override - public boolean onConnect(ConnectionRequest req, ConnectionCallback callback) { - if (req.getRoute().getId().equals(TEST_ROUTE_ID)) { - mStub = new OneStub(); - ConnectionInfo connection = new ConnectionInfo.Builder(req.getRoute()) - .setProtocolStub(MediaPlayerProtocol.class, mStub) - .build(); - callback.onConnected(connection); - return true; - } - return false; - } - - @Override - public void onDisconnect() { - mStub = null; - } - - private boolean isMatch(MediaRouteSelector selector) { - if (!selector.containsProtocol(MediaPlayerProtocol.class)) { - return false; - } - for (String protocol : selector.getRequiredProtocols()) { - if (!protocol.equals(MediaPlayerProtocol.class.getName())) { - return false; - } - } - return true; - } - } - - private final class OneStub extends MediaPlayerProtocol.Stub { - MediaInfo mMediaInfo; - - public OneStub() { - super(mHandler); - } - - @Override - public void onLoad(MediaInfo mediaInfo, boolean autoplay, long playPosition, - Bundle extras) { - if (DEBUG) { - Log.d(TAG, "Attempting to play " + mediaInfo.getContentId()); - } - // look up the route and send a play command to it - mMediaInfo = mediaInfo; - Bundle bundle = new Bundle(); - bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, mediaInfo.getContentId()); - mRenderer.setContent(bundle); - } - - @Override - public void onPlay(Bundle extras) { - mRenderer.onPlay(); - } - - @Override - public void onPause(Bundle extras) { - mRenderer.onPause(); - } - } - - private class RenderListener implements Renderer.Listener { - - @Override - public void onError(int type, int extra, Bundle extras, Throwable error) { - Log.d(TAG, "Sending onError with type " + type + " and extra " + extra); - sendStatusUpdate(PlaybackState.STATE_ERROR); - } - - @Override - public void onStateChanged(int newState) { - long position = -1; - if (mRenderer != null) { - position = mRenderer.getSeekPosition(); - } - int pbState; - float rate = 0; - String errorMsg = null; - switch (newState) { - case Renderer.STATE_ENDED: - case Renderer.STATE_STOPPED: - pbState = PlaybackState.STATE_STOPPED; - break; - case Renderer.STATE_INIT: - case Renderer.STATE_PREPARING: - pbState = PlaybackState.STATE_BUFFERING; - break; - case Renderer.STATE_ERROR: - pbState = PlaybackState.STATE_ERROR; - break; - case Renderer.STATE_PAUSED: - pbState = PlaybackState.STATE_PAUSED; - break; - case Renderer.STATE_PLAYING: - pbState = PlaybackState.STATE_PLAYING; - rate = 1; - break; - default: - pbState = PlaybackState.STATE_ERROR; - errorMsg = "unknown state"; - break; - } - PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState); - bob.setState(pbState, position, rate, SystemClock.elapsedRealtime()); - bob.setErrorMessage(errorMsg); - mPlaybackState = bob.build(); - - sendStatusUpdate(mPlaybackState.getState()); - } - - @Override - public void onBufferingUpdate(int percent) { - } - - @Override - public void onFocusLost() { - Log.d(TAG, "Focus lost, pausing"); - // Don't update state here, we'll get a separate call to - // onStateChanged when it pauses - mRenderer.onPause(); - } - - @Override - public void onNextStarted() { - } - - private void sendStatusUpdate(int state) { - if (mStub != null) { - MediaStatus status = new MediaStatus(1, mStub.mMediaInfo); - switch (state) { - case PlaybackState.STATE_BUFFERING: - case PlaybackState.STATE_FAST_FORWARDING: - case PlaybackState.STATE_REWINDING: - case PlaybackState.STATE_SKIPPING_TO_NEXT: - case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: - status.setPlayerState(MediaStatus.PLAYER_STATE_BUFFERING); - break; - case PlaybackState.STATE_CONNECTING: - case PlaybackState.STATE_STOPPED: - status.setPlayerState(MediaStatus.PLAYER_STATE_IDLE); - break; - case PlaybackState.STATE_PAUSED: - status.setPlayerState(MediaStatus.PLAYER_STATE_PAUSED); - break; - case PlaybackState.STATE_PLAYING: - status.setPlayerState(MediaStatus.PLAYER_STATE_PLAYING); - break; - case PlaybackState.STATE_NONE: - case PlaybackState.STATE_ERROR: - default: - status.setPlayerState(MediaStatus.PLAYER_STATE_UNKNOWN); - break; - } - mStub.sendStatusUpdatedEvent(status, null); - } - } - } -} diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 9eea663..87762a6 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -16,19 +16,15 @@ package android.view; -import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.MergeCookie; import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; -import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; -import com.android.layoutlib.bridge.android.support.RecyclerViewUtil; -import com.android.layoutlib.bridge.android.support.RecyclerViewUtil.LayoutManagerType; import com.android.layoutlib.bridge.impl.ParserFactory; -import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.resources.ResourceType; import com.android.util.Pair; @@ -233,22 +229,6 @@ public final class BridgeInflater extends LayoutInflater { if (viewKey != null) { bc.addViewKey(view, viewKey); } - if (RenderSessionImpl.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) { - String type = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, - BridgeConstants.ATTR_LAYOUT_MANAGER_TYPE); - if (type != null) { - LayoutManagerType layoutManagerType = LayoutManagerType.getByLogicalName(type); - if (layoutManagerType == null) { - layoutManagerType = LayoutManagerType.getByClassName(type); - } - if (layoutManagerType == null) { - // add the classname itself. - bc.addCookie(view, type); - } else { - bc.addCookie(view, layoutManagerType); - } - } - } } } diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java index ec3a8d6..30512aa 100644 --- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java +++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java @@ -19,6 +19,7 @@ package android.view; import com.android.layoutlib.bridge.impl.ResourceHelper; import android.graphics.Canvas; +import android.graphics.Canvas_Delegate; import android.graphics.LinearGradient; import android.graphics.Outline; import android.graphics.Paint; @@ -125,6 +126,9 @@ public class RectShadowPainter { private static void sideShadow(Canvas canvas, Paint edgePaint, RectF edgeShadowRect, float dx, float dy, int rotations) { + if (isRectEmpty(edgeShadowRect)) { + return; + } int saved = canvas.save(); canvas.translate(dx, dy); canvas.rotate(rotations * PERPENDICULAR_ANGLE); @@ -153,4 +157,15 @@ public class RectShadowPainter { canvas.drawPath(path, paint); canvas.restoreToCount(saved); } + + /** + * Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks. + * <p/> + * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float, + * float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up + * drawing empty rectangles, which results in IllegalArgumentException. + */ + private static boolean isRectEmpty(RectF rect) { + return (int) rect.left >= (int) rect.right || (int) rect.top >= (int) rect.bottom; + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 094778d..eb5f597 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -93,6 +93,8 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE; + /** * Custom implementation of Context/Activity to handle non compiled resources. */ @@ -306,7 +308,7 @@ public final class BridgeContext extends Context { // check if this is a style resource if (value instanceof StyleResourceValue) { // get the id that will represent this style. - outValue.resourceId = getDynamicIdByStyle((StyleResourceValue)value); + outValue.resourceId = getDynamicIdByStyle((StyleResourceValue) value); return true; } @@ -812,6 +814,14 @@ public final class BridgeContext extends Context { } + @Override + public String getPackageName() { + if (mApplicationInfo.packageName == null) { + mApplicationInfo.packageName = mLayoutlibCallback.getFlag(FLAG_KEY_APPLICATION_PACKAGE); + } + return mApplicationInfo.packageName; + } + // ------------- private new methods /** @@ -1225,12 +1235,6 @@ public final class BridgeContext extends Context { } @Override - public String getPackageName() { - // pass - return null; - } - - @Override public String getBasePackageName() { // pass return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java index c44a57c..8899e53 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java @@ -164,7 +164,8 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public void showInputMethodPickerFromClient(IInputMethodClient arg0) throws RemoteException { + public void showInputMethodPickerFromClient(IInputMethodClient arg0, + int arg1) throws RemoteException { // TODO Auto-generated method stub } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java index 2f45473..b98f96f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java @@ -16,6 +16,7 @@ package com.android.layoutlib.bridge.android; +import com.android.ide.common.rendering.api.RenderParams; import com.android.ide.common.rendering.api.SessionParams.Key; /** @@ -31,10 +32,21 @@ public final class RenderParamsFlags { new Key<String>("rootTag", String.class); public static final Key<Boolean> FLAG_KEY_DISABLE_BITMAP_CACHING = new Key<Boolean>("disableBitmapCaching", Boolean.class); - public static final Key<Boolean> FLAG_KEY_RECYCLER_VIEW_SUPPORT = - new Key<Boolean>("recyclerViewSupport", Boolean.class); public static final Key<Boolean> FLAG_KEY_RENDER_ALL_DRAWABLE_STATES = new Key<Boolean>("renderAllDrawableStates", Boolean.class); + /** + * To tell LayoutLib that the IDE supports RecyclerView. + * <p/> + * Default is false. + */ + public static final Key<Boolean> FLAG_KEY_RECYCLER_VIEW_SUPPORT = + new Key<Boolean>("recyclerViewSupport", Boolean.class); + /** + * The application package name. Used via + * {@link com.android.ide.common.rendering.api.LayoutlibCallback#getFlag(Key)} + */ + public static final Key<String> FLAG_KEY_APPLICATION_PACKAGE = + new Key<String>("applicationPackage", String.class); // Disallow instances. private RenderParamsFlags() {} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java index 9273ac2..e4c7288 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java @@ -27,11 +27,12 @@ import com.android.layoutlib.bridge.android.RenderParamsFlags; import android.content.Context; import android.view.View; -import android.widget.LinearLayout; import java.lang.reflect.Method; -import static com.android.layoutlib.bridge.util.ReflectionUtils.*; +import static com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException; +import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod; +import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke; /** * Utility class for working with android.support.v7.widget.RecyclerView @@ -39,17 +40,15 @@ import static com.android.layoutlib.bridge.util.ReflectionUtils.*; @SuppressWarnings("SpellCheckingInspection") // for "recycler". public class RecyclerViewUtil { - /** - * Used by {@link LayoutManagerType}. - * <p/> - * Not declared inside the enum, since it needs to be accessible in the constructor. - */ - private static final Object CONTEXT = new Object(); - - public static final String CN_RECYCLER_VIEW = "android.support.v7.widget.RecyclerView"; + private static final String RV_PKG_PREFIX = "android.support.v7.widget."; + public static final String CN_RECYCLER_VIEW = RV_PKG_PREFIX + "RecyclerView"; private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager"; private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter"; + // LinearLayoutManager related constants. + private static final String CN_LINEAR_LAYOUT_MANAGER = RV_PKG_PREFIX + "LinearLayoutManager"; + private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class}; + /** * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView} @@ -71,37 +70,33 @@ public class RecyclerViewUtil { private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context, @NonNull LayoutlibCallback callback) throws ReflectionException { - Object cookie = context.getCookie(recyclerView); - assert cookie == null || cookie instanceof LayoutManagerType || cookie instanceof String; - if (!(cookie instanceof LayoutManagerType)) { - if (cookie != null) { - // TODO: When layoutlib API is updated, try to load the class with a null - // constructor or a constructor taking one argument - the context. - Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, - "LayoutManager (" + cookie + ") not found, falling back to " + - "LinearLayoutManager", null); - } - cookie = LayoutManagerType.getDefault(); + if (getLayoutManager(recyclerView) == null) { + // Only set the layout manager if not already set by the recycler view. + Object layoutManager = createLayoutManager(context, callback); + setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager"); } - Object layoutManager = createLayoutManager((LayoutManagerType) cookie, context, callback); - setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager"); } + /** Creates a LinearLayoutManager using the provided context. */ @Nullable - private static Object createLayoutManager(@Nullable LayoutManagerType type, - @NonNull Context context, @NonNull LayoutlibCallback callback) + private static Object createLayoutManager(@NonNull Context context, + @NonNull LayoutlibCallback callback) throws ReflectionException { - if (type == null) { - type = LayoutManagerType.getDefault(); - } try { - return callback.loadView(type.getClassName(), type.getSignature(), type.getArgs(context)); + return callback.loadView(CN_LINEAR_LAYOUT_MANAGER, LLM_CONSTRUCTOR_SIGNATURE, + new Object[]{ context}); } catch (Exception e) { throw new ReflectionException(e); } } @Nullable + private static Object getLayoutManager(View recyclerview) throws ReflectionException { + Method getLayoutManager = getMethod(recyclerview.getClass(), "getLayoutManager"); + return getLayoutManager != null ? invoke(getLayoutManager, recyclerview) : null; + } + + @Nullable private static Object createAdapter(@NonNull SessionParams params) throws ReflectionException { Boolean ideSupport = params.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT); if (ideSupport != Boolean.TRUE) { @@ -145,74 +140,4 @@ public class RecyclerViewUtil { } throw new RuntimeException("invalid object/classname combination."); } - - /** Supported LayoutManagers. */ - public enum LayoutManagerType { - LINEAR_LAYOUT_MANGER("Linear", - "android.support.v7.widget.LinearLayoutManager", - new Class[]{Context.class}, new Object[]{CONTEXT}), - GRID_LAYOUT_MANAGER("Grid", - "android.support.v7.widget.GridLayoutManager", - new Class[]{Context.class, int.class}, new Object[]{CONTEXT, 2}), - STAGGERED_GRID_LAYOUT_MANAGER("StaggeredGrid", - "android.support.v7.widget.StaggeredGridLayoutManager", - new Class[]{int.class, int.class}, new Object[]{2, LinearLayout.VERTICAL}); - - private String mLogicalName; - private String mClassName; - private Class[] mSignature; - private Object[] mArgs; - - LayoutManagerType(String logicalName, String className, Class[] signature, Object[] args) { - mLogicalName = logicalName; - mClassName = className; - mSignature = signature; - mArgs = args; - } - - String getClassName() { - return mClassName; - } - - Class[] getSignature() { - return mSignature; - } - - @NonNull - Object[] getArgs(Context context) { - Object[] args = new Object[mArgs.length]; - System.arraycopy(mArgs, 0, args, 0, mArgs.length); - for (int i = 0; i < args.length; i++) { - if (args[i] == CONTEXT) { - args[i] = context; - } - } - return args; - } - - @NonNull - public static LayoutManagerType getDefault() { - return LINEAR_LAYOUT_MANGER; - } - - @Nullable - public static LayoutManagerType getByLogicalName(@NonNull String logicalName) { - for (LayoutManagerType type : values()) { - if (logicalName.equals(type.mLogicalName)) { - return type; - } - } - return null; - } - - @Nullable - public static LayoutManagerType getByClassName(@NonNull String className) { - for (LayoutManagerType type : values()) { - if (className.equals(type.mClassName)) { - return type; - } - } - return null; - } - } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java index 9f9b968..dc89d0c 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java @@ -74,7 +74,7 @@ public class Config { } public static String getTime(int platformVersion) { - if (platformVersion == 0) { + if (isGreaterOrEqual(platformVersion, LOLLIPOP_MR1)) { return "5:10"; } if (platformVersion < GINGERBREAD) { @@ -117,7 +117,7 @@ public class Config { } public static String getWifiIconType(int platformVersion) { - return platformVersion == 0 ? "xml" : "png"; + return isGreaterOrEqual(platformVersion, LOLLIPOP) ? "xml" : "png"; } /** diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java index 9450b6c..04aadff 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java @@ -21,6 +21,10 @@ import com.android.resources.Density; import org.xmlpull.v1.XmlPullParserException; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.util.AttributeSet; +import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; @@ -29,6 +33,21 @@ public class NavigationBar extends CustomBar { /** Navigation bar background color attribute name. */ private static final String ATTR_COLOR = "navigationBarColor"; + /** + * Constructor to be used when creating the {@link NavigationBar} as a regular control. + * This is currently used by the theme editor. + */ + public NavigationBar(Context context, AttributeSet attrs) + throws XmlPullParserException { + this((BridgeContext) context, + Density.getEnum(((BridgeContext) context).getMetrics().densityDpi), + LinearLayout.HORIZONTAL, // In this mode, it doesn't need to be render vertically + ((BridgeContext) context).getConfiguration().getLayoutDirection() == + View.LAYOUT_DIRECTION_RTL, + (context.getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0, + context.getApplicationInfo().targetSdkVersion); + } + public NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl, boolean rtlEnabled, int simulatedPlatformVersion) throws XmlPullParserException { super(context, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml", diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java index e5f1f68..a0ed0e8 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java @@ -25,7 +25,9 @@ import com.android.resources.Density; import org.xmlpull.v1.XmlPullParserException; +import android.content.Context; import android.graphics.drawable.Drawable; +import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.ImageView; @@ -39,7 +41,20 @@ public class StatusBar extends CustomBar { private final int mSimulatedPlatformVersion; /** Status bar background color attribute name. */ - private static final String ATTR_COLOR = "colorPrimaryDark"; + private static final String ATTR_COLOR = "statusBarColor"; + + /** + * Constructor to be used when creating the {@link StatusBar} as a regular control. This + * is currently used by the theme editor. + */ + public StatusBar(Context context, AttributeSet attrs) throws XmlPullParserException { + this((BridgeContext) context, + Density.getEnum(((BridgeContext) context).getMetrics().densityDpi), + LinearLayout.HORIZONTAL, // In this mode, it doesn't need to be render vertically + ((BridgeContext) context).getConfiguration().getLayoutDirection() == + View.LAYOUT_DIRECTION_RTL, + context.getApplicationInfo().targetSdkVersion); + } public StatusBar(BridgeContext context, Density density, int direction, boolean RtlEnabled, int simulatedPlatformVersion) throws XmlPullParserException { @@ -50,6 +65,7 @@ public class StatusBar extends CustomBar { // FIXME: use FILL_H? setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT); + int color = getThemeAttrColor(ATTR_COLOR, true); setBackgroundColor(color == 0 ? Config.getStatusBarColor(simulatedPlatformVersion) : color); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java index 3dee1e2..26f9000 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java @@ -42,6 +42,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * Action to render a given Drawable provided through {@link DrawableParams#getDrawable()}. * diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index d957259..f6e5ef1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -1075,7 +1075,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { private void findStatusBar(RenderResources resources, DisplayMetrics metrics) { boolean windowFullscreen = getBooleanThemeValue(resources, - "windowFullscreen", false, !isThemeAppCompat(resources)); + "windowFullscreen", false, true); if (!windowFullscreen && !mWindowIsFloating) { // default value @@ -1210,15 +1210,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21). boolean isThemeAppCompat = false; for (int i = 0; i < 50; i++) { + if (defaultTheme == null) { + break; + } // for loop ensures that we don't run into cyclic theme inheritance. if (defaultTheme.getName().startsWith("Theme.AppCompat")) { isThemeAppCompat = true; break; } defaultTheme = resources.getParent(defaultTheme); - if (defaultTheme == null) { - break; - } } mIsThemeAppCompat = isThemeAppCompat; } diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class Binary files differindex d252462..8af93eb 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class Binary files differindex d109302..069f9f7 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class Binary files differindex 816ecc8..36e2688 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class Binary files differindex b034b75..ca438ad 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class Binary files differindex f86b1d3..a98abf5 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class Binary files differindex 8bbae90..7d8cc84 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class Binary files differindex 8af745d..7e6113b 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png Binary files differnew file mode 100644 index 0000000..c9b76be --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml new file mode 100644 index 0000000..2da2cb9 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml @@ -0,0 +1,393 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:ignore="HardcodedText,LabelFor,TextFields,ContentDescription,RtlHardcoded"> + + <FrameLayout + android:id="@id/frameLayout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentTop="true" + android:layout_marginEnd="311dp"> + + <TextView + android:id="@id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left|top" + android:text="New Text" /> + </FrameLayout> + + <TextView + android:id="@id/textView2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_below="@id/frameLayout" + android:text="Large Text" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + <TextView + android:id="@id/textView3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_toEndOf="@id/textView2" + android:text="Medium Text" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <TextView + android:id="@id/textView4" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@id/textView2" + android:layout_toEndOf="@id/textView2" + android:text="Small Text" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + <Button + android:id="@id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/textView3" + android:layout_toEndOf="@id/textView4" + android:text="New Button" /> + + <Button + android:id="@id/button2" + style="?android:attr/buttonStyleSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_toEndOf="@id/button" + android:text="New Button" /> + + <CheckBox + android:id="@id/checkBox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignEnd="@id/button" + android:layout_below="@id/button" + android:text="New CheckBox" /> + + <Switch + android:id="@id/switch1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_below="@id/textView2" + android:text="New Switch" /> + + <ImageButton + android:id="@id/imageButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/button" + android:layout_toEndOf="@id/switch1" /> + + <ImageView + android:id="@id/imageView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_launcher" + android:layout_below="@id/button" + android:layout_toEndOf="@id/imageButton" /> + + <GridLayout + android:id="@id/gridLayout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_below="@id/imageButton" + android:columnCount="2" + android:rowCount="2"> + + <ProgressBar + android:id="@id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_column="0" + android:layout_row="0" /> + + <ProgressBar + android:id="@id/progressBar2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_column="1" + android:layout_row="0" /> + + <ProgressBar + android:id="@id/progressBar3" + style="?android:attr/progressBarStyleSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_column="0" + android:layout_row="1" /> + + <ProgressBar + android:id="@id/progressBar4" + style="?android:attr/progressBarStyleHorizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_column="1" + android:layout_row="1" /> + </GridLayout> + + <SeekBar + android:id="@id/seekBar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@id/gridLayout" + android:layout_toEndOf="@id/gridLayout" /> + + <RatingBar + android:id="@id/ratingBar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/switch2" + android:layout_toEndOf="@id/gridLayout" /> + + <Switch + android:id="@id/switch2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/seekBar" + android:layout_toEndOf="@id/switch1" + android:checked="true" /> + + <EditText + android:id="@id/editText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBottom="@id/ratingBar" + android:layout_alignParentStart="true" + android:text="plain text" /> + + <EditText + android:id="@id/editText2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_below="@id/ratingBar" + android:ems="3" + android:inputType="textPersonName" + android:text="Name" /> + + <EditText + android:id="@id/editText3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/editText2" + android:ems="2" + android:inputType="textPassword" + android:text="password" /> + + <EditText + android:id="@id/editText4" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@id/editText3" + android:layout_toEndOf="@id/editText3" + android:ems="3" + android:inputType="numberPassword" + android:text="numeric password" /> + + <EditText + android:id="@id/editText5" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/editText3" + android:layout_toStartOf="@id/editText6" + android:ems="7" + android:inputType="textEmailAddress" + android:text="email@domain.com" /> + + <EditText + android:id="@id/editText6" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_below="@id/editText4" + android:ems="7" + android:inputType="phone" + android:text="+11235554344" /> + + <EditText + android:id="@id/editText7" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/editText" + android:layout_toEndOf="@id/editText4" + android:ems="10" + android:inputType="textPostalAddress" + android:text="1600 Amphitheatre" /> + + <EditText + android:id="@id/editText9" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/editText5" + android:layout_alignParentStart="true" + android:ems="3" + android:inputType="time" + android:text="12:12" /> + + <RadioGroup + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/editText5" + android:layout_toEndOf="@id/editText9" + android:orientation="horizontal"> + + <RadioButton + android:id="@id/radioButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="New RadioButton" /> + + <RadioButton + android:id="@id/radioButton2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="New RadioButton" /> + + </RadioGroup> + + <CheckedTextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="CheckedTextView" + android:id="@id/checkedTextView" + android:layout_below="@id/button2" + android:layout_alignParentEnd="true" + android:layout_alignStart="@id/button2" /> + + <DialerFilter + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/checkBox" + android:layout_toStartOf="@id/quickContactBadge" + android:id="@id/dialerFilter" + android:layout_above="@id/ratingBar" + android:layout_toEndOf="@id/seekBar"> + + <EditText + android:id="@android:id/hint" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Hint" /> + + <EditText + android:id="@android:id/primary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@android:id/hint" + android:text="Primary" /> + </DialerFilter> + + <QuickContactBadge + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@id/quickContactBadge" + android:layout_below="@id/checkedTextView" + android:layout_alignParentEnd="true" /> + + <android.inputmethodservice.ExtractEditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="ExtractEditText" + android:id="@id/extractEditText" + android:layout_below="@id/editText9" + android:layout_alignParentEnd="true" + android:layout_alignStart="@id/checkedTextView" /> + + <ZoomControls + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@id/zoomControls" + android:layout_below="@id/editText9" + android:layout_alignParentStart="true" /> + + <TextureView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@id/textureView" + android:layout_below="@id/zoomControls" + android:layout_alignParentStart="true" + android:layout_alignBottom="@id/extractEditText" + android:layout_toStartOf="@id/editText3" /> + + <ListView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@id/listView" + android:layout_below="@id/textureView" + android:layout_alignParentStart="true" + android:layout_alignEnd="@id/textureView" /> + + <GridView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@id/gridView" + android:layout_below="@id/extractEditText" + android:layout_alignParentEnd="true" + android:layout_alignStart="@id/extractEditText" /> + + <ScrollView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@id/scrollView" + android:layout_below="@id/zoomControls" + android:layout_toRightOf="@id/listView" + android:layout_toLeftOf="@id/extractEditText"> + + <TabHost + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@id/tabHost"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TabWidget + android:id="@android:id/tabs" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <FrameLayout + android:id="@android:id/tabcontent" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:id="@id/linearLayout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"/> + + <LinearLayout + android:id="@id/linearLayout2" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"/> + + </FrameLayout> + </LinearLayout> + </TabHost> +</ScrollView> + + <SearchView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@id/searchView" + android:layout_alignBottom="@id/zoomControls" + android:layout_toEndOf="@id/seekBar" /> + +</RelativeLayout> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/ids.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/ids.xml new file mode 100644 index 0000000..1dc2fa0 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/ids.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <item type="id" name="button" /> + <item type="id" name="button2" /> + <item type="id" name="checkBox" /> + <item type="id" name="checkedTextView" /> + <item type="id" name="dialerFilter" /> + <item type="id" name="editText" /> + <item type="id" name="editText2" /> + <item type="id" name="editText3" /> + <item type="id" name="editText4" /> + <item type="id" name="editText5" /> + <item type="id" name="editText6" /> + <item type="id" name="editText7" /> + <item type="id" name="editText8" /> + <item type="id" name="editText9" /> + <item type="id" name="extractEditText" /> + <item type="id" name="frameLayout" /> + <item type="id" name="gridLayout" /> + <item type="id" name="gridView" /> + <item type="id" name="imageButton" /> + <item type="id" name="imageView" /> + <item type="id" name="linearLayout" /> + <item type="id" name="linearLayout2" /> + <item type="id" name="listView" /> + <item type="id" name="progressBar" /> + <item type="id" name="progressBar2" /> + <item type="id" name="progressBar3" /> + <item type="id" name="progressBar4" /> + <item type="id" name="quickContactBadge" /> + <item type="id" name="radioButton" /> + <item type="id" name="radioButton2" /> + <item type="id" name="ratingBar" /> + <item type="id" name="scrollView" /> + <item type="id" name="searchView" /> + <item type="id" name="seekBar" /> + <item type="id" name="spinner" /> + <item type="id" name="switch1" /> + <item type="id" name="switch2" /> + <item type="id" name="tabHost" /> + <item type="id" name="textView" /> + <item type="id" name="textView2" /> + <item type="id" name="textView3" /> + <item type="id" name="textView4" /> + <item type="id" name="textureView" /> + <item type="id" name="zoomControls" /> +</resources>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java index ac23564..f2a039e 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -261,7 +261,7 @@ public class Main { new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) { @NonNull @Override - protected ResourceItem createResourceItem(String name) { + protected ResourceItem createResourceItem(@NonNull String name) { return new ResourceItem(name); } }; @@ -275,14 +275,27 @@ public class Main { ConfigGenerator.getEnumMap(attrs), getLayoutLog()); } + /** Text activity.xml */ + @Test + public void testActivity() throws ClassNotFoundException { + renderAndVerify("activity.xml", "activity.png"); + + } + + /** Test allwidgets.xml */ + @Test + public void testAllWidgets() throws ClassNotFoundException { + renderAndVerify("allwidgets.xml", "allwidgets.png"); + } + /** - * Create a new rendering session and test that rendering /layout/activity.xml on nexus 5 - * doesn't throw any exceptions. + * Create a new rendering session and test that rendering given layout on nexus 5 + * doesn't throw any exceptions and matches the provided image. */ - @Test - public void testRendering() throws ClassNotFoundException { + private void renderAndVerify(String layoutFileName, String goldenFileName) + throws ClassNotFoundException { // Create the layout pull parser. - LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/activity.xml"); + LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutFileName); // Create LayoutLibCallback. LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); layoutLibCallback.initResources(); @@ -301,7 +314,7 @@ public class Main { session.getResult().getErrorMessage()); } try { - String goldenImagePath = APP_TEST_DIR + "/golden/activity.png"; + String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName; ImageUtils.requireSimilar(goldenImagePath, session.getImage()); } catch (IOException e) { getLogger().error(e, e.getMessage()); @@ -309,7 +322,7 @@ public class Main { } /** - * Uses Theme.Material and Target sdk version as 21. + * Uses Theme.Material and Target sdk version as 22. */ private SessionParams getSessionParams(LayoutPullParser layoutParser, ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback) { @@ -327,7 +340,7 @@ public class Main { resourceResolver, layoutLibCallback, 0, - 21, // TODO: Make it more configurable to run tests for various versions. + 22, // TODO: Make it more configurable to run tests for various versions. getLayoutLog()); } @@ -381,17 +394,17 @@ public class Main { } @Override - public void warning(String msgFormat, Object... args) { + public void warning(@NonNull String msgFormat, Object... args) { failWithMsg(msgFormat, args); } @Override - public void info(String msgFormat, Object... args) { + public void info(@NonNull String msgFormat, Object... args) { // pass. } @Override - public void verbose(String msgFormat, Object... args) { + public void verbose(@NonNull String msgFormat, Object... args) { // pass. } }; @@ -399,7 +412,7 @@ public class Main { return mLogger; } - private static void failWithMsg(String msgFormat, Object... args) { - fail(msgFormat == null || args == null ? "" : String.format(msgFormat, args)); + private static void failWithMsg(@NonNull String msgFormat, Object... args) { + fail(args == null ? "" : String.format(msgFormat, args)); } } diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java index a5c3202..1191df6 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java @@ -21,12 +21,11 @@ import com.android.ide.common.resources.configuration.CountryCodeQualifier; import com.android.ide.common.resources.configuration.DensityQualifier; import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.common.resources.configuration.KeyboardStateQualifier; -import com.android.ide.common.resources.configuration.LanguageQualifier; import com.android.ide.common.resources.configuration.LayoutDirectionQualifier; +import com.android.ide.common.resources.configuration.LocaleQualifier; import com.android.ide.common.resources.configuration.NavigationMethodQualifier; import com.android.ide.common.resources.configuration.NetworkCodeQualifier; import com.android.ide.common.resources.configuration.NightModeQualifier; -import com.android.ide.common.resources.configuration.RegionQualifier; import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; import com.android.ide.common.resources.configuration.ScreenRatioQualifier; @@ -158,10 +157,9 @@ public class ConfigGenerator { config.setUiModeQualifier(new UiModeQualifier(UiMode.NORMAL)); config.setNightModeQualifier(new NightModeQualifier(NightMode.NOTNIGHT)); config.setCountryCodeQualifier(new CountryCodeQualifier()); - config.setLanguageQualifier(new LanguageQualifier()); config.setLayoutDirectionQualifier(new LayoutDirectionQualifier()); config.setNetworkCodeQualifier(new NetworkCodeQualifier()); - config.setRegionQualifier(new RegionQualifier()); + config.setLocaleQualifier(new LocaleQualifier()); config.setVersionQualifier(new VersionQualifier()); return config; } diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java index 0a5e798..5b648ef 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java @@ -23,8 +23,8 @@ import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; -import com.android.resources.ResourceType; import com.android.ide.common.resources.IntArrayWrapper; +import com.android.resources.ResourceType; import com.android.util.Pair; import com.android.utils.ILogger; @@ -36,6 +36,8 @@ import java.util.Map; import com.google.android.collect.Maps; +import static org.junit.Assert.fail; + @SuppressWarnings("deprecation") // For Pair public class LayoutLibTestCallback extends LayoutlibCallback { @@ -121,7 +123,7 @@ public class LayoutLibTestCallback extends LayoutlibCallback { @Override public ILayoutPullParser getParser(String layoutName) { - org.junit.Assert.fail("This method shouldn't be called by this version of LayoutLib."); + fail("This method shouldn't be called by this version of LayoutLib."); return null; } |