diff options
340 files changed, 23762 insertions, 4732 deletions
diff --git a/Android.bp b/Android.bp index 04e1dd6e3cdd..4ef04579b7f8 100644 --- a/Android.bp +++ b/Android.bp @@ -330,6 +330,8 @@ java_library { "core/java/android/view/IPinnedStackController.aidl", "core/java/android/view/IPinnedStackListener.aidl", "core/java/android/view/IRemoteAnimationRunner.aidl", + "core/java/android/view/IRecentsAnimationController.aidl", + "core/java/android/view/IRecentsAnimationRunner.aidl", "core/java/android/view/IRemoteAnimationFinishedCallback.aidl", "core/java/android/view/IRotationWatcher.aidl", "core/java/android/view/IWallpaperVisibilityListener.aidl", @@ -473,8 +475,8 @@ java_library { "telecomm/java/com/android/internal/telecom/IInCallService.aidl", "telecomm/java/com/android/internal/telecom/ITelecomService.aidl", "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl", - "telephony/java/android/telephony/data/IDataService.aidl", - "telephony/java/android/telephony/data/IDataServiceCallback.aidl", + "telephony/java/android/telephony/data/IDataService.aidl", + "telephony/java/android/telephony/data/IDataServiceCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl", @@ -484,13 +486,14 @@ java_library { "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl", - "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", + "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl", "telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl", "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl", "telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl", "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl", + "telephony/java/android/telephony/INetworkService.aidl", + "telephony/java/android/telephony/INetworkServiceCallback.aidl", "telephony/java/com/android/ims/internal/IImsCallSession.aidl", "telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl", "telephony/java/com/android/ims/internal/IImsConfig.aidl", @@ -663,7 +666,9 @@ java_library { ], // Loaded with System.loadLibrary by android.view.textclassifier - required: ["libtextclassifier"], + required: [ + "libtextclassifier", + "libmedia2_jni",], javac_shard_size: 150, @@ -825,7 +830,9 @@ java_library { srcs: [ "core/java/android/os/HidlSupport.java", + "core/java/android/annotation/IntDef.java", "core/java/android/annotation/NonNull.java", + "core/java/android/annotation/SystemApi.java", "core/java/android/os/HwBinder.java", "core/java/android/os/HwBlob.java", "core/java/android/os/HwParcel.java", diff --git a/api/current.txt b/api/current.txt index e85beab85e72..7edbea8be692 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6,6 +6,7 @@ package android { public static final class Manifest.permission { ctor public Manifest.permission(); + field public static final java.lang.String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"; field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES"; field public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; field public static final java.lang.String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"; @@ -6348,6 +6349,7 @@ package android.app.admin { method public void onReceive(android.content.Context, android.content.Intent); method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent); method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long); + method public void onTransferAffiliatedProfileOwnershipComplete(android.content.Context, android.os.UserHandle); method public void onTransferOwnershipComplete(android.content.Context, android.os.PersistableBundle); method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle); method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle); @@ -6369,7 +6371,7 @@ package android.app.admin { field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin"; field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING"; field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE"; - field public static final java.lang.String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE"; + field public static final java.lang.String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE"; field public static final java.lang.String SUPPORT_TRANSFER_OWNERSHIP_META_DATA = "android.app.support_transfer_ownership"; } @@ -6457,6 +6459,7 @@ package android.app.admin { method public boolean getStorageEncryption(android.content.ComponentName); method public int getStorageEncryptionStatus(); method public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy(); + method public android.os.PersistableBundle getTransferOwnershipBundle(); method public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName); method public android.os.Bundle getUserRestrictions(android.content.ComponentName); method public java.lang.String getWifiMacAddress(android.content.ComponentName); @@ -22343,6 +22346,40 @@ package android.media { field public static final int QUALITY_MEDIUM = 1; // 0x1 } + public final class DataSourceDesc { + method public long getEndPosition(); + method public java.io.FileDescriptor getFileDescriptor(); + method public long getFileDescriptorLength(); + method public long getFileDescriptorOffset(); + method public long getId(); + method public android.media.Media2DataSource getMedia2DataSource(); + method public long getStartPosition(); + method public int getType(); + method public android.net.Uri getUri(); + method public android.content.Context getUriContext(); + method public java.util.List<java.net.HttpCookie> getUriCookies(); + method public java.util.Map<java.lang.String, java.lang.String> getUriHeaders(); + field public static final long LONG_MAX = 576460752303423487L; // 0x7ffffffffffffffL + field public static final int TYPE_CALLBACK = 1; // 0x1 + field public static final int TYPE_FD = 2; // 0x2 + field public static final int TYPE_NONE = 0; // 0x0 + field public static final int TYPE_URI = 3; // 0x3 + } + + public static class DataSourceDesc.Builder { + ctor public DataSourceDesc.Builder(); + ctor public DataSourceDesc.Builder(android.media.DataSourceDesc); + method public android.media.DataSourceDesc build(); + method public android.media.DataSourceDesc.Builder setDataSource(android.media.Media2DataSource); + method public android.media.DataSourceDesc.Builder setDataSource(java.io.FileDescriptor); + method public android.media.DataSourceDesc.Builder setDataSource(java.io.FileDescriptor, long, long); + method public android.media.DataSourceDesc.Builder setDataSource(android.content.Context, android.net.Uri); + method public android.media.DataSourceDesc.Builder setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.net.HttpCookie>); + method public android.media.DataSourceDesc.Builder setEndPosition(long); + method public android.media.DataSourceDesc.Builder setId(long); + method public android.media.DataSourceDesc.Builder setStartPosition(long); + } + public final class DeniedByServerException extends android.media.MediaDrmException { ctor public DeniedByServerException(java.lang.String); } @@ -22619,6 +22656,12 @@ package android.media { method public abstract void onJetUserIdUpdate(android.media.JetPlayer, int, int); } + public abstract class Media2DataSource implements java.io.Closeable { + ctor public Media2DataSource(); + method public abstract long getSize() throws java.io.IOException; + method public abstract int readAt(long, byte[], int, int) throws java.io.IOException; + } + public class MediaActionSound { ctor public MediaActionSound(); method public void load(int); @@ -23073,6 +23116,7 @@ package android.media { public static final class MediaCodecInfo.EncoderCapabilities { method public android.util.Range<java.lang.Integer> getComplexityRange(); + method public android.util.Range<java.lang.Integer> getQualityRange(); method public boolean isBitrateModeSupported(int); field public static final int BITRATE_MODE_CBR = 2; // 0x2 field public static final int BITRATE_MODE_CQ = 0; // 0x0 @@ -23415,6 +23459,7 @@ package android.media { field public static final java.lang.String KEY_PRIORITY = "priority"; field public static final java.lang.String KEY_PROFILE = "profile"; field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown"; + field public static final java.lang.String KEY_QUALITY = "quality"; field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; field public static final java.lang.String KEY_ROTATION = "rotation-degrees"; field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate"; @@ -23815,6 +23860,169 @@ package android.media { field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1 } + public abstract class MediaPlayer2 implements android.media.AudioRouting java.lang.AutoCloseable { + method public abstract void addPlaylistItem(int, android.media.DataSourceDesc); + method public abstract void attachAuxEffect(int); + method public abstract void clearPendingCommands(); + method public abstract void close(); + method public static final android.media.MediaPlayer2 create(); + method public abstract void deselectTrack(int); + method public abstract android.media.DataSourceDesc editPlaylistItem(int, android.media.DataSourceDesc); + method public abstract int getAudioSessionId(); + method public abstract android.media.DataSourceDesc getCurrentDataSource(); + method public abstract int getCurrentPlaylistItemIndex(); + method public abstract int getCurrentPosition(); + method public abstract android.media.MediaPlayer2.DrmInfo getDrmInfo(); + method public abstract java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract int getDuration(); + method public abstract android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract int getLoopingMode(); + method public abstract android.os.PersistableBundle getMetrics(); + method public abstract android.media.PlaybackParams getPlaybackParams(); + method public abstract java.util.List<android.media.DataSourceDesc> getPlaylist(); + method public abstract int getSelectedTrack(int); + method public abstract android.media.SyncParams getSyncParams(); + method public abstract android.media.MediaTimestamp getTimestamp(); + method public abstract java.util.List<android.media.MediaPlayer2.TrackInfo> getTrackInfo(); + method public abstract int getVideoHeight(); + method public abstract int getVideoWidth(); + method public abstract boolean isPlaying(); + method public abstract void movePlaylistItem(int, int); + method public abstract void pause(); + method public abstract void play(); + method public abstract void prepareAsync(); + method public abstract void prepareDrm(java.util.UUID) throws android.media.MediaPlayer2.ProvisioningNetworkErrorException, android.media.MediaPlayer2.ProvisioningServerErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException; + method public abstract byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract void registerDrmEventCallback(java.util.concurrent.Executor, android.media.MediaPlayer2.DrmEventCallback); + method public abstract void registerEventCallback(java.util.concurrent.Executor, android.media.MediaPlayer2.EventCallback); + method public abstract void releaseDrm() throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract android.media.DataSourceDesc removePlaylistItem(int); + method public abstract void reset(); + method public abstract void restoreKeys(byte[]) throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract void seekTo(long, int); + method public abstract void selectTrack(int); + method public abstract void setAudioAttributes(android.media.AudioAttributes); + method public abstract void setAudioSessionId(int); + method public abstract void setAuxEffectSendLevel(float); + method public abstract void setCurrentPlaylistItem(int); + method public abstract void setDataSource(android.media.DataSourceDesc) throws java.io.IOException; + method public abstract void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer2.NoDrmSchemeException; + method public abstract void setLoopingMode(int); + method public abstract void setNextPlaylistItem(int); + method public abstract void setOnDrmConfigHelper(android.media.MediaPlayer2.OnDrmConfigHelper); + method public abstract void setPlaybackParams(android.media.PlaybackParams); + method public abstract void setPlaylist(java.util.List<android.media.DataSourceDesc>, int) throws java.io.IOException; + method public abstract void setSurface(android.view.Surface); + method public abstract void setSyncParams(android.media.SyncParams); + method public abstract void setVolume(float, float); + method public abstract void unregisterDrmEventCallback(android.media.MediaPlayer2.DrmEventCallback); + method public abstract void unregisterEventCallback(android.media.MediaPlayer2.EventCallback); + field public static final int LOOPING_MODE_FULL = 1; // 0x1 + field public static final int LOOPING_MODE_NONE = 0; // 0x0 + field public static final int LOOPING_MODE_SHUFFLE = 3; // 0x3 + field public static final int LOOPING_MODE_SINGLE = 2; // 0x2 + field public static final int MEDIA_ERROR_IO = -1004; // 0xfffffc14 + field public static final int MEDIA_ERROR_MALFORMED = -1007; // 0xfffffc11 + field public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; // 0xc8 + field public static final int MEDIA_ERROR_TIMED_OUT = -110; // 0xffffff92 + field public static final int MEDIA_ERROR_UNKNOWN = 1; // 0x1 + field public static final int MEDIA_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e + field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324 + field public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4; // 0x4 + field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320 + field public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be + field public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd + field public static final int MEDIA_INFO_COMPLETE_CALL_PAUSE = 102; // 0x66 + field public static final int MEDIA_INFO_COMPLETE_CALL_PLAY = 101; // 0x65 + field public static final int MEDIA_INFO_COMPLETE_CALL_SEEK = 103; // 0x67 + field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322 + field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321 + field public static final int MEDIA_INFO_PLAYBACK_COMPLETE = 5; // 0x5 + field public static final int MEDIA_INFO_PLAYLIST_END = 6; // 0x6 + field public static final int MEDIA_INFO_PREPARED = 100; // 0x64 + field public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; // 0x2 + field public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386 + field public static final int MEDIA_INFO_UNKNOWN = 1; // 0x1 + field public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385 + field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325 + field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3 + field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc + field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3 + field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1 + field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2 + field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0 + field public static final int SEEK_CLOSEST = 3; // 0x3 + field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2 + field public static final int SEEK_NEXT_SYNC = 1; // 0x1 + field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0 + field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1 + } + + public static abstract class MediaPlayer2.DrmEventCallback { + ctor public MediaPlayer2.DrmEventCallback(); + method public void onDrmInfo(android.media.MediaPlayer2, android.media.MediaPlayer2.DrmInfo); + method public void onDrmPrepared(android.media.MediaPlayer2, int); + } + + public static abstract class MediaPlayer2.DrmInfo { + ctor public MediaPlayer2.DrmInfo(); + method public abstract java.util.Map<java.util.UUID, byte[]> getPssh(); + method public abstract java.util.List<java.util.UUID> getSupportedSchemes(); + } + + public static abstract class MediaPlayer2.EventCallback { + ctor public MediaPlayer2.EventCallback(); + method public void onBufferingUpdate(android.media.MediaPlayer2, long, int); + method public void onError(android.media.MediaPlayer2, long, int, int); + method public void onInfo(android.media.MediaPlayer2, long, int, int); + method public void onTimedMetaDataAvailable(android.media.MediaPlayer2, long, android.media.TimedMetaData); + method public void onVideoSizeChanged(android.media.MediaPlayer2, long, int, int); + } + + public static final class MediaPlayer2.MetricsConstants { + field public static final java.lang.String CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + field public static final java.lang.String CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + field public static final java.lang.String DURATION = "android.media.mediaplayer.durationMs"; + field public static final java.lang.String ERRORS = "android.media.mediaplayer.err"; + field public static final java.lang.String ERROR_CODE = "android.media.mediaplayer.errcode"; + field public static final java.lang.String FRAMES = "android.media.mediaplayer.frames"; + field public static final java.lang.String FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + field public static final java.lang.String HEIGHT = "android.media.mediaplayer.height"; + field public static final java.lang.String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime"; + field public static final java.lang.String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime"; + field public static final java.lang.String PLAYING = "android.media.mediaplayer.playingMs"; + field public static final java.lang.String WIDTH = "android.media.mediaplayer.width"; + } + + public static abstract class MediaPlayer2.NoDrmSchemeException extends android.media.MediaDrmException { + ctor protected MediaPlayer2.NoDrmSchemeException(java.lang.String); + } + + public static abstract interface MediaPlayer2.OnDrmConfigHelper { + method public abstract void onDrmConfig(android.media.MediaPlayer2); + } + + public static abstract class MediaPlayer2.ProvisioningNetworkErrorException extends android.media.MediaDrmException { + ctor protected MediaPlayer2.ProvisioningNetworkErrorException(java.lang.String); + } + + public static abstract class MediaPlayer2.ProvisioningServerErrorException extends android.media.MediaDrmException { + ctor protected MediaPlayer2.ProvisioningServerErrorException(java.lang.String); + } + + public static abstract class MediaPlayer2.TrackInfo { + ctor public MediaPlayer2.TrackInfo(); + method public abstract android.media.MediaFormat getFormat(); + method public abstract java.lang.String getLanguage(); + method public abstract int getTrackType(); + method public abstract java.lang.String toString(); + field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2 + field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5 + field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4 + field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0 + field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1 + } + public class MediaRecorder implements android.media.AudioRouting { ctor public MediaRecorder(); method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); @@ -27381,14 +27589,14 @@ package android.net.wifi { field public java.lang.String providerFriendlyName; field public long[] roamingConsortiumIds; field public int status; - field public java.lang.String[] wepKeys; - field public int wepTxKeyIndex; + field public deprecated java.lang.String[] wepKeys; + field public deprecated int wepTxKeyIndex; } public static class WifiConfiguration.AuthAlgorithm { field public static final int LEAP = 2; // 0x2 field public static final int OPEN = 0; // 0x0 - field public static final int SHARED = 1; // 0x1 + field public static final deprecated int SHARED = 1; // 0x1 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "auth_alg"; } @@ -27396,8 +27604,8 @@ package android.net.wifi { public static class WifiConfiguration.GroupCipher { field public static final int CCMP = 3; // 0x3 field public static final int TKIP = 2; // 0x2 - field public static final int WEP104 = 1; // 0x1 - field public static final int WEP40 = 0; // 0x0 + field public static final deprecated int WEP104 = 1; // 0x1 + field public static final deprecated int WEP40 = 0; // 0x0 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "group"; } @@ -27406,7 +27614,7 @@ package android.net.wifi { field public static final int IEEE8021X = 3; // 0x3 field public static final int NONE = 0; // 0x0 field public static final int WPA_EAP = 2; // 0x2 - field public static final int WPA_PSK = 1; // 0x1 + field public static final deprecated int WPA_PSK = 1; // 0x1 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "key_mgmt"; } @@ -27414,14 +27622,14 @@ package android.net.wifi { public static class WifiConfiguration.PairwiseCipher { field public static final int CCMP = 2; // 0x2 field public static final int NONE = 0; // 0x0 - field public static final int TKIP = 1; // 0x1 + field public static final deprecated int TKIP = 1; // 0x1 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "pairwise"; } public static class WifiConfiguration.Protocol { field public static final int RSN = 1; // 0x1 - field public static final int WPA = 0; // 0x0 + field public static final deprecated int WPA = 0; // 0x0 field public static final java.lang.String[] strings; field public static final java.lang.String varName = "proto"; } @@ -40203,6 +40411,7 @@ package android.telecom { method public void onCallEvent(java.lang.String, android.os.Bundle); method public void onDisconnect(); method public void onExtrasChanged(android.os.Bundle); + method public void onHandoverComplete(); method public void onHold(); method public void onPlayDtmfTone(char); method public void onPostDialContinue(boolean); @@ -40694,6 +40903,7 @@ package android.telecom { field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL"; field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED"; field public static final java.lang.String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED"; + field public static final java.lang.String ACTION_SHOW_ASSISTED_DIALING_SETTINGS = "android.telecom.action.SHOW_ASSISTED_DIALING_SETTINGS"; field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS"; field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS"; field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION"; @@ -40790,6 +41000,7 @@ package android.telephony { field public static final int EUTRAN = 3; // 0x3 field public static final int GERAN = 1; // 0x1 field public static final int IWLAN = 5; // 0x5 + field public static final int UNKNOWN = 0; // 0x0 field public static final int UTRAN = 2; // 0x2 } @@ -40894,6 +41105,8 @@ package android.telephony { method public void notifyConfigChangedForSubId(int); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe + field public static final java.lang.String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; + field public static final java.lang.String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -44671,42 +44884,42 @@ package android.util { method public void previousMonth(); } - public final class MutableBoolean { + public final deprecated class MutableBoolean { ctor public MutableBoolean(boolean); field public boolean value; } - public final class MutableByte { + public final deprecated class MutableByte { ctor public MutableByte(byte); field public byte value; } - public final class MutableChar { + public final deprecated class MutableChar { ctor public MutableChar(char); field public char value; } - public final class MutableDouble { + public final deprecated class MutableDouble { ctor public MutableDouble(double); field public double value; } - public final class MutableFloat { + public final deprecated class MutableFloat { ctor public MutableFloat(float); field public float value; } - public final class MutableInt { + public final deprecated class MutableInt { ctor public MutableInt(int); field public int value; } - public final class MutableLong { + public final deprecated class MutableLong { ctor public MutableLong(long); field public long value; } - public final class MutableShort { + public final deprecated class MutableShort { ctor public MutableShort(short); field public short value; } @@ -49374,6 +49587,7 @@ package android.view.inputmethod { method public abstract boolean performEditorAction(int); method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle); method public abstract boolean reportFullscreenMode(boolean); + method public default void reportLanguageHint(android.os.LocaleList); method public abstract boolean requestCursorUpdates(int); method public abstract boolean sendKeyEvent(android.view.KeyEvent); method public abstract boolean setComposingRegion(int, int); diff --git a/api/system-current.txt b/api/system-current.txt index 2ed8f90e3c7c..e1ec1deb9def 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -253,6 +253,7 @@ package android.app { public class AppOpsManager { method public static java.lang.String[] getOpStrs(); method public void setUidMode(java.lang.String, int, int); + field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; @@ -382,7 +383,6 @@ package android.app.admin { method public java.lang.CharSequence getDeviceOwnerOrganizationName(); method public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); method public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); - method public java.lang.CharSequence getPrintingDisabledReason(); method public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method public java.lang.String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; method public int getUserProvisioningState(); @@ -3484,6 +3484,125 @@ package android.os { field public static final java.lang.String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES"; } + public class HidlSupport { + method public static boolean deepEquals(java.lang.Object, java.lang.Object); + method public static int deepHashCode(java.lang.Object); + method public static boolean interfacesEqual(android.os.IHwInterface, java.lang.Object); + } + + public abstract class HwBinder implements android.os.IHwBinder { + method public static final void configureRpcThreadpool(long, boolean); + method public static final void joinRpcThreadpool(); + } + + public class HwBlob { + ctor public HwBlob(int); + method public final void copyToBoolArray(long, boolean[], int); + method public final void copyToDoubleArray(long, double[], int); + method public final void copyToFloatArray(long, float[], int); + method public final void copyToInt16Array(long, short[], int); + method public final void copyToInt32Array(long, int[], int); + method public final void copyToInt64Array(long, long[], int); + method public final void copyToInt8Array(long, byte[], int); + method public final boolean getBool(long); + method public final double getDouble(long); + method public final float getFloat(long); + method public final short getInt16(long); + method public final int getInt32(long); + method public final long getInt64(long); + method public final byte getInt8(long); + method public final java.lang.String getString(long); + method public final long handle(); + method public final void putBlob(long, android.os.HwBlob); + method public final void putBool(long, boolean); + method public final void putBoolArray(long, boolean[]); + method public final void putDouble(long, double); + method public final void putDoubleArray(long, double[]); + method public final void putFloat(long, float); + method public final void putFloatArray(long, float[]); + method public final void putInt16(long, short); + method public final void putInt16Array(long, short[]); + method public final void putInt32(long, int); + method public final void putInt32Array(long, int[]); + method public final void putInt64(long, long); + method public final void putInt64Array(long, long[]); + method public final void putInt8(long, byte); + method public final void putInt8Array(long, byte[]); + method public final void putString(long, java.lang.String); + method public static java.lang.Boolean[] wrapArray(boolean[]); + method public static java.lang.Long[] wrapArray(long[]); + method public static java.lang.Byte[] wrapArray(byte[]); + method public static java.lang.Short[] wrapArray(short[]); + method public static java.lang.Integer[] wrapArray(int[]); + method public static java.lang.Float[] wrapArray(float[]); + method public static java.lang.Double[] wrapArray(double[]); + } + + public class HwParcel { + ctor public HwParcel(); + method public final void enforceInterface(java.lang.String); + method public final boolean readBool(); + method public final java.util.ArrayList<java.lang.Boolean> readBoolVector(); + method public final android.os.HwBlob readBuffer(long); + method public final double readDouble(); + method public final java.util.ArrayList<java.lang.Double> readDoubleVector(); + method public final android.os.HwBlob readEmbeddedBuffer(long, long, long, boolean); + method public final float readFloat(); + method public final java.util.ArrayList<java.lang.Float> readFloatVector(); + method public final short readInt16(); + method public final java.util.ArrayList<java.lang.Short> readInt16Vector(); + method public final int readInt32(); + method public final java.util.ArrayList<java.lang.Integer> readInt32Vector(); + method public final long readInt64(); + method public final java.util.ArrayList<java.lang.Long> readInt64Vector(); + method public final byte readInt8(); + method public final java.util.ArrayList<java.lang.Byte> readInt8Vector(); + method public final java.lang.String readString(); + method public final java.util.ArrayList<java.lang.String> readStringVector(); + method public final android.os.IHwBinder readStrongBinder(); + method public final void release(); + method public final void releaseTemporaryStorage(); + method public final void send(); + method public final void verifySuccess(); + method public final void writeBool(boolean); + method public final void writeBoolVector(java.util.ArrayList<java.lang.Boolean>); + method public final void writeBuffer(android.os.HwBlob); + method public final void writeDouble(double); + method public final void writeDoubleVector(java.util.ArrayList<java.lang.Double>); + method public final void writeFloat(float); + method public final void writeFloatVector(java.util.ArrayList<java.lang.Float>); + method public final void writeInt16(short); + method public final void writeInt16Vector(java.util.ArrayList<java.lang.Short>); + method public final void writeInt32(int); + method public final void writeInt32Vector(java.util.ArrayList<java.lang.Integer>); + method public final void writeInt64(long); + method public final void writeInt64Vector(java.util.ArrayList<java.lang.Long>); + method public final void writeInt8(byte); + method public final void writeInt8Vector(java.util.ArrayList<java.lang.Byte>); + method public final void writeInterfaceToken(java.lang.String); + method public final void writeStatus(int); + method public final void writeString(java.lang.String); + method public final void writeStringVector(java.util.ArrayList<java.lang.String>); + method public final void writeStrongBinder(android.os.IHwBinder); + field public static final int STATUS_SUCCESS = 0; // 0x0 + } + + public static abstract class HwParcel.Status implements java.lang.annotation.Annotation { + } + + public abstract interface IHwBinder { + method public abstract boolean linkToDeath(android.os.IHwBinder.DeathRecipient, long); + method public abstract boolean unlinkToDeath(android.os.IHwBinder.DeathRecipient); + } + + public static abstract interface IHwBinder.DeathRecipient { + method public abstract void serviceDied(long); + } + + public abstract interface IHwInterface { + method public abstract android.os.IHwBinder asBinder(); + } + public class IncidentManager { method public void reportIncident(android.os.IncidentReportArgs); method public void reportIncident(java.lang.String, byte[]); @@ -4557,6 +4676,63 @@ package android.telephony { field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming"; } + public class NetworkRegistrationState implements android.os.Parcelable { + ctor public NetworkRegistrationState(int, int, int, int, int, boolean, int[], android.telephony.CellIdentity); + ctor protected NetworkRegistrationState(android.os.Parcel); + method public int describeContents(); + method public int getAccessNetworkTechnology(); + method public int[] getAvailableServices(); + method public int getDomain(); + method public int getRegState(); + method public int getTransportType(); + method public boolean isEmergencyEnabled(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationState> CREATOR; + field public static final int DOMAIN_CS = 1; // 0x1 + field public static final int DOMAIN_PS = 2; // 0x2 + field public static final int REG_STATE_DENIED = 3; // 0x3 + field public static final int REG_STATE_HOME = 1; // 0x1 + field public static final int REG_STATE_NOT_REG_NOT_SEARCHING = 0; // 0x0 + field public static final int REG_STATE_NOT_REG_SEARCHING = 2; // 0x2 + field public static final int REG_STATE_ROAMING = 5; // 0x5 + field public static final int REG_STATE_UNKNOWN = 4; // 0x4 + field public static final int SERVICE_TYPE_DATA = 2; // 0x2 + field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5 + field public static final int SERVICE_TYPE_SMS = 3; // 0x3 + field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4 + field public static final int SERVICE_TYPE_VOICE = 1; // 0x1 + } + + public abstract class NetworkService extends android.app.Service { + method protected abstract android.telephony.NetworkService.NetworkServiceProvider createNetworkServiceProvider(int); + field public static final java.lang.String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID"; + field public static final java.lang.String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService"; + } + + public class NetworkService.NetworkServiceProvider { + ctor public NetworkService.NetworkServiceProvider(int); + method public void getNetworkRegistrationState(int, android.telephony.NetworkServiceCallback); + method public final int getSlotId(); + method public final void notifyNetworkRegistrationStateChanged(); + method protected void onDestroy(); + } + + public class NetworkServiceCallback { + method public void onGetNetworkRegistrationStateComplete(int, android.telephony.NetworkRegistrationState); + field public static final int RESULT_ERROR_BUSY = 3; // 0x3 + field public static final int RESULT_ERROR_FAILED = 5; // 0x5 + field public static final int RESULT_ERROR_ILLEGAL_STATE = 4; // 0x4 + field public static final int RESULT_ERROR_INVALID_ARG = 2; // 0x2 + field public static final int RESULT_ERROR_UNSUPPORTED = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + } + + public class ServiceState implements android.os.Parcelable { + method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStates(); + method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStates(int); + method public android.telephony.NetworkRegistrationState getNetworkRegistrationStates(int, int); + } + public final class SmsManager { method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>); method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent); @@ -4650,6 +4826,7 @@ package android.telephony { method public int getSimApplicationState(); method public int getSimCardState(); method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms(); + method public android.telephony.UiccSlotInfo[] getUiccSlotsInfo(); method public android.os.Bundle getVisualVoicemailSettings(); method public int getVoiceActivationState(); method public boolean handlePinMmi(java.lang.String); @@ -4676,6 +4853,7 @@ package android.telephony { method public int[] supplyPinReportResult(java.lang.String); method public boolean supplyPuk(java.lang.String, java.lang.String); method public int[] supplyPukReportResult(java.lang.String, java.lang.String); + method public boolean switchSlots(int[]); method public void toggleRadioOnOff(); method public void updateServiceLocation(); field public static final java.lang.String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED"; @@ -4697,6 +4875,25 @@ package android.telephony { field public static final int SIM_STATE_PRESENT = 11; // 0xb } + public class UiccSlotInfo implements android.os.Parcelable { + ctor public UiccSlotInfo(boolean, boolean, java.lang.String, int); + method public int describeContents(); + method public java.lang.String getCardId(); + method public int getCardStateInfo(); + method public boolean getIsActive(); + method public boolean getIsEuicc(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CARD_STATE_INFO_ABSENT = 1; // 0x1 + field public static final int CARD_STATE_INFO_ERROR = 3; // 0x3 + field public static final int CARD_STATE_INFO_PRESENT = 2; // 0x2 + field public static final int CARD_STATE_INFO_RESTRICTED = 4; // 0x4 + field public static final android.os.Parcelable.Creator<android.telephony.UiccSlotInfo> CREATOR; + field public final java.lang.String cardId; + field public final int cardStateInfo; + field public final boolean isActive; + field public final boolean isEuicc; + } + public abstract class VisualVoicemailService extends android.app.Service { method public static final void sendVisualVoicemailSms(android.content.Context, android.telecom.PhoneAccountHandle, java.lang.String, short, java.lang.String, android.app.PendingIntent); method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings); diff --git a/api/test-current.txt b/api/test-current.txt index 254fc15a4cbf..4e8f904b96b7 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -48,6 +48,7 @@ package android.app { public class AppOpsManager { method public static java.lang.String[] getOpStrs(); method public void setMode(int, int, java.lang.String, int); + field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; @@ -1036,6 +1037,10 @@ package android.view.autofill { package android.widget { + public abstract class AbsListView extends android.widget.AdapterView implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener { + method public final boolean shouldDrawSelector(); + } + public class CalendarView extends android.widget.FrameLayout { method public boolean getBoundsForDate(long, android.graphics.Rect); } diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index a7daa3f2b63a..9200f6445c01 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -221,6 +221,44 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ include $(BUILD_STATIC_JAVA_LIBRARY) +############################## +# statsd micro benchmark +############################## + +include $(CLEAR_VARS) +LOCAL_MODULE := statsd_benchmark + +LOCAL_SRC_FILES := $(statsd_common_src) \ + benchmark/main.cpp \ + benchmark/hello_world_benchmark.cpp \ + benchmark/log_event_benchmark.cpp + +LOCAL_C_INCLUDES := $(statsd_common_c_includes) + +LOCAL_CFLAGS := -Wall \ + -Werror \ + -Wno-unused-parameter \ + -Wno-unused-variable \ + -Wno-unused-function \ + +# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374 +LOCAL_CFLAGS += -Wno-varargs + +LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes) + +LOCAL_STATIC_LIBRARIES := \ + $(statsd_common_static_libraries) + +LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ + libgtest_prod + +LOCAL_PROTOC_OPTIMIZE_TYPE := lite + +LOCAL_MODULE_TAGS := eng tests + +include $(BUILD_NATIVE_BENCHMARK) + + statsd_common_src:= statsd_common_aidl_includes:= statsd_common_c_includes:= @@ -228,6 +266,4 @@ statsd_common_static_libraries:= statsd_common_shared_libraries:= -############################## - include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/cmds/statsd/benchmark/hello_world_benchmark.cpp b/cmds/statsd/benchmark/hello_world_benchmark.cpp new file mode 100644 index 000000000000..c732d394bf64 --- /dev/null +++ b/cmds/statsd/benchmark/hello_world_benchmark.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "benchmark/benchmark.h" + +static void BM_StringCreation(benchmark::State& state) { + while (state.KeepRunning()) std::string empty_string; +} +// Register the function as a benchmark +BENCHMARK(BM_StringCreation); + +// Define another benchmark +static void BM_StringCopy(benchmark::State& state) { + std::string x = "hello"; + while (state.KeepRunning()) std::string copy(x); +} +BENCHMARK(BM_StringCopy); diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp new file mode 100644 index 000000000000..43addc28f074 --- /dev/null +++ b/cmds/statsd/benchmark/log_event_benchmark.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <vector> +#include "benchmark/benchmark.h" +#include "logd/LogEvent.h" + +namespace android { +namespace os { +namespace statsd { + +using std::vector; + +/* Special markers for android_log_list_element type */ +static const char EVENT_TYPE_LIST_STOP = '\n'; /* declare end of list */ +static const char EVENT_TYPE_UNKNOWN = '?'; /* protocol error */ + +static const char EVENT_TYPE_INT = 0; +static const char EVENT_TYPE_LONG = 1; +static const char EVENT_TYPE_STRING = 2; +static const char EVENT_TYPE_LIST = 3; +static const char EVENT_TYPE_FLOAT = 4; + +static const int kLogMsgHeaderSize = 28; + +static void write4Bytes(int val, vector<char>* buffer) { + buffer->push_back(static_cast<char>(val)); + buffer->push_back(static_cast<char>((val >> 8) & 0xFF)); + buffer->push_back(static_cast<char>((val >> 16) & 0xFF)); + buffer->push_back(static_cast<char>((val >> 24) & 0xFF)); +} + +static void getSimpleLogMsgData(log_msg* msg) { + vector<char> buffer; + // stats_log tag id + write4Bytes(1937006964, &buffer); + buffer.push_back(EVENT_TYPE_LIST); + buffer.push_back(2); // field counts; + buffer.push_back(EVENT_TYPE_INT); + write4Bytes(10 /* atom id */, &buffer); + buffer.push_back(EVENT_TYPE_INT); + write4Bytes(99 /* a value to log*/, &buffer); + buffer.push_back(EVENT_TYPE_LIST_STOP); + + msg->entry_v1.len = buffer.size(); + msg->entry.hdr_size = kLogMsgHeaderSize; + msg->entry_v1.sec = time(nullptr); + std::copy(buffer.begin(), buffer.end(), msg->buf + kLogMsgHeaderSize); +} + +static void BM_LogEventCreation(benchmark::State& state) { + log_msg msg; + getSimpleLogMsgData(&msg); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(LogEvent(msg)); + } +} +BENCHMARK(BM_LogEventCreation); + +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/benchmark/main.cpp b/cmds/statsd/benchmark/main.cpp new file mode 100644 index 000000000000..08921f3c3f52 --- /dev/null +++ b/cmds/statsd/benchmark/main.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <benchmark/benchmark.h> + +BENCHMARK_MAIN(); diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 25264004ee47..1b8efe02d0d4 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -137,15 +137,17 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { StatsdStats::getInstance().noteAtomLogged( event->GetTagId(), event->GetTimestampNs() / NS_PER_SEC); - if (mMetricsManagers.empty()) { - return; - } - // Hard-coded logic to update the isolated uid's in the uid-map. // The field numbers need to be currently updated by hand with atoms.proto if (event->GetTagId() == android::util::ISOLATED_UID_CHANGED) { onIsolatedUidChangedEventLocked(*event); - } else { + } + + if (mMetricsManagers.empty()) { + return; + } + + if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) { // Map the isolated uid to host uid if necessary. mapIsolatedUidToHostUidIfNecessaryLocked(event); } diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index ba628b849147..8975c542018c 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -380,6 +380,8 @@ status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8 "The config can only be set for other UIDs on eng or userdebug " "builds.\n"); } + } else if (argCount == 2 && args[1] == "remove") { + good = true; } if (!good) { diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 42994b558208..496c29b24657 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -101,8 +101,8 @@ void ConfigManager::RemoveConfig(const ConfigKey& key) { } void ConfigManager::remove_saved_configs(const ConfigKey& key) { - string prefix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId()); - StorageManager::deletePrefixedFiles(STATS_SERVICE_DIR, prefix.c_str()); + string suffix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId()); + StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str()); } void ConfigManager::RemoveConfigs(int uid) { @@ -111,6 +111,7 @@ void ConfigManager::RemoveConfigs(int uid) { for (auto it = mConfigs.begin(); it != mConfigs.end();) { // Remove from map if (it->GetUid() == uid) { + remove_saved_configs(*it); removed.push_back(*it); mConfigReceivers.erase(*it); it = mConfigs.erase(it); diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 34fa3c404d10..1ca793c81878 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -42,6 +42,7 @@ LogEvent::LogEvent(log_msg& msg) { mLogUid = msg.entry_v4.uid; init(mContext); if (mContext) { + // android_log_destroy will set mContext to NULL android_log_destroy(&mContext); } } @@ -64,12 +65,17 @@ void LogEvent::init() { mContext = create_android_log_parser(buffer, len); init(mContext); // destroy the context to save memory. - android_log_destroy(&mContext); + if (mContext) { + // android_log_destroy will set mContext to NULL + android_log_destroy(&mContext); + } } } LogEvent::~LogEvent() { if (mContext) { + // This is for the case when LogEvent is created using the test interface + // but init() isn't called. android_log_destroy(&mContext); } } diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 3d6984cea97d..5a4efd4b54a4 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -156,7 +156,7 @@ private: // This field is used when statsD wants to create log event object and write fields to it. After // calling init() function, this object would be destroyed to save memory usage. // When the log event is created from log msg, this field is never initiated. - android_log_context mContext; + android_log_context mContext = NULL; uint64_t mTimestampNs; diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index eefb7dc046ec..91279661b61f 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -281,20 +281,10 @@ int UidMap::getHostUidOrSelf(int uid) const { void UidMap::clearOutput() { mOutput.Clear(); - // Re-initialize the initial state for the outputs. This results in extra data being uploaded - // but helps ensure we can re-construct the UID->app name, versionCode mapping in server. - auto snapshot = mOutput.add_snapshots(); - for (auto it : mMap) { - auto t = snapshot->add_package_info(); - t->set_name(it.second.packageName); - t->set_version(it.second.versionCode); - t->set_uid(it.first); - } - // Also update the guardrail trackers. StatsdStats::getInstance().setUidMapChanges(0); StatsdStats::getInstance().setUidMapSnapshots(1); - mBytesUsed = snapshot->ByteSize(); + mBytesUsed = 0; StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); } @@ -348,6 +338,19 @@ UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) { ++it_deltas; } } + + if (mOutput.snapshots_size() == 0) { + // Produce another snapshot. This results in extra data being uploaded but helps + // ensure we can re-construct the UID->app name, versionCode mapping in server. + auto snapshot = mOutput.add_snapshots(); + snapshot->set_timestamp_nanos(timestamp); + for (auto it : mMap) { + auto t = snapshot->add_package_info(); + t->set_name(it.second.packageName); + t->set_version(it.second.versionCode); + t->set_uid(it.first); + } + } } mBytesUsed = mOutput.ByteSize(); // Compute actual size after potential deletions. StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed); diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 1d75e20d7eaa..f12770175132 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -78,7 +78,7 @@ void StorageManager::deleteAllFiles(const char* path) { } } -void StorageManager::deletePrefixedFiles(const char* path, const char* prefix) { +void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) { unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); if (dir == NULL) { VLOG("Directory does not exist: %s", path); @@ -88,10 +88,14 @@ void StorageManager::deletePrefixedFiles(const char* path, const char* prefix) { dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; - if (name[0] == '.' || strncmp(name, prefix, strlen(prefix)) != 0) { + if (name[0] == '.') { continue; } - deleteFile(StringPrintf("%s/%s", path, name).c_str()); + size_t nameLen = strlen(name); + size_t suffixLen = strlen(suffix); + if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) { + deleteFile(StringPrintf("%s/%s", path, name).c_str()); + } } } diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index caf5b8b42887..f9988fe8c427 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -47,9 +47,9 @@ public: static void deleteAllFiles(const char* path); /** - * Deletes all files whose name matches with a provided prefix. + * Deletes all files whose name matches with a provided suffix. */ - static void deletePrefixedFiles(const char* path, const char* prefix); + static void deleteSuffixedFiles(const char* path, const char* suffix); /** * Send broadcasts to relevant receiver for each data stored on disk. diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp index 5292f24f0bee..f26c10d33e67 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -178,16 +178,16 @@ TEST(UidMapTest, TestClearingOutput) { EXPECT_EQ(1, results.snapshots_size()); // It should be cleared now - EXPECT_EQ(0, m.mOutput.snapshots_size()); + EXPECT_EQ(1, m.mOutput.snapshots_size()); results = m.getOutput(3, config1); - EXPECT_EQ(0, results.snapshots_size()); + EXPECT_EQ(1, results.snapshots_size()); // Now add another configuration. m.OnConfigUpdated(config2); m.updateApp(5, String16(kApp1.c_str()), 1000, 40); EXPECT_EQ(1, m.mOutput.changes_size()); results = m.getOutput(6, config1); - EXPECT_EQ(0, results.snapshots_size()); + EXPECT_EQ(1, results.snapshots_size()); EXPECT_EQ(1, results.changes_size()); EXPECT_EQ(1, m.mOutput.changes_size()); @@ -197,15 +197,15 @@ TEST(UidMapTest, TestClearingOutput) { // We still can't remove anything. results = m.getOutput(8, config1); - EXPECT_EQ(0, results.snapshots_size()); + EXPECT_EQ(1, results.snapshots_size()); EXPECT_EQ(2, results.changes_size()); EXPECT_EQ(2, m.mOutput.changes_size()); results = m.getOutput(9, config2); - EXPECT_EQ(0, results.snapshots_size()); + EXPECT_EQ(1, results.snapshots_size()); EXPECT_EQ(2, results.changes_size()); // At this point both should be cleared. - EXPECT_EQ(0, m.mOutput.snapshots_size()); + EXPECT_EQ(1, m.mOutput.snapshots_size()); EXPECT_EQ(0, m.mOutput.changes_size()); } @@ -228,10 +228,8 @@ TEST(UidMapTest, TestMemoryComputed) { m.updateApp(3, String16(kApp1.c_str()), 1000, 40); EXPECT_TRUE(m.mBytesUsed > snapshot_bytes); - size_t bytesWithSnapshotChange = m.mBytesUsed; m.getOutput(2, config1); - EXPECT_TRUE(m.mBytesUsed < bytesWithSnapshotChange); size_t prevBytes = m.mBytesUsed; m.getOutput(4, config1); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index cd029c06b91d..f0ef49f95f2e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6330,6 +6330,8 @@ public class Activity extends ContextThemeWrapper } else { writer.print(prefix); writer.println("No AutofillManager"); } + + ResourcesManager.getInstance().dump(prefix, writer); } /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index b5a941283184..80350584bce6 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -181,7 +181,8 @@ public class ActivityManager { BUGREPORT_OPTION_INTERACTIVE, BUGREPORT_OPTION_REMOTE, BUGREPORT_OPTION_WEAR, - BUGREPORT_OPTION_TELEPHONY + BUGREPORT_OPTION_TELEPHONY, + BUGREPORT_OPTION_WIFI }) public @interface BugreportMode {} /** @@ -216,6 +217,12 @@ public class ActivityManager { public static final int BUGREPORT_OPTION_TELEPHONY = 4; /** + * Takes a lightweight bugreport that only includes a few sections related to Wifi. + * @hide + */ + public static final int BUGREPORT_OPTION_WIFI = 5; + + /** * <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code * <meta-data>}</a> name for a 'home' Activity that declares a package that is to be * uninstalled in lieu of the declaring one. The package named here must be diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 4bcd677e1f4e..fee58274a5fc 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -207,6 +207,12 @@ public class ActivityOptions { "android.activity.taskOverlayCanResume"; /** + * See {@link #setAvoidMoveToFront()}. + * @hide + */ + private static final String KEY_AVOID_MOVE_TO_FRONT = "android.activity.avoidMoveToFront"; + + /** * Where the split-screen-primary stack should be positioned. * @hide */ @@ -307,6 +313,7 @@ public class ActivityOptions { private boolean mDisallowEnterPictureInPictureWhileLaunching; private boolean mTaskOverlay; private boolean mTaskOverlayCanResume; + private boolean mAvoidMoveToFront; private AppTransitionAnimationSpec mAnimSpecs[]; private int mRotationAnimationHint = -1; private Bundle mAppVerificationBundle; @@ -923,6 +930,7 @@ public class ActivityOptions { mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false); mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false); + mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false); mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean( @@ -1239,6 +1247,25 @@ public class ActivityOptions { return mTaskOverlayCanResume; } + /** + * Sets whether the activity launched should not cause the activity stack it is contained in to + * be moved to the front as a part of launching. + * + * @hide + */ + public void setAvoidMoveToFront() { + mAvoidMoveToFront = true; + } + + /** + * @return whether the activity launch should prevent moving the associated activity stack to + * the front. + * @hide + */ + public boolean getAvoidMoveToFront() { + return mAvoidMoveToFront; + } + /** @hide */ public int getSplitScreenCreateMode() { return mSplitScreenCreateMode; @@ -1416,6 +1443,7 @@ public class ActivityOptions { b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId); b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay); b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume); + b.putBoolean(KEY_AVOID_MOVE_TO_FRONT, mAvoidMoveToFront); b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode); b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, mDisallowEnterPictureInPictureWhileLaunching); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 7ca680239eef..e923fb217bea 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -263,8 +263,10 @@ public class AppOpsManager { public static final int OP_REQUEST_DELETE_PACKAGES = 72; /** @hide Bind an accessibility service. */ public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73; + /** @hide Continue handover of a call from another app */ + public static final int OP_ACCEPT_HANDOVER = 74; /** @hide */ - public static final int _NUM_OP = 74; + public static final int _NUM_OP = 75; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -378,7 +380,13 @@ public class AppOpsManager { /** Answer incoming phone calls */ public static final String OPSTR_ANSWER_PHONE_CALLS = "android:answer_phone_calls"; - + /** + * Accept call handover + * @hide + */ + @SystemApi @TestApi + public static final String OPSTR_ACCEPT_HANDOVER + = "android:accept_handover"; /** @hide */ @SystemApi @TestApi public static final String OPSTR_GPS = "android:gps"; @@ -528,6 +536,7 @@ public class AppOpsManager { OP_USE_SIP, OP_PROCESS_OUTGOING_CALLS, OP_ANSWER_PHONE_CALLS, + OP_ACCEPT_HANDOVER, // Microphone OP_RECORD_AUDIO, // Camera @@ -626,6 +635,7 @@ public class AppOpsManager { OP_CHANGE_WIFI_STATE, OP_REQUEST_DELETE_PACKAGES, OP_BIND_ACCESSIBILITY_SERVICE, + OP_ACCEPT_HANDOVER, }; /** @@ -706,6 +716,7 @@ public class AppOpsManager { OPSTR_CHANGE_WIFI_STATE, OPSTR_REQUEST_DELETE_PACKAGES, OPSTR_BIND_ACCESSIBILITY_SERVICE, + OPSTR_ACCEPT_HANDOVER, }; /** @@ -787,6 +798,7 @@ public class AppOpsManager { "CHANGE_WIFI_STATE", "REQUEST_DELETE_PACKAGES", "BIND_ACCESSIBILITY_SERVICE", + "ACCEPT_HANDOVER", }; /** @@ -868,6 +880,7 @@ public class AppOpsManager { Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.REQUEST_DELETE_PACKAGES, Manifest.permission.BIND_ACCESSIBILITY_SERVICE, + Manifest.permission.ACCEPT_HANDOVER, }; /** @@ -950,6 +963,7 @@ public class AppOpsManager { null, // OP_CHANGE_WIFI_STATE null, // REQUEST_DELETE_PACKAGES null, // OP_BIND_ACCESSIBILITY_SERVICE + null, // ACCEPT_HANDOVER }; /** @@ -1031,6 +1045,7 @@ public class AppOpsManager { false, // OP_CHANGE_WIFI_STATE false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER }; /** @@ -1111,6 +1126,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES AppOpsManager.MODE_ALLOWED, // OP_BIND_ACCESSIBILITY_SERVICE + AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER }; /** @@ -1195,6 +1211,7 @@ public class AppOpsManager { false, // OP_CHANGE_WIFI_STATE false, // OP_REQUEST_DELETE_PACKAGES false, // OP_BIND_ACCESSIBILITY_SERVICE + false, // ACCEPT_HANDOVER }; /** diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 5f5d834425b6..56bc184e89c4 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -65,6 +65,7 @@ import android.os.PersistableBundle; import android.os.StrictMode; import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; +import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationDefinition; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; @@ -357,6 +358,20 @@ interface IActivityManager { */ void requestTelephonyBugReport(in String shareTitle, in String shareDescription); + /** + * Deprecated - This method is only used by Wifi, and it will soon be replaced by a proper + * bug report API. + * + * Takes a minimal bugreport of Wifi-related state. + * + * @param shareTitle should be a valid legible string less than 50 chars long + * @param shareDescription should be less than 91 bytes when encoded into UTF-8 format + * + * @throws IllegalArgumentException if shareTitle or shareDescription is too big or if the + * parameters cannot be encoding to an UTF-8 charset. + */ + void requestWifiBugReport(in String shareTitle, in String shareDescription); + long inputDispatchingTimedOut(int pid, boolean aboveSystem, in String reason); void clearPendingBackup(); Intent getIntentForIntentSender(in IIntentSender sender); @@ -427,8 +442,9 @@ interface IActivityManager { in Bundle options, int userId); int startAssistantActivity(in String callingPackage, int callingPid, int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId); - int startRecentsActivity(in IAssistDataReceiver assistDataReceiver, in Bundle options, - in Bundle activityOptions, int userId); + void startRecentsActivity(in Intent intent, in IAssistDataReceiver assistDataReceiver, + in IRecentsAnimationRunner recentsAnimationRunner); + void cancelRecentsAnimation(); int startActivityFromRecents(int taskId, in Bundle options); Bundle getActivityOptions(in IBinder token); List<IBinder> getAppTasks(in String callingPackage); diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index fb11272d7e62..b96e028076e5 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -21,6 +21,7 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; @@ -34,6 +35,7 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; +import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.view.Display; @@ -41,9 +43,13 @@ import android.view.DisplayAdjustments; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.IndentingPrintWriter; +import java.io.IOException; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; @@ -59,12 +65,7 @@ public class ResourcesManager { * Predicate that returns true if a WeakReference is gc'ed. */ private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = - new Predicate<WeakReference<Resources>>() { - @Override - public boolean test(WeakReference<Resources> weakRef) { - return weakRef == null || weakRef.get() == null; - } - }; + weakRef -> weakRef == null || weakRef.get() == null; /** * The global compatibility settings. @@ -89,6 +90,48 @@ public class ResourcesManager { */ private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); + private static class ApkKey { + public final String path; + public final boolean sharedLib; + public final boolean overlay; + + ApkKey(String path, boolean sharedLib, boolean overlay) { + this.path = path; + this.sharedLib = sharedLib; + this.overlay = overlay; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + this.path.hashCode(); + result = 31 * result + Boolean.hashCode(this.sharedLib); + result = 31 * result + Boolean.hashCode(this.overlay); + return result; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ApkKey)) { + return false; + } + ApkKey other = (ApkKey) obj; + return this.path.equals(other.path) && this.sharedLib == other.sharedLib + && this.overlay == other.overlay; + } + } + + /** + * The ApkAssets we are caching and intend to hold strong references to. + */ + private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15); + + /** + * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't + * in our LRU cache. Bonus resources :) + */ + private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); + /** * Resources and base configuration override associated with an Activity. */ @@ -260,6 +303,41 @@ public class ResourcesManager { } } + private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) + throws IOException { + final ApkKey newKey = new ApkKey(path, sharedLib, overlay); + ApkAssets apkAssets = mLoadedApkAssets.get(newKey); + if (apkAssets != null) { + return apkAssets; + } + + // Optimistically check if this ApkAssets exists somewhere else. + final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); + if (apkAssetsRef != null) { + apkAssets = apkAssetsRef.get(); + if (apkAssets != null) { + mLoadedApkAssets.put(newKey, apkAssets); + return apkAssets; + } else { + // Clean up the reference. + mCachedApkAssets.remove(newKey); + } + } + + // We must load this from disk. + if (overlay) { + final String idmapPath = "/data/resource-cache/" + + path.substring(1).replace('/', '@') + + "@idmap"; + apkAssets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); + } else { + apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib); + } + mLoadedApkAssets.put(newKey, apkAssets); + mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); + return apkAssets; + } + /** * Creates an AssetManager from the paths within the ResourcesKey. * @@ -270,13 +348,15 @@ public class ResourcesManager { */ @VisibleForTesting protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { - AssetManager assets = new AssetManager(); + final ArrayList<ApkAssets> apkAssets = new ArrayList<>(); // resDir can be null if the 'android' package is creating a new Resources object. // This is fine, since each AssetManager automatically loads the 'android' package // already. if (key.mResDir != null) { - if (assets.addAssetPath(key.mResDir) == 0) { + try { + apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/)); + } catch (IOException e) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null; } @@ -284,7 +364,10 @@ public class ResourcesManager { if (key.mSplitResDirs != null) { for (final String splitResDir : key.mSplitResDirs) { - if (assets.addAssetPath(splitResDir) == 0) { + try { + apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/, + false /*overlay*/)); + } catch (IOException e) { Log.e(TAG, "failed to add split asset path " + splitResDir); return null; } @@ -293,7 +376,13 @@ public class ResourcesManager { if (key.mOverlayDirs != null) { for (final String idmapPath : key.mOverlayDirs) { - assets.addOverlayPath(idmapPath); + try { + apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/)); + } catch (IOException e) { + Log.w(TAG, "failed to add overlay path " + idmapPath); + + // continue. + } } } @@ -302,16 +391,77 @@ public class ResourcesManager { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. - if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { + try { + apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/)); + } catch (IOException e) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); + + // continue. } } } } + + AssetManager assets = new AssetManager(); + assets.setApkAssets(apkAssets.toArray(new ApkAssets[apkAssets.size()]), + false /*invalidateCaches*/); return assets; } + private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) { + int count = 0; + for (WeakReference<T> ref : collection) { + final T value = ref != null ? ref.get() : null; + if (value != null) { + count++; + } + } + return count; + } + + /** + * @hide + */ + public void dump(String prefix, PrintWriter printWriter) { + synchronized (this) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + for (int i = 0; i < prefix.length() / 2; i++) { + pw.increaseIndent(); + } + + pw.println("ResourcesManager:"); + pw.increaseIndent(); + pw.print("cached apks: total="); + pw.print(mLoadedApkAssets.size()); + pw.print(" created="); + pw.print(mLoadedApkAssets.createCount()); + pw.print(" evicted="); + pw.print(mLoadedApkAssets.evictionCount()); + pw.print(" hit="); + pw.print(mLoadedApkAssets.hitCount()); + pw.print(" miss="); + pw.print(mLoadedApkAssets.missCount()); + pw.print(" max="); + pw.print(mLoadedApkAssets.maxSize()); + pw.println(); + + pw.print("total apks: "); + pw.println(countLiveReferences(mCachedApkAssets.values())); + + pw.print("resources: "); + + int references = countLiveReferences(mResourceReferences); + for (ActivityResources activityResources : mActivityResourceReferences.values()) { + references += countLiveReferences(activityResources.activityResources); + } + pw.println(references); + + pw.print("resource impls: "); + pw.println(countLiveReferences(mResourceImpls.values())); + } + } + private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { Configuration config; final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); @@ -630,28 +780,16 @@ public class ResourcesManager { // We will create the ResourcesImpl object outside of holding this lock. } - } - - // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. - ResourcesImpl resourcesImpl = createResourcesImpl(key); - if (resourcesImpl == null) { - return null; - } - synchronized (this) { - ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); - if (existingResourcesImpl != null) { - if (DEBUG) { - Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl - + " new impl=" + resourcesImpl); - } - resourcesImpl.getAssets().close(); - resourcesImpl = existingResourcesImpl; - } else { - // Add this ResourcesImpl to the cache. - mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); + // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. + ResourcesImpl resourcesImpl = createResourcesImpl(key); + if (resourcesImpl == null) { + return null; } + // Add this ResourcesImpl to the cache. + mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); + final Resources resources; if (activityToken != null) { resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index ffb3affb03b3..28e845a04e44 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -483,19 +483,28 @@ public class DeviceAdminReceiver extends BroadcastReceiver { "android.app.action.TRANSFER_OWNERSHIP_COMPLETE"; /** + * Broadcast action: notify the device owner that the ownership of one of its affiliated + * profiles is transferred. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE = + "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE"; + + /** * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that * allows a mobile device management application to pass data to the management application * instance after owner transfer. * - * <p> - * If the transfer is successful, the new device owner receives the data in + * <p>If the transfer is successful, the new owner receives the data in * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}. * The bundle is not changed during the ownership transfer. * * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) */ - public static final String EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE = - "android.app.extra.TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE"; + public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE = + "android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE"; /** * Name under which a device administration component indicates whether it supports transfer of @@ -994,6 +1003,26 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Called on the device owner when the ownership of one of its affiliated profiles is + * transferred. + * + * <p>This can be used when transferring both device and profile ownership when using + * work profile on a fully managed device. The process would look like this: + * <ol> + * <li>Transfer profile ownership</li> + * <li>The device owner gets notified with this callback</li> + * <li>Transfer device ownership</li> + * <li>Both profile and device ownerships have been transferred</li> + * </ol> + * + * @param context the running context as per {@link #onReceive} + * @param user the {@link UserHandle} of the affiliated user + * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle) + */ + public void onTransferAffiliatedProfileOwnershipComplete(Context context, UserHandle user) { + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -1063,8 +1092,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver { onUserSwitched(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER)); } else if (ACTION_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) { PersistableBundle bundle = - intent.getParcelableExtra(EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE); + intent.getParcelableExtra(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE); onTransferOwnershipComplete(context, bundle); + } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) { + onTransferAffiliatedProfileOwnershipComplete(context, + intent.getParcelableExtra(Intent.EXTRA_USER)); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 95e7fe059bb9..8f76032b4c62 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9144,9 +9144,13 @@ public class DevicePolicyManager { * <li>A profile owner can only be transferred to a new profile owner</li> * </ul> * - * <p>Use the {@code bundle} parameter to pass data to the new administrator. The parameters + * <p>Use the {@code bundle} parameter to pass data to the new administrator. The data * will be received in the - * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)} callback. + * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)} + * callback of the new administrator. + * + * <p>The transfer has failed if the original administrator is still the corresponding owner + * after calling this method. * * <p>The incoming target administrator must have the * {@link DeviceAdminReceiver#SUPPORT_TRANSFER_OWNERSHIP_META_DATA} <code>meta-data</code> tag @@ -9157,11 +9161,11 @@ public class DevicePolicyManager { * @param target which {@link DeviceAdminReceiver} we want the new administrator to be * @param bundle data to be sent to the new administrator * @throws SecurityException if {@code admin} is not a device owner nor a profile owner - * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null}, - * both are components in the same package or {@code target} is not an active admin + * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null}, they + * are components in the same package or {@code target} is not an active admin */ public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target, - PersistableBundle bundle) { + @Nullable PersistableBundle bundle) { throwIfParentInstance("transferOwnership"); try { mService.transferOwnership(admin, target, bundle); @@ -9286,22 +9290,6 @@ public class DevicePolicyManager { } /** - * Returns error message to be displayed when printing is disabled. - * - * Used only by PrintService. - * @return Localized error message. - * @hide - */ - @SystemApi - public CharSequence getPrintingDisabledReason() { - try { - return mService.getPrintingDisabledReason(); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - } - - /** * Called by device owner to add an override APN. * * @param admin which {@link DeviceAdminReceiver} this request is associated with @@ -9434,4 +9422,24 @@ public class DevicePolicyManager { } return false; } + + /** + * Returns the data passed from the current administrator to the new administrator during an + * ownership transfer. This is the same {@code bundle} passed in + * {@link #transferOwnership(ComponentName, ComponentName, PersistableBundle)}. + * + * <p>Returns <code>null</code> if no ownership transfer was started for the calling user. + * + * @see #transferOwnership + * @see DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle) + */ + @Nullable + public PersistableBundle getTransferOwnershipBundle() { + throwIfParentInstance("getTransferOwnershipBundle"); + try { + return mService.getTransferOwnershipBundle(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 531bef014c0b..ebaf4648d80a 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -132,4 +132,13 @@ public abstract class DevicePolicyManagerInternal { * @param userId The user in question */ public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId); + + /** + * Return text of error message if printing is disabled. + * Called by Print Service when printing is disabled by PO or DO when printing is attempted. + * + * @param userId The user in question + * @return localized error message + */ + public abstract CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index a5ca4cf12ffa..daee6b41a365 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -391,7 +391,9 @@ interface IDevicePolicyManager { boolean isLogoutEnabled(); List<String> getDisallowedSystemApps(in ComponentName admin, int userId, String provisioningAction); + void transferOwnership(in ComponentName admin, in ComponentName target, in PersistableBundle bundle); + PersistableBundle getTransferOwnershipBundle(); void setStartUserSessionMessage(in ComponentName admin, in CharSequence startUserSessionMessage); void setEndUserSessionMessage(in ComponentName admin, in CharSequence endUserSessionMessage); @@ -400,7 +402,6 @@ interface IDevicePolicyManager { void setPrintingEnabled(in ComponentName admin, boolean enabled); boolean isPrintingEnabled(); - CharSequence getPrintingDisabledReason(); List<String> setMeteredDataDisabled(in ComponentName admin, in List<String> packageNames); List<String> getMeteredDataDisabled(in ComponentName admin); diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 13ec4fdb7956..09a46b8acf4b 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -16,14 +16,10 @@ package android.content.pm; -import android.annotation.IntDef; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Overall information about the contents of a package. This corresponds * to all of the information collected from AndroidManifest.xml. @@ -370,28 +366,9 @@ public class PackageInfo implements Parcelable { public int overlayPriority; /** - * Flag for use with {@link #mOverlayFlags}. Marks the overlay as static, meaning it cannot - * be enabled/disabled at runtime. - */ - static final int FLAG_OVERLAY_STATIC = 1 << 1; - - /** - * Flag for use with {@link #mOverlayFlags}. Marks the overlay as trusted (not 3rd party). - */ - static final int FLAG_OVERLAY_TRUSTED = 1 << 2; - - @IntDef(flag = true, prefix = "FLAG_OVERLAY_", value = { - FLAG_OVERLAY_STATIC, - FLAG_OVERLAY_TRUSTED - }) - @Retention(RetentionPolicy.SOURCE) - @interface OverlayFlags {} - - /** - * Modifiers that affect the state of this overlay. See {@link #FLAG_OVERLAY_STATIC}, - * {@link #FLAG_OVERLAY_TRUSTED}. + * Whether the overlay is static, meaning it cannot be enabled/disabled at runtime. */ - @OverlayFlags int mOverlayFlags; + boolean mOverlayIsStatic; /** * The user-visible SDK version (ex. 26) of the framework against which the application claims @@ -424,7 +401,7 @@ public class PackageInfo implements Parcelable { * @hide */ public boolean isOverlayPackage() { - return overlayTarget != null && (mOverlayFlags & FLAG_OVERLAY_TRUSTED) != 0; + return overlayTarget != null; } /** @@ -433,7 +410,7 @@ public class PackageInfo implements Parcelable { * @hide */ public boolean isStaticOverlayPackage() { - return overlayTarget != null && (mOverlayFlags & FLAG_OVERLAY_STATIC) != 0; + return overlayTarget != null && mOverlayIsStatic; } @Override @@ -488,7 +465,7 @@ public class PackageInfo implements Parcelable { dest.writeString(requiredAccountType); dest.writeString(overlayTarget); dest.writeInt(overlayPriority); - dest.writeInt(mOverlayFlags); + dest.writeBoolean(mOverlayIsStatic); dest.writeInt(compileSdkVersion); dest.writeString(compileSdkVersionCodename); } @@ -543,7 +520,7 @@ public class PackageInfo implements Parcelable { requiredAccountType = source.readString(); overlayTarget = source.readString(); overlayPriority = source.readInt(); - mOverlayFlags = source.readInt(); + mOverlayIsStatic = source.readBoolean(); compileSdkVersion = source.readInt(); compileSdkVersionCodename = source.readString(); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 5b5ccf547b54..c705ef52f6b5 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -54,6 +54,7 @@ import android.content.pm.PackageParserCacheHelper.WriteHelper; import android.content.pm.split.DefaultSplitAssetLoader; import android.content.pm.split.SplitAssetDependencyLoader; import android.content.pm.split.SplitAssetLoader; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -679,15 +680,7 @@ public class PackageParser { pi.requiredAccountType = p.mRequiredAccountType; pi.overlayTarget = p.mOverlayTarget; pi.overlayPriority = p.mOverlayPriority; - - if (p.mIsStaticOverlay) { - pi.mOverlayFlags |= PackageInfo.FLAG_OVERLAY_STATIC; - } - - if (p.mTrustedOverlay) { - pi.mOverlayFlags |= PackageInfo.FLAG_OVERLAY_TRUSTED; - } - + pi.mOverlayIsStatic = p.mOverlayIsStatic; pi.compileSdkVersion = p.mCompileSdkVersion; pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename; pi.firstInstallTime = firstInstallTime; @@ -1318,24 +1311,6 @@ public class PackageParser { } } - private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) - throws PackageParserException { - if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + apkPath); - } - - // The AssetManager guarantees uniqueness for asset paths, so if this asset path - // already exists in the AssetManager, addAssetPath will only return the cookie - // assigned to it. - int cookie = assets.addAssetPath(apkPath); - if (cookie == 0) { - throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + apkPath); - } - return cookie; - } - private Package parseBaseApk(File apkFile, AssetManager assets, int flags) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); @@ -1351,13 +1326,15 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); - final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); - - Resources res = null; XmlResourceParser parser = null; try { - res = new Resources(assets, mMetrics, null); + final int cookie = assets.findCookieForPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + final Resources res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError); @@ -1392,15 +1369,18 @@ public class PackageParser { if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); - final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); - final Resources res; XmlResourceParser parser = null; try { - res = new Resources(assets, mMetrics, null); - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); + // This must always succeed, as the path has been added to the AssetManager before. + final int cookie = assets.findCookieForPath(apkPath); + if (cookie == 0) { + throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, + "Failed adding asset path: " + apkPath); + } + parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + res = new Resources(assets, mMetrics, null); final String[] outError = new String[1]; pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError); @@ -1602,21 +1582,19 @@ public class PackageParser { int flags) throws PackageParserException { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); - AssetManager assets = null; XmlResourceParser parser = null; try { - assets = newConfiguredAssetManager(); - int cookie = fd != null - ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath); - if (cookie == 0) { + final ApkAssets apkAssets; + try { + apkAssets = fd != null + ? ApkAssets.loadFromFd(fd, debugPathName, false, false) + : ApkAssets.loadFromPath(apkPath); + } catch (IOException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Failed to parse " + apkPath); } - final DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - - parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); + parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME); final SigningDetails signingDetails; if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { @@ -1642,7 +1620,7 @@ public class PackageParser { "Failed to parse " + apkPath, e); } finally { IoUtils.closeQuietly(parser); - IoUtils.closeQuietly(assets); + // TODO(b/72056911): Implement and call close() on ApkAssets. } } @@ -2085,7 +2063,7 @@ public class PackageParser { pkg.mOverlayPriority = sa.getInt( com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority, 0); - pkg.mIsStaticOverlay = sa.getBoolean( + pkg.mOverlayIsStatic = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic, false); final String propName = sa.getString( @@ -6094,8 +6072,7 @@ public class PackageParser { public String mOverlayTarget; public int mOverlayPriority; - public boolean mIsStaticOverlay; - public boolean mTrustedOverlay; + public boolean mOverlayIsStatic; public int mCompileSdkVersion; public String mCompileSdkVersionCodename; @@ -6625,8 +6602,7 @@ public class PackageParser { mRequiredAccountType = dest.readString(); mOverlayTarget = dest.readString(); mOverlayPriority = dest.readInt(); - mIsStaticOverlay = (dest.readInt() == 1); - mTrustedOverlay = (dest.readInt() == 1); + mOverlayIsStatic = (dest.readInt() == 1); mCompileSdkVersion = dest.readInt(); mCompileSdkVersionCodename = dest.readString(); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); @@ -6749,8 +6725,7 @@ public class PackageParser { dest.writeString(mRequiredAccountType); dest.writeString(mOverlayTarget); dest.writeInt(mOverlayPriority); - dest.writeInt(mIsStaticOverlay ? 1 : 0); - dest.writeInt(mTrustedOverlay ? 1 : 0); + dest.writeInt(mOverlayIsStatic ? 1 : 0); dest.writeInt(mCompileSdkVersion); dest.writeString(mCompileSdkVersionCodename); dest.writeArraySet(mUpgradeKeySets); diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java index 99eb4702d32e..9e3a8f48996c 100644 --- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java @@ -15,10 +15,13 @@ */ package android.content.pm.split; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; +import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; @@ -26,6 +29,8 @@ import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; +import java.io.IOException; + /** * Loads the base and split APKs into a single AssetManager. * @hide @@ -33,68 +38,66 @@ import libcore.io.IoUtils; public class DefaultSplitAssetLoader implements SplitAssetLoader { private final String mBaseCodePath; private final String[] mSplitCodePaths; - private final int mFlags; - + private final @ParseFlags int mFlags; private AssetManager mCachedAssetManager; - public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) { + public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) { mBaseCodePath = pkg.baseCodePath; mSplitCodePaths = pkg.splitCodePaths; mFlags = flags; } - private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) - throws PackageParser.PackageParserException { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) { - throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + apkPath); + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); } - if (assets.addAssetPath(apkPath) == 0) { - throw new PackageParser.PackageParserException( - INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + apkPath); + try { + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); } } @Override - public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { + public AssetManager getBaseAssetManager() throws PackageParserException { if (mCachedAssetManager != null) { return mCachedAssetManager; } - AssetManager assets = new AssetManager(); - try { - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); - loadApkIntoAssetManager(assets, mBaseCodePath, mFlags); - - if (!ArrayUtils.isEmpty(mSplitCodePaths)) { - for (String apkPath : mSplitCodePaths) { - loadApkIntoAssetManager(assets, apkPath, mFlags); - } - } + ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null + ? mSplitCodePaths.length : 0) + 1]; - mCachedAssetManager = assets; - assets = null; - return mCachedAssetManager; - } finally { - if (assets != null) { - IoUtils.closeQuietly(assets); + // Load the base. + int splitIdx = 0; + apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); + + // Load any splits. + if (!ArrayUtils.isEmpty(mSplitCodePaths)) { + for (String apkPath : mSplitCodePaths) { + apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags); } } + + AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + + mCachedAssetManager = assets; + return mCachedAssetManager; } @Override - public AssetManager getSplitAssetManager(int splitIdx) - throws PackageParser.PackageParserException { + public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException { return getBaseAssetManager(); } @Override public void close() throws Exception { - if (mCachedAssetManager != null) { - IoUtils.closeQuietly(mCachedAssetManager); - } + IoUtils.closeQuietly(mCachedAssetManager); } } diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java index 16023f0d9d97..58eaabfa62f2 100644 --- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java @@ -15,17 +15,21 @@ */ package android.content.pm.split; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.annotation.NonNull; +import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.PackageParser.ParseFlags; +import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; import libcore.io.IoUtils; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -34,17 +38,15 @@ import java.util.Collections; * is to be used when an application opts-in to isolated split loading. * @hide */ -public class SplitAssetDependencyLoader - extends SplitDependencyLoader<PackageParser.PackageParserException> +public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException> implements SplitAssetLoader { private final String[] mSplitPaths; - private final int mFlags; - - private String[][] mCachedPaths; - private AssetManager[] mCachedAssetManagers; + private final @ParseFlags int mFlags; + private final ApkAssets[][] mCachedSplitApks; + private final AssetManager[] mCachedAssetManagers; public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, - SparseArray<int[]> dependencies, int flags) { + SparseArray<int[]> dependencies, @ParseFlags int flags) { super(dependencies); // The base is inserted into index 0, so we need to shift all the splits by 1. @@ -53,7 +55,7 @@ public class SplitAssetDependencyLoader System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length); mFlags = flags; - mCachedPaths = new String[mSplitPaths.length][]; + mCachedSplitApks = new ApkAssets[mSplitPaths.length][]; mCachedAssetManagers = new AssetManager[mSplitPaths.length]; } @@ -62,58 +64,60 @@ public class SplitAssetDependencyLoader return mCachedAssetManagers[splitIdx] != null; } - private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags) - throws PackageParser.PackageParserException { - final AssetManager assets = new AssetManager(); + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); + } + try { - assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Build.VERSION.RESOURCES_SDK_INT); - - for (String assetPath : assetPaths) { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && - !PackageParser.isApkPath(assetPath)) { - throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, - "Invalid package file: " + assetPath); - } - - if (assets.addAssetPath(assetPath) == 0) { - throw new PackageParser.PackageParserException( - INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Failed adding asset path: " + assetPath); - } - } - return assets; - } catch (Throwable e) { - IoUtils.closeQuietly(assets); - throw e; + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); } } + private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { + final AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + return assets; + } + @Override protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices, - int parentSplitIdx) throws PackageParser.PackageParserException { - final ArrayList<String> assetPaths = new ArrayList<>(); + int parentSplitIdx) throws PackageParserException { + final ArrayList<ApkAssets> assets = new ArrayList<>(); + + // Include parent ApkAssets. if (parentSplitIdx >= 0) { - Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]); + Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]); } - assetPaths.add(mSplitPaths[splitIdx]); + // Include this ApkAssets. + assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags)); + + // Load and include all config splits for this feature. for (int configSplitIdx : configSplitIndices) { - assetPaths.add(mSplitPaths[configSplitIdx]); + assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags)); } - mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]); - mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx], - mFlags); + + // Cache the results. + mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]); + mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]); } @Override - public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException { + public AssetManager getBaseAssetManager() throws PackageParserException { loadDependenciesForSplit(0); return mCachedAssetManagers[0]; } @Override - public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException { + public AssetManager getSplitAssetManager(int idx) throws PackageParserException { // Since we insert the base at position 0, and PackageParser keeps splits separate from // the base, we need to adjust the index. loadDependenciesForSplit(idx + 1); diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java new file mode 100644 index 000000000000..fd664bc731d0 --- /dev/null +++ b/core/java/android/content/res/ApkAssets.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.res; + +import android.annotation.NonNull; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * The loaded, immutable, in-memory representation of an APK. + * + * The main implementation is native C++ and there is very little API surface exposed here. The APK + * is mainly accessed via {@link AssetManager}. + * + * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, + * making the creation of AssetManagers very cheap. + * @hide + */ +public final class ApkAssets { + @GuardedBy("this") private final long mNativePtr; + @GuardedBy("this") private StringBlock mStringBlock; + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { + return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param system When true, the APK is loaded as a system APK (framework). + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system) + throws IOException { + return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param system When true, the APK is loaded as a system APK (framework). + * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are + * loaded as a shared library. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system, + boolean forceSharedLibrary) throws IOException { + return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/); + } + + /** + * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications. + * + * Performs a dup of the underlying fd, so you must take care of still closing + * the FileDescriptor yourself (and can do that whenever you want). + * + * @param fd The FileDescriptor of an open, readable APK. + * @param friendlyName The friendly name used to identify this ApkAssets when logging. + * @param system When true, the APK is loaded as a system APK (framework). + * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are + * loaded as a shared library. + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, boolean system, boolean forceSharedLibrary) + throws IOException { + return new ApkAssets(fd, friendlyName, system, forceSharedLibrary); + } + + /** + * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path + * is encoded within the IDMAP. + * + * @param idmapPath Path to the IDMAP of an overlay APK. + * @param system When true, the APK is loaded as a system APK (framework). + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system) + throws IOException { + return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/); + } + + private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + throws IOException { + Preconditions.checkNotNull(path, "path"); + mNativePtr = nativeLoad(path, system, forceSharedLib, overlay); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system, + boolean forceSharedLib) throws IOException { + Preconditions.checkNotNull(fd, "fd"); + Preconditions.checkNotNull(friendlyName, "friendlyName"); + mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + @NonNull String getAssetPath() { + synchronized (this) { + return nativeGetAssetPath(mNativePtr); + } + } + + CharSequence getStringFromPool(int idx) { + synchronized (this) { + return mStringBlock.get(idx); + } + } + + /** + * Retrieve a parser for a compiled XML file. This is associated with a single APK and + * <em>NOT</em> a full AssetManager. This means that shared-library references will not be + * dynamically assigned runtime package IDs. + * + * @param fileName The path to the file within the APK. + * @return An XmlResourceParser. + * @throws IOException if the file was not found or an error occurred retrieving it. + */ + public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); + synchronized (this) { + long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); + try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) { + XmlResourceParser parser = block.newParser(); + // If nativeOpenXml doesn't throw, it will always return a valid native pointer, + // which makes newParser always return non-null. But let's be paranoid. + if (parser == null) { + throw new AssertionError("block.newParser() returned a null parser"); + } + return parser; + } + } + } + + /** + * Returns false if the underlying APK was changed since this ApkAssets was loaded. + */ + public boolean isUpToDate() { + synchronized (this) { + return nativeIsUpToDate(mNativePtr); + } + } + + @Override + protected void finalize() throws Throwable { + nativeDestroy(mNativePtr); + } + + private static native long nativeLoad( + @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + throws IOException; + private static native long nativeLoadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, boolean system, boolean forceSharedLib) + throws IOException; + private static native void nativeDestroy(long ptr); + private static native @NonNull String nativeGetAssetPath(long ptr); + private static native long nativeGetStringBlock(long ptr); + private static native boolean nativeIsUpToDate(long ptr); + private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; +} diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 78665609bdd4..abaf7014f9d0 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -18,9 +18,11 @@ package android.content.res; import android.annotation.AnyRes; import android.annotation.ArrayRes; +import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; +import android.annotation.StyleRes; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; import android.os.ParcelFileDescriptor; @@ -28,10 +30,18 @@ import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; -import java.io.FileDescriptor; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import java.io.BufferedReader; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.channels.FileLock; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; /** @@ -42,7 +52,19 @@ import java.util.HashMap; * bytes. */ public final class AssetManager implements AutoCloseable { - /* modes used when opening an asset */ + private static final String TAG = "AssetManager"; + private static final boolean DEBUG_REFS = false; + + private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk"; + + private static final Object sSync = new Object(); + + private static final ApkAssets[] sEmptyApkAssets = new ApkAssets[0]; + + // Not private for LayoutLib's BridgeAssetManager. + @GuardedBy("sSync") static AssetManager sSystem = null; + + @GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0]; /** * Mode for {@link #open(String, int)}: no specific information about how @@ -65,88 +87,311 @@ public final class AssetManager implements AutoCloseable { */ public static final int ACCESS_BUFFER = 3; - private static final String TAG = "AssetManager"; - private static final boolean localLOGV = false || false; - - private static final boolean DEBUG_REFS = false; - - private static final Object sSync = new Object(); - /*package*/ static AssetManager sSystem = null; + @GuardedBy("this") private final TypedValue mValue = new TypedValue(); + @GuardedBy("this") private final long[] mOffsets = new long[2]; - private final TypedValue mValue = new TypedValue(); - private final long[] mOffsets = new long[2]; - - // For communication with native code. - private long mObject; + // Pointer to native implementation, stuffed inside a long. + @GuardedBy("this") private long mObject; + + // The loaded asset paths. + @GuardedBy("this") private ApkAssets[] mApkAssets; + + // Debug/reference counting implementation. + @GuardedBy("this") private boolean mOpen = true; + @GuardedBy("this") private int mNumRefs = 1; + @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks; - private StringBlock mStringBlocks[] = null; - - private int mNumRefs = 1; - private boolean mOpen = true; - private HashMap<Long, RuntimeException> mRefStacks; - /** * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrieving the * appropriate asset manager with {@link Resources#getAssets}. Not for * use by applications. - * {@hide} + * @hide */ public AssetManager() { - synchronized (this) { - if (DEBUG_REFS) { - mNumRefs = 0; - incRefsLocked(this.hashCode()); - } - init(false); - if (localLOGV) Log.v(TAG, "New asset manager: " + this); - ensureSystemAssets(); + final ApkAssets[] assets; + synchronized (sSync) { + createSystemAssetsInZygoteLocked(); + assets = sSystemApkAssets; } - } - private static void ensureSystemAssets() { - synchronized (sSync) { - if (sSystem == null) { - AssetManager system = new AssetManager(true); - system.makeStringBlocks(null); - sSystem = system; - } + mObject = nativeCreate(); + if (DEBUG_REFS) { + mNumRefs = 0; + incRefsLocked(hashCode()); } + + // Always set the framework resources. + setApkAssets(assets, false /*invalidateCaches*/); } - - private AssetManager(boolean isSystem) { + + /** + * Private constructor that doesn't call ensureSystemAssets. + * Used for the creation of system assets. + */ + @SuppressWarnings("unused") + private AssetManager(boolean sentinel) { + mObject = nativeCreate(); if (DEBUG_REFS) { - synchronized (this) { - mNumRefs = 0; - incRefsLocked(this.hashCode()); + mNumRefs = 0; + incRefsLocked(hashCode()); + } + } + + /** + * This must be called from Zygote so that system assets are shared by all applications. + * @hide + */ + private static void createSystemAssetsInZygoteLocked() { + if (sSystem != null) { + return; + } + + // Make sure that all IDMAPs are up to date. + nativeVerifySystemIdmaps(); + + try { + ArrayList<ApkAssets> apkAssets = new ArrayList<>(); + apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/)); + + // Load all static RROs. + try (FileInputStream fis = new FileInputStream( + "/data/resource-cache/overlays.list"); + BufferedReader br = new BufferedReader(new InputStreamReader(fis))) { + // Acquire a lock so that any idmap scanning doesn't impact the current set. + try (FileLock flock = fis.getChannel().lock(0, Long.MAX_VALUE, + true /*shared*/)) { + for (String line; (line = br.readLine()) != null; ) { + String idmapPath = line.split(" ")[1]; + apkAssets.add( + ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/)); + } + } } + + sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]); + sSystem = new AssetManager(true /*sentinel*/); + sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/); + } catch (IOException e) { + throw new IllegalStateException("Failed to create system AssetManager", e); } - init(true); - if (localLOGV) Log.v(TAG, "New asset manager: " + this); } /** * Return a global shared asset manager that provides access to only * system assets (no application assets). - * {@hide} + * @hide */ public static AssetManager getSystem() { - ensureSystemAssets(); - return sSystem; + synchronized (sSync) { + createSystemAssetsInZygoteLocked(); + return sSystem; + } } /** * Close this asset manager. */ + @Override public void close() { - synchronized(this) { - //System.out.println("Release: num=" + mNumRefs - // + ", released=" + mReleased); + synchronized (this) { + if (!mOpen) { + return; + } + + mOpen = false; + decRefsLocked(hashCode()); + } + } + + /** + * Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)} + * family of methods. + * + * @param apkAssets The new set of paths. + * @param invalidateCaches Whether to invalidate any caches. This should almost always be true. + * Set this to false if you are appending new resources + * (not new configurations). + * @hide + */ + public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) { + Preconditions.checkNotNull(apkAssets, "apkAssets"); + + // Copy the apkAssets, but prepend the system assets (framework + overlays). + final ApkAssets[] newApkAssets = new ApkAssets[apkAssets.length + sSystemApkAssets.length]; + System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length); + System.arraycopy(apkAssets, 0, newApkAssets, sSystemApkAssets.length, apkAssets.length); + + synchronized (this) { + ensureOpenLocked(); + mApkAssets = newApkAssets; + nativeSetApkAssets(mObject, mApkAssets, invalidateCaches); + if (invalidateCaches) { + // Invalidate all caches. + invalidateCachesLocked(-1); + } + } + } + + /** + * Invalidates the caches in this AssetManager according to the bitmask `diff`. + * + * @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}. + * @see ActivityInfo.Config + */ + private void invalidateCachesLocked(int diff) { + // TODO(adamlesinski): Currently there are no caches to invalidate in Java code. + } + + /** + * Returns the set of ApkAssets loaded by this AssetManager. If the AssetManager is closed, this + * returns a 0-length array. + * @hide + */ + public @NonNull ApkAssets[] getApkAssets() { + synchronized (this) { if (mOpen) { - mOpen = false; - decRefsLocked(this.hashCode()); + return mApkAssets; } } + return sEmptyApkAssets; + } + + /** + * Returns a cookie for use with the other APIs of AssetManager. + * @return 0 if the path was not found, otherwise a positive integer cookie representing + * this path in the AssetManager. + * @hide + */ + public int findCookieForPath(@NonNull String path) { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureValidLocked(); + final int count = mApkAssets.length; + for (int i = 0; i < count; i++) { + if (path.equals(mApkAssets[i].getAssetPath())) { + return i + 1; + } + } + } + return 0; + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addAssetPath(String path) { + return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/); + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addAssetPathAsSharedLibrary(String path) { + return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/); + } + + /** + * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)} + * @hide + */ + @Deprecated + public int addOverlayPath(String path) { + return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/); + } + + private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureOpenLocked(); + final int count = mApkAssets.length; + for (int i = 0; i < count; i++) { + if (mApkAssets[i].getAssetPath().equals(path)) { + return i + 1; + } + } + + final ApkAssets assets; + try { + if (overlay) { + // TODO(b/70343104): This hardcoded path will be removed once + // addAssetPathInternal is deleted. + final String idmapPath = "/data/resource-cache/" + + path.substring(1).replace('/', '@') + + "@idmap"; + assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); + } else { + assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib); + } + } catch (IOException e) { + return 0; + } + + final ApkAssets[] newApkAssets = Arrays.copyOf(mApkAssets, count + 1); + newApkAssets[count] = assets; + setApkAssets(newApkAssets, true); + return count + 1; + } + } + + /** + * Ensures that the native implementation has not been destroyed. + * The AssetManager may have been closed, but references to it still exist + * and therefore the native implementation is not destroyed. + */ + private void ensureValidLocked() { + if (mObject == 0) { + throw new RuntimeException("AssetManager has been destroyed"); + } + } + + /** + * Ensures that the AssetManager has not been explicitly closed. If this method passes, + * then this implies that ensureValidLocked() also passes. + */ + private void ensureOpenLocked() { + // If mOpen is true, this implies that mObject != 0. + if (!mOpen) { + throw new RuntimeException("AssetManager has been closed"); + } + } + + /** + * Populates {@code outValue} with the data associated a particular + * resource identifier for the current configuration. + * + * @param resId the resource identifier to load + * @param densityDpi the density bucket for which to load the resource + * @param outValue the typed value in which to put the data + * @param resolveRefs {@code true} to resolve references, {@code false} + * to leave them unresolved + * @return {@code true} if the data was loaded into {@code outValue}, + * {@code false} otherwise + */ + boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, + boolean resolveRefs) { + Preconditions.checkNotNull(outValue, "outValue"); + synchronized (this) { + ensureValidLocked(); + final int cookie = nativeGetResourceValue( + mObject, resId, (short) densityDpi, outValue, resolveRefs); + if (cookie <= 0) { + return false; + } + + // Convert the changing configurations flags populated by native code. + outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( + outValue.changingConfigurations); + + if (outValue.type == TypedValue.TYPE_STRING) { + outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data); + } + return true; + } } /** @@ -156,8 +401,7 @@ public final class AssetManager implements AutoCloseable { * @param resId the resource identifier to load * @return the string value, or {@code null} */ - @Nullable - final CharSequence getResourceText(@StringRes int resId) { + @Nullable CharSequence getResourceText(@StringRes int resId) { synchronized (this) { final TypedValue outValue = mValue; if (getResourceValue(resId, 0, outValue, true)) { @@ -172,15 +416,15 @@ public final class AssetManager implements AutoCloseable { * identifier for the current configuration. * * @param resId the resource identifier to load - * @param bagEntryId + * @param bagEntryId the index into the bag to load * @return the string value, or {@code null} */ - @Nullable - final CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) { + @Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) { synchronized (this) { + ensureValidLocked(); final TypedValue outValue = mValue; - final int block = loadResourceBagValue(resId, bagEntryId, outValue, true); - if (block < 0) { + final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue); + if (cookie <= 0) { return null; } @@ -189,52 +433,60 @@ public final class AssetManager implements AutoCloseable { outValue.changingConfigurations); if (outValue.type == TypedValue.TYPE_STRING) { - return mStringBlocks[block].get(outValue.data); + return mApkAssets[cookie - 1].getStringFromPool(outValue.data); } return outValue.coerceToString(); } } + int getResourceArraySize(@ArrayRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceArraySize(mObject, resId); + } + } + /** - * Retrieves the string array associated with a particular resource - * identifier for the current configuration. + * Populates `outData` with array elements of `resId`. `outData` is normally + * used with + * {@link TypedArray}. * - * @param resId the resource identifier of the string array - * @return the string array, or {@code null} + * Each logical element in `outData` is {@link TypedArray#STYLE_NUM_ENTRIES} + * long, + * with the indices of the data representing the type, value, asset cookie, + * resource ID, + * configuration change mask, and density of the element. + * + * @param resId The resource ID of an array resource. + * @param outData The array to populate with data. + * @return The length of the array. + * + * @see TypedArray#STYLE_TYPE + * @see TypedArray#STYLE_DATA + * @see TypedArray#STYLE_ASSET_COOKIE + * @see TypedArray#STYLE_RESOURCE_ID + * @see TypedArray#STYLE_CHANGING_CONFIGURATIONS + * @see TypedArray#STYLE_DENSITY */ - @Nullable - final String[] getResourceStringArray(@ArrayRes int resId) { - return getArrayStringResource(resId); + int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) { + Preconditions.checkNotNull(outData, "outData"); + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceArray(mObject, resId, outData); + } } /** - * Populates {@code outValue} with the data associated a particular - * resource identifier for the current configuration. + * Retrieves the string array associated with a particular resource + * identifier for the current configuration. * - * @param resId the resource identifier to load - * @param densityDpi the density bucket for which to load the resource - * @param outValue the typed value in which to put the data - * @param resolveRefs {@code true} to resolve references, {@code false} - * to leave them unresolved - * @return {@code true} if the data was loaded into {@code outValue}, - * {@code false} otherwise + * @param resId the resource identifier of the string array + * @return the string array, or {@code null} */ - final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, - boolean resolveRefs) { + @Nullable String[] getResourceStringArray(@ArrayRes int resId) { synchronized (this) { - final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs); - if (block < 0) { - return false; - } - - // Convert the changing configurations flags populated by native code. - outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - outValue.changingConfigurations); - - if (outValue.type == TypedValue.TYPE_STRING) { - outValue.string = mStringBlocks[block].get(outValue.data); - } - return true; + ensureValidLocked(); + return nativeGetResourceStringArray(mObject, resId); } } @@ -244,26 +496,48 @@ public final class AssetManager implements AutoCloseable { * * @param resId the resource id of the string array */ - final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) { + @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) { synchronized (this) { - final int[] rawInfoArray = getArrayStringInfo(resId); + ensureValidLocked(); + final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId); if (rawInfoArray == null) { return null; } + final int rawInfoArrayLen = rawInfoArray.length; final int infoArrayLen = rawInfoArrayLen / 2; - int block; - int index; final CharSequence[] retArray = new CharSequence[infoArrayLen]; for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) { - block = rawInfoArray[i]; - index = rawInfoArray[i + 1]; - retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null; + int cookie = rawInfoArray[i]; + int index = rawInfoArray[i + 1]; + retArray[j] = (index >= 0 && cookie > 0) + ? mApkAssets[cookie - 1].getStringFromPool(index) : null; } return retArray; } } + @Nullable int[] getResourceIntArray(@ArrayRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceIntArray(mObject, resId); + } + } + + /** + * Get the attributes for a style resource. These are the <item> + * elements in + * a <style> resource. + * @param resId The resource ID of the style + * @return An array of attribute IDs. + */ + @AttrRes int[] getStyleAttributes(@StyleRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetStyleAttributes(mObject, resId); + } + } + /** * Populates {@code outValue} with the data associated with a particular * resource identifier for the current configuration. Resolves theme @@ -277,73 +551,88 @@ public final class AssetManager implements AutoCloseable { * @return {@code true} if the data was loaded into {@code outValue}, * {@code false} otherwise */ - final boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue, + boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue, boolean resolveRefs) { - final int block = loadThemeAttributeValue(theme, resId, outValue, resolveRefs); - if (block < 0) { - return false; + Preconditions.checkNotNull(outValue, "outValue"); + synchronized (this) { + ensureValidLocked(); + final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue, + resolveRefs); + if (cookie <= 0) { + return false; + } + + // Convert the changing configurations flags populated by native code. + outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( + outValue.changingConfigurations); + + if (outValue.type == TypedValue.TYPE_STRING) { + outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data); + } + return true; } + } - // Convert the changing configurations flags populated by native code. - outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - outValue.changingConfigurations); + void dumpTheme(long theme, int priority, String tag, String prefix) { + synchronized (this) { + ensureValidLocked(); + nativeThemeDump(mObject, theme, priority, tag, prefix); + } + } - if (outValue.type == TypedValue.TYPE_STRING) { - final StringBlock[] blocks = ensureStringBlocks(); - outValue.string = blocks[block].get(outValue.data); + @Nullable String getResourceName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceName(mObject, resId); } - return true; } - /** - * Ensures the string blocks are loaded. - * - * @return the string blocks - */ - @NonNull - final StringBlock[] ensureStringBlocks() { + @Nullable String getResourcePackageName(@AnyRes int resId) { synchronized (this) { - if (mStringBlocks == null) { - makeStringBlocks(sSystem.mStringBlocks); - } - return mStringBlocks; + ensureValidLocked(); + return nativeGetResourcePackageName(mObject, resId); } } - /*package*/ final void makeStringBlocks(StringBlock[] seed) { - final int seedNum = (seed != null) ? seed.length : 0; - final int num = getStringBlockCount(); - mStringBlocks = new StringBlock[num]; - if (localLOGV) Log.v(TAG, "Making string blocks for " + this - + ": " + num); - for (int i=0; i<num; i++) { - if (i < seedNum) { - mStringBlocks[i] = seed[i]; - } else { - mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); - } + @Nullable String getResourceTypeName(@AnyRes int resId) { + synchronized (this) { + ensureValidLocked(); + return nativeGetResourceTypeName(mObject, resId); } } - /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) { + @Nullable String getResourceEntryName(@AnyRes int resId) { synchronized (this) { - // Cookies map to string blocks starting at 1. - return mStringBlocks[cookie - 1].get(id); + ensureValidLocked(); + return nativeGetResourceEntryName(mObject, resId); } } + @AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType, + @Nullable String defPackage) { + synchronized (this) { + ensureValidLocked(); + // name is checked in JNI. + return nativeGetResourceIdentifier(mObject, name, defType, defPackage); + } + } + + CharSequence getPooledStringForCookie(int cookie, int id) { + // Cookies map to ApkAssets starting at 1. + return getApkAssets()[cookie - 1].getStringFromPool(id); + } + /** * Open an asset using ACCESS_STREAMING mode. This provides access to * files that have been bundled with an application as assets -- that is, * files placed in to the "assets" directory. * - * @param fileName The name of the asset to open. This name can be - * hierarchical. + * @param fileName The name of the asset to open. This name can be hierarchical. * * @see #open(String, int) * @see #list */ - public final InputStream open(String fileName) throws IOException { + public @NonNull InputStream open(@NonNull String fileName) throws IOException { return open(fileName, ACCESS_STREAMING); } @@ -353,8 +642,7 @@ public final class AssetManager implements AutoCloseable { * with an application as assets -- that is, files placed in to the * "assets" directory. * - * @param fileName The name of the asset to open. This name can be - * hierarchical. + * @param fileName The name of the asset to open. This name can be hierarchical. * @param accessMode Desired access mode for retrieving the data. * * @see #ACCESS_UNKNOWN @@ -364,34 +652,40 @@ public final class AssetManager implements AutoCloseable { * @see #open(String) * @see #list */ - public final InputStream open(String fileName, int accessMode) - throws IOException { + public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long asset = openAsset(fileName, accessMode); - if (asset != 0) { - AssetInputStream res = new AssetInputStream(asset); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long asset = nativeOpenAsset(mObject, fileName, accessMode); + if (asset == 0) { + throw new FileNotFoundException("Asset file: " + fileName); } + final AssetInputStream assetInputStream = new AssetInputStream(asset); + incRefsLocked(assetInputStream.hashCode()); + return assetInputStream; } - throw new FileNotFoundException("Asset file: " + fileName); } - public final AssetFileDescriptor openFd(String fileName) - throws IOException { + /** + * Open an uncompressed asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides access to files that have been bundled with an application as assets -- that + * is, files placed in to the "assets" directory. + * + * The asset must be uncompressed, or an exception will be thrown. + * + * @param fileName The name of the asset to open. This name can be hierarchical. + * @return An open AssetFileDescriptor. + */ + public @NonNull AssetFileDescriptor openFd(@NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets); - if (pfd != null) { - return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); + ensureOpenLocked(); + final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets); + if (pfd == null) { + throw new FileNotFoundException("Asset file: " + fileName); } + return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } - throw new FileNotFoundException("Asset file: " + fileName); } /** @@ -406,90 +700,121 @@ public final class AssetManager implements AutoCloseable { * * @see #open */ - public native final String[] list(String path) - throws IOException; + public @Nullable String[] list(@NonNull String path) throws IOException { + Preconditions.checkNotNull(path, "path"); + synchronized (this) { + ensureValidLocked(); + return nativeList(mObject, path); + } + } /** - * {@hide} * Open a non-asset file as an asset using ACCESS_STREAMING mode. This * provides direct access to all of the files included in an application * package (not only its assets). Applications should not normally use * this. - * + * + * @param fileName Name of the asset to retrieve. + * * @see #open(String) + * @hide */ - public final InputStream openNonAsset(String fileName) throws IOException { + public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException { return openNonAsset(0, fileName, ACCESS_STREAMING); } /** - * {@hide} * Open a non-asset file as an asset using a specific access mode. This * provides direct access to all of the files included in an application * package (not only its assets). Applications should not normally use * this. - * + * + * @param fileName Name of the asset to retrieve. + * @param accessMode Desired access mode for retrieving the data. + * + * @see #ACCESS_UNKNOWN + * @see #ACCESS_STREAMING + * @see #ACCESS_RANDOM + * @see #ACCESS_BUFFER * @see #open(String, int) + * @hide */ - public final InputStream openNonAsset(String fileName, int accessMode) - throws IOException { + public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode) + throws IOException { return openNonAsset(0, fileName, accessMode); } /** - * {@hide} * Open a non-asset in a specified package. Not for use by applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. + * @hide */ - public final InputStream openNonAsset(int cookie, String fileName) - throws IOException { + public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName) + throws IOException { return openNonAsset(cookie, fileName, ACCESS_STREAMING); } /** - * {@hide} * Open a non-asset in a specified package. Not for use by applications. - * + * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. * @param accessMode Desired access mode for retrieving the data. + * @hide */ - public final InputStream openNonAsset(int cookie, String fileName, int accessMode) - throws IOException { + public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode) + throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long asset = openNonAssetNative(cookie, fileName, accessMode); - if (asset != 0) { - AssetInputStream res = new AssetInputStream(asset); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode); + if (asset == 0) { + throw new FileNotFoundException("Asset absolute file: " + fileName); } + final AssetInputStream assetInputStream = new AssetInputStream(asset); + incRefsLocked(assetInputStream.hashCode()); + return assetInputStream; } - throw new FileNotFoundException("Asset absolute file: " + fileName); } - public final AssetFileDescriptor openNonAssetFd(String fileName) + /** + * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides direct access to all of the files included in an application + * package (not only its assets). Applications should not normally use this. + * + * The asset must not be compressed, or an exception will be thrown. + * + * @param fileName Name of the asset to retrieve. + */ + public @NonNull AssetFileDescriptor openNonAssetFd(@NonNull String fileName) throws IOException { return openNonAssetFd(0, fileName); } - - public final AssetFileDescriptor openNonAssetFd(int cookie, - String fileName) throws IOException { + + /** + * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}. + * This provides direct access to all of the files included in an application + * package (not only its assets). Applications should not normally use this. + * + * The asset must not be compressed, or an exception will be thrown. + * + * @param cookie Identifier of the package to be opened. + * @param fileName Name of the asset to retrieve. + */ + public @NonNull AssetFileDescriptor openNonAssetFd(int cookie, @NonNull String fileName) + throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - ParcelFileDescriptor pfd = openNonAssetFdNative(cookie, - fileName, mOffsets); - if (pfd != null) { - return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); + ensureOpenLocked(); + final ParcelFileDescriptor pfd = + nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets); + if (pfd == null) { + throw new FileNotFoundException("Asset absolute file: " + fileName); } + return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]); } - throw new FileNotFoundException("Asset absolute file: " + fileName); } /** @@ -497,7 +822,7 @@ public final class AssetManager implements AutoCloseable { * * @param fileName The name of the file to retrieve. */ - public final XmlResourceParser openXmlResourceParser(String fileName) + public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName) throws IOException { return openXmlResourceParser(0, fileName); } @@ -508,270 +833,265 @@ public final class AssetManager implements AutoCloseable { * @param cookie Identifier of the package to be opened. * @param fileName The name of the file to retrieve. */ - public final XmlResourceParser openXmlResourceParser(int cookie, - String fileName) throws IOException { - XmlBlock block = openXmlBlockAsset(cookie, fileName); - XmlResourceParser rp = block.newParser(); - block.close(); - return rp; + public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName) + throws IOException { + try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) { + XmlResourceParser parser = block.newParser(); + // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with + // a valid native pointer, which makes newParser always return non-null. But let's + // be paranoid. + if (parser == null) { + throw new AssertionError("block.newParser() returned a null parser"); + } + return parser; + } } /** - * {@hide} - * Retrieve a non-asset as a compiled XML file. Not for use by - * applications. + * Retrieve a non-asset as a compiled XML file. Not for use by applications. * * @param fileName The name of the file to retrieve. + * @hide */ - /*package*/ final XmlBlock openXmlBlockAsset(String fileName) - throws IOException { + @NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException { return openXmlBlockAsset(0, fileName); } /** - * {@hide} * Retrieve a non-asset as a compiled XML file. Not for use by * applications. * * @param cookie Identifier of the package to be opened. * @param fileName Name of the asset to retrieve. + * @hide */ - /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName) - throws IOException { + @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException { + Preconditions.checkNotNull(fileName, "fileName"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long xmlBlock = openXmlAssetNative(cookie, fileName); - if (xmlBlock != 0) { - XmlBlock res = new XmlBlock(this, xmlBlock); - incRefsLocked(res.hashCode()); - return res; + ensureOpenLocked(); + final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); + if (xmlBlock == 0) { + throw new FileNotFoundException("Asset XML file: " + fileName); } + final XmlBlock block = new XmlBlock(this, xmlBlock); + incRefsLocked(block.hashCode()); + return block; } - throw new FileNotFoundException("Asset XML file: " + fileName); } - /*package*/ void xmlBlockGone(int id) { + void xmlBlockGone(int id) { synchronized (this) { decRefsLocked(id); } } - /*package*/ final long createTheme() { + void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes, + @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress, + long outIndicesAddress) { + Preconditions.checkNotNull(inAttrs, "inAttrs"); synchronized (this) { - if (!mOpen) { - throw new RuntimeException("Assetmanager has been closed"); - } - long res = newTheme(); - incRefsLocked(res); - return res; + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes, + parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress, + outIndicesAddress); } } - /*package*/ final void releaseTheme(long theme) { + boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes, + @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues, + @NonNull int[] outIndices) { + Preconditions.checkNotNull(inAttrs, "inAttrs"); + Preconditions.checkNotNull(outValues, "outValues"); + Preconditions.checkNotNull(outIndices, "outIndices"); synchronized (this) { - deleteTheme(theme); - decRefsLocked(theme); + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + return nativeResolveAttrs(mObject, + themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices); } } + boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs, + @NonNull int[] outValues, @NonNull int[] outIndices) { + Preconditions.checkNotNull(parser, "parser"); + Preconditions.checkNotNull(inAttrs, "inAttrs"); + Preconditions.checkNotNull(outValues, "outValues"); + Preconditions.checkNotNull(outIndices, "outIndices"); + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + return nativeRetrieveAttributes( + mObject, parser.mParseState, inAttrs, outValues, outIndices); + } + } + + long createTheme() { + synchronized (this) { + ensureValidLocked(); + long themePtr = nativeThemeCreate(mObject); + incRefsLocked(themePtr); + return themePtr; + } + } + + void releaseTheme(long themePtr) { + synchronized (this) { + nativeThemeDestroy(themePtr); + decRefsLocked(themePtr); + } + } + + void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) { + synchronized (this) { + // Need to synchronize on AssetManager because we will be accessing + // the native implementation of AssetManager. + ensureValidLocked(); + nativeThemeApplyStyle(mObject, themePtr, resId, force); + } + } + + @Override protected void finalize() throws Throwable { - try { - if (DEBUG_REFS && mNumRefs != 0) { - Log.w(TAG, "AssetManager " + this - + " finalized with non-zero refs: " + mNumRefs); - if (mRefStacks != null) { - for (RuntimeException e : mRefStacks.values()) { - Log.w(TAG, "Reference from here", e); - } + if (DEBUG_REFS && mNumRefs != 0) { + Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs); + if (mRefStacks != null) { + for (RuntimeException e : mRefStacks.values()) { + Log.w(TAG, "Reference from here", e); } } - destroy(); - } finally { - super.finalize(); + } + + if (mObject != 0) { + nativeDestroy(mObject); } } - + + /* No Locking is needed for AssetInputStream because an AssetInputStream is not-thread + safe and it does not rely on AssetManager once it has been created. It completely owns the + underlying Asset. */ public final class AssetInputStream extends InputStream { + private long mAssetNativePtr; + private long mLength; + private long mMarkPos; + /** * @hide */ public final int getAssetInt() { throw new UnsupportedOperationException(); } + /** * @hide */ public final long getNativeAsset() { - return mAsset; + return mAssetNativePtr; } - private AssetInputStream(long asset) - { - mAsset = asset; - mLength = getAssetLength(asset); + + private AssetInputStream(long assetNativePtr) { + mAssetNativePtr = assetNativePtr; + mLength = nativeAssetGetLength(assetNativePtr); } + + @Override public final int read() throws IOException { - return readAssetChar(mAsset); - } - public final boolean markSupported() { - return true; - } - public final int available() throws IOException { - long len = getAssetRemainingLength(mAsset); - return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len; + ensureOpen(); + return nativeAssetReadChar(mAssetNativePtr); } - public final void close() throws IOException { - synchronized (AssetManager.this) { - if (mAsset != 0) { - destroyAsset(mAsset); - mAsset = 0; - decRefsLocked(hashCode()); - } - } - } - public final void mark(int readlimit) { - mMarkPos = seekAsset(mAsset, 0, 0); - } - public final void reset() throws IOException { - seekAsset(mAsset, mMarkPos, -1); - } - public final int read(byte[] b) throws IOException { - return readAsset(mAsset, b, 0, b.length); + + @Override + public final int read(@NonNull byte[] b) throws IOException { + ensureOpen(); + Preconditions.checkNotNull(b, "b"); + return nativeAssetRead(mAssetNativePtr, b, 0, b.length); } - public final int read(byte[] b, int off, int len) throws IOException { - return readAsset(mAsset, b, off, len); + + @Override + public final int read(@NonNull byte[] b, int off, int len) throws IOException { + ensureOpen(); + Preconditions.checkNotNull(b, "b"); + return nativeAssetRead(mAssetNativePtr, b, off, len); } + + @Override public final long skip(long n) throws IOException { - long pos = seekAsset(mAsset, 0, 0); - if ((pos+n) > mLength) { - n = mLength-pos; + ensureOpen(); + long pos = nativeAssetSeek(mAssetNativePtr, 0, 0); + if ((pos + n) > mLength) { + n = mLength - pos; } if (n > 0) { - seekAsset(mAsset, n, 0); + nativeAssetSeek(mAssetNativePtr, n, 0); } return n; } - protected void finalize() throws Throwable - { - close(); + @Override + public final int available() throws IOException { + ensureOpen(); + final long len = nativeAssetGetRemainingLength(mAssetNativePtr); + return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len; } - private long mAsset; - private long mLength; - private long mMarkPos; - } - - /** - * Add an additional set of assets to the asset manager. This can be - * either a directory or ZIP file. Not for use by applications. Returns - * the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public final int addAssetPath(String path) { - return addAssetPathInternal(path, false); - } - - /** - * Add an application assets to the asset manager and loading it as shared library. - * This can be either a directory or ZIP file. Not for use by applications. Returns - * the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public final int addAssetPathAsSharedLibrary(String path) { - return addAssetPathInternal(path, true); - } - - private final int addAssetPathInternal(String path, boolean appAsLib) { - synchronized (this) { - int res = addAssetPathNative(path, appAsLib); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final boolean markSupported() { + return true; } - } - - private native final int addAssetPathNative(String path, boolean appAsLib); - /** - * Add an additional set of assets to the asset manager from an already open - * FileDescriptor. Not for use by applications. - * This does not give full AssetManager functionality for these assets, - * since the origin of the file is not known for purposes of sharing, - * overlay resolution, and other features. However it does allow you - * to do simple access to the contents of the given fd as an apk file. - * Performs a dup of the underlying fd, so you must take care of still closing - * the FileDescriptor yourself (and can do that whenever you want). - * Returns the cookie of the added asset, or 0 on failure. - * {@hide} - */ - public int addAssetFd(FileDescriptor fd, String debugPathName) { - return addAssetFdInternal(fd, debugPathName, false); - } - - private int addAssetFdInternal(FileDescriptor fd, String debugPathName, - boolean appAsLib) { - synchronized (this) { - int res = addAssetFdNative(fd, debugPathName, appAsLib); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final void mark(int readlimit) { + ensureOpen(); + mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0); } - } - - private native int addAssetFdNative(FileDescriptor fd, String debugPathName, - boolean appAsLib); - /** - * Add a set of assets to overlay an already added set of assets. - * - * This is only intended for application resources. System wide resources - * are handled before any Java code is executed. - * - * {@hide} - */ - - public final int addOverlayPath(String idmapPath) { - synchronized (this) { - int res = addOverlayPathNative(idmapPath); - makeStringBlocks(mStringBlocks); - return res; + @Override + public final void reset() throws IOException { + ensureOpen(); + nativeAssetSeek(mAssetNativePtr, mMarkPos, -1); } - } - /** - * See addOverlayPath. - * - * {@hide} - */ - public native final int addOverlayPathNative(String idmapPath); + @Override + public final void close() throws IOException { + if (mAssetNativePtr != 0) { + nativeAssetDestroy(mAssetNativePtr); + mAssetNativePtr = 0; - /** - * Add multiple sets of assets to the asset manager at once. See - * {@link #addAssetPath(String)} for more information. Returns array of - * cookies for each added asset with 0 indicating failure, or null if - * the input array of paths is null. - * {@hide} - */ - public final int[] addAssetPaths(String[] paths) { - if (paths == null) { - return null; + synchronized (AssetManager.this) { + decRefsLocked(hashCode()); + } + } } - int[] cookies = new int[paths.length]; - for (int i = 0; i < paths.length; i++) { - cookies[i] = addAssetPath(paths[i]); + @Override + protected void finalize() throws Throwable { + close(); } - return cookies; + private void ensureOpen() { + if (mAssetNativePtr == 0) { + throw new IllegalStateException("AssetInputStream is closed"); + } + } } /** * Determine whether the state in this asset manager is up-to-date with * the files on the filesystem. If false is returned, you need to * instantiate a new AssetManager class to see the new data. - * {@hide} + * @hide */ - public native final boolean isUpToDate(); + public boolean isUpToDate() { + for (ApkAssets apkAssets : getApkAssets()) { + if (!apkAssets.isUpToDate()) { + return false; + } + } + return true; + } /** * Get the locales that this asset manager contains data for. @@ -784,7 +1104,12 @@ public final class AssetManager implements AutoCloseable { * are of the form {@code ll_CC} where {@code ll} is a two letter language code, * and {@code CC} is a two letter country code. */ - public native final String[] getLocales(); + public String[] getLocales() { + synchronized (this) { + ensureValidLocked(); + return nativeGetLocales(mObject, false /*excludeSystem*/); + } + } /** * Same as getLocales(), except that locales that are only provided by the system (i.e. those @@ -794,131 +1119,57 @@ public final class AssetManager implements AutoCloseable { * assets support Cherokee and French, getLocales() would return * [Cherokee, English, French, German], while getNonSystemLocales() would return * [Cherokee, French]. - * {@hide} + * @hide */ - public native final String[] getNonSystemLocales(); - - /** {@hide} */ - public native final Configuration[] getSizeConfigurations(); + public String[] getNonSystemLocales() { + synchronized (this) { + ensureValidLocked(); + return nativeGetLocales(mObject, true /*excludeSystem*/); + } + } /** - * Change the configuation used when retrieving resources. Not for use by - * applications. - * {@hide} + * @hide */ - public native final void setConfiguration(int mcc, int mnc, String locale, - int orientation, int touchscreen, int density, int keyboard, - int keyboardHidden, int navigation, int screenWidth, int screenHeight, - int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, - int screenLayout, int uiMode, int colorMode, int majorVersion); + Configuration[] getSizeConfigurations() { + synchronized (this) { + ensureValidLocked(); + return nativeGetSizeConfigurations(mObject); + } + } /** - * Retrieve the resource identifier for the given resource name. + * Change the configuration used when retrieving resources. Not for use by + * applications. + * @hide */ - /*package*/ native final int getResourceIdentifier(String name, - String defType, - String defPackage); + public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation, + int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, + int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, + int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion) { + synchronized (this) { + ensureValidLocked(); + nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density, + keyboard, keyboardHidden, navigation, screenWidth, screenHeight, + smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode, + colorMode, majorVersion); + } + } - /*package*/ native final String getResourceName(int resid); - /*package*/ native final String getResourcePackageName(int resid); - /*package*/ native final String getResourceTypeName(int resid); - /*package*/ native final String getResourceEntryName(int resid); - - private native final long openAsset(String fileName, int accessMode); - private final native ParcelFileDescriptor openAssetFd(String fileName, - long[] outOffsets) throws IOException; - private native final long openNonAssetNative(int cookie, String fileName, - int accessMode); - private native ParcelFileDescriptor openNonAssetFdNative(int cookie, - String fileName, long[] outOffsets) throws IOException; - private native final void destroyAsset(long asset); - private native final int readAssetChar(long asset); - private native final int readAsset(long asset, byte[] b, int off, int len); - private native final long seekAsset(long asset, long offset, int whence); - private native final long getAssetLength(long asset); - private native final long getAssetRemainingLength(long asset); - - /** Returns true if the resource was found, filling in mRetStringBlock and - * mRetData. */ - private native final int loadResourceValue(int ident, short density, TypedValue outValue, - boolean resolve); - /** Returns true if the resource was found, filling in mRetStringBlock and - * mRetData. */ - private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, - boolean resolve); - /*package*/ static final int STYLE_NUM_ENTRIES = 6; - /*package*/ static final int STYLE_TYPE = 0; - /*package*/ static final int STYLE_DATA = 1; - /*package*/ static final int STYLE_ASSET_COOKIE = 2; - /*package*/ static final int STYLE_RESOURCE_ID = 3; - - /* Offset within typed data array for native changingConfigurations. */ - static final int STYLE_CHANGING_CONFIGURATIONS = 4; - - /*package*/ static final int STYLE_DENSITY = 5; - /*package*/ native static final void applyStyle(long theme, - int defStyleAttr, int defStyleRes, long xmlParser, - int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress); - /*package*/ native static final boolean resolveAttrs(long theme, - int defStyleAttr, int defStyleRes, int[] inValues, - int[] inAttrs, int[] outValues, int[] outIndices); - /*package*/ native final boolean retrieveAttributes( - long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices); - /*package*/ native final int getArraySize(int resource); - /*package*/ native final int retrieveArray(int resource, int[] outValues); - private native final int getStringBlockCount(); - private native final long getNativeStringBlock(int block); - - /** - * {@hide} - */ - public native final String getCookieName(int cookie); - - /** - * {@hide} - */ - public native final SparseArray<String> getAssignedPackageIdentifiers(); - - /** - * {@hide} - */ - public native static final int getGlobalAssetCount(); - - /** - * {@hide} - */ - public native static final String getAssetAllocations(); - /** - * {@hide} + * @hide */ - public native static final int getGlobalAssetManagerCount(); - - private native final long newTheme(); - private native final void deleteTheme(long theme); - /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force); - /*package*/ native static final void copyTheme(long dest, long source); - /*package*/ native static final void clearTheme(long theme); - /*package*/ native static final int loadThemeAttributeValue(long theme, int ident, - TypedValue outValue, - boolean resolve); - /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix); - /*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme); - - private native final long openXmlAssetNative(int cookie, String fileName); - - private native final String[] getArrayStringResource(int arrayRes); - private native final int[] getArrayStringInfo(int arrayRes); - /*package*/ native final int[] getArrayIntResource(int arrayRes); - /*package*/ native final int[] getStyleAttributes(int themeRes); - - private native final void init(boolean isSystem); - private native final void destroy(); - - private final void incRefsLocked(long id) { + public SparseArray<String> getAssignedPackageIdentifiers() { + synchronized (this) { + ensureValidLocked(); + return nativeGetAssignedPackageIdentifiers(mObject); + } + } + + private void incRefsLocked(long id) { if (DEBUG_REFS) { if (mRefStacks == null) { - mRefStacks = new HashMap<Long, RuntimeException>(); + mRefStacks = new HashMap<>(); } RuntimeException ex = new RuntimeException(); ex.fillInStackTrace(); @@ -926,16 +1177,118 @@ public final class AssetManager implements AutoCloseable { } mNumRefs++; } - - private final void decRefsLocked(long id) { + + private void decRefsLocked(long id) { if (DEBUG_REFS && mRefStacks != null) { mRefStacks.remove(id); } mNumRefs--; - //System.out.println("Dec streams: mNumRefs=" + mNumRefs - // + " mReleased=" + mReleased); - if (mNumRefs == 0) { - destroy(); + if (mNumRefs == 0 && mObject != 0) { + nativeDestroy(mObject); + mObject = 0; + mApkAssets = sEmptyApkAssets; } } + + // AssetManager setup native methods. + private static native long nativeCreate(); + private static native void nativeDestroy(long ptr); + private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets, + boolean invalidateCaches); + private static native void nativeSetConfiguration(long ptr, int mcc, int mnc, + @Nullable String locale, int orientation, int touchscreen, int density, int keyboard, + int keyboardHidden, int navigation, int screenWidth, int screenHeight, + int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, + int uiMode, int colorMode, int majorVersion); + private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers( + long ptr); + + // File native methods. + private static native @Nullable String[] nativeList(long ptr, @NonNull String path) + throws IOException; + private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode); + private static native @Nullable ParcelFileDescriptor nativeOpenAssetFd(long ptr, + @NonNull String fileName, long[] outOffsets) throws IOException; + private static native long nativeOpenNonAsset(long ptr, int cookie, @NonNull String fileName, + int accessMode); + private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie, + @NonNull String fileName, @NonNull long[] outOffsets) throws IOException; + private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName); + + // Primitive resource native methods. + private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density, + @NonNull TypedValue outValue, boolean resolveReferences); + private static native int nativeGetResourceBagValue(long ptr, @AnyRes int resId, int bagEntryId, + @NonNull TypedValue outValue); + + private static native @Nullable @AttrRes int[] nativeGetStyleAttributes(long ptr, + @StyleRes int resId); + private static native @Nullable String[] nativeGetResourceStringArray(long ptr, + @ArrayRes int resId); + private static native @Nullable int[] nativeGetResourceStringArrayInfo(long ptr, + @ArrayRes int resId); + private static native @Nullable int[] nativeGetResourceIntArray(long ptr, @ArrayRes int resId); + private static native int nativeGetResourceArraySize(long ptr, @ArrayRes int resId); + private static native int nativeGetResourceArray(long ptr, @ArrayRes int resId, + @NonNull int[] outValues); + + // Resource name/ID native methods. + private static native @AnyRes int nativeGetResourceIdentifier(long ptr, @NonNull String name, + @Nullable String defType, @Nullable String defPackage); + private static native @Nullable String nativeGetResourceName(long ptr, @AnyRes int resid); + private static native @Nullable String nativeGetResourcePackageName(long ptr, + @AnyRes int resid); + private static native @Nullable String nativeGetResourceTypeName(long ptr, @AnyRes int resid); + private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid); + private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem); + private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr); + + // Style attribute retrieval native methods. + private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr, + @StyleRes int defStyleRes, long xmlParserPtr, @NonNull int[] inAttrs, + long outValuesAddress, long outIndicesAddress); + private static native boolean nativeResolveAttrs(long ptr, long themePtr, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes, @Nullable int[] inValues, + @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices); + private static native boolean nativeRetrieveAttributes(long ptr, long xmlParserPtr, + @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices); + + // Theme related native methods + private static native long nativeThemeCreate(long ptr); + private static native void nativeThemeDestroy(long themePtr); + private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId, + boolean force); + static native void nativeThemeCopy(long destThemePtr, long sourceThemePtr); + static native void nativeThemeClear(long themePtr); + private static native int nativeThemeGetAttributeValue(long ptr, long themePtr, + @AttrRes int resId, @NonNull TypedValue outValue, boolean resolve); + private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag, + String prefix); + static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr); + + // AssetInputStream related native methods. + private static native void nativeAssetDestroy(long assetPtr); + private static native int nativeAssetReadChar(long assetPtr); + private static native int nativeAssetRead(long assetPtr, byte[] b, int off, int len); + private static native long nativeAssetSeek(long assetPtr, long offset, int whence); + private static native long nativeAssetGetLength(long assetPtr); + private static native long nativeAssetGetRemainingLength(long assetPtr); + + private static native void nativeVerifySystemIdmaps(); + + // Global debug native methods. + /** + * @hide + */ + public static native int getGlobalAssetCount(); + + /** + * @hide + */ + public static native String getAssetAllocations(); + + /** + * @hide + */ + public static native int getGlobalAssetManagerCount(); } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index e173653cd961..8f58891ed556 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -590,7 +590,7 @@ public class Resources { */ @NonNull public int[] getIntArray(@ArrayRes int id) throws NotFoundException { - int[] res = mResourcesImpl.getAssets().getArrayIntResource(id); + int[] res = mResourcesImpl.getAssets().getResourceIntArray(id); if (res != null) { return res; } @@ -613,13 +613,13 @@ public class Resources { @NonNull public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException { final ResourcesImpl impl = mResourcesImpl; - int len = impl.getAssets().getArraySize(id); + int len = impl.getAssets().getResourceArraySize(id); if (len < 0) { throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id)); } TypedArray array = TypedArray.obtain(this, len); - array.mLength = impl.getAssets().retrieveArray(id, array.mData); + array.mLength = impl.getAssets().getResourceArray(id, array.mData); array.mIndices[0] = 0; return array; @@ -1789,8 +1789,7 @@ public class Resources { // out the attributes from the XML file (applying type information // contained in the resources and such). XmlBlock.Parser parser = (XmlBlock.Parser)set; - mResourcesImpl.getAssets().retrieveAttributes(parser.mParseState, attrs, - array.mData, array.mIndices); + mResourcesImpl.getAssets().retrieveAttributes(parser, attrs, array.mData, array.mIndices); array.mXml = parser; diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 97cb78bc4243..2a4b278bdca8 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -168,7 +168,6 @@ public class ResourcesImpl { mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo()); - mAssets.ensureStringBlocks(); } public DisplayAdjustments getDisplayAdjustments() { @@ -1274,8 +1273,7 @@ public class ResourcesImpl { void applyStyle(int resId, boolean force) { synchronized (mKey) { - AssetManager.applyThemeStyle(mTheme, resId, force); - + mAssets.applyStyleToTheme(mTheme, resId, force); mThemeResId = resId; mKey.append(resId, force); } @@ -1284,7 +1282,7 @@ public class ResourcesImpl { void setTo(ThemeImpl other) { synchronized (mKey) { synchronized (other.mKey) { - AssetManager.copyTheme(mTheme, other.mTheme); + AssetManager.nativeThemeCopy(mTheme, other.mTheme); mThemeResId = other.mThemeResId; mKey.setTo(other.getKey()); @@ -1307,12 +1305,10 @@ public class ResourcesImpl { // out the attributes from the XML file (applying type information // contained in the resources and such). final XmlBlock.Parser parser = (XmlBlock.Parser) set; - AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, - parser != null ? parser.mParseState : 0, - attrs, attrs.length, array.mDataAddress, array.mIndicesAddress); + mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs, + array.mDataAddress, array.mIndicesAddress); array.mTheme = wrapper; array.mXml = parser; - return array; } } @@ -1329,7 +1325,7 @@ public class ResourcesImpl { } final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); - AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); + mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices); array.mTheme = wrapper; array.mXml = null; return array; @@ -1349,14 +1345,14 @@ public class ResourcesImpl { @Config int getChangingConfigurations() { synchronized (mKey) { final @NativeConfig int nativeChangingConfig = - AssetManager.getThemeChangingConfigurations(mTheme); + AssetManager.nativeThemeGetChangingConfigurations(mTheme); return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); } } public void dump(int priority, String tag, String prefix) { synchronized (mKey) { - AssetManager.dumpTheme(mTheme, priority, tag, prefix); + mAssets.dumpTheme(mTheme, priority, tag, prefix); } } @@ -1385,13 +1381,13 @@ public class ResourcesImpl { */ void rebase() { synchronized (mKey) { - AssetManager.clearTheme(mTheme); + AssetManager.nativeThemeClear(mTheme); // Reapply the same styles in the same order. for (int i = 0; i < mKey.mCount; i++) { final int resId = mKey.mResId[i]; final boolean force = mKey.mForce[i]; - AssetManager.applyThemeStyle(mTheme, resId, force); + mAssets.applyStyleToTheme(mTheme, resId, force); } } } diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index f33c75168a5f..cbb3c6df0558 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -61,6 +61,15 @@ public class TypedArray { return attrs; } + // STYLE_ prefixed constants are offsets within the typed data array. + static final int STYLE_NUM_ENTRIES = 6; + static final int STYLE_TYPE = 0; + static final int STYLE_DATA = 1; + static final int STYLE_ASSET_COOKIE = 2; + static final int STYLE_RESOURCE_ID = 3; + static final int STYLE_CHANGING_CONFIGURATIONS = 4; + static final int STYLE_DENSITY = 5; + private final Resources mResources; private DisplayMetrics mMetrics; private AssetManager mAssets; @@ -78,7 +87,7 @@ public class TypedArray { private void resize(int len) { mLength = len; - final int dataLen = len * AssetManager.STYLE_NUM_ENTRIES; + final int dataLen = len * STYLE_NUM_ENTRIES; final int indicesLen = len + 1; final VMRuntime runtime = VMRuntime.getRuntime(); if (mDataAddress == 0 || mData.length < dataLen) { @@ -166,9 +175,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return null; } else if (type == TypedValue.TYPE_STRING) { @@ -203,9 +212,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return null; } else if (type == TypedValue.TYPE_STRING) { @@ -242,14 +251,13 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_STRING) { - final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; + final int cookie = data[index + STYLE_ASSET_COOKIE]; if (cookie < 0) { - return mXml.getPooledString( - data[index+AssetManager.STYLE_DATA]).toString(); + return mXml.getPooledString(data[index + STYLE_DATA]).toString(); } } return null; @@ -274,11 +282,11 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); + data[index + STYLE_CHANGING_CONFIGURATIONS]); if ((changingConfigs & ~allowedChangingConfigs) != 0) { return null; } @@ -320,14 +328,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA] != 0; + return data[index + STYLE_DATA] != 0; } final TypedValue v = mValue; @@ -359,14 +367,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } final TypedValue v = mValue; @@ -396,16 +404,16 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_FLOAT) { - return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]); + return Float.intBitsToFloat(data[index + STYLE_DATA]); } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } final TypedValue v = mValue; @@ -446,15 +454,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_STRING) { final TypedValue value = mValue; if (getValueAt(index, value)) { @@ -498,7 +506,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -533,7 +541,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -564,15 +572,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -612,15 +620,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimension( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimension(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -661,15 +668,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelOffset( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelOffset(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -711,15 +717,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -755,16 +760,15 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index+AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -795,15 +799,14 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { - return data[index+AssetManager.STYLE_DATA]; + return data[index + STYLE_DATA]; } else if (type == TypedValue.TYPE_DIMENSION) { - return TypedValue.complexToDimensionPixelSize( - data[index + AssetManager.STYLE_DATA], mMetrics); + return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics); } return defValue; @@ -833,15 +836,14 @@ public class TypedArray { } final int attrIndex = index; - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return defValue; } else if (type == TypedValue.TYPE_FRACTION) { - return TypedValue.complexToFraction( - data[index+AssetManager.STYLE_DATA], base, pbase); + return TypedValue.complexToFraction(data[index + STYLE_DATA], base, pbase); } else if (type == TypedValue.TYPE_ATTRIBUTE) { final TypedValue value = mValue; getValueAt(index, value); @@ -874,10 +876,10 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) { - final int resid = data[index+AssetManager.STYLE_RESOURCE_ID]; + if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) { + final int resid = data[index + STYLE_RESOURCE_ID]; if (resid != 0) { return resid; } @@ -902,10 +904,10 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { - return data[index + AssetManager.STYLE_DATA]; + if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { + return data[index + STYLE_DATA]; } return defValue; } @@ -939,7 +941,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -975,7 +977,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new UnsupportedOperationException( "Failed to resolve attribute at index " + index + ": " + value); @@ -1006,7 +1008,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { return mResources.getTextArray(value.resourceId); } return null; @@ -1027,7 +1029,7 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue); + return getValueAt(index * STYLE_NUM_ENTRIES, outValue); } /** @@ -1043,8 +1045,8 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; - return mData[index + AssetManager.STYLE_TYPE]; + index *= STYLE_NUM_ENTRIES; + return mData[index + STYLE_TYPE]; } /** @@ -1063,9 +1065,9 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; return type != TypedValue.TYPE_NULL; } @@ -1084,11 +1086,11 @@ public class TypedArray { throw new RuntimeException("Cannot make calls to a recycled instance!"); } - index *= AssetManager.STYLE_NUM_ENTRIES; + index *= STYLE_NUM_ENTRIES; final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; return type != TypedValue.TYPE_NULL - || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; + || data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY; } /** @@ -1109,7 +1111,7 @@ public class TypedArray { } final TypedValue value = mValue; - if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) { + if (getValueAt(index * STYLE_NUM_ENTRIES, value)) { return value; } return null; @@ -1181,16 +1183,16 @@ public class TypedArray { final int[] data = mData; final int N = length(); for (int i = 0; i < N; i++) { - final int index = i * AssetManager.STYLE_NUM_ENTRIES; - if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { + final int index = i * STYLE_NUM_ENTRIES; + if (data[index + STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { // Not an attribute, ignore. continue; } // Null the entry so that we can safely call getZzz(). - data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL; + data[index + STYLE_TYPE] = TypedValue.TYPE_NULL; - final int attr = data[index + AssetManager.STYLE_DATA]; + final int attr = data[index + STYLE_DATA]; if (attr == 0) { // Useless data, ignore. continue; @@ -1231,45 +1233,44 @@ public class TypedArray { final int[] data = mData; final int N = length(); for (int i = 0; i < N; i++) { - final int index = i * AssetManager.STYLE_NUM_ENTRIES; - final int type = data[index + AssetManager.STYLE_TYPE]; + final int index = i * STYLE_NUM_ENTRIES; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { continue; } changingConfig |= ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); + data[index + STYLE_CHANGING_CONFIGURATIONS]); } return changingConfig; } private boolean getValueAt(int index, TypedValue outValue) { final int[] data = mData; - final int type = data[index+AssetManager.STYLE_TYPE]; + final int type = data[index + STYLE_TYPE]; if (type == TypedValue.TYPE_NULL) { return false; } outValue.type = type; - outValue.data = data[index+AssetManager.STYLE_DATA]; - outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; - outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID]; + outValue.data = data[index + STYLE_DATA]; + outValue.assetCookie = data[index + STYLE_ASSET_COOKIE]; + outValue.resourceId = data[index + STYLE_RESOURCE_ID]; outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava( - data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]); - outValue.density = data[index+AssetManager.STYLE_DENSITY]; + data[index + STYLE_CHANGING_CONFIGURATIONS]); + outValue.density = data[index + STYLE_DENSITY]; outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null; return true; } private CharSequence loadStringValueAt(int index) { final int[] data = mData; - final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE]; + final int cookie = data[index + STYLE_ASSET_COOKIE]; if (cookie < 0) { if (mXml != null) { - return mXml.getPooledString( - data[index+AssetManager.STYLE_DATA]); + return mXml.getPooledString(data[index + STYLE_DATA]); } return null; } - return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]); + return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]); } /** @hide */ diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index e6b957414ea8..d4ccffb83ca5 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -16,6 +16,7 @@ package android.content.res; +import android.annotation.Nullable; import android.util.TypedValue; import com.android.internal.util.XmlUtils; @@ -33,7 +34,7 @@ import java.io.Reader; * * {@hide} */ -final class XmlBlock { +final class XmlBlock implements AutoCloseable { private static final boolean DEBUG=false; public XmlBlock(byte[] data) { @@ -48,6 +49,7 @@ final class XmlBlock { mStrings = new StringBlock(nativeGetStringBlock(mNative), false); } + @Override public void close() { synchronized (this) { if (mOpen) { @@ -478,13 +480,13 @@ final class XmlBlock { * are doing! The given native object must exist for the entire lifetime * of this newly creating XmlBlock. */ - XmlBlock(AssetManager assets, long xmlBlock) { + XmlBlock(@Nullable AssetManager assets, long xmlBlock) { mAssets = assets; mNative = xmlBlock; mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false); } - private final AssetManager mAssets; + private @Nullable final AssetManager mAssets; private final long mNative; /*package*/ final StringBlock mStrings; private boolean mOpen = true; diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 36673cd66ca2..4de4880b7c17 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -656,6 +656,34 @@ public final class DisplayManager { } /** + * Temporarily sets the brightness of the display. + * <p> + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. + * </p> + * + * @param brightness The brightness value from 0 to 255. + * + * @hide Requires signature permission. + */ + public void setTemporaryBrightness(int brightness) { + mGlobal.setTemporaryBrightness(brightness); + } + + /** + * Temporarily sets the auto brightness adjustment factor. + * <p> + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. + * </p> + * + * @param adjustment The adjustment factor from -1.0 to 1.0. + * + * @hide Requires signature permission. + */ + public void setTemporaryAutoBrightnessAdjustment(float adjustment) { + mGlobal.setTemporaryAutoBrightnessAdjustment(adjustment); + } + + /** * Listens for changes in available display devices. */ public interface DisplayListener { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 9c851f1fd106..2d5f5e041486 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -489,6 +489,42 @@ public final class DisplayManagerGlobal { } } + /** + * Temporarily sets the brightness of the display. + * <p> + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. + * </p> + * + * @param brightness The brightness value from 0 to 255. + * + * @hide Requires signature permission. + */ + public void setTemporaryBrightness(int brightness) { + try { + mDm.setTemporaryBrightness(brightness); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Temporarily sets the auto brightness adjustment factor. + * <p> + * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission. + * </p> + * + * @param adjustment The adjustment factor from -1.0 to 1.0. + * + * @hide Requires signature permission. + */ + public void setTemporaryAutoBrightnessAdjustment(float adjustment) { + try { + mDm.setTemporaryAutoBrightnessAdjustment(adjustment); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, int event) { diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 078958ad881a..1cfad4f0168f 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -214,23 +214,12 @@ public abstract class DisplayManagerInternal { // nearby, turning it off temporarily until the object is moved away. public boolean useProximitySensor; - // The desired screen brightness in the range 0 (minimum / off) to 255 (brightest). - // The display power controller may choose to clamp the brightness. - // When auto-brightness is enabled, this field should specify a nominal default - // value to use while waiting for the light sensor to report enough data. - public int screenBrightness; + // An override of the screen brightness. Set to -1 is used if there's no override. + public int screenBrightnessOverride; - // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter). - public float screenAutoBrightnessAdjustment; - - // Set to true if screenBrightness and screenAutoBrightnessAdjustment were both - // set by the user as opposed to being programmatically controlled by apps. - public boolean brightnessSetByUser; - - // Set to true if screenBrightness or screenAutoBrightnessAdjustment are being set - // temporarily. This is typically set while the user has their finger on the brightness - // control, before they've selected the final brightness value. - public boolean brightnessIsTemporary; + // An override of the screen auto-brightness adjustment factor in the range -1 (dimmer) to + // 1 (brighter). Set to Float.NaN if there's no override. + public float screenAutoBrightnessAdjustmentOverride; // If true, enables automatic brightness control. public boolean useAutoBrightness; @@ -262,10 +251,10 @@ public abstract class DisplayManagerInternal { public DisplayPowerRequest() { policy = POLICY_BRIGHT; useProximitySensor = false; - screenBrightness = PowerManager.BRIGHTNESS_ON; - screenAutoBrightnessAdjustment = 0.0f; - screenLowPowerBrightnessFactor = 0.5f; + screenBrightnessOverride = -1; useAutoBrightness = false; + screenAutoBrightnessAdjustmentOverride = Float.NaN; + screenLowPowerBrightnessFactor = 0.5f; blockScreenOn = false; dozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; dozeScreenState = Display.STATE_UNKNOWN; @@ -286,12 +275,10 @@ public abstract class DisplayManagerInternal { public void copyFrom(DisplayPowerRequest other) { policy = other.policy; useProximitySensor = other.useProximitySensor; - screenBrightness = other.screenBrightness; - screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment; - screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor; - brightnessSetByUser = other.brightnessSetByUser; - brightnessIsTemporary = other.brightnessIsTemporary; + screenBrightnessOverride = other.screenBrightnessOverride; useAutoBrightness = other.useAutoBrightness; + screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride; + screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor; blockScreenOn = other.blockScreenOn; lowPowerMode = other.lowPowerMode; boostScreenBrightness = other.boostScreenBrightness; @@ -309,13 +296,12 @@ public abstract class DisplayManagerInternal { return other != null && policy == other.policy && useProximitySensor == other.useProximitySensor - && screenBrightness == other.screenBrightness - && screenAutoBrightnessAdjustment == other.screenAutoBrightnessAdjustment + && screenBrightnessOverride == other.screenBrightnessOverride + && useAutoBrightness == other.useAutoBrightness + && floatEquals(screenAutoBrightnessAdjustmentOverride, + other.screenAutoBrightnessAdjustmentOverride) && screenLowPowerBrightnessFactor == other.screenLowPowerBrightnessFactor - && brightnessSetByUser == other.brightnessSetByUser - && brightnessIsTemporary == other.brightnessIsTemporary - && useAutoBrightness == other.useAutoBrightness && blockScreenOn == other.blockScreenOn && lowPowerMode == other.lowPowerMode && boostScreenBrightness == other.boostScreenBrightness @@ -323,6 +309,10 @@ public abstract class DisplayManagerInternal { && dozeScreenState == other.dozeScreenState; } + private boolean floatEquals(float f1, float f2) { + return f1 == f2 || Float.isNaN(f1) && Float.isNaN(f2); + } + @Override public int hashCode() { return 0; // don't care @@ -332,12 +322,11 @@ public abstract class DisplayManagerInternal { public String toString() { return "policy=" + policyToString(policy) + ", useProximitySensor=" + useProximitySensor - + ", screenBrightness=" + screenBrightness - + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment - + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor - + ", brightnessSetByUser=" + brightnessSetByUser - + ", brightnessIsTemporary=" + brightnessIsTemporary + + ", screenBrightnessOverride=" + screenBrightnessOverride + ", useAutoBrightness=" + useAutoBrightness + + ", screenAutoBrightnessAdjustmentOverride=" + + screenAutoBrightnessAdjustmentOverride + + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor + ", blockScreenOn=" + blockScreenOn + ", lowPowerMode=" + lowPowerMode + ", boostScreenBrightness=" + boostScreenBrightness diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 5b7b32fe5201..13599cfa0b7d 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -92,4 +92,10 @@ interface IDisplayManager { // the same as the calling user. void setBrightnessConfigurationForUser(in BrightnessConfiguration c, int userId, String packageName); + + // Temporarily sets the display brightness. + void setTemporaryBrightness(int brightness); + + // Temporarily sets the auto brightness adjustment factor. + void setTemporaryAutoBrightnessAdjustment(float adjustment); } diff --git a/core/java/android/net/KeepalivePacketData.aidl b/core/java/android/net/KeepalivePacketData.aidl new file mode 100644 index 000000000000..d456b53fd188 --- /dev/null +++ b/core/java/android/net/KeepalivePacketData.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +parcelable KeepalivePacketData; diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java index f6b73b7d6949..08d4ff5da966 100644 --- a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java +++ b/core/java/android/net/KeepalivePacketData.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.android.server.connectivity; - -import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN; -import static android.net.util.NetworkConstants.UDP_HEADER_LEN; +package android.net; import android.system.OsConstants; import android.net.ConnectivityManager; -import android.net.NetworkUtils; import android.net.util.IpUtils; +import android.os.Parcel; +import android.os.Parcelable; +import android.system.OsConstants; +import android.util.Log; import java.net.Inet4Address; import java.net.Inet6Address; @@ -38,9 +38,8 @@ import static android.net.ConnectivityManager.PacketKeepalive.*; * * @hide */ -public class KeepalivePacketData { - /** Protocol of the packet to send; one of the OsConstants.ETH_P_* values. */ - public final int protocol; +public class KeepalivePacketData implements Parcelable { + private static final String TAG = "KeepalivePacketData"; /** Source IP address */ public final InetAddress srcAddress; @@ -54,54 +53,57 @@ public class KeepalivePacketData { /** Destination port */ public final int dstPort; - /** Destination MAC address. Can change if routing changes. */ - public byte[] dstMac; - /** Packet data. A raw byte string of packet data, not including the link-layer header. */ - public final byte[] data; + private final byte[] mPacket; + private static final int IPV4_HEADER_LENGTH = 20; + private static final int UDP_HEADER_LENGTH = 8; + + // This should only be constructed via static factory methods, such as + // nattKeepalivePacket protected KeepalivePacketData(InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException { this.srcAddress = srcAddress; this.dstAddress = dstAddress; this.srcPort = srcPort; this.dstPort = dstPort; - this.data = data; + this.mPacket = data; // Check we have two IP addresses of the same family. - if (srcAddress == null || dstAddress == null || - !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) { - throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); - } - - // Set the protocol. - if (this.dstAddress instanceof Inet4Address) { - this.protocol = OsConstants.ETH_P_IP; - } else if (this.dstAddress instanceof Inet6Address) { - this.protocol = OsConstants.ETH_P_IPV6; - } else { + if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName() + .equals(dstAddress.getClass().getName())) { + Log.e(TAG, "Invalid or mismatched InetAddresses in KeepalivePacketData"); throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); } // Check the ports. if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) { + Log.e(TAG, "Invalid ports in KeepalivePacketData"); throw new InvalidPacketException(ERROR_INVALID_PORT); } } public static class InvalidPacketException extends Exception { - final public int error; + public final int error; public InvalidPacketException(int error) { this.error = error; } } - /** - * Creates an IPsec NAT-T keepalive packet with the specified parameters. - */ + public byte[] getPacket() { + return mPacket.clone(); + } + public static KeepalivePacketData nattKeepalivePacket( - InetAddress srcAddress, int srcPort, - InetAddress dstAddress, int dstPort) throws InvalidPacketException { + InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) + throws InvalidPacketException { + + // FIXME: remove this and actually support IPv6 keepalives + if (srcAddress instanceof Inet6Address && dstAddress instanceof Inet6Address) { + // Optimistically returning an IPv6 Keepalive Packet with no data, + // which currently only works on cellular + return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, new byte[0]); + } if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); @@ -111,7 +113,7 @@ public class KeepalivePacketData { throw new InvalidPacketException(ERROR_INVALID_PORT); } - int length = IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + 1; + int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1; ByteBuffer buf = ByteBuffer.allocate(length); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort((short) 0x4500); // IP version and TOS @@ -130,8 +132,43 @@ public class KeepalivePacketData { buf.putShort((short) 0); // UDP checksum buf.put((byte) 0xff); // NAT-T keepalive buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); - buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_MIN_LEN)); + buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH)); return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); } + + /* Parcelable Implementation */ + public int describeContents() { + return 0; + } + + /** Write to parcel */ + public void writeToParcel(Parcel out, int flags) { + out.writeString(srcAddress.getHostAddress()); + out.writeString(dstAddress.getHostAddress()); + out.writeInt(srcPort); + out.writeInt(dstPort); + out.writeByteArray(mPacket); + } + + private KeepalivePacketData(Parcel in) { + srcAddress = NetworkUtils.numericToInetAddress(in.readString()); + dstAddress = NetworkUtils.numericToInetAddress(in.readString()); + srcPort = in.readInt(); + dstPort = in.readInt(); + mPacket = in.createByteArray(); + } + + /** Parcelable Creator */ + public static final Parcelable.Creator<KeepalivePacketData> CREATOR = + new Parcelable.Creator<KeepalivePacketData>() { + public KeepalivePacketData createFromParcel(Parcel in) { + return new KeepalivePacketData(in); + } + + public KeepalivePacketData[] newArray(int size) { + return new KeepalivePacketData[size]; + } + }; + } diff --git a/services/net/java/android/net/util/IpUtils.java b/core/java/android/net/util/IpUtils.java index e037c4035aca..e037c4035aca 100644 --- a/services/net/java/android/net/util/IpUtils.java +++ b/core/java/android/net/util/IpUtils.java diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index b1794a6dfa6c..62731e84a401 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -292,6 +292,16 @@ public class Environment { } /** {@hide} */ + public static File getDataVendorCeDirectory(int userId) { + return buildPath(getDataDirectory(), "vendor_ce", String.valueOf(userId)); + } + + /** {@hide} */ + public static File getDataVendorDeDirectory(int userId) { + return buildPath(getDataDirectory(), "vendor_de", String.valueOf(userId)); + } + + /** {@hide} */ public static File getProfileSnapshotPath(String packageName, String codePath) { return buildPath(buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName, "primary.prof.snapshot")); diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java index 4d7d9319c94e..335bf9d86fc7 100644 --- a/core/java/android/os/HidlSupport.java +++ b/core/java/android/os/HidlSupport.java @@ -16,6 +16,8 @@ package android.os; +import android.annotation.SystemApi; + import java.util.Arrays; import java.util.Collection; import java.util.Iterator; @@ -25,6 +27,7 @@ import java.util.Objects; import java.util.stream.IntStream; /** @hide */ +@SystemApi public class HidlSupport { /** * Similar to Objects.deepEquals, but also take care of lists. @@ -36,7 +39,9 @@ public class HidlSupport { * 2.3 Both are Lists, elements are checked recursively * 2.4 (If both are collections other than lists or maps, throw an error) * 2.5 lft.equals(rgt) returns true + * @hide */ + @SystemApi public static boolean deepEquals(Object lft, Object rgt) { if (lft == rgt) { return true; @@ -91,6 +96,7 @@ public class HidlSupport { * and should be avoided). * * @param <E> Inner object type. + * @hide */ public static final class Mutable<E> { public E value; @@ -106,7 +112,9 @@ public class HidlSupport { /** * Similar to Arrays.deepHashCode, but also take care of lists. + * @hide */ + @SystemApi public static int deepHashCode(Object o) { if (o == null) { return 0; @@ -133,6 +141,7 @@ public class HidlSupport { return o.hashCode(); } + /** @hide */ private static void throwErrorIfUnsupportedType(Object o) { if (o instanceof Collection<?> && !(o instanceof List<?>)) { throw new UnsupportedOperationException( @@ -146,6 +155,7 @@ public class HidlSupport { } } + /** @hide */ private static int primitiveArrayHashCode(Object o) { Class<?> elementType = o.getClass().getComponentType(); if (elementType == boolean.class) { @@ -185,7 +195,9 @@ public class HidlSupport { * - If both interfaces are stubs, asBinder() returns the object itself. By default, * auto-generated IFoo.Stub does not override equals(), but an implementation can * optionally override it, and {@code interfacesEqual} will use it here. + * @hide */ + @SystemApi public static boolean interfacesEqual(IHwInterface lft, Object rgt) { if (lft == rgt) { return true; @@ -201,6 +213,10 @@ public class HidlSupport { /** * Return PID of process if sharable to clients. + * @hide */ public static native int getPidIfSharable(); + + /** @hide */ + public HidlSupport() {} } diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index 5e2a0815b60d..ecac002940cc 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -16,16 +16,20 @@ package android.os; +import android.annotation.SystemApi; + import libcore.util.NativeAllocationRegistry; import java.util.NoSuchElementException; /** @hide */ +@SystemApi public abstract class HwBinder implements IHwBinder { private static final String TAG = "HwBinder"; private static final NativeAllocationRegistry sNativeRegistry; + /** @hide */ public HwBinder() { native_setup(); @@ -34,33 +38,55 @@ public abstract class HwBinder implements IHwBinder { mNativeContext); } + /** @hide */ @Override public final native void transact( int code, HwParcel request, HwParcel reply, int flags) throws RemoteException; + /** @hide */ public abstract void onTransact( int code, HwParcel request, HwParcel reply, int flags) throws RemoteException; + /** @hide */ public native final void registerService(String serviceName) throws RemoteException; + /** @hide */ public static final IHwBinder getService( String iface, String serviceName) throws RemoteException, NoSuchElementException { return getService(iface, serviceName, false /* retry */); } + /** @hide */ public static native final IHwBinder getService( String iface, String serviceName, boolean retry) throws RemoteException, NoSuchElementException; + /** + * Configures how many threads the process-wide hwbinder threadpool + * has to process incoming requests. + * + * @hide + */ + @SystemApi public static native final void configureRpcThreadpool( long maxThreads, boolean callerWillJoin); + /** + * Current thread will join hwbinder threadpool and process + * commands in the pool. Should be called after configuring + * a threadpool with callerWillJoin true and then registering + * the provided service if this thread doesn't need to do + * anything else. + * + * @hide + */ + @SystemApi public static native final void joinRpcThreadpool(); // Returns address of the "freeFunction". @@ -83,6 +109,7 @@ public abstract class HwBinder implements IHwBinder { /** * Notifies listeners that a system property has changed + * @hide */ public static void reportSyspropChanged() { native_report_sysprop_change(); diff --git a/core/java/android/os/HwBlob.java b/core/java/android/os/HwBlob.java index 5e9b9ae3d49a..405651e992a3 100644 --- a/core/java/android/os/HwBlob.java +++ b/core/java/android/os/HwBlob.java @@ -17,10 +17,17 @@ package android.os; import android.annotation.NonNull; +import android.annotation.SystemApi; import libcore.util.NativeAllocationRegistry; -/** @hide */ +/** + * Represents fixed sized allocation of marshalled data used. Helper methods + * allow for access to the unmarshalled data in a variety of ways. + * + * @hide + */ +@SystemApi public class HwBlob { private static final String TAG = "HwBlob"; @@ -34,48 +41,276 @@ public class HwBlob { mNativeContext); } + /** + * @param offset offset to unmarshall a boolean from + * @return the unmarshalled boolean value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final boolean getBool(long offset); + /** + * @param offset offset to unmarshall a byte from + * @return the unmarshalled byte value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final byte getInt8(long offset); + /** + * @param offset offset to unmarshall a short from + * @return the unmarshalled short value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final short getInt16(long offset); + /** + * @param offset offset to unmarshall an int from + * @return the unmarshalled int value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final int getInt32(long offset); + /** + * @param offset offset to unmarshall a long from + * @return the unmarshalled long value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final long getInt64(long offset); + /** + * @param offset offset to unmarshall a float from + * @return the unmarshalled float value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final float getFloat(long offset); + /** + * @param offset offset to unmarshall a double from + * @return the unmarshalled double value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final double getDouble(long offset); + /** + * @param offset offset to unmarshall a string from + * @return the unmarshalled string value + * @throws IndexOutOfBoundsException when offset is out of this HwBlob + */ public native final String getString(long offset); /** - The copyTo... methods copy the blob's data, starting from the given - byte offset, into the array. A total of "size" _elements_ are copied. + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jboolean)] out of the blob. */ public native final void copyToBoolArray(long offset, boolean[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jbyte)] out of the blob. + */ public native final void copyToInt8Array(long offset, byte[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jshort)] out of the blob. + */ public native final void copyToInt16Array(long offset, short[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jint)] out of the blob. + */ public native final void copyToInt32Array(long offset, int[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jlong)] out of the blob. + */ public native final void copyToInt64Array(long offset, long[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jfloat)] out of the blob. + */ public native final void copyToFloatArray(long offset, float[] array, int size); + /** + * Copy the blobs data starting from the given byte offset into the range, copying + * a total of size elements. + * + * @param offset starting location in blob + * @param array destination array + * @param size total number of elements to copy + * @throws IllegalArgumentException array.length < size + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jdouble)] out of the blob. + */ public native final void copyToDoubleArray(long offset, double[] array, int size); + /** + * Writes a boolean value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jboolean)] is out of range + */ public native final void putBool(long offset, boolean x); + /** + * Writes a byte value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jbyte)] is out of range + */ public native final void putInt8(long offset, byte x); + /** + * Writes a short value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jshort)] is out of range + */ public native final void putInt16(long offset, short x); + /** + * Writes a int value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jint)] is out of range + */ public native final void putInt32(long offset, int x); + /** + * Writes a long value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jlong)] is out of range + */ public native final void putInt64(long offset, long x); + /** + * Writes a float value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jfloat)] is out of range + */ public native final void putFloat(long offset, float x); + /** + * Writes a double value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jdouble)] is out of range + */ public native final void putDouble(long offset, double x); + /** + * Writes a string value at an offset. + * + * @param offset location to write value + * @param x value to write + * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jstring)] is out of range + */ public native final void putString(long offset, String x); + /** + * Put a boolean array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jboolean)] out of the blob. + */ public native final void putBoolArray(long offset, boolean[] x); + /** + * Put a byte array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jbyte)] out of the blob. + */ public native final void putInt8Array(long offset, byte[] x); + /** + * Put a short array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jshort)] out of the blob. + */ public native final void putInt16Array(long offset, short[] x); + /** + * Put a int array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jint)] out of the blob. + */ public native final void putInt32Array(long offset, int[] x); + /** + * Put a long array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jlong)] out of the blob. + */ public native final void putInt64Array(long offset, long[] x); + /** + * Put a float array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jfloat)] out of the blob. + */ public native final void putFloatArray(long offset, float[] x); + /** + * Put a double array contiguously at an offset in the blob. + * + * @param offset location to write values + * @param x array to write + * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jdouble)] out of the blob. + */ public native final void putDoubleArray(long offset, double[] x); + /** + * Write another HwBlob into this blob at the specified location. + * + * @param offset location to write value + * @param blob data to write + * @throws IndexOutOfBoundsException if [offset, offset + blob's size] outside of the range of + * this blob. + */ public native final void putBlob(long offset, HwBlob blob); + /** + * @return current handle of HwBlob for reference in a parcelled binder transaction + */ public native final long handle(); + /** + * Convert a primitive to a wrapped array for boolean. + * + * @param array from array + * @return transformed array + */ public static Boolean[] wrapArray(@NonNull boolean[] array) { final int n = array.length; Boolean[] wrappedArray = new Boolean[n]; @@ -85,6 +320,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for long. + * + * @param array from array + * @return transformed array + */ public static Long[] wrapArray(@NonNull long[] array) { final int n = array.length; Long[] wrappedArray = new Long[n]; @@ -94,6 +335,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for byte. + * + * @param array from array + * @return transformed array + */ public static Byte[] wrapArray(@NonNull byte[] array) { final int n = array.length; Byte[] wrappedArray = new Byte[n]; @@ -103,6 +350,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for short. + * + * @param array from array + * @return transformed array + */ public static Short[] wrapArray(@NonNull short[] array) { final int n = array.length; Short[] wrappedArray = new Short[n]; @@ -112,6 +365,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for int. + * + * @param array from array + * @return transformed array + */ public static Integer[] wrapArray(@NonNull int[] array) { final int n = array.length; Integer[] wrappedArray = new Integer[n]; @@ -121,6 +380,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for float. + * + * @param array from array + * @return transformed array + */ public static Float[] wrapArray(@NonNull float[] array) { final int n = array.length; Float[] wrappedArray = new Float[n]; @@ -130,6 +395,12 @@ public class HwBlob { return wrappedArray; } + /** + * Convert a primitive to a wrapped array for double. + * + * @param array from array + * @return transformed array + */ public static Double[] wrapArray(@NonNull double[] array) { final int n = array.length; Double[] wrappedArray = new Double[n]; diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java index 4ba1144742db..0eb62c95ed71 100644 --- a/core/java/android/os/HwParcel.java +++ b/core/java/android/os/HwParcel.java @@ -16,17 +16,32 @@ package android.os; -import java.util.ArrayList; -import java.util.Arrays; +import android.annotation.IntDef; +import android.annotation.SystemApi; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; + /** @hide */ +@SystemApi public class HwParcel { private static final String TAG = "HwParcel"; + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_SUCCESS, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Status {} + + /** + * Success return error for a transaction. Written to parcels + * using writeStatus. + */ public static final int STATUS_SUCCESS = 0; - public static final int STATUS_ERROR = -1; private static final NativeAllocationRegistry sNativeRegistry; @@ -38,6 +53,9 @@ public class HwParcel { mNativeContext); } + /** + * Creates an initialized and empty parcel. + */ public HwParcel() { native_setup(true /* allocate */); @@ -46,25 +64,106 @@ public class HwParcel { mNativeContext); } + /** + * Writes an interface token into the parcel used to verify that + * a transaction has made it to the write type of interface. + * + * @param interfaceName fully qualified name of interface message + * is being sent to. + */ public native final void writeInterfaceToken(String interfaceName); + /** + * Writes a boolean value to the end of the parcel. + * @param val to write + */ public native final void writeBool(boolean val); + /** + * Writes a byte value to the end of the parcel. + * @param val to write + */ public native final void writeInt8(byte val); + /** + * Writes a short value to the end of the parcel. + * @param val to write + */ public native final void writeInt16(short val); + /** + * Writes a int value to the end of the parcel. + * @param val to write + */ public native final void writeInt32(int val); + /** + * Writes a long value to the end of the parcel. + * @param val to write + */ public native final void writeInt64(long val); + /** + * Writes a float value to the end of the parcel. + * @param val to write + */ public native final void writeFloat(float val); + /** + * Writes a double value to the end of the parcel. + * @param val to write + */ public native final void writeDouble(double val); + /** + * Writes a String value to the end of the parcel. + * + * Note, this will be converted to UTF-8 when it is written. + * + * @param val to write + */ public native final void writeString(String val); + /** + * Writes an array of boolean values to the end of the parcel. + * @param val to write + */ private native final void writeBoolVector(boolean[] val); + /** + * Writes an array of byte values to the end of the parcel. + * @param val to write + */ private native final void writeInt8Vector(byte[] val); + /** + * Writes an array of short values to the end of the parcel. + * @param val to write + */ private native final void writeInt16Vector(short[] val); + /** + * Writes an array of int values to the end of the parcel. + * @param val to write + */ private native final void writeInt32Vector(int[] val); + /** + * Writes an array of long values to the end of the parcel. + * @param val to write + */ private native final void writeInt64Vector(long[] val); + /** + * Writes an array of float values to the end of the parcel. + * @param val to write + */ private native final void writeFloatVector(float[] val); + /** + * Writes an array of double values to the end of the parcel. + * @param val to write + */ private native final void writeDoubleVector(double[] val); + /** + * Writes an array of String values to the end of the parcel. + * + * Note, these will be converted to UTF-8 as they are written. + * + * @param val to write + */ private native final void writeStringVector(String[] val); + /** + * Helper method to write a list of Booleans to val. + * @param val list to write + */ public final void writeBoolVector(ArrayList<Boolean> val) { final int n = val.size(); boolean[] array = new boolean[n]; @@ -75,6 +174,10 @@ public class HwParcel { writeBoolVector(array); } + /** + * Helper method to write a list of Booleans to the end of the parcel. + * @param val list to write + */ public final void writeInt8Vector(ArrayList<Byte> val) { final int n = val.size(); byte[] array = new byte[n]; @@ -85,6 +188,10 @@ public class HwParcel { writeInt8Vector(array); } + /** + * Helper method to write a list of Shorts to the end of the parcel. + * @param val list to write + */ public final void writeInt16Vector(ArrayList<Short> val) { final int n = val.size(); short[] array = new short[n]; @@ -95,6 +202,10 @@ public class HwParcel { writeInt16Vector(array); } + /** + * Helper method to write a list of Integers to the end of the parcel. + * @param val list to write + */ public final void writeInt32Vector(ArrayList<Integer> val) { final int n = val.size(); int[] array = new int[n]; @@ -105,6 +216,10 @@ public class HwParcel { writeInt32Vector(array); } + /** + * Helper method to write a list of Longs to the end of the parcel. + * @param val list to write + */ public final void writeInt64Vector(ArrayList<Long> val) { final int n = val.size(); long[] array = new long[n]; @@ -115,6 +230,10 @@ public class HwParcel { writeInt64Vector(array); } + /** + * Helper method to write a list of Floats to the end of the parcel. + * @param val list to write + */ public final void writeFloatVector(ArrayList<Float> val) { final int n = val.size(); float[] array = new float[n]; @@ -125,6 +244,10 @@ public class HwParcel { writeFloatVector(array); } + /** + * Helper method to write a list of Doubles to the end of the parcel. + * @param val list to write + */ public final void writeDoubleVector(ArrayList<Double> val) { final int n = val.size(); double[] array = new double[n]; @@ -135,93 +258,272 @@ public class HwParcel { writeDoubleVector(array); } + /** + * Helper method to write a list of Strings to the end of the parcel. + * @param val list to write + */ public final void writeStringVector(ArrayList<String> val) { writeStringVector(val.toArray(new String[val.size()])); } + /** + * Write a hwbinder object to the end of the parcel. + * @param binder value to write + */ public native final void writeStrongBinder(IHwBinder binder); + /** + * Checks to make sure that the interface name matches the name written by the parcel + * sender by writeInterfaceToken + * + * @throws SecurityException interface doesn't match + */ public native final void enforceInterface(String interfaceName); + + /** + * Reads a boolean value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final boolean readBool(); + /** + * Reads a byte value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final byte readInt8(); + /** + * Reads a short value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final short readInt16(); + /** + * Reads a int value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final int readInt32(); + /** + * Reads a long value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final long readInt64(); + /** + * Reads a float value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final float readFloat(); + /** + * Reads a double value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final double readDouble(); + /** + * Reads a String value from the current location in the parcel. + * @return value parsed from the parcel + * @throws IllegalArgumentException if the parcel has no more data + */ public native final String readString(); + /** + * Reads an array of boolean values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final boolean[] readBoolVectorAsArray(); + /** + * Reads an array of byte values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final byte[] readInt8VectorAsArray(); + /** + * Reads an array of short values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final short[] readInt16VectorAsArray(); + /** + * Reads an array of int values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final int[] readInt32VectorAsArray(); + /** + * Reads an array of long values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final long[] readInt64VectorAsArray(); + /** + * Reads an array of float values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final float[] readFloatVectorAsArray(); + /** + * Reads an array of double values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final double[] readDoubleVectorAsArray(); + /** + * Reads an array of String values from the parcel. + * @return array of parsed values + * @throws IllegalArgumentException if the parcel has no more data + */ private native final String[] readStringVectorAsArray(); + /** + * Convenience method to read a Boolean vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList<Boolean> readBoolVector() { Boolean[] array = HwBlob.wrapArray(readBoolVectorAsArray()); return new ArrayList<Boolean>(Arrays.asList(array)); } + /** + * Convenience method to read a Byte vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList<Byte> readInt8Vector() { Byte[] array = HwBlob.wrapArray(readInt8VectorAsArray()); return new ArrayList<Byte>(Arrays.asList(array)); } + /** + * Convenience method to read a Short vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList<Short> readInt16Vector() { Short[] array = HwBlob.wrapArray(readInt16VectorAsArray()); return new ArrayList<Short>(Arrays.asList(array)); } + /** + * Convenience method to read a Integer vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList<Integer> readInt32Vector() { Integer[] array = HwBlob.wrapArray(readInt32VectorAsArray()); return new ArrayList<Integer>(Arrays.asList(array)); } + /** + * Convenience method to read a Long vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList<Long> readInt64Vector() { Long[] array = HwBlob.wrapArray(readInt64VectorAsArray()); return new ArrayList<Long>(Arrays.asList(array)); } + /** + * Convenience method to read a Float vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList<Float> readFloatVector() { Float[] array = HwBlob.wrapArray(readFloatVectorAsArray()); return new ArrayList<Float>(Arrays.asList(array)); } + /** + * Convenience method to read a Double vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList<Double> readDoubleVector() { Double[] array = HwBlob.wrapArray(readDoubleVectorAsArray()); return new ArrayList<Double>(Arrays.asList(array)); } + /** + * Convenience method to read a String vector as an ArrayList. + * @return array of parsed values. + * @throws IllegalArgumentException if the parcel has no more data + */ public final ArrayList<String> readStringVector() { return new ArrayList<String>(Arrays.asList(readStringVectorAsArray())); } + /** + * Reads a strong binder value from the parcel. + * @return binder object read from parcel or null if no binder can be read + * @throws IllegalArgumentException if the parcel has no more data + */ public native final IHwBinder readStrongBinder(); - // Handle is stored as part of the blob. + /** + * Read opaque segment of data as a blob. + * @return blob of size expectedSize + * @throws IllegalArgumentException if the parcel has no more data + */ public native final HwBlob readBuffer(long expectedSize); + /** + * Read a buffer written using scatter gather. + * + * @param expectedSize size that buffer should be + * @param parentHandle handle from which to read the embedded buffer + * @param offset offset into parent + * @param nullable whether or not to allow for a null return + * @return blob of data with size expectedSize + * @throws NoSuchElementException if an embedded buffer is not available to read + * @throws IllegalArgumentException if expectedSize < 0 + * @throws NullPointerException if the transaction specified the blob to be null + * but nullable is false + */ public native final HwBlob readEmbeddedBuffer( long expectedSize, long parentHandle, long offset, boolean nullable); + /** + * Write a buffer into the transaction. + * @param blob blob to write into the parcel. + */ public native final void writeBuffer(HwBlob blob); - + /** + * Write a status value into the blob. + * @param status value to write + */ public native final void writeStatus(int status); + /** + * @throws IllegalArgumentException if a success vaue cannot be read + * @throws RemoteException if success value indicates a transaction error + */ public native final void verifySuccess(); + /** + * Should be called to reduce memory pressure when this object no longer needs + * to be written to. + */ public native final void releaseTemporaryStorage(); + /** + * Should be called when object is no longer needed to reduce possible memory + * pressure if the Java GC does not get to this object in time. + */ public native final void release(); + /** + * Sends the parcel to the specified destination. + */ public native final void send(); // Returns address of the "freeFunction". diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java index 619f4dc631d5..ce9f6c1654c2 100644 --- a/core/java/android/os/IHwBinder.java +++ b/core/java/android/os/IHwBinder.java @@ -16,26 +16,47 @@ package android.os; +import android.annotation.SystemApi; + /** @hide */ +@SystemApi public interface IHwBinder { // These MUST match their corresponding libhwbinder/IBinder.h definition !!! + /** @hide */ public static final int FIRST_CALL_TRANSACTION = 1; + /** @hide */ public static final int FLAG_ONEWAY = 1; + /** @hide */ public void transact( int code, HwParcel request, HwParcel reply, int flags) throws RemoteException; + /** @hide */ public IHwInterface queryLocalInterface(String descriptor); /** * Interface for receiving a callback when the process hosting a service * has gone away. */ + @SystemApi public interface DeathRecipient { + /** + * Callback for a registered process dying. + */ + @SystemApi public void serviceDied(long cookie); } + /** + * Notifies the death recipient with the cookie when the process containing + * this binder dies. + */ + @SystemApi public boolean linkToDeath(DeathRecipient recipient, long cookie); + /** + * Unregisters the death recipient from this binder. + */ + @SystemApi public boolean unlinkToDeath(DeathRecipient recipient); } diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java index 7c5ac6f44a49..a2f59a9abb81 100644 --- a/core/java/android/os/IHwInterface.java +++ b/core/java/android/os/IHwInterface.java @@ -16,7 +16,13 @@ package android.os; +import android.annotation.SystemApi; /** @hide */ +@SystemApi public interface IHwInterface { + /** + * Returns the binder object that corresponds to an interface. + */ + @SystemApi public IHwBinder asBinder(); } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 75f7c1f58ab9..1681f11fa526 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -63,11 +63,6 @@ interface IPowerManager // --- deprecated --- boolean isScreenBrightnessBoosted(); - // temporarily overrides the screen brightness settings to allow the user to - // see the effect of a settings change without applying it immediately - void setTemporaryScreenBrightnessSettingOverride(int brightness); - void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj); - // sets the attention light (used by phone app only) void setAttentionLight(boolean on, int color); } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 61172e11fec2..3d17ffb7329f 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1001,24 +1001,6 @@ public final class PowerManager { return false; } - /** - * Sets the brightness of the backlights (screen, keyboard, button). - * <p> - * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. - * </p> - * - * @param brightness The brightness value from 0 to 255. - * - * @hide Requires signature permission. - */ - public void setBacklightBrightness(int brightness) { - try { - mService.setTemporaryScreenBrightnessSettingOverride(brightness); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Returns true if the specified wake lock level is supported. * diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index daf6bd571932..baa90e75d589 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9335,13 +9335,52 @@ public final class Settings { public static final String RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS = "recommended_network_evaluator_cache_expiry_ms"; - /** + /** * Settings to allow BLE scans to be enabled even when Bluetooth is turned off for * connectivity. * @hide */ - public static final String BLE_SCAN_ALWAYS_AVAILABLE = - "ble_scan_always_enabled"; + public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled"; + + /** + * The length in milliseconds of a BLE scan window in a low-power scan mode. + * @hide + */ + public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms"; + + /** + * The length in milliseconds of a BLE scan window in a balanced scan mode. + * @hide + */ + public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms"; + + /** + * The length in milliseconds of a BLE scan window in a low-latency scan mode. + * @hide + */ + public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS = + "ble_scan_low_latency_window_ms"; + + /** + * The length in milliseconds of a BLE scan interval in a low-power scan mode. + * @hide + */ + public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS = + "ble_scan_low_power_interval_ms"; + + /** + * The length in milliseconds of a BLE scan interval in a balanced scan mode. + * @hide + */ + public static final String BLE_SCAN_BALANCED_INTERVAL_MS = + "ble_scan_balanced_interval_ms"; + + /** + * The length in milliseconds of a BLE scan interval in a low-latency scan mode. + * @hide + */ + public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS = + "ble_scan_low_latency_interval_ms"; /** * Used to save the Wifi_ON state prior to tethering. diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index ce38ebb9bea7..6fa5312be5cc 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -347,7 +347,14 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback TextLine line = TextLine.obtain(); line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); - fm.width = (int) Math.ceil(line.metrics(fm)); + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + // Reaching here means there is only one paragraph. + MeasuredParagraph mp = mt.getMeasuredParagraph(0); + fm.width = (int) Math.ceil(mp.getWidth(0, mp.getTextLength())); + } else { + fm.width = (int) Math.ceil(line.metrics(fm)); + } TextLine.recycle(line); return fm; diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index c7d4a4ac69aa..45fbf6f58209 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -176,6 +176,15 @@ public class MeasuredParagraph { } /** + * Returns the length of the paragraph. + * + * This is always available. + */ + public int getTextLength() { + return mTextLength; + } + + /** * Returns the characters to be measured. * * This is always available. @@ -212,7 +221,7 @@ public class MeasuredParagraph { /** * Returns the whole text width. * - * This is available only if the MeasureText is computed with computeForMeasurement. + * This is available only if the MeasuredParagraph is computed with buildForMeasurement. * Returns 0 in other cases. */ public @FloatRange(from = 0.0f) float getWholeWidth() { @@ -222,7 +231,7 @@ public class MeasuredParagraph { /** * Returns the individual character's width. * - * This is available only if the MeasureText is computed with computeForMeasurement. + * This is available only if the MeasuredParagraph is computed with buildForMeasurement. * Returns empty array in other cases. */ public @NonNull FloatArray getWidths() { @@ -234,7 +243,7 @@ public class MeasuredParagraph { * * If the input text is not a spanned string, this has one value that is the length of the text. * - * This is available only if the MeasureText is computed with computeForStaticLayout. + * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. * Returns empty array in other cases. */ public @NonNull IntArray getSpanEndCache() { @@ -246,7 +255,7 @@ public class MeasuredParagraph { * * This array holds the repeat of top, bottom, ascent, descent of font metrics value. * - * This is available only if the MeasureText is computed with computeForStaticLayout. + * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. * Returns empty array in other cases. */ public @NonNull IntArray getFontMetrics() { @@ -256,7 +265,7 @@ public class MeasuredParagraph { /** * Returns the native ptr of the MeasuredParagraph. * - * This is available only if the MeasureText is computed with computeForStaticLayout. + * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. * Returns 0 in other cases. */ public /* Maybe Zero */ long getNativePtr() { @@ -264,6 +273,30 @@ public class MeasuredParagraph { } /** + * Returns the width of the given range. + * + * This is not available if the MeasuredParagraph is computed with buildForBidi. + * Returns 0 if the MeasuredParagraph is computed with buildForBidi. + * + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + */ + public float getWidth(int start, int end) { + if (mNativePtr == 0) { + // We have result in Java. + final float[] widths = mWidths.getRawArray(); + float r = 0.0f; + for (int i = start; i < end; ++i) { + r += widths[i]; + } + return r; + } else { + // We have result in native. + return nGetWidth(mNativePtr, start, end); + } + } + + /** * Generates new MeasuredParagraph for Bidi computation. * * If recycle is null, this returns new instance. If recycle is not null, this fills computed @@ -357,6 +390,7 @@ public class MeasuredParagraph { @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, boolean computeHyphenation, + boolean computeLayout, @Nullable MeasuredParagraph recycle) { final MeasuredParagraph mt = recycle == null ? obtain() : recycle; mt.resetAndAnalyzeBidi(text, start, end, textDir); @@ -367,7 +401,7 @@ public class MeasuredParagraph { try { mt.bindNativeObject( nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer, - computeHyphenation)); + computeHyphenation, computeLayout)); } finally { nFreeBuilder(nativeBuilderPtr); } @@ -397,7 +431,7 @@ public class MeasuredParagraph { } } mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer, - computeHyphenation)); + computeHyphenation, computeLayout)); } finally { nFreeBuilder(nativeBuilderPtr); } @@ -595,7 +629,7 @@ public class MeasuredParagraph { * * If forward=false is passed, returns the minimum index from the end instead. * - * This only works if the MeasuredParagraph is computed with computeForMeasurement. + * This only works if the MeasuredParagraph is computed with buildForMeasurement. * Undefined behavior in other case. */ @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { @@ -626,7 +660,7 @@ public class MeasuredParagraph { /** * Returns the length of the substring. * - * This only works if the MeasuredParagraph is computed with computeForMeasurement. + * This only works if the MeasuredParagraph is computed with buildForMeasurement. * Undefined behavior in other case. */ @FloatRange(from = 0.0f) float measure(int start, int limit) { @@ -672,10 +706,16 @@ public class MeasuredParagraph { private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr, @NonNull char[] text, - boolean computeHyphenation); + boolean computeHyphenation, + boolean computeLayout); private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); @CriticalNative + private static native float nGetWidth(/* Non Zero */ long nativePtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end); + + @CriticalNative private static native /* Non Zero */ long nGetReleaseFunc(); } diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index b96b48954ee5..ff23395d92a5 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -155,6 +155,11 @@ public class MeasuredText implements Spanned { * @return the measured text. */ public @NonNull MeasuredText build() { + return build(true /* build full layout result */); + } + + /** @hide */ + public @NonNull MeasuredText build(boolean computeLayout) { final boolean needHyphenation = mBreakStrategy != Layout.BREAK_STRATEGY_SIMPLE && mHyphenationFrequency != Layout.HYPHENATION_FREQUENCY_NONE; @@ -175,7 +180,7 @@ public class MeasuredText implements Spanned { paragraphEnds.add(paraEnd); measuredTexts.add(MeasuredParagraph.buildForStaticLayout( mPaint, mText, paraStart, paraEnd, mTextDir, needHyphenation, - null /* no recycle */)); + computeLayout, null /* no recycle */)); } return new MeasuredText(mText, mStart, mEnd, mPaint, mTextDir, mBreakStrategy, @@ -198,7 +203,8 @@ public class MeasuredText implements Spanned { mText = text; mStart = start; mEnd = end; - mPaint = paint; + // Copy the paint so that we can keep the reference of typeface in native layout result. + mPaint = new TextPaint(paint); mMeasuredParagraphs = measuredTexts; mParagraphBreakPoints = paragraphBreakPoints; mTextDir = textDir; @@ -283,6 +289,50 @@ public class MeasuredText implements Spanned { return mHyphenationFrequency; } + /** + * Returns true if the given TextPaint gives the same result of text layout for this text. + * @hide + */ + public boolean canUseMeasuredResult(@NonNull TextPaint paint) { + return mPaint.getTextSize() == paint.getTextSize() + && mPaint.getTextSkewX() == paint.getTextSkewX() + && mPaint.getTextScaleX() == paint.getTextScaleX() + && mPaint.getLetterSpacing() == paint.getLetterSpacing() + && mPaint.getWordSpacing() == paint.getWordSpacing() + && mPaint.getFlags() == paint.getFlags() // Maybe not all flag affects text layout. + && mPaint.getTextLocales() == paint.getTextLocales() // need to be equals? + && mPaint.getFontVariationSettings() == paint.getFontVariationSettings() + && mPaint.getTypeface() == paint.getTypeface() + && TextUtils.equals(mPaint.getFontFeatureSettings(), paint.getFontFeatureSettings()); + } + + /** @hide */ + public int findParaIndex(@IntRange(from = 0) int pos) { + // TODO: Maybe good to remove paragraph concept from MeasuredText and add substring layout + // support to StaticLayout. + for (int i = 0; i < mParagraphBreakPoints.length; ++i) { + if (pos < mParagraphBreakPoints[i]) { + return i; + } + } + throw new IndexOutOfBoundsException( + "pos must be less than " + mParagraphBreakPoints[mParagraphBreakPoints.length - 1] + + ", gave " + pos); + } + + /** @hide */ + public float getWidth(@IntRange(from = 0) int start, @IntRange(from = 0) int end) { + final int paraIndex = findParaIndex(start); + final int paraStart = getParagraphStart(paraIndex); + final int paraEnd = getParagraphEnd(paraIndex); + if (start < paraStart || paraEnd < end) { + throw new RuntimeException("Cannot measured across the paragraph:" + + "para: (" + paraStart + ", " + paraEnd + "), " + + "request: (" + start + ", " + end + ")"); + } + return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart); + } + /////////////////////////////////////////////////////////////////////////////////////////////// // Spanned overrides // diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 80d7ef58e813..e62f4216f33a 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -678,7 +678,7 @@ public class StaticLayout extends Layout { .setTextDirection(textDir) .setBreakStrategy(b.mBreakStrategy) .setHyphenationFrequency(b.mHyphenationFrequency) - .build(); + .build(false /* full layout is not necessary for line breaking */); spanned = (source instanceof Spanned) ? (Spanned) source : null; } else { final CharSequence original = measured.getText(); diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 86cc0141b0a4..55367dcce47e 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -60,6 +60,7 @@ public class TextLine { private char[] mChars; private boolean mCharsValid; private Spanned mSpanned; + private MeasuredText mMeasured; // Additional width of whitespace for justification. This value is per whitespace, thus // the line width will increase by mAddedWidth x (number of stretchable whitespaces). @@ -118,6 +119,7 @@ public class TextLine { tl.mSpanned = null; tl.mTabs = null; tl.mChars = null; + tl.mMeasured = null; tl.mMetricAffectingSpanSpanSet.recycle(); tl.mCharacterStyleSpanSet.recycle(); @@ -168,6 +170,14 @@ public class TextLine { hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0; } + mMeasured = null; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + if (mt.canUseMeasuredResult(paint)) { + mMeasured = mt; + } + } + mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT; if (mCharsValid) { @@ -736,8 +746,13 @@ public class TextLine { return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset); } else { final int delta = mStart; - return wp.getRunAdvance(mText, delta + start, delta + end, - delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); + if (mMeasured == null) { + // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text. + return wp.getRunAdvance(mText, delta + start, delta + end, + delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); + } else { + return mMeasured.getWidth(start + delta, end + delta); + } } } diff --git a/core/java/android/util/MutableBoolean.java b/core/java/android/util/MutableBoolean.java index ed837ab6afd3..44e73cc38b8f 100644 --- a/core/java/android/util/MutableBoolean.java +++ b/core/java/android/util/MutableBoolean.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableBoolean { public boolean value; diff --git a/core/java/android/util/MutableByte.java b/core/java/android/util/MutableByte.java index cc6b00a8046f..b9ec25dab2ad 100644 --- a/core/java/android/util/MutableByte.java +++ b/core/java/android/util/MutableByte.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableByte { public byte value; diff --git a/core/java/android/util/MutableChar.java b/core/java/android/util/MutableChar.java index 9a2e2bce5915..9f7a9ae82c32 100644 --- a/core/java/android/util/MutableChar.java +++ b/core/java/android/util/MutableChar.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableChar { public char value; diff --git a/core/java/android/util/MutableDouble.java b/core/java/android/util/MutableDouble.java index bd7329a380ee..56e539bc0f6f 100644 --- a/core/java/android/util/MutableDouble.java +++ b/core/java/android/util/MutableDouble.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableDouble { public double value; diff --git a/core/java/android/util/MutableFloat.java b/core/java/android/util/MutableFloat.java index e6f2d7dc7b30..6d7ad59d2473 100644 --- a/core/java/android/util/MutableFloat.java +++ b/core/java/android/util/MutableFloat.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableFloat { public float value; diff --git a/core/java/android/util/MutableInt.java b/core/java/android/util/MutableInt.java index a3d8606d916a..bb2456601be3 100644 --- a/core/java/android/util/MutableInt.java +++ b/core/java/android/util/MutableInt.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableInt { public int value; diff --git a/core/java/android/util/MutableLong.java b/core/java/android/util/MutableLong.java index 575068ea9364..86e70e1baebf 100644 --- a/core/java/android/util/MutableLong.java +++ b/core/java/android/util/MutableLong.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableLong { public long value; diff --git a/core/java/android/util/MutableShort.java b/core/java/android/util/MutableShort.java index 48fb232b5287..b94ab0732251 100644 --- a/core/java/android/util/MutableShort.java +++ b/core/java/android/util/MutableShort.java @@ -17,7 +17,9 @@ package android.util; /** + * @deprecated This class will be removed from a future version of the Android API. */ +@Deprecated public final class MutableShort { public short value; diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl new file mode 100644 index 000000000000..5607b1134e5b --- /dev/null +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.app.ActivityManager; +import android.view.IRemoteAnimationFinishedCallback; +import android.graphics.GraphicBuffer; + +/** + * Passed to the {@link IRecentsAnimationRunner} in order for the runner to control to let the + * runner control certain aspects of the recents animation, and to notify window manager when the + * animation has completed. + * + * {@hide} + */ +interface IRecentsAnimationController { + + /** + * Takes a screenshot of the task associated with the given {@param taskId}. Only valid for the + * current set of task ids provided to the handler. + */ + ActivityManager.TaskSnapshot screenshotTask(int taskId); + + /** + * Notifies to the system that the animation into Recents should end, and all leashes associated + * with remote animation targets should be relinquished. If {@param moveHomeToTop} is true, then + * the home activity should be moved to the top. Otherwise, the home activity is hidden and the + * user is returned to the app. + */ + void finish(boolean moveHomeToTop); + + /** + * Called by the handler to indicate that the recents animation input consumer should be + * enabled. This is currently used to work around an issue where registering an input consumer + * mid-animation causes the existing motion event chain to be canceled. Instead, the caller + * may register the recents animation input consumer prior to starting the recents animation + * and then enable it mid-animation to start receiving touch events. + */ + void setInputConsumerEnabled(boolean enabled); +} diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl new file mode 100644 index 000000000000..ea6226b3ea69 --- /dev/null +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.view.RemoteAnimationTarget; +import android.view.IRecentsAnimationController; + +/** + * Interface that is used to callback from window manager to the process that runs a recents + * animation to start or cancel it. + * + * {@hide} + */ +oneway interface IRecentsAnimationRunner { + + /** + * Called when the system is ready for the handler to start animating all the visible tasks. + */ + void onAnimationStart(in IRecentsAnimationController controller, + in RemoteAnimationTarget[] apps); + + /** + * Called when the system needs to cancel the current animation. This can be due to the + * wallpaper not drawing in time, or the handler not finishing the animation within a predefined + * amount of time. + */ + void onAnimationCanceled(); +} diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 04fa637b72f2..1d7c1dedc62e 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -26,6 +26,8 @@ import android.util.SparseArray; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import java.util.Objects; + /** * Object used to report movement (mouse, pen, finger, trackball) events. * Motion events may hold either absolute or relative movements and other data, @@ -173,6 +175,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { private static final long NS_PER_MS = 1000000; private static final String LABEL_PREFIX = "AXIS_"; + private static final boolean DEBUG_CONCISE_TOSTRING = false; + /** * An invalid pointer id. * @@ -3236,31 +3240,42 @@ public final class MotionEvent extends InputEvent implements Parcelable { public String toString() { StringBuilder msg = new StringBuilder(); msg.append("MotionEvent { action=").append(actionToString(getAction())); - msg.append(", actionButton=").append(buttonStateToString(getActionButton())); + appendUnless("0", msg, ", actionButton=", buttonStateToString(getActionButton())); final int pointerCount = getPointerCount(); for (int i = 0; i < pointerCount; i++) { - msg.append(", id[").append(i).append("]=").append(getPointerId(i)); - msg.append(", x[").append(i).append("]=").append(getX(i)); - msg.append(", y[").append(i).append("]=").append(getY(i)); - msg.append(", toolType[").append(i).append("]=").append( - toolTypeToString(getToolType(i))); + appendUnless(i, msg, ", id[" + i + "]=", getPointerId(i)); + float x = getX(i); + float y = getY(i); + if (!DEBUG_CONCISE_TOSTRING || x != 0f || y != 0f) { + msg.append(", x[").append(i).append("]=").append(x); + msg.append(", y[").append(i).append("]=").append(y); + } + appendUnless(TOOL_TYPE_SYMBOLIC_NAMES.get(TOOL_TYPE_FINGER), + msg, ", toolType[" + i + "]=", toolTypeToString(getToolType(i))); } - msg.append(", buttonState=").append(MotionEvent.buttonStateToString(getButtonState())); - msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState())); - msg.append(", flags=0x").append(Integer.toHexString(getFlags())); - msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags())); - msg.append(", pointerCount=").append(pointerCount); - msg.append(", historySize=").append(getHistorySize()); + appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState())); + appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState())); + appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags())); + appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags())); + appendUnless(1, msg, ", pointerCount=", pointerCount); + appendUnless(0, msg, ", historySize=", getHistorySize()); msg.append(", eventTime=").append(getEventTime()); - msg.append(", downTime=").append(getDownTime()); - msg.append(", deviceId=").append(getDeviceId()); - msg.append(", source=0x").append(Integer.toHexString(getSource())); + if (!DEBUG_CONCISE_TOSTRING) { + msg.append(", downTime=").append(getDownTime()); + msg.append(", deviceId=").append(getDeviceId()); + msg.append(", source=0x").append(Integer.toHexString(getSource())); + } msg.append(" }"); return msg.toString(); } + private static <T> void appendUnless(T defValue, StringBuilder sb, String key, T value) { + if (DEBUG_CONCISE_TOSTRING && Objects.equals(defValue, value)) return; + sb.append(key).append(value); + } + /** * Returns a string that represents the symbolic name of the specified unmasked action * such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent numeric constant diff --git a/core/java/android/view/RecordingCanvas.java b/core/java/android/view/RecordingCanvas.java index 5088cdc948ef..fbb862be54ef 100644 --- a/core/java/android/view/RecordingCanvas.java +++ b/core/java/android/view/RecordingCanvas.java @@ -34,6 +34,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.TemporaryBuffer; import android.text.GraphicsOperations; +import android.text.MeasuredText; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; @@ -473,7 +474,8 @@ public class RecordingCanvas extends Canvas { } nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount, - x, y, isRtl, paint.getNativeInstance()); + x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */, + 0 /* measured text offset */); } @Override @@ -503,8 +505,20 @@ public class RecordingCanvas extends Canvas { int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + long measuredTextPtr = 0; + int measuredTextOffset = 0; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + int paraIndex = mt.findParaIndex(start); + if (end <= mt.getParagraphEnd(paraIndex)) { + // Only support if the target is in the same paragraph. + measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr(); + measuredTextOffset = start - mt.getParagraphStart(paraIndex); + } + } nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, - 0, contextLen, x, y, isRtl, paint.getNativeInstance()); + 0, contextLen, x, y, isRtl, paint.getNativeInstance(), + measuredTextPtr, measuredTextOffset); TemporaryBuffer.recycle(buf); } } @@ -626,7 +640,8 @@ public class RecordingCanvas extends Canvas { @FastNative private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count, - int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint); + int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint, + long nativeMeasuredText, int measuredTextOffset); @FastNative private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java index f39e618e169d..c28c3894482d 100644 --- a/core/java/android/view/RemoteAnimationTarget.java +++ b/core/java/android/view/RemoteAnimationTarget.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.IntDef; +import android.app.WindowConfiguration; import android.graphics.Point; import android.graphics.Rect; import android.os.Parcel; @@ -98,8 +99,14 @@ public class RemoteAnimationTarget implements Parcelable { */ public final Rect sourceContainerBounds; + /** + * The window configuration for the target. + */ + public final WindowConfiguration windowConfiguration; + public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, - Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds) { + Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds, + WindowConfiguration windowConfig) { this.mode = mode; this.taskId = taskId; this.leash = leash; @@ -108,6 +115,7 @@ public class RemoteAnimationTarget implements Parcelable { this.prefixOrderIndex = prefixOrderIndex; this.position = new Point(position); this.sourceContainerBounds = new Rect(sourceContainerBounds); + this.windowConfiguration = windowConfig; } public RemoteAnimationTarget(Parcel in) { @@ -119,6 +127,7 @@ public class RemoteAnimationTarget implements Parcelable { prefixOrderIndex = in.readInt(); position = in.readParcelable(null); sourceContainerBounds = in.readParcelable(null); + windowConfiguration = in.readParcelable(null); } @Override @@ -136,6 +145,7 @@ public class RemoteAnimationTarget implements Parcelable { dest.writeInt(prefixOrderIndex); dest.writeParcelable(position, 0 /* flags */); dest.writeParcelable(sourceContainerBounds, 0 /* flags */); + dest.writeParcelable(windowConfiguration, 0 /* flags */); } public static final Creator<RemoteAnimationTarget> CREATOR diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a2ecfc469182..b0ea3f167608 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11253,6 +11253,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!isLayoutValid()) { mPrivateFlags |= PFLAG_WANTS_FOCUS; + } else { + clearParentsWantFocus(); } handleFocusGainInternal(direction, previouslyFocusedRect); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 3bb3a4c17b8f..1c5e87197750 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -98,11 +98,13 @@ public interface WindowManager extends ViewManager { int DOCKED_BOTTOM = 4; /** @hide */ - final static String INPUT_CONSUMER_PIP = "pip_input_consumer"; + String INPUT_CONSUMER_PIP = "pip_input_consumer"; /** @hide */ - final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer"; + String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer"; /** @hide */ - final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; + String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; + /** @hide */ + String INPUT_CONSUMER_RECENTS_ANIMATION = "recents_animation_input_consumer"; /** * Not set up for a transition. diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index e5545405728d..eba91763acf1 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -898,4 +899,37 @@ public interface InputConnection { */ boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts); + + /** + * Called by the input method to tell a hint about the locales of text to be committed. + * + * <p>This is just a hint for editor authors (and the system) to choose better options when + * they have to disambiguate languages, like editor authors can do for input methods with + * {@link EditorInfo#hintLocales}.</p> + * + * <p>The language hint provided by this callback should have higher priority than + * {@link InputMethodSubtype#getLanguageTag()}, which cannot be updated dynamically.</p> + * + * <p>Note that in general it is discouraged for input method to specify + * {@link android.text.style.LocaleSpan} when inputting text, mainly because of application + * compatibility concerns.</p> + * <ul> + * <li>When an existing text that already has {@link android.text.style.LocaleSpan} is being + * modified by both the input method and application, there is no reliable and easy way to + * keep track of who modified {@link android.text.style.LocaleSpan}. For instance, if the + * text was updated by JavaScript, it it highly likely that span information is completely + * removed, while some input method attempts to preserve spans if possible.</li> + * <li>There is no clear semantics regarding whether {@link android.text.style.LocaleSpan} + * means a weak (ignorable) hint or a strong hint. This becomes more problematic when + * multiple {@link android.text.style.LocaleSpan} instances are specified to the same + * text region, especially when those spans are conflicting.</li> + * </ul> + * @param languageHint list of languages sorted by the priority and/or probability + */ + default void reportLanguageHint(@NonNull LocaleList languageHint) { + // Intentionally empty. + // + // We need to have *some* default implementation for the source compatibility. + // See Bug 72127682 for details. + } } diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index f671e22b4922..cbe6856bba41 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -16,8 +16,10 @@ package android.view.inputmethod; +import android.annotation.NonNull; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.view.KeyEvent; /** @@ -303,4 +305,13 @@ public class InputConnectionWrapper implements InputConnection { public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return mTarget.commitContent(inputContentInfo, flags, opts); } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override + public void reportLanguageHint(@NonNull LocaleList languageHint) { + mTarget.reportLanguageHint(languageHint); + } } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 6bee58f96f8a..594d24005262 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -19,6 +19,7 @@ package android.widget; import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.annotation.NonNull; +import android.annotation.TestApi; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -30,6 +31,7 @@ import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; import android.os.Debug; import android.os.Handler; +import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; import android.os.StrictMode; @@ -2744,7 +2746,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } private void drawSelector(Canvas canvas) { - if (!mSelectorRect.isEmpty()) { + if (shouldDrawSelector()) { final Drawable selector = mSelector; selector.setBounds(mSelectorRect); selector.draw(canvas); @@ -2752,6 +2754,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * @hide + */ + @TestApi + public final boolean shouldDrawSelector() { + return !mSelectorRect.isEmpty(); + } + + /** * Controls whether the selection highlight drawable should be drawn on top of the item or * behind it. * @@ -6026,6 +6036,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return getTarget().commitContent(inputContentInfo, flags, opts); } + + @Override + public void reportLanguageHint(@NonNull LocaleList languageHint) { + getTarget().reportLanguageHint(languageHint); + } } /** diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 079ba0bbe906..f9a2341fc3e3 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -53,6 +53,8 @@ public final class Zygote { public static final int DISABLE_VERIFIER = 1 << 9; /** Only use oat files located in /system. Otherwise use dex/jar/apk . */ public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10; + /** Do not enfore hidden API access restrictions. */ + public static final int DISABLE_HIDDEN_API_CHECKS = 1 << 11; /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; @@ -156,6 +158,9 @@ public final class Zygote { */ public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { + // SystemServer is always allowed to use hidden APIs. + runtimeFlags |= DISABLE_HIDDEN_API_CHECKS; + VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 28291aefd036..e08caa8e199d 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.os.Looper; import android.os.Message; import android.os.RemoteException; @@ -64,6 +65,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140; private static final int DO_CLOSE_CONNECTION = 150; private static final int DO_COMMIT_CONTENT = 160; + private static final int DO_REPORT_LANGUAGE_HINT = 170; @GuardedBy("mLock") @Nullable @@ -217,6 +219,10 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { callback)); } + public void reportLanguageHint(@NonNull LocaleList languageHint) { + dispatchMessage(obtainMessageO(DO_REPORT_LANGUAGE_HINT, languageHint)); + } + void dispatchMessage(Message msg) { // If we are calling this from the main thread, then we can call // right through. Otherwise, we need to send the message to the @@ -577,6 +583,16 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } return; } + case DO_REPORT_LANGUAGE_HINT: { + final LocaleList languageHint = (LocaleList) msg.obj; + final InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "reportLanguageHint on inactive InputConnection"); + return; + } + ic.reportLanguageHint(languageHint); + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); } diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index c22799179b72..e69a87ffc1dd 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -17,6 +17,7 @@ package com.android.internal.view; import android.os.Bundle; +import android.os.LocaleList; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; @@ -78,4 +79,6 @@ import com.android.internal.view.IInputContextCallback; void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec, IInputContextCallback callback); + + void reportLanguageHint(in LocaleList languageHint); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 5b65bbe1e90e..34be598dd615 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.inputmethodservice.AbstractInputMethodService; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; @@ -620,6 +621,14 @@ public class InputConnectionWrapper implements InputConnection { } @AnyThread + public void reportLanguageHint(@NonNull LocaleList languageHint) { + try { + mIInputContext.reportLanguageHint(languageHint); + } catch (RemoteException e) { + } + } + + @AnyThread private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) { return (mMissingMethods & methodFlag) == methodFlag; } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 47765d9a82e3..102ff95e6d32 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -110,8 +110,8 @@ cc_library_shared { "android_util_AssetManager.cpp", "android_util_Binder.cpp", "android_util_EventLog.cpp", - "android_util_MemoryIntArray.cpp", "android_util_Log.cpp", + "android_util_MemoryIntArray.cpp", "android_util_PathParser.cpp", "android_util_Process.cpp", "android_util_StringBlock.cpp", @@ -189,6 +189,7 @@ cc_library_shared { "android_backup_FileBackupHelperBase.cpp", "android_backup_BackupHelperDispatcher.cpp", "android_app_backup_FullBackup.cpp", + "android_content_res_ApkAssets.cpp", "android_content_res_ObbScanner.cpp", "android_content_res_Configuration.cpp", "android_animation_PropertyValuesHolder.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index aa9a82415f97..e3b5c8f3136a 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -122,6 +122,7 @@ extern int register_android_util_MemoryIntArray(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_content_StringBlock(JNIEnv* env); extern int register_android_content_XmlBlock(JNIEnv* env); +extern int register_android_content_res_ApkAssets(JNIEnv* env); extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_CanvasProperty(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); @@ -1340,6 +1341,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_AssetManager), REG_JNI(register_android_content_StringBlock), REG_JNI(register_android_content_XmlBlock), + REG_JNI(register_android_content_res_ApkAssets), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), REG_JNI(register_android_text_MeasuredParagraph), diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index dd3e6f02e9fe..c50026ea570e 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -28,7 +28,7 @@ #include <nativehelper/ScopedUtfChars.h> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_util_AssetManager.h> -#include <androidfw/AssetManager.h> +#include <androidfw/AssetManager2.h> #include "Utils.h" #include "FontUtils.h" @@ -224,7 +224,8 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b NPE_CHECK_RETURN_ZERO(env, jpath); NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); - AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr); + + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(env, jassetMgr); if (NULL == mgr) { builder->axes.clear(); return false; @@ -236,27 +237,33 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b return false; } - Asset* asset; - if (isAsset) { - asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - } else { - asset = cookie ? mgr->openNonAsset(static_cast<int32_t>(cookie), str.c_str(), - Asset::ACCESS_BUFFER) : mgr->openNonAsset(str.c_str(), Asset::ACCESS_BUFFER); + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + if (isAsset) { + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + } else if (cookie > 0) { + // Valid java cookies are 1-based, but AssetManager cookies are 0-based. + asset = locked_mgr->OpenNonAsset(str.c_str(), static_cast<ApkAssetsCookie>(cookie - 1), + Asset::ACCESS_BUFFER); + } else { + asset = locked_mgr->OpenNonAsset(str.c_str(), Asset::ACCESS_BUFFER); + } } - if (NULL == asset) { + if (nullptr == asset) { builder->axes.clear(); return false; } const void* buf = asset->getBuffer(false); if (NULL == buf) { - delete asset; builder->axes.clear(); return false; } - sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset)); + sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, + asset.release())); return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic); } diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 1e7f5f5c1022..49cbb545b019 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -307,7 +307,8 @@ namespace PaintGlue { static void getTextPath(JNIEnv* env, Paint* paint, const Typeface* typeface, const jchar* text, jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) { minikin::Layout layout = MinikinUtils::doLayout( - paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count); + paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count, + nullptr, 0); size_t nGlyphs = layout.nGlyphs(); uint16_t* glyphs = new uint16_t[nGlyphs]; SkPoint* pos = new SkPoint[nGlyphs]; @@ -349,7 +350,8 @@ namespace PaintGlue { SkIRect ir; minikin::Layout layout = MinikinUtils::doLayout(&paint, - static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count); + static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count, nullptr, + 0); minikin::MinikinRect rect; layout.getBounds(&rect); r.fLeft = rect.mLeft; @@ -465,7 +467,7 @@ namespace PaintGlue { } minikin::Layout layout = MinikinUtils::doLayout(paint, static_cast<minikin::Bidi>(bidiFlags), typeface, str.get(), 0, str.size(), - str.size()); + str.size(), nullptr, 0); size_t nGlyphs = countNonSpaceGlyphs(layout); if (nGlyphs != 1 && nChars > 1) { // multiple-character input, and was not a ligature @@ -485,7 +487,8 @@ namespace PaintGlue { // U+1F1FF (REGIONAL INDICATOR SYMBOL LETTER Z) is \uD83C\uDDFF in UTF16. static const jchar ZZ_FLAG_STR[] = { 0xD83C, 0xDDFF, 0xD83C, 0xDDFF }; minikin::Layout zzLayout = MinikinUtils::doLayout(paint, - static_cast<minikin::Bidi>(bidiFlags), typeface, ZZ_FLAG_STR, 0, 4, 4); + static_cast<minikin::Bidi>(bidiFlags), typeface, ZZ_FLAG_STR, 0, 4, 4, + nullptr, 0); if (zzLayout.nGlyphs() != 1 || layoutContainsNotdef(zzLayout)) { // The font collection doesn't have a glyph for unknown flag. Just return true. return true; diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index 09e37e1a3de6..49a24a30f77e 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -361,7 +361,7 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName code->sdkVersion = sdkVersion; code->javaAssetManager = env->NewGlobalRef(jAssetMgr); - code->assetManager = assetManagerForJavaObject(env, jAssetMgr); + code->assetManager = NdkAssetManagerForJavaObject(env, jAssetMgr); if (obbDir != NULL) { dirStr = env->GetStringUTFChars(obbDir, NULL); diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp new file mode 100644 index 000000000000..c0f151b71c93 --- /dev/null +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "android-base/unique_fd.h" +#include "androidfw/ApkAssets.h" +#include "utils/misc.h" + +#include "core_jni_helpers.h" +#include "jni.h" +#include "nativehelper/ScopedUtfChars.h" + +using ::android::base::unique_fd; + +namespace android { + +static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system, + jboolean force_shared_lib, jboolean overlay) { + ScopedUtfChars path(env, java_path); + if (path.c_str() == nullptr) { + return 0; + } + + std::unique_ptr<const ApkAssets> apk_assets; + if (overlay) { + apk_assets = ApkAssets::LoadOverlay(path.c_str(), system); + } else if (force_shared_lib) { + apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system); + } else { + apk_assets = ApkAssets::Load(path.c_str(), system); + } + + if (apk_assets == nullptr) { + std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str()); + jniThrowException(env, "java/io/IOException", error_msg.c_str()); + return 0; + } + return reinterpret_cast<jlong>(apk_assets.release()); +} + +static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor, + jstring friendly_name, jboolean system, jboolean force_shared_lib) { + ScopedUtfChars friendly_name_utf8(env, friendly_name); + if (friendly_name_utf8.c_str() == nullptr) { + return 0; + } + + int fd = jniGetFDFromFileDescriptor(env, file_descriptor); + if (fd < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); + return 0; + } + + unique_fd dup_fd(::dup(fd)); + if (dup_fd < 0) { + jniThrowIOException(env, errno); + return 0; + } + + std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), + friendly_name_utf8.c_str(), + system, force_shared_lib); + if (apk_assets == nullptr) { + std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d", + friendly_name_utf8.c_str(), dup_fd.get()); + jniThrowException(env, "java/io/IOException", error_msg.c_str()); + return 0; + } + return reinterpret_cast<jlong>(apk_assets.release()); +} + +static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + delete reinterpret_cast<ApkAssets*>(ptr); +} + +static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + return env->NewStringUTF(apk_assets->GetPath().c_str()); +} + +static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool()); +} + +static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + (void)apk_assets; + return JNI_TRUE; +} + +static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) { + ScopedUtfChars path_utf8(env, file_name); + if (path_utf8.c_str() == nullptr) { + return 0; + } + + const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr); + std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(), + Asset::AccessMode::ACCESS_RANDOM); + if (asset == nullptr) { + jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str()); + return 0; + } + + // DynamicRefTable is only needed when looking up resource references. Opening an XML file + // directly from an ApkAssets has no notion of proper resource references. + std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/); + status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true); + asset.reset(); + + if (err != NO_ERROR) { + jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); + return 0; + } + return reinterpret_cast<jlong>(xml_tree.release()); +} + +// JNI registration. +static const JNINativeMethod gApkAssetsMethods[] = { + {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad}, + {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J", + (void*)NativeLoadFromFd}, + {"nativeDestroy", "(J)V", (void*)NativeDestroy}, + {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, + {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, + {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, + {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, +}; + +int register_android_content_res_ApkAssets(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods, + arraysize(gApkAssetsMethods)); +} + +} // namespace android diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index f08b89c8c988..6b961f54cf88 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -30,6 +30,10 @@ #include "SkRegion.h" #include "SkVertices.h" +namespace minikin { +class MeasuredText; +} // namespace minikin + namespace android { namespace CanvasJNI { @@ -480,7 +484,7 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray t const Typeface* typeface = paint->getAndroidTypeface(); jchar* jchars = env->GetCharArrayElements(text, NULL); get_canvas(canvasHandle)->drawText(jchars + index, 0, count, count, x, y, - static_cast<minikin::Bidi>(bidiFlags), *paint, typeface); + static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr, 0); env->ReleaseCharArrayElements(text, jchars, JNI_ABORT); } @@ -492,20 +496,22 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring tex const int count = end - start; const jchar* jchars = env->GetStringChars(text, NULL); get_canvas(canvasHandle)->drawText(jchars + start, 0, count, count, x, y, - static_cast<minikin::Bidi>(bidiFlags), *paint, typeface); + static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr, 0); env->ReleaseStringChars(text, jchars); } static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, jint contextIndex, jint contextCount, jfloat x, jfloat y, - jboolean isRtl, jlong paintHandle) { + jboolean isRtl, jlong paintHandle, jlong mtHandle, jint mtOffset) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); + minikin::MeasuredText* mt = reinterpret_cast<minikin::MeasuredText*>(mtHandle); const Typeface* typeface = paint->getAndroidTypeface(); const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; jchar* jchars = env->GetCharArrayElements(text, NULL); get_canvas(canvasHandle)->drawText(jchars + contextIndex, index - contextIndex, count, - contextCount, x, y, bidiFlags, *paint, typeface); + contextCount, x, y, bidiFlags, *paint, typeface, mt, + mtOffset); env->ReleaseCharArrayElements(text, jchars, JNI_ABORT); } @@ -520,7 +526,7 @@ static void drawTextRunString(JNIEnv* env, jobject obj, jlong canvasHandle, jstr jint contextCount = contextEnd - contextStart; const jchar* jchars = env->GetStringChars(text, NULL); get_canvas(canvasHandle)->drawText(jchars + contextStart, start - contextStart, count, - contextCount, x, y, bidiFlags, *paint, typeface); + contextCount, x, y, bidiFlags, *paint, typeface, nullptr, 0); env->ReleaseStringChars(text, jchars); } @@ -628,7 +634,7 @@ static const JNINativeMethod gDrawMethods[] = { {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray}, {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars}, {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString}, - {"nDrawTextRun","(J[CIIIIFFZJ)V", (void*) CanvasJNI::drawTextRunChars}, + {"nDrawTextRun","(J[CIIIIFFZJJI)V", (void*) CanvasJNI::drawTextRunChars}, {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString}, {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars}, {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString}, diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 2be947158fc5..376a79717677 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -608,9 +608,10 @@ android_media_AudioSystem_getOutputLatency(JNIEnv *env, jobject clazz, jint stre } static jint -android_media_AudioSystem_setLowRamDevice(JNIEnv *env, jobject clazz, jboolean isLowRamDevice) +android_media_AudioSystem_setLowRamDevice( + JNIEnv *env, jobject clazz, jboolean isLowRamDevice, jlong totalMemory) { - return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice); + return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice, (int64_t) totalMemory); } static jint @@ -1819,7 +1820,7 @@ static const JNINativeMethod gMethods[] = { {"getPrimaryOutputSamplingRate", "()I", (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate}, {"getPrimaryOutputFrameCount", "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount}, {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency}, - {"setLowRamDevice", "(Z)I", (void *)android_media_AudioSystem_setLowRamDevice}, + {"setLowRamDevice", "(ZJ)I", (void *)android_media_AudioSystem_setLowRamDevice}, {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger}, {"listAudioPorts", "(Ljava/util/ArrayList;[I)I", (void *)android_media_AudioSystem_listAudioPorts}, diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp index 58c05b44ed5c..f0e449d52998 100644 --- a/core/jni/android_text_MeasuredParagraph.cpp +++ b/core/jni/android_text_MeasuredParagraph.cpp @@ -85,12 +85,14 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong // Regular JNI static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr, - jcharArray javaText, jboolean computeHyphenation) { + jcharArray javaText, jboolean computeHyphenation, + jboolean computeLayout) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); // Pass the ownership to Java. - return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation).release()); + return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation, + computeLayout).release()); } // Regular JNI @@ -99,6 +101,16 @@ static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) { } // CriticalNative +static jfloat nGetWidth(jlong ptr, jint start, jint end) { + minikin::MeasuredText* mt = toMeasuredParagraph(ptr); + float r = 0.0f; + for (int i = start; i < end; ++i) { + r += mt->widths[i]; + } + return r; +} + +// CriticalNative static jlong nGetReleaseFunc() { return toJLong(&releaseMeasuredParagraph); } @@ -108,10 +120,11 @@ static const JNINativeMethod gMethods[] = { {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, - {"nBuildNativeMeasuredParagraph", "(J[CZ)J", (void*) nBuildNativeMeasuredParagraph}, + {"nBuildNativeMeasuredParagraph", "(J[CZZ)J", (void*) nBuildNativeMeasuredParagraph}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, // MeasuredParagraph native functions. + {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives }; diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index c6828c4f60de..403937be7088 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -1,1846 +1,1437 @@ -/* //device/libs/android_runtime/android_util_AssetManager.cpp -** -** Copyright 2006, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ +/* + * Copyright 2006, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #define LOG_TAG "asset" -#include <android_runtime/android_util_AssetManager.h> - #include <inttypes.h> #include <linux/capability.h> #include <stdio.h> -#include <sys/types.h> -#include <sys/wait.h> #include <sys/stat.h> #include <sys/system_properties.h> +#include <sys/types.h> +#include <sys/wait.h> #include <private/android_filesystem_config.h> // for AID_SYSTEM +#include "android-base/logging.h" +#include "android-base/properties.h" +#include "android-base/stringprintf.h" +#include "android_runtime/android_util_AssetManager.h" +#include "android_runtime/AndroidRuntime.h" +#include "android_util_Binder.h" #include "androidfw/Asset.h" #include "androidfw/AssetManager.h" +#include "androidfw/AssetManager2.h" #include "androidfw/AttributeResolution.h" +#include "androidfw/MutexGuard.h" #include "androidfw/ResourceTypes.h" -#include "android_runtime/AndroidRuntime.h" -#include "android_util_Binder.h" #include "core_jni_helpers.h" #include "jni.h" -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedStringChars.h> -#include <nativehelper/ScopedUtfChars.h> +#include "nativehelper/JNIHelp.h" +#include "nativehelper/ScopedPrimitiveArray.h" +#include "nativehelper/ScopedStringChars.h" +#include "nativehelper/ScopedUtfChars.h" #include "utils/Log.h" -#include "utils/misc.h" #include "utils/String8.h" +#include "utils/misc.h" extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap); extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap); +using ::android::base::StringPrintf; namespace android { -static const bool kThrowOnBadId = false; - // ---------------------------------------------------------------------------- -static struct typedvalue_offsets_t -{ - jfieldID mType; - jfieldID mData; - jfieldID mString; - jfieldID mAssetCookie; - jfieldID mResourceId; - jfieldID mChangingConfigurations; - jfieldID mDensity; +static struct typedvalue_offsets_t { + jfieldID mType; + jfieldID mData; + jfieldID mString; + jfieldID mAssetCookie; + jfieldID mResourceId; + jfieldID mChangingConfigurations; + jfieldID mDensity; } gTypedValueOffsets; -static struct assetfiledescriptor_offsets_t -{ - jfieldID mFd; - jfieldID mStartOffset; - jfieldID mLength; +static struct assetfiledescriptor_offsets_t { + jfieldID mFd; + jfieldID mStartOffset; + jfieldID mLength; } gAssetFileDescriptorOffsets; -static struct assetmanager_offsets_t -{ - jfieldID mObject; +static struct assetmanager_offsets_t { + jfieldID mObject; } gAssetManagerOffsets; -static struct sparsearray_offsets_t -{ - jclass classObject; - jmethodID constructor; - jmethodID put; +static struct { + jfieldID native_ptr; +} gApkAssetsFields; + +static struct sparsearray_offsets_t { + jclass classObject; + jmethodID constructor; + jmethodID put; } gSparseArrayOffsets; -static struct configuration_offsets_t -{ - jclass classObject; - jmethodID constructor; - jfieldID mSmallestScreenWidthDpOffset; - jfieldID mScreenWidthDpOffset; - jfieldID mScreenHeightDpOffset; +static struct configuration_offsets_t { + jclass classObject; + jmethodID constructor; + jfieldID mSmallestScreenWidthDpOffset; + jfieldID mScreenWidthDpOffset; + jfieldID mScreenHeightDpOffset; } gConfigurationOffsets; -jclass g_stringClass = NULL; +jclass g_stringClass = nullptr; // ---------------------------------------------------------------------------- -static jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table, - const Res_value& value, uint32_t ref, ssize_t block, - uint32_t typeSpecFlags, ResTable_config* config = NULL); - -jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table, - const Res_value& value, uint32_t ref, ssize_t block, - uint32_t typeSpecFlags, ResTable_config* config) -{ - env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType); - env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie, - static_cast<jint>(table->getTableCookie(block))); - env->SetIntField(outValue, gTypedValueOffsets.mData, value.data); - env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL); - env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref); - env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations, - typeSpecFlags); - if (config != NULL) { - env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density); - } - return block; +// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0. +constexpr inline static jint ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) { + return cookie != kInvalidCookie ? static_cast<jint>(cookie + 1) : -1; } -// This is called by zygote (running as user root) as part of preloadResources. -static void verifySystemIdmaps() -{ - pid_t pid; - char system_id[10]; - - snprintf(system_id, sizeof(system_id), "%d", AID_SYSTEM); - - switch (pid = fork()) { - case -1: - ALOGE("failed to fork for idmap: %s", strerror(errno)); - break; - case 0: // child - { - struct __user_cap_header_struct capheader; - struct __user_cap_data_struct capdata; - - memset(&capheader, 0, sizeof(capheader)); - memset(&capdata, 0, sizeof(capdata)); - - capheader.version = _LINUX_CAPABILITY_VERSION; - capheader.pid = 0; - - if (capget(&capheader, &capdata) != 0) { - ALOGE("capget: %s\n", strerror(errno)); - exit(1); - } - - capdata.effective = capdata.permitted; - if (capset(&capheader, &capdata) != 0) { - ALOGE("capset: %s\n", strerror(errno)); - exit(1); - } - - if (setgid(AID_SYSTEM) != 0) { - ALOGE("setgid: %s\n", strerror(errno)); - exit(1); - } - - if (setuid(AID_SYSTEM) != 0) { - ALOGE("setuid: %s\n", strerror(errno)); - exit(1); - } - - // Generic idmap parameters - const char* argv[8]; - int argc = 0; - struct stat st; - - memset(argv, NULL, sizeof(argv)); - argv[argc++] = AssetManager::IDMAP_BIN; - argv[argc++] = "--scan"; - argv[argc++] = AssetManager::TARGET_PACKAGE_NAME; - argv[argc++] = AssetManager::TARGET_APK_PATH; - argv[argc++] = AssetManager::IDMAP_DIR; - - // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined, - // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR. - char subdir[PROP_VALUE_MAX]; - int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir); - if (len > 0) { - String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir; - if (stat(overlayPath.string(), &st) == 0) { - argv[argc++] = overlayPath.string(); - } - } - if (stat(AssetManager::OVERLAY_DIR, &st) == 0) { - argv[argc++] = AssetManager::OVERLAY_DIR; - } - - // Finally, invoke idmap (if any overlay directory exists) - if (argc > 5) { - execv(AssetManager::IDMAP_BIN, (char* const*)argv); - ALOGE("failed to execv for idmap: %s", strerror(errno)); - exit(1); // should never get here - } else { - exit(0); - } - } - break; - default: // parent - waitpid(pid, NULL, 0); - break; - } +constexpr inline static ApkAssetsCookie JavaCookieToApkAssetsCookie(jint cookie) { + return cookie > 0 ? static_cast<ApkAssetsCookie>(cookie - 1) : kInvalidCookie; } -// ---------------------------------------------------------------------------- - -// this guy is exported to other jni routines -AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj) -{ - jlong amHandle = env->GetLongField(obj, gAssetManagerOffsets.mObject); - AssetManager* am = reinterpret_cast<AssetManager*>(amHandle); - if (am != NULL) { - return am; - } - jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!"); - return NULL; +// This is called by zygote (running as user root) as part of preloadResources. +static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { + switch (pid_t pid = fork()) { + case -1: + PLOG(ERROR) << "failed to fork for idmap"; + break; + + // child + case 0: { + struct __user_cap_header_struct capheader; + struct __user_cap_data_struct capdata; + + memset(&capheader, 0, sizeof(capheader)); + memset(&capdata, 0, sizeof(capdata)); + + capheader.version = _LINUX_CAPABILITY_VERSION; + capheader.pid = 0; + + if (capget(&capheader, &capdata) != 0) { + PLOG(ERROR) << "capget"; + exit(1); + } + + capdata.effective = capdata.permitted; + if (capset(&capheader, &capdata) != 0) { + PLOG(ERROR) << "capset"; + exit(1); + } + + if (setgid(AID_SYSTEM) != 0) { + PLOG(ERROR) << "setgid"; + exit(1); + } + + if (setuid(AID_SYSTEM) != 0) { + PLOG(ERROR) << "setuid"; + exit(1); + } + + // Generic idmap parameters + const char* argv[8]; + int argc = 0; + struct stat st; + + memset(argv, 0, sizeof(argv)); + argv[argc++] = AssetManager::IDMAP_BIN; + argv[argc++] = "--scan"; + argv[argc++] = AssetManager::TARGET_PACKAGE_NAME; + argv[argc++] = AssetManager::TARGET_APK_PATH; + argv[argc++] = AssetManager::IDMAP_DIR; + + // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined, + // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR. + std::string overlay_theme_path = base::GetProperty(AssetManager::OVERLAY_THEME_DIR_PROPERTY, + ""); + if (!overlay_theme_path.empty()) { + overlay_theme_path = std::string(AssetManager::OVERLAY_DIR) + "/" + overlay_theme_path; + if (stat(overlay_theme_path.c_str(), &st) == 0) { + argv[argc++] = overlay_theme_path.c_str(); + } + } + + if (stat(AssetManager::OVERLAY_DIR, &st) == 0) { + argv[argc++] = AssetManager::OVERLAY_DIR; + } + + // Finally, invoke idmap (if any overlay directory exists) + if (argc > 5) { + execv(AssetManager::IDMAP_BIN, (char* const*)argv); + PLOG(ERROR) << "failed to execv for idmap"; + exit(1); // should never get here + } else { + exit(0); + } + } break; + + // parent + default: + waitpid(pid, nullptr, 0); + break; + } } -static jlong android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz, - jstring fileName, jint mode) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - ALOGV("openAsset in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Empty file name"); - return -1; - } - - if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM - && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); - return -1; - } - - Asset* a = am->open(fileName8.c_str(), (Asset::AccessMode)mode); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return -1; - } - - //printf("Created Asset Stream: %p\n", a); - - return reinterpret_cast<jlong>(a); +static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref, + uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) { + env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType); + env->SetIntField(out_typed_value, gTypedValueOffsets.mAssetCookie, + ApkAssetsCookieToJavaCookie(cookie)); + env->SetIntField(out_typed_value, gTypedValueOffsets.mData, value.data); + env->SetObjectField(out_typed_value, gTypedValueOffsets.mString, nullptr); + env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, ref); + env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, type_spec_flags); + if (config != nullptr) { + env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, config->density); + } + return static_cast<jint>(ApkAssetsCookieToJavaCookie(cookie)); } -static jobject returnParcelFileDescriptor(JNIEnv* env, Asset* a, jlongArray outOffsets) -{ - off64_t startOffset, length; - int fd = a->openFileDescriptor(&startOffset, &length); - delete a; - - if (fd < 0) { - jniThrowException(env, "java/io/FileNotFoundException", - "This file can not be opened as a file descriptor; it is probably compressed"); - return NULL; - } - - jlong* offsets = (jlong*)env->GetPrimitiveArrayCritical(outOffsets, 0); - if (offsets == NULL) { - close(fd); - return NULL; - } - - offsets[0] = startOffset; - offsets[1] = length; - - env->ReleasePrimitiveArrayCritical(outOffsets, offsets, 0); +// ---------------------------------------------------------------------------- - jobject fileDesc = jniCreateFileDescriptor(env, fd); - if (fileDesc == NULL) { - close(fd); - return NULL; - } +// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance. +struct GuardedAssetManager : public ::AAssetManager { + Guarded<AssetManager2> guarded_assetmanager; +}; - return newParcelFileDescriptor(env, fileDesc); +::AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) { + jlong assetmanager_handle = env->GetLongField(jassetmanager, gAssetManagerOffsets.mObject); + ::AAssetManager* am = reinterpret_cast<::AAssetManager*>(assetmanager_handle); + if (am == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!"); + return nullptr; + } + return am; } -static jobject android_content_AssetManager_openAssetFd(JNIEnv* env, jobject clazz, - jstring fileName, jlongArray outOffsets) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ALOGV("openAssetFd in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return NULL; - } - - Asset* a = am->open(fileName8.c_str(), Asset::ACCESS_RANDOM); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return NULL; - } - - //printf("Created Asset Stream: %p\n", a); - - return returnParcelFileDescriptor(env, a, outOffsets); +Guarded<AssetManager2>* AssetManagerForNdkAssetManager(::AAssetManager* assetmanager) { + if (assetmanager == nullptr) { + return nullptr; + } + return &reinterpret_cast<GuardedAssetManager*>(assetmanager)->guarded_assetmanager; } -static jlong android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject clazz, - jint cookie, - jstring fileName, - jint mode) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - ALOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return -1; - } - - if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM - && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); - return -1; - } - - Asset* a = cookie - ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), - (Asset::AccessMode)mode) - : am->openNonAsset(fileName8.c_str(), (Asset::AccessMode)mode); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return -1; - } - - //printf("Created Asset Stream: %p\n", a); - - return reinterpret_cast<jlong>(a); +Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) { + return AssetManagerForNdkAssetManager(NdkAssetManagerForJavaObject(env, jassetmanager)); } -static jobject android_content_AssetManager_openNonAssetFdNative(JNIEnv* env, jobject clazz, - jint cookie, - jstring fileName, - jlongArray outOffsets) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ALOGV("openNonAssetFd in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return NULL; - } - - Asset* a = cookie - ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), Asset::ACCESS_RANDOM) - : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_RANDOM); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return NULL; - } - - //printf("Created Asset Stream: %p\n", a); - - return returnParcelFileDescriptor(env, a, outOffsets); +static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) { + return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr)); } -static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz, - jstring fileName) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return NULL; - } - - AssetDir* dir = am->openDir(fileName8.c_str()); - - if (dir == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return NULL; - } - - size_t N = dir->getFileCount(); - - jobjectArray array = env->NewObjectArray(dir->getFileCount(), - g_stringClass, NULL); - if (array == NULL) { - delete dir; - return NULL; - } - - for (size_t i=0; i<N; i++) { - const String8& name = dir->getFileName(i); - jstring str = env->NewStringUTF(name.string()); - if (str == NULL) { - delete dir; - return NULL; - } - env->SetObjectArrayElement(array, i, str); - env->DeleteLocalRef(str); - } - - delete dir; - - return array; +static jobject ReturnParcelFileDescriptor(JNIEnv* env, std::unique_ptr<Asset> asset, + jlongArray out_offsets) { + off64_t start_offset, length; + int fd = asset->openFileDescriptor(&start_offset, &length); + asset.reset(); + + if (fd < 0) { + jniThrowException(env, "java/io/FileNotFoundException", + "This file can not be opened as a file descriptor; it is probably " + "compressed"); + return nullptr; + } + + jlong* offsets = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(out_offsets, 0)); + if (offsets == nullptr) { + close(fd); + return nullptr; + } + + offsets[0] = start_offset; + offsets[1] = length; + + env->ReleasePrimitiveArrayCritical(out_offsets, offsets, 0); + + jobject file_desc = jniCreateFileDescriptor(env, fd); + if (file_desc == nullptr) { + close(fd); + return nullptr; + } + return newParcelFileDescriptor(env, file_desc); } -static void android_content_AssetManager_destroyAsset(JNIEnv* env, jobject clazz, - jlong assetHandle) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - //printf("Destroying Asset Stream: %p\n", a); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return; - } - - delete a; +static jint NativeGetGlobalAssetCount(JNIEnv* /*env*/, jobject /*clazz*/) { + return Asset::getGlobalCount(); } -static jint android_content_AssetManager_readAssetChar(JNIEnv* env, jobject clazz, - jlong assetHandle) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; - } - - uint8_t b; - ssize_t res = a->read(&b, 1); - return res == 1 ? b : -1; +static jobject NativeGetAssetAllocations(JNIEnv* env, jobject /*clazz*/) { + String8 alloc = Asset::getAssetAllocations(); + if (alloc.length() <= 0) { + return nullptr; + } + return env->NewStringUTF(alloc.string()); } -static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz, - jlong assetHandle, jbyteArray bArray, - jint off, jint len) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL || bArray == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; - } - - if (len == 0) { - return 0; - } - - jsize bLen = env->GetArrayLength(bArray); - if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", ""); - return -1; - } - - jbyte* b = env->GetByteArrayElements(bArray, NULL); - ssize_t res = a->read(b+off, len); - env->ReleaseByteArrayElements(bArray, b, 0); - - if (res > 0) return static_cast<jint>(res); - - if (res < 0) { - jniThrowException(env, "java/io/IOException", ""); - } - return -1; +static jint NativeGetGlobalAssetManagerCount(JNIEnv* /*env*/, jobject /*clazz*/) { + // TODO(adamlesinski): Switch to AssetManager2. + return AssetManager::getGlobalCount(); } -static jlong android_content_AssetManager_seekAsset(JNIEnv* env, jobject clazz, - jlong assetHandle, - jlong offset, jint whence) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; - } - - return a->seek( - offset, (whence > 0) ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR)); +static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) { + // AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and + // AssetManager2 in a contiguous block (GuardedAssetManager). + return reinterpret_cast<jlong>(new GuardedAssetManager()); } -static jlong android_content_AssetManager_getAssetLength(JNIEnv* env, jobject clazz, - jlong assetHandle) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; - } - - return a->getLength(); +static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + delete reinterpret_cast<GuardedAssetManager*>(ptr); } -static jlong android_content_AssetManager_getAssetRemainingLength(JNIEnv* env, jobject clazz, - jlong assetHandle) -{ - Asset* a = reinterpret_cast<Asset*>(assetHandle); - - if (a == NULL) { - jniThrowNullPointerException(env, "asset"); - return -1; +static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jobjectArray apk_assets_array, jboolean invalidate_caches) { + const jsize apk_assets_len = env->GetArrayLength(apk_assets_array); + std::vector<const ApkAssets*> apk_assets; + apk_assets.reserve(apk_assets_len); + for (jsize i = 0; i < apk_assets_len; i++) { + jobject obj = env->GetObjectArrayElement(apk_assets_array, i); + if (obj == nullptr) { + std::string msg = StringPrintf("ApkAssets at index %d is null", i); + jniThrowNullPointerException(env, msg.c_str()); + return; } - return a->getRemainingLength(); -} - -static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz, - jstring path, jboolean appAsLib) -{ - ScopedUtfChars path8(env, path); - if (path8.c_str() == NULL) { - return 0; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; + jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr); + if (env->ExceptionCheck()) { + return; } + apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr)); + } - int32_t cookie; - bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib); - - return (res) ? static_cast<jint>(cookie) : 0; + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + assetmanager->SetApkAssets(apk_assets, invalidate_caches); } -static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz, - jstring idmapPath) -{ - ScopedUtfChars idmapPath8(env, idmapPath); - if (idmapPath8.c_str() == NULL) { - return 0; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - int32_t cookie; - bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie); - - return (res) ? (jint)cookie : 0; +static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc, + jstring locale, jint orientation, jint touchscreen, jint density, + jint keyboard, jint keyboard_hidden, jint navigation, + jint screen_width, jint screen_height, + jint smallest_screen_width_dp, jint screen_width_dp, + jint screen_height_dp, jint screen_layout, jint ui_mode, + jint color_mode, jint major_version) { + ResTable_config configuration; + memset(&configuration, 0, sizeof(configuration)); + configuration.mcc = static_cast<uint16_t>(mcc); + configuration.mnc = static_cast<uint16_t>(mnc); + configuration.orientation = static_cast<uint8_t>(orientation); + configuration.touchscreen = static_cast<uint8_t>(touchscreen); + configuration.density = static_cast<uint16_t>(density); + configuration.keyboard = static_cast<uint8_t>(keyboard); + configuration.inputFlags = static_cast<uint8_t>(keyboard_hidden); + configuration.navigation = static_cast<uint8_t>(navigation); + configuration.screenWidth = static_cast<uint16_t>(screen_width); + configuration.screenHeight = static_cast<uint16_t>(screen_height); + configuration.smallestScreenWidthDp = static_cast<uint16_t>(smallest_screen_width_dp); + configuration.screenWidthDp = static_cast<uint16_t>(screen_width_dp); + configuration.screenHeightDp = static_cast<uint16_t>(screen_height_dp); + configuration.screenLayout = static_cast<uint8_t>(screen_layout); + configuration.uiMode = static_cast<uint8_t>(ui_mode); + configuration.colorMode = static_cast<uint8_t>(color_mode); + configuration.sdkVersion = static_cast<uint16_t>(major_version); + + if (locale != nullptr) { + ScopedUtfChars locale_utf8(env, locale); + CHECK(locale_utf8.c_str() != nullptr); + configuration.setBcp47Locale(locale_utf8.c_str()); + } + + // Constants duplicated from Java class android.content.res.Configuration. + static const jint kScreenLayoutRoundMask = 0x300; + static const jint kScreenLayoutRoundShift = 8; + + // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer + // in C++. We must extract the round qualifier out of the Java screenLayout and put it + // into screenLayout2. + configuration.screenLayout2 = + static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + assetmanager->SetConfiguration(configuration); } -static jint android_content_AssetManager_addAssetFd(JNIEnv* env, jobject clazz, - jobject fileDescriptor, jstring debugPathName, - jboolean appAsLib) -{ - ScopedUtfChars debugPathName8(env, debugPathName); +static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (fd < 0) { - jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); - return 0; - } + jobject sparse_array = + env->NewObject(gSparseArrayOffsets.classObject, gSparseArrayOffsets.constructor); - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } + if (sparse_array == nullptr) { + // An exception is pending. + return nullptr; + } - int dupfd = ::dup(fd); - if (dupfd < 0) { - jniThrowIOException(env, errno); - return 0; + assetmanager->ForEachPackage([&](const std::string& package_name, uint8_t package_id) { + jstring jpackage_name = env->NewStringUTF(package_name.c_str()); + if (jpackage_name == nullptr) { + // An exception is pending. + return; } - int32_t cookie; - bool res = am->addAssetFd(dupfd, String8(debugPathName8.c_str()), &cookie, appAsLib); - - return (res) ? static_cast<jint>(cookie) : 0; -} - -static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return JNI_TRUE; - } - return am->isUpToDate() ? JNI_TRUE : JNI_FALSE; + env->CallVoidMethod(sparse_array, gSparseArrayOffsets.put, static_cast<jint>(package_id), + jpackage_name); + }); + return sparse_array; } -static jobjectArray getLocales(JNIEnv* env, jobject clazz, bool includeSystemLocales) -{ - Vector<String8> locales; - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; +static jobjectArray NativeList(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring path) { + ScopedUtfChars path_utf8(env, path); + if (path_utf8.c_str() == nullptr) { + // This will throw NPE. + return nullptr; + } + + std::vector<std::string> all_file_paths; + { + StringPiece normalized_path = path_utf8.c_str(); + if (normalized_path.data()[0] == '/') { + normalized_path = normalized_path.substr(1); + } + std::string root_path = StringPrintf("assets/%s", normalized_path.data()); + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + for (const ApkAssets* assets : assetmanager->GetApkAssets()) { + assets->ForEachFile(root_path, [&](const StringPiece& file_path, FileType type) { + if (type == FileType::kFileTypeRegular) { + all_file_paths.push_back(file_path.to_string()); + } + }); } + } - am->getLocales(&locales, includeSystemLocales); + jobjectArray array = env->NewObjectArray(all_file_paths.size(), g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } - const int N = locales.size(); + jsize index = 0; + for (const std::string& file_path : all_file_paths) { + jstring java_string = env->NewStringUTF(file_path.c_str()); - jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL); - if (result == NULL) { - return NULL; + // Check for errors creating the strings (if malformed or no memory). + if (env->ExceptionCheck()) { + return nullptr; } - for (int i=0; i<N; i++) { - jstring str = env->NewStringUTF(locales[i].string()); - if (str == NULL) { - return NULL; - } - env->SetObjectArrayElement(result, i, str); - env->DeleteLocalRef(str); - } + env->SetObjectArrayElement(array, index++, java_string); - return result; + // If we have a large amount of string in our array, we might overflow the + // local reference table of the VM. + env->DeleteLocalRef(java_string); + } + return array; } -static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz) -{ - return getLocales(env, clazz, true /* include system locales */); +static jlong NativeOpenAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path, + jint access_mode) { + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM && + access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset = + assetmanager->Open(asset_path_utf8.c_str(), static_cast<Asset::AccessMode>(access_mode)); + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + return reinterpret_cast<jlong>(asset.release()); } -static jobjectArray android_content_AssetManager_getNonSystemLocales(JNIEnv* env, jobject clazz) -{ - return getLocales(env, clazz, false /* don't include system locales */); +static jobject NativeOpenAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path, + jlongArray out_offsets) { + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return nullptr; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset = assetmanager->Open(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM); + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return nullptr; + } + return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets); } -static jobject constructConfigurationObject(JNIEnv* env, const ResTable_config& config) { - jobject result = env->NewObject(gConfigurationOffsets.classObject, - gConfigurationOffsets.constructor); - if (result == NULL) { - return NULL; - } - - env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset, - config.smallestScreenWidthDp); - env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp); - env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp); - - return result; +static jlong NativeOpenNonAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path, jint access_mode) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM && + access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, + static_cast<Asset::AccessMode>(access_mode)); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), + static_cast<Asset::AccessMode>(access_mode)); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + return reinterpret_cast<jlong>(asset.release()); } -static jobjectArray getSizeConfigurationsInternal(JNIEnv* env, - const Vector<ResTable_config>& configs) { - const int N = configs.size(); - jobjectArray result = env->NewObjectArray(N, gConfigurationOffsets.classObject, NULL); - if (result == NULL) { - return NULL; - } - - for (int i=0; i<N; i++) { - jobject config = constructConfigurationObject(env, configs[i]); - if (config == NULL) { - env->DeleteLocalRef(result); - return NULL; - } - - env->SetObjectArrayElement(result, i, config); - env->DeleteLocalRef(config); - } - - return result; +static jobject NativeOpenNonAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path, jlongArray out_offsets) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return nullptr; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return nullptr; + } + return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets); } -static jobjectArray android_content_AssetManager_getSizeConfigurations(JNIEnv* env, jobject clazz) { - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - const ResTable& res(am->getResources()); - Vector<ResTable_config> configs; - res.getConfigurations(&configs, false /* ignoreMipmap */, true /* ignoreAndroidPackage */); - - return getSizeConfigurationsInternal(env, configs); +static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie, + jstring asset_path) { + ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie); + ScopedUtfChars asset_path_utf8(env, asset_path); + if (asset_path_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::unique_ptr<Asset> asset; + if (cookie != kInvalidCookie) { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM); + } else { + asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie); + } + + if (!asset) { + jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str()); + return 0; + } + + // May be nullptr. + const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie); + + std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table); + status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true); + asset.reset(); + + if (err != NO_ERROR) { + jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); + return 0; + } + return reinterpret_cast<jlong>(xml_tree.release()); } -static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz, - jint mcc, jint mnc, - jstring locale, jint orientation, - jint touchscreen, jint density, - jint keyboard, jint keyboardHidden, - jint navigation, - jint screenWidth, jint screenHeight, - jint smallestScreenWidthDp, - jint screenWidthDp, jint screenHeightDp, - jint screenLayout, jint uiMode, - jint colorMode, jint sdkVersion) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return; - } - - ResTable_config config; - memset(&config, 0, sizeof(config)); - - const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL; - - // Constants duplicated from Java class android.content.res.Configuration. - static const jint kScreenLayoutRoundMask = 0x300; - static const jint kScreenLayoutRoundShift = 8; - - config.mcc = (uint16_t)mcc; - config.mnc = (uint16_t)mnc; - config.orientation = (uint8_t)orientation; - config.touchscreen = (uint8_t)touchscreen; - config.density = (uint16_t)density; - config.keyboard = (uint8_t)keyboard; - config.inputFlags = (uint8_t)keyboardHidden; - config.navigation = (uint8_t)navigation; - config.screenWidth = (uint16_t)screenWidth; - config.screenHeight = (uint16_t)screenHeight; - config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp; - config.screenWidthDp = (uint16_t)screenWidthDp; - config.screenHeightDp = (uint16_t)screenHeightDp; - config.screenLayout = (uint8_t)screenLayout; - config.uiMode = (uint8_t)uiMode; - config.colorMode = (uint8_t)colorMode; - config.sdkVersion = (uint16_t)sdkVersion; - config.minorVersion = 0; - - // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer - // in C++. We must extract the round qualifier out of the Java screenLayout and put it - // into screenLayout2. - config.screenLayout2 = - (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); - - am->setConfiguration(config, locale8); - - if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8); +static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jshort density, jobject typed_value, + jboolean resolve_references) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Res_value value; + ResTable_config selected_config; + uint32_t flags; + ApkAssetsCookie cookie = + assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/, + static_cast<uint16_t>(density), &value, &selected_config, &flags); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t ref = static_cast<uint32_t>(resid); + if (resolve_references) { + cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + } + return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value); } -static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobject clazz, - jstring name, - jstring defType, - jstring defPackage) -{ - ScopedStringChars name16(env, name); - if (name16.get() == NULL) { - return 0; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - const char16_t* defType16 = reinterpret_cast<const char16_t*>(defType) - ? reinterpret_cast<const char16_t*>(env->GetStringChars(defType, NULL)) - : NULL; - jsize defTypeLen = defType - ? env->GetStringLength(defType) : 0; - const char16_t* defPackage16 = reinterpret_cast<const char16_t*>(defPackage) - ? reinterpret_cast<const char16_t*>(env->GetStringChars(defPackage, - NULL)) - : NULL; - jsize defPackageLen = defPackage - ? env->GetStringLength(defPackage) : 0; - - jint ident = am->getResources().identifierForName( - reinterpret_cast<const char16_t*>(name16.get()), name16.size(), - defType16, defTypeLen, defPackage16, defPackageLen); - - if (defPackage16) { - env->ReleaseStringChars(defPackage, - reinterpret_cast<const jchar*>(defPackage16)); - } - if (defType16) { - env->ReleaseStringChars(defType, - reinterpret_cast<const jchar*>(defType16)); - } - - return ident; +static jint NativeGetResourceBagValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jint bag_entry_id, jobject typed_value) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t type_spec_flags = bag->type_spec_flags; + ApkAssetsCookie cookie = kInvalidCookie; + const Res_value* bag_value = nullptr; + for (const ResolvedBag::Entry& entry : bag) { + if (entry.key == static_cast<uint32_t>(bag_entry_id)) { + cookie = entry.cookie; + bag_value = &entry.value; + + // Keep searching (the old implementation did that). + } + } + + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + Res_value value = *bag_value; + uint32_t ref = static_cast<uint32_t>(resid); + ResTable_config selected_config; + cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &type_spec_flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + return CopyValue(env, cookie, value, ref, type_spec_flags, nullptr, typed_value); } -static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - String16 str; - if (name.package != NULL) { - str.setTo(name.package, name.packageLen); - } - if (name.type8 != NULL || name.type != NULL) { - if (str.size() > 0) { - char16_t div = ':'; - str.append(&div, 1); - } - if (name.type8 != NULL) { - str.append(String16(name.type8, name.typeLen)); - } else { - str.append(name.type, name.typeLen); - } - } - if (name.name8 != NULL || name.name != NULL) { - if (str.size() > 0) { - char16_t div = '/'; - str.append(&div, 1); - } - if (name.name8 != NULL) { - str.append(String16(name.name8, name.nameLen)); - } else { - str.append(name.name, name.nameLen); - } - } - - return env->NewString((const jchar*)str.string(), str.size()); +static jintArray NativeGetStyleAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count); + if (env->ExceptionCheck()) { + return nullptr; + } + + for (uint32_t i = 0; i < bag->entry_count; i++) { + jint attr_resid = bag->entries[i].key; + env->SetIntArrayRegion(array, i, 1, &attr_resid); + } + return array; } -static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - if (name.package != NULL) { - return env->NewString((const jchar*)name.package, name.packageLen); - } - - return NULL; +static jobjectArray NativeGetResourceStringArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jobjectArray array = env->NewObjectArray(bag->entry_count, g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } + + for (uint32_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + + // Resolve any references to their final value. + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return nullptr; + } + + if (value.dataType == Res_value::TYPE_STRING) { + const ApkAssets* apk_assets = assetmanager->GetApkAssets()[cookie]; + const ResStringPool* pool = apk_assets->GetLoadedArsc()->GetStringPool(); + + jstring java_string = nullptr; + size_t str_len; + const char* str_utf8 = pool->string8At(value.data, &str_len); + if (str_utf8 != nullptr) { + java_string = env->NewStringUTF(str_utf8); + } else { + const char16_t* str_utf16 = pool->stringAt(value.data, &str_len); + java_string = env->NewString(reinterpret_cast<const jchar*>(str_utf16), str_len); + } + + // Check for errors creating the strings (if malformed or no memory). + if (env->ExceptionCheck()) { + return nullptr; + } + + env->SetObjectArrayElement(array, i, java_string); + + // If we have a large amount of string in our array, we might overflow the + // local reference table of the VM. + env->DeleteLocalRef(java_string); + } + } + return array; } -static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } - - if (name.type8 != NULL) { - return env->NewStringUTF(name.type8); - } - - if (name.type != NULL) { - return env->NewString((const jchar*)name.type, name.typeLen); - } +static jintArray NativeGetResourceStringArrayInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count * 2); + if (array == nullptr) { + return nullptr; + } + + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr)); + if (buffer == nullptr) { + return nullptr; + } + + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT); + return nullptr; + } + + jint string_index = -1; + if (value.dataType == Res_value::TYPE_STRING) { + string_index = static_cast<jint>(value.data); + } + + buffer[i * 2] = ApkAssetsCookieToJavaCookie(cookie); + buffer[(i * 2) + 1] = string_index; + } + env->ReleasePrimitiveArrayCritical(array, buffer, 0); + return array; +} - return NULL; +static jintArray NativeGetResourceIntArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return nullptr; + } + + jintArray array = env->NewIntArray(bag->entry_count); + if (array == nullptr) { + return nullptr; + } + + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr)); + if (buffer == nullptr) { + return nullptr; + } + + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + uint32_t flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT); + return nullptr; + } + + if (value.dataType >= Res_value::TYPE_FIRST_INT && value.dataType <= Res_value::TYPE_LAST_INT) { + buffer[i] = static_cast<jint>(value.data); + } + } + env->ReleasePrimitiveArrayCritical(array, buffer, 0); + return array; } -static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jobject clazz, - jint resid) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } +static jint NativeGetResourceArraySize(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return -1; + } + return static_cast<jint>(bag->entry_count); +} - ResTable::resource_name name; - if (!am->getResources().getResourceName(resid, true, &name)) { - return NULL; - } +static jint NativeGetResourceArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid, + jintArray out_data) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid)); + if (bag == nullptr) { + return -1; + } - if (name.name8 != NULL) { - return env->NewStringUTF(name.name8); - } + const jsize out_data_length = env->GetArrayLength(out_data); + if (env->ExceptionCheck()) { + return -1; + } - if (name.name != NULL) { - return env->NewString((const jchar*)name.name, name.nameLen); - } + if (static_cast<jsize>(bag->entry_count) > out_data_length * STYLE_NUM_ENTRIES) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Input array is not large enough"); + return -1; + } - return NULL; + jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_data, nullptr)); + if (buffer == nullptr) { + return -1; + } + + jint* cursor = buffer; + for (size_t i = 0; i < bag->entry_count; i++) { + const ResolvedBag::Entry& entry = bag->entries[i]; + Res_value value = entry.value; + ResTable_config selected_config; + selected_config.density = 0; + uint32_t flags = bag->type_spec_flags; + uint32_t ref; + ApkAssetsCookie cookie = + assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + env->ReleasePrimitiveArrayCritical(out_data, buffer, JNI_ABORT); + return -1; + } + + // Deal with the special @null value -- it turns back to TYPE_NULL. + if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { + value.dataType = Res_value::TYPE_NULL; + value.data = Res_value::DATA_NULL_UNDEFINED; + } + + cursor[STYLE_TYPE] = static_cast<jint>(value.dataType); + cursor[STYLE_DATA] = static_cast<jint>(value.data); + cursor[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); + cursor[STYLE_RESOURCE_ID] = static_cast<jint>(ref); + cursor[STYLE_CHANGING_CONFIGURATIONS] = static_cast<jint>(flags); + cursor[STYLE_DENSITY] = static_cast<jint>(selected_config.density); + cursor += STYLE_NUM_ENTRIES; + } + env->ReleasePrimitiveArrayCritical(out_data, buffer, 0); + return static_cast<jint>(bag->entry_count); } -static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, - jint ident, - jshort density, - jobject outValue, - jboolean resolve) -{ - if (outValue == NULL) { - jniThrowNullPointerException(env, "outValue"); - return 0; - } - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); - - Res_value value; - ResTable_config config; - uint32_t typeSpecFlags; - ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } - uint32_t ref = ident; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } - } - if (block >= 0) { - return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); - } - - return static_cast<jint>(block); +static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name, + jstring def_type, jstring def_package) { + ScopedUtfChars name_utf8(env, name); + if (name_utf8.c_str() == nullptr) { + // This will throw NPE. + return 0; + } + + std::string type; + if (def_type != nullptr) { + ScopedUtfChars type_utf8(env, def_type); + CHECK(type_utf8.c_str() != nullptr); + type = type_utf8.c_str(); + } + + std::string package; + if (def_package != nullptr) { + ScopedUtfChars package_utf8(env, def_package); + CHECK(package_utf8.c_str() != nullptr); + package = package_utf8.c_str(); + } + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + return static_cast<jint>(assetmanager->GetResourceId(name_utf8.c_str(), type, package)); } -static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobject clazz, - jint ident, jint bagEntryId, - jobject outValue, jboolean resolve) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); +static jstring NativeGetResourceName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + AssetManager2::ResourceName name; + if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) { + return nullptr; + } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); + std::string result; + if (name.package != nullptr) { + result.append(name.package, name.package_len); + } - ssize_t block = -1; - Res_value value; - - const ResTable::bag_entry* entry = NULL; - uint32_t typeSpecFlags; - ssize_t entryCount = res.getBagLocked(ident, &entry, &typeSpecFlags); - - for (ssize_t i=0; i<entryCount; i++) { - if (((uint32_t)bagEntryId) == entry->map.name.ident) { - block = entry->stringBlock; - value = entry->map.value; - } - entry++; + if (name.type != nullptr || name.type16 != nullptr) { + if (!result.empty()) { + result += ":"; } - res.unlock(); - - if (block < 0) { - return static_cast<jint>(block); + if (name.type != nullptr) { + result.append(name.type, name.type_len); + } else { + result += util::Utf16ToUtf8(StringPiece16(name.type16, name.type_len)); } + } - uint32_t ref = ident; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } + if (name.entry != nullptr || name.entry16 != nullptr) { + if (!result.empty()) { + result += "/"; } - if (block >= 0) { - return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags); - } - - return static_cast<jint>(block); -} - -static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - return am->getResources().getTableCount(); -} -static jlong android_content_AssetManager_getNativeStringBlock(JNIEnv* env, jobject clazz, - jint block) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; + if (name.entry != nullptr) { + result.append(name.entry, name.entry_len); + } else { + result += util::Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len)); } - return reinterpret_cast<jlong>(am->getResources().getTableStringBlock(block)); + } + return env->NewStringUTF(result.c_str()); } -static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject clazz, - jint cookie) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - String8 name(am->getAssetPath(static_cast<int32_t>(cookie))); - if (name.length() == 0) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name"); - return NULL; - } - jstring str = env->NewStringUTF(name.string()); - return str; +static jstring NativeGetResourcePackageName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + AssetManager2::ResourceName name; + if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) { + return nullptr; + } + + if (name.package != nullptr) { + return env->NewStringUTF(name.package); + } + return nullptr; } -static jobject android_content_AssetManager_getAssignedPackageIdentifiers(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - const ResTable& res = am->getResources(); - - jobject sparseArray = env->NewObject(gSparseArrayOffsets.classObject, - gSparseArrayOffsets.constructor); - const size_t N = res.getBasePackageCount(); - for (size_t i = 0; i < N; i++) { - const String16 name = res.getBasePackageName(i); - env->CallVoidMethod( - sparseArray, gSparseArrayOffsets.put, - static_cast<jint>(res.getBasePackageId(i)), - env->NewString(reinterpret_cast<const jchar*>(name.string()), - name.size())); - } - return sparseArray; +static jstring NativeGetResourceTypeName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + AssetManager2::ResourceName name; + if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) { + return nullptr; + } + + if (name.type != nullptr) { + return env->NewStringUTF(name.type); + } else if (name.type16 != nullptr) { + return env->NewString(reinterpret_cast<const jchar*>(name.type16), name.type_len); + } + return nullptr; } -static jlong android_content_AssetManager_newTheme(JNIEnv* env, jobject clazz) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - return reinterpret_cast<jlong>(new ResTable::Theme(am->getResources())); +static jstring NativeGetResourceEntryName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + AssetManager2::ResourceName name; + if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) { + return nullptr; + } + + if (name.entry != nullptr) { + return env->NewStringUTF(name.entry); + } else if (name.entry16 != nullptr) { + return env->NewString(reinterpret_cast<const jchar*>(name.entry16), name.entry_len); + } + return nullptr; } -static void android_content_AssetManager_deleteTheme(JNIEnv* env, jobject clazz, - jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - delete theme; +static jobjectArray NativeGetLocales(JNIEnv* env, jclass /*class*/, jlong ptr, + jboolean exclude_system) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::set<std::string> locales = + assetmanager->GetResourceLocales(exclude_system, true /*merge_equivalent_languages*/); + + jobjectArray array = env->NewObjectArray(locales.size(), g_stringClass, nullptr); + if (array == nullptr) { + return nullptr; + } + + size_t idx = 0; + for (const std::string& locale : locales) { + jstring java_string = env->NewStringUTF(locale.c_str()); + if (java_string == nullptr) { + return nullptr; + } + env->SetObjectArrayElement(array, idx++, java_string); + env->DeleteLocalRef(java_string); + } + return array; } -static void android_content_AssetManager_applyThemeStyle(JNIEnv* env, jobject clazz, - jlong themeHandle, - jint styleRes, - jboolean force) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - theme->applyStyle(styleRes, force ? true : false); +static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config& config) { + jobject result = + env->NewObject(gConfigurationOffsets.classObject, gConfigurationOffsets.constructor); + if (result == nullptr) { + return nullptr; + } + + env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset, + config.smallestScreenWidthDp); + env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp); + env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp); + return result; } -static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz, - jlong destHandle, jlong srcHandle) -{ - ResTable::Theme* dest = reinterpret_cast<ResTable::Theme*>(destHandle); - ResTable::Theme* src = reinterpret_cast<ResTable::Theme*>(srcHandle); - dest->setTo(*src); -} +static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + std::set<ResTable_config> configurations = + assetmanager->GetResourceConfigurations(true /*exclude_system*/, false /*exclude_mipmap*/); -static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - theme->clear(); -} + jobjectArray array = + env->NewObjectArray(configurations.size(), gConfigurationOffsets.classObject, nullptr); + if (array == nullptr) { + return nullptr; + } -static jint android_content_AssetManager_loadThemeAttributeValue( - JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - const ResTable& res(theme->getResTable()); - - Res_value value; - // XXX value could be different in different configs! - uint32_t typeSpecFlags = 0; - ssize_t block = theme->getAttribute(ident, &value, &typeSpecFlags); - uint32_t ref = 0; - if (resolve) { - block = res.resolveReference(&value, block, &ref, &typeSpecFlags); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return 0; - } - } + size_t idx = 0; + for (const ResTable_config& configuration : configurations) { + jobject java_configuration = ConstructConfigurationObject(env, configuration); + if (java_configuration == nullptr) { + return nullptr; } - return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block; -} -static jint android_content_AssetManager_getThemeChangingConfigurations(JNIEnv* env, jobject clazz, - jlong themeHandle) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - return theme->getChangingConfigurations(); + env->SetObjectArrayElement(array, idx++, java_configuration); + env->DeleteLocalRef(java_configuration); + } + return array; } -static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz, - jlong themeHandle, jint pri, - jstring tag, jstring prefix) -{ - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); - const ResTable& res(theme->getResTable()); - (void)res; - - // XXX Need to use params. - theme->dumpToLog(); +static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr, + jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr); + uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr); + uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr); + + jsize attrs_len = env->GetArrayLength(java_attrs); + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return; + } + + ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr), + static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len, + out_values, out_indices); + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); } -static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject clazz, - jlong themeToken, - jint defStyleAttr, - jint defStyleRes, - jintArray inValues, - jintArray attrs, - jintArray outValues, - jintArray outIndices) -{ - if (themeToken == 0) { - jniThrowNullPointerException(env, "theme token"); - return JNI_FALSE; - } - if (attrs == NULL) { - jniThrowNullPointerException(env, "attrs"); - return JNI_FALSE; - } - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - const jsize NI = env->GetArrayLength(attrs); - const jsize NV = env->GetArrayLength(outValues); - if (NV < (NI*STYLE_NUM_ENTRIES)) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); +static jboolean NativeResolveAttrs(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint def_style_attr, jint def_style_resid, jintArray java_values, + jintArray java_attrs, jintArray out_java_values, + jintArray out_java_indices) { + const jsize attrs_len = env->GetArrayLength(java_attrs); + const jsize out_values_len = env->GetArrayLength(out_java_values); + if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small"); + return JNI_FALSE; + } + + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return JNI_FALSE; + } + + jint* values = nullptr; + jsize values_len = 0; + if (java_values != nullptr) { + values_len = env->GetArrayLength(java_values); + values = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_values, nullptr)); + if (values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return JNI_FALSE; + } + } + + jint* out_values = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr)); + if (out_values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + return JNI_FALSE; + } + + jint* out_indices = nullptr; + if (out_java_indices != nullptr) { + jsize out_indices_len = env->GetArrayLength(out_java_indices); + if (out_indices_len > attrs_len) { + out_indices = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr)); + if (out_indices == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT); return JNI_FALSE; - } + } + } + } + + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + bool result = ResolveAttrs( + theme, static_cast<uint32_t>(def_style_attr), static_cast<uint32_t>(def_style_resid), + reinterpret_cast<uint32_t*>(values), values_len, reinterpret_cast<uint32_t*>(attrs), + attrs_len, reinterpret_cast<uint32_t*>(out_values), reinterpret_cast<uint32_t*>(out_indices)); + if (out_indices != nullptr) { + env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0); + } + + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0); + if (values != nullptr) { + env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT); + } + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return result ? JNI_TRUE : JNI_FALSE; +} - jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); - if (src == NULL) { +static jboolean NativeRetrieveAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jlong xml_parser_ptr, jintArray java_attrs, + jintArray out_java_values, jintArray out_java_indices) { + const jsize attrs_len = env->GetArrayLength(java_attrs); + const jsize out_values_len = env->GetArrayLength(out_java_values); + if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small"); + return JNI_FALSE; + } + + jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr)); + if (attrs == nullptr) { + return JNI_FALSE; + } + + jint* out_values = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr)); + if (out_values == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return JNI_FALSE; + } + + jint* out_indices = nullptr; + if (out_java_indices != nullptr) { + jsize out_indices_len = env->GetArrayLength(out_java_indices); + if (out_indices_len > attrs_len) { + out_indices = + reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr)); + if (out_indices == nullptr) { + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT); return JNI_FALSE; + } } + } - jint* srcValues = (jint*)env->GetPrimitiveArrayCritical(inValues, 0); - const jsize NSV = srcValues == NULL ? 0 : env->GetArrayLength(inValues); + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr); - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - if (baseDest == NULL) { - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return JNI_FALSE; - } - - jint* indices = NULL; - if (outIndices != NULL) { - if (env->GetArrayLength(outIndices) > NI) { - indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0); - } - } + bool result = RetrieveAttributes(assetmanager.get(), xml_parser, + reinterpret_cast<uint32_t*>(attrs), attrs_len, + reinterpret_cast<uint32_t*>(out_values), + reinterpret_cast<uint32_t*>(out_indices)); - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken); - bool result = ResolveAttrs(theme, defStyleAttr, defStyleRes, - (uint32_t*) srcValues, NSV, - (uint32_t*) src, NI, - (uint32_t*) baseDest, - (uint32_t*) indices); - - if (indices != NULL) { - env->ReleasePrimitiveArrayCritical(outIndices, indices, 0); - } - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - env->ReleasePrimitiveArrayCritical(inValues, srcValues, 0); - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return result ? JNI_TRUE : JNI_FALSE; + if (out_indices != nullptr) { + env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0); + } + env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0); + env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT); + return result ? JNI_TRUE : JNI_FALSE; } -static void android_content_AssetManager_applyStyle(JNIEnv* env, jobject, jlong themeToken, - jint defStyleAttr, jint defStyleRes, jlong xmlParserToken, jintArray attrsObj, jint length, - jlong outValuesAddress, jlong outIndicesAddress) { - jint* attrs = env->GetIntArrayElements(attrsObj, 0); - ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken); - ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken); - uint32_t* outValues = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outValuesAddress)); - uint32_t* outIndices = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outIndicesAddress)); - ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes, - reinterpret_cast<const uint32_t*>(attrs), length, outValues, outIndices); - env->ReleaseIntArrayElements(attrsObj, attrs, JNI_ABORT); +static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + return reinterpret_cast<jlong>(assetmanager->NewTheme().release()); } -static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz, - jlong xmlParserToken, - jintArray attrs, - jintArray outValues, - jintArray outIndices) -{ - if (xmlParserToken == 0) { - jniThrowNullPointerException(env, "xmlParserToken"); - return JNI_FALSE; - } - if (attrs == NULL) { - jniThrowNullPointerException(env, "attrs"); - return JNI_FALSE; - } - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return JNI_FALSE; - } - const ResTable& res(am->getResources()); - ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken; - - const jsize NI = env->GetArrayLength(attrs); - const jsize NV = env->GetArrayLength(outValues); - if (NV < (NI*STYLE_NUM_ENTRIES)) { - jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); - return JNI_FALSE; - } - - jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); - if (src == NULL) { - return JNI_FALSE; - } - - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - if (baseDest == NULL) { - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return JNI_FALSE; - } - - jint* indices = NULL; - if (outIndices != NULL) { - if (env->GetArrayLength(outIndices) > NI) { - indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0); - } - } - - bool result = RetrieveAttributes(&res, xmlParser, - (uint32_t*) src, NI, - (uint32_t*) baseDest, - (uint32_t*) indices); - - if (indices != NULL) { - env->ReleasePrimitiveArrayCritical(outIndices, indices, 0); - } - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - env->ReleasePrimitiveArrayCritical(attrs, src, 0); - return result ? JNI_TRUE : JNI_FALSE; +static void NativeThemeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) { + delete reinterpret_cast<Theme*>(theme_ptr); } -static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz, - jint id) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - const ResTable& res(am->getResources()); - - res.lock(); - const ResTable::bag_entry* defStyleEnt = NULL; - ssize_t bagOff = res.getBagLocked(id, &defStyleEnt); - res.unlock(); - - return static_cast<jint>(bagOff); +static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint resid, jboolean force) { + // AssetManager is accessed via the theme, so grab an explicit lock here. + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + theme->ApplyStyle(static_cast<uint32_t>(resid), force); + + // TODO(adamlesinski): Consider surfacing exception when result is failure. + // CTS currently expects no exceptions from this method. + // std::string error_msg = StringPrintf("Failed to apply style 0x%08x to theme", resid); + // jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str()); } -static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject clazz, - jint id, - jintArray outValues) -{ - if (outValues == NULL) { - jniThrowNullPointerException(env, "out values"); - return JNI_FALSE; - } - - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return JNI_FALSE; - } - const ResTable& res(am->getResources()); - ResTable_config config; - Res_value value; - ssize_t block; - - const jsize NV = env->GetArrayLength(outValues); - - jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); - jint* dest = baseDest; - if (dest == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", ""); - return JNI_FALSE; - } - - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - - const ResTable::bag_entry* arrayEnt = NULL; - uint32_t arrayTypeSetFlags = 0; - ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags); - const ResTable::bag_entry* endArrayEnt = arrayEnt + - (bagOff >= 0 ? bagOff : 0); - - int i = 0; - uint32_t typeSetFlags; - while (i < NV && arrayEnt < endArrayEnt) { - block = arrayEnt->stringBlock; - typeSetFlags = arrayTypeSetFlags; - config.density = 0; - value = arrayEnt->map.value; - - uint32_t resid = 0; - if (value.dataType != Res_value::TYPE_NULL) { - // Take care of resolving the found resource to its final value. - //printf("Resolving attribute reference\n"); - ssize_t newBlock = res.resolveReference(&value, block, &resid, - &typeSetFlags, &config); - if (kThrowOnBadId) { - if (newBlock == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return JNI_FALSE; - } - } - if (newBlock >= 0) block = newBlock; - } - - // Deal with the special @null value -- it turns back to TYPE_NULL. - if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { - value.dataType = Res_value::TYPE_NULL; - value.data = Res_value::DATA_NULL_UNDEFINED; - } - - //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); - - // Write the final value back to Java. - dest[STYLE_TYPE] = value.dataType; - dest[STYLE_DATA] = value.data; - dest[STYLE_ASSET_COOKIE] = reinterpret_cast<jint>(res.getTableCookie(block)); - dest[STYLE_RESOURCE_ID] = resid; - dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags; - dest[STYLE_DENSITY] = config.density; - dest += STYLE_NUM_ENTRIES; - i+= STYLE_NUM_ENTRIES; - arrayEnt++; - } - - i /= STYLE_NUM_ENTRIES; - - res.unlock(); - - env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - - return i; +static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_theme_ptr, + jlong src_theme_ptr) { + Theme* dst_theme = reinterpret_cast<Theme*>(dst_theme_ptr); + Theme* src_theme = reinterpret_cast<Theme*>(src_theme_ptr); + if (!dst_theme->SetTo(*src_theme)) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "Themes are from different AssetManagers"); + } } -static jlong android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz, - jint cookie, - jstring fileName) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return 0; - } - - ALOGV("openXmlAsset in %p (Java object %p)\n", am, clazz); - - ScopedUtfChars fileName8(env, fileName); - if (fileName8.c_str() == NULL) { - return 0; - } - - int32_t assetCookie = static_cast<int32_t>(cookie); - Asset* a = assetCookie - ? am->openNonAsset(assetCookie, fileName8.c_str(), Asset::ACCESS_BUFFER) - : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER, &assetCookie); - - if (a == NULL) { - jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str()); - return 0; - } - - const DynamicRefTable* dynamicRefTable = - am->getResources().getDynamicRefTableForCookie(assetCookie); - ResXMLTree* block = new ResXMLTree(dynamicRefTable); - status_t err = block->setTo(a->getBuffer(true), a->getLength(), true); - a->close(); - delete a; - - if (err != NO_ERROR) { - jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); - return 0; - } - - return reinterpret_cast<jlong>(block); +static void NativeThemeClear(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) { + reinterpret_cast<Theme*>(theme_ptr)->Clear(); } -static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N * 2); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - for (size_t i = 0, j = 0; ((ssize_t)i)<N; i++, bag++) { - jint stringIndex = -1; - jint stringBlock = 0; - value = bag->map.value; - - // Take care of resolving the found resource to its final value. - stringBlock = res.resolveReference(&value, bag->stringBlock, NULL); - if (value.dataType == Res_value::TYPE_STRING) { - stringIndex = value.data; - } - - if (kThrowOnBadId) { - if (stringBlock == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - - //todo: It might be faster to allocate a C array to contain - // the blocknums and indices, put them in there and then - // do just one SetIntArrayRegion() - env->SetIntArrayRegion(array, j, 1, &stringBlock); - env->SetIntArrayRegion(array, j + 1, 1, &stringIndex); - j = j + 2; - } - res.unlockBag(startOfBag); - return array; +static jint NativeThemeGetAttributeValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint resid, jobject typed_value, + jboolean resolve_references) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + + Res_value value; + uint32_t flags; + ApkAssetsCookie cookie = theme->GetAttribute(static_cast<uint32_t>(resid), &value, &flags); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + + uint32_t ref = 0u; + if (resolve_references) { + ResTable_config selected_config; + cookie = + theme->GetAssetManager()->ResolveReference(cookie, &value, &selected_config, &flags, &ref); + if (cookie == kInvalidCookie) { + return ApkAssetsCookieToJavaCookie(kInvalidCookie); + } + } + return CopyValue(env, cookie, value, ref, flags, nullptr, typed_value); } -static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL); - if (env->ExceptionCheck()) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - size_t strLen = 0; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - value = bag->map.value; - jstring str = NULL; - - // Take care of resolving the found resource to its final value. - ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - if (value.dataType == Res_value::TYPE_STRING) { - const ResStringPool* pool = res.getTableStringBlock(block); - const char* str8 = pool->string8At(value.data, &strLen); - if (str8 != NULL) { - str = env->NewStringUTF(str8); - } else { - const char16_t* str16 = pool->stringAt(value.data, &strLen); - str = env->NewString(reinterpret_cast<const jchar*>(str16), - strLen); - } - - // If one of our NewString{UTF} calls failed due to memory, an - // exception will be pending. - if (env->ExceptionCheck()) { - res.unlockBag(startOfBag); - return NULL; - } - - env->SetObjectArrayElement(array, i, str); - - // str is not NULL at that point, otherwise ExceptionCheck would have been true. - // If we have a large amount of strings in our array, we might - // overflow the local reference table of the VM. - env->DeleteLocalRef(str); - } - } - res.unlockBag(startOfBag); - return array; +static void NativeThemeDump(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jlong theme_ptr, + jint priority, jstring tag, jstring prefix) { + ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + CHECK(theme->GetAssetManager() == &(*assetmanager)); + (void) assetmanager; + (void) theme; + (void) priority; + (void) tag; + (void) prefix; } -static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, jobject clazz, - jint arrayResId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(arrayResId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } - - Res_value value; - const ResTable::bag_entry* bag = startOfBag; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - value = bag->map.value; - - // Take care of resolving the found resource to its final value. - ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); - if (kThrowOnBadId) { - if (block == BAD_INDEX) { - jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); - return array; - } - } - if (value.dataType >= Res_value::TYPE_FIRST_INT - && value.dataType <= Res_value::TYPE_LAST_INT) { - int intVal = value.data; - env->SetIntArrayRegion(array, i, 1, &intVal); - } - } - res.unlockBag(startOfBag); - return array; +static jint NativeThemeGetChangingConfigurations(JNIEnv* /*env*/, jclass /*clazz*/, + jlong theme_ptr) { + Theme* theme = reinterpret_cast<Theme*>(theme_ptr); + return static_cast<jint>(theme->GetChangingConfigurations()); } -static jintArray android_content_AssetManager_getStyleAttributes(JNIEnv* env, jobject clazz, - jint styleId) -{ - AssetManager* am = assetManagerForJavaObject(env, clazz); - if (am == NULL) { - return NULL; - } - const ResTable& res(am->getResources()); - - const ResTable::bag_entry* startOfBag; - const ssize_t N = res.lockBag(styleId, &startOfBag); - if (N < 0) { - return NULL; - } - - jintArray array = env->NewIntArray(N); - if (array == NULL) { - res.unlockBag(startOfBag); - return NULL; - } +static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + delete reinterpret_cast<Asset*>(asset_ptr); +} - const ResTable::bag_entry* bag = startOfBag; - for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { - int resourceId = bag->map.name.ident; - env->SetIntArrayRegion(array, i, 1, &resourceId); - } - res.unlockBag(startOfBag); - return array; +static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + uint8_t b; + ssize_t res = asset->read(&b, sizeof(b)); + return res == sizeof(b) ? static_cast<jint>(b) : -1; } -static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem) -{ - if (isSystem) { - verifySystemIdmaps(); - } - AssetManager* am = new AssetManager(); - if (am == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", ""); - return; - } +static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer, + jint offset, jint len) { + if (len == 0) { + return 0; + } - am->addDefaultAssets(); + jsize buffer_len = env->GetArrayLength(java_buffer); + if (offset < 0 || offset >= buffer_len || len < 0 || len > buffer_len || + offset > buffer_len - len) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", ""); + return -1; + } - ALOGV("Created AssetManager %p for Java object %p\n", am, clazz); - env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am)); -} + ScopedByteArrayRW byte_array(env, java_buffer); + if (byte_array.get() == nullptr) { + return -1; + } -static void android_content_AssetManager_destroy(JNIEnv* env, jobject clazz) -{ - AssetManager* am = (AssetManager*) - (env->GetLongField(clazz, gAssetManagerOffsets.mObject)); - ALOGV("Destroying AssetManager %p for Java object %p\n", am, clazz); - if (am != NULL) { - delete am; - env->SetLongField(clazz, gAssetManagerOffsets.mObject, 0); - } + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + ssize_t res = asset->read(byte_array.get() + offset, len); + if (res < 0) { + jniThrowException(env, "java/io/IOException", ""); + return -1; + } + return res > 0 ? static_cast<jint>(res) : -1; } -static jint android_content_AssetManager_getGlobalAssetCount(JNIEnv* env, jobject clazz) -{ - return Asset::getGlobalCount(); +static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset, + jint whence) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->seek( + static_cast<off64_t>(offset), (whence > 0 ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR)))); } -static jobject android_content_AssetManager_getAssetAllocations(JNIEnv* env, jobject clazz) -{ - String8 alloc = Asset::getAssetAllocations(); - if (alloc.length() <= 0) { - return NULL; - } - - jstring str = env->NewStringUTF(alloc.string()); - return str; +static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->getLength()); } -static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, jobject clazz) -{ - return AssetManager::getGlobalCount(); +static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { + Asset* asset = reinterpret_cast<Asset*>(asset_ptr); + return static_cast<jlong>(asset->getRemainingLength()); } // ---------------------------------------------------------------------------- -/* - * JNI registration. - */ +// JNI registration. static const JNINativeMethod gAssetManagerMethods[] = { - /* name, signature, funcPtr */ - - // Basic asset stuff. - { "openAsset", "(Ljava/lang/String;I)J", - (void*) android_content_AssetManager_openAsset }, - { "openAssetFd", "(Ljava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", - (void*) android_content_AssetManager_openAssetFd }, - { "openNonAssetNative", "(ILjava/lang/String;I)J", - (void*) android_content_AssetManager_openNonAssetNative }, - { "openNonAssetFdNative", "(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", - (void*) android_content_AssetManager_openNonAssetFdNative }, - { "list", "(Ljava/lang/String;)[Ljava/lang/String;", - (void*) android_content_AssetManager_list }, - { "destroyAsset", "(J)V", - (void*) android_content_AssetManager_destroyAsset }, - { "readAssetChar", "(J)I", - (void*) android_content_AssetManager_readAssetChar }, - { "readAsset", "(J[BII)I", - (void*) android_content_AssetManager_readAsset }, - { "seekAsset", "(JJI)J", - (void*) android_content_AssetManager_seekAsset }, - { "getAssetLength", "(J)J", - (void*) android_content_AssetManager_getAssetLength }, - { "getAssetRemainingLength", "(J)J", - (void*) android_content_AssetManager_getAssetRemainingLength }, - { "addAssetPathNative", "(Ljava/lang/String;Z)I", - (void*) android_content_AssetManager_addAssetPath }, - { "addAssetFdNative", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)I", - (void*) android_content_AssetManager_addAssetFd }, - { "addOverlayPathNative", "(Ljava/lang/String;)I", - (void*) android_content_AssetManager_addOverlayPath }, - { "isUpToDate", "()Z", - (void*) android_content_AssetManager_isUpToDate }, - - // Resources. - { "getLocales", "()[Ljava/lang/String;", - (void*) android_content_AssetManager_getLocales }, - { "getNonSystemLocales", "()[Ljava/lang/String;", - (void*) android_content_AssetManager_getNonSystemLocales }, - { "getSizeConfigurations", "()[Landroid/content/res/Configuration;", - (void*) android_content_AssetManager_getSizeConfigurations }, - { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIIII)V", - (void*) android_content_AssetManager_setConfiguration }, - { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", - (void*) android_content_AssetManager_getResourceIdentifier }, - { "getResourceName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceName }, - { "getResourcePackageName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourcePackageName }, - { "getResourceTypeName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceTypeName }, - { "getResourceEntryName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getResourceEntryName }, - { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadResourceValue }, - { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadResourceBagValue }, - { "getStringBlockCount","()I", - (void*) android_content_AssetManager_getStringBlockCount }, - { "getNativeStringBlock","(I)J", - (void*) android_content_AssetManager_getNativeStringBlock }, - { "getCookieName","(I)Ljava/lang/String;", - (void*) android_content_AssetManager_getCookieName }, - { "getAssignedPackageIdentifiers","()Landroid/util/SparseArray;", - (void*) android_content_AssetManager_getAssignedPackageIdentifiers }, - - // Themes. - { "newTheme", "()J", - (void*) android_content_AssetManager_newTheme }, - { "deleteTheme", "(J)V", - (void*) android_content_AssetManager_deleteTheme }, - { "applyThemeStyle", "(JIZ)V", - (void*) android_content_AssetManager_applyThemeStyle }, - { "copyTheme", "(JJ)V", - (void*) android_content_AssetManager_copyTheme }, - { "clearTheme", "(J)V", - (void*) android_content_AssetManager_clearTheme }, - { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I", - (void*) android_content_AssetManager_loadThemeAttributeValue }, - { "getThemeChangingConfigurations", "(J)I", - (void*) android_content_AssetManager_getThemeChangingConfigurations }, - { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V", - (void*) android_content_AssetManager_dumpTheme }, - { "applyStyle","(JIIJ[IIJJ)V", - (void*) android_content_AssetManager_applyStyle }, - { "resolveAttrs","(JII[I[I[I[I)Z", - (void*) android_content_AssetManager_resolveAttrs }, - { "retrieveAttributes","(J[I[I[I)Z", - (void*) android_content_AssetManager_retrieveAttributes }, - { "getArraySize","(I)I", - (void*) android_content_AssetManager_getArraySize }, - { "retrieveArray","(I[I)I", - (void*) android_content_AssetManager_retrieveArray }, - - // XML files. - { "openXmlAssetNative", "(ILjava/lang/String;)J", - (void*) android_content_AssetManager_openXmlAssetNative }, - - // Arrays. - { "getArrayStringResource","(I)[Ljava/lang/String;", - (void*) android_content_AssetManager_getArrayStringResource }, - { "getArrayStringInfo","(I)[I", - (void*) android_content_AssetManager_getArrayStringInfo }, - { "getArrayIntResource","(I)[I", - (void*) android_content_AssetManager_getArrayIntResource }, - { "getStyleAttributes","(I)[I", - (void*) android_content_AssetManager_getStyleAttributes }, - - // Bookkeeping. - { "init", "(Z)V", - (void*) android_content_AssetManager_init }, - { "destroy", "()V", - (void*) android_content_AssetManager_destroy }, - { "getGlobalAssetCount", "()I", - (void*) android_content_AssetManager_getGlobalAssetCount }, - { "getAssetAllocations", "()Ljava/lang/String;", - (void*) android_content_AssetManager_getAssetAllocations }, - { "getGlobalAssetManagerCount", "()I", - (void*) android_content_AssetManager_getGlobalAssetManagerCount }, + // AssetManager setup methods. + {"nativeCreate", "()J", (void*)NativeCreate}, + {"nativeDestroy", "(J)V", (void*)NativeDestroy}, + {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets}, + {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIII)V", + (void*)NativeSetConfiguration}, + {"nativeGetAssignedPackageIdentifiers", "(J)Landroid/util/SparseArray;", + (void*)NativeGetAssignedPackageIdentifiers}, + + // AssetManager file methods. + {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList}, + {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset}, + {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", + (void*)NativeOpenAssetFd}, + {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset}, + {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;", + (void*)NativeOpenNonAssetFd}, + {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset}, + + // AssetManager resource methods. + {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue}, + {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I", + (void*)NativeGetResourceBagValue}, + {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes}, + {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;", + (void*)NativeGetResourceStringArray}, + {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo}, + {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray}, + {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize}, + {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray}, + + // AssetManager resource name/ID methods. + {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + (void*)NativeGetResourceIdentifier}, + {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName}, + {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName}, + {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName}, + {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName}, + {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales}, + {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;", + (void*)NativeGetSizeConfigurations}, + + // Style attribute related methods. + {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle}, + {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs}, + {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes}, + + // Theme related methods. + {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate}, + {"nativeThemeDestroy", "(J)V", (void*)NativeThemeDestroy}, + {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle}, + {"nativeThemeCopy", "(JJ)V", (void*)NativeThemeCopy}, + {"nativeThemeClear", "(J)V", (void*)NativeThemeClear}, + {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I", + (void*)NativeThemeGetAttributeValue}, + {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump}, + {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations}, + + // AssetInputStream methods. + {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy}, + {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar}, + {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead}, + {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek}, + {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength}, + {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength}, + + // System/idmap related methods. + {"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps}, + + // Global management/debug methods. + {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount}, + {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations}, + {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount}, }; -int register_android_content_AssetManager(JNIEnv* env) -{ - jclass typedValue = FindClassOrDie(env, "android/util/TypedValue"); - gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I"); - gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I"); - gTypedValueOffsets.mString = GetFieldIDOrDie(env, typedValue, "string", - "Ljava/lang/CharSequence;"); - gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I"); - gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I"); - gTypedValueOffsets.mChangingConfigurations = GetFieldIDOrDie(env, typedValue, - "changingConfigurations", "I"); - gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I"); - - jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor"); - gAssetFileDescriptorOffsets.mFd = GetFieldIDOrDie(env, assetFd, "mFd", - "Landroid/os/ParcelFileDescriptor;"); - gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J"); - gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J"); - - jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager"); - gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J"); - - jclass stringClass = FindClassOrDie(env, "java/lang/String"); - g_stringClass = MakeGlobalRefOrDie(env, stringClass); - - jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray"); - gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass); - gSparseArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, - "<init>", "()V"); - gSparseArrayOffsets.put = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", - "(ILjava/lang/Object;)V"); - - jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration"); - gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass); - gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, - "<init>", "()V"); - gConfigurationOffsets.mSmallestScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass, - "smallestScreenWidthDp", "I"); - gConfigurationOffsets.mScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass, - "screenWidthDp", "I"); - gConfigurationOffsets.mScreenHeightDpOffset = GetFieldIDOrDie(env, configurationClass, - "screenHeightDp", "I"); - - return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods, - NELEM(gAssetManagerMethods)); +int register_android_content_AssetManager(JNIEnv* env) { + jclass apk_assets_class = FindClassOrDie(env, "android/content/res/ApkAssets"); + gApkAssetsFields.native_ptr = GetFieldIDOrDie(env, apk_assets_class, "mNativePtr", "J"); + + jclass typedValue = FindClassOrDie(env, "android/util/TypedValue"); + gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I"); + gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I"); + gTypedValueOffsets.mString = + GetFieldIDOrDie(env, typedValue, "string", "Ljava/lang/CharSequence;"); + gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I"); + gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I"); + gTypedValueOffsets.mChangingConfigurations = + GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I"); + gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I"); + + jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor"); + gAssetFileDescriptorOffsets.mFd = + GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;"); + gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J"); + gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J"); + + jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager"); + gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J"); + + jclass stringClass = FindClassOrDie(env, "java/lang/String"); + g_stringClass = MakeGlobalRefOrDie(env, stringClass); + + jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray"); + gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass); + gSparseArrayOffsets.constructor = + GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "<init>", "()V"); + gSparseArrayOffsets.put = + GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", "(ILjava/lang/Object;)V"); + + jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration"); + gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass); + gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, "<init>", "()V"); + gConfigurationOffsets.mSmallestScreenWidthDpOffset = + GetFieldIDOrDie(env, configurationClass, "smallestScreenWidthDp", "I"); + gConfigurationOffsets.mScreenWidthDpOffset = + GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I"); + gConfigurationOffsets.mScreenHeightDpOffset = + GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I"); + + return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods, + NELEM(gAssetManagerMethods)); } }; // namespace android diff --git a/core/jni/include/android_runtime/android_util_AssetManager.h b/core/jni/include/android_runtime/android_util_AssetManager.h index 8dd933707a6a..2c1e3579eb92 100644 --- a/core/jni/include/android_runtime/android_util_AssetManager.h +++ b/core/jni/include/android_runtime/android_util_AssetManager.h @@ -14,17 +14,20 @@ * limitations under the License. */ -#ifndef android_util_AssetManager_H -#define android_util_AssetManager_H +#ifndef ANDROID_RUNTIME_ASSETMANAGER_H +#define ANDROID_RUNTIME_ASSETMANAGER_H -#include <androidfw/AssetManager.h> +#include "androidfw/AssetManager2.h" +#include "androidfw/MutexGuard.h" #include "jni.h" namespace android { -extern AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject assetMgr); +extern AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager); +extern Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager); +extern Guarded<AssetManager2>* AssetManagerForNdkAssetManager(AAssetManager* assetmanager); -} +} // namespace android -#endif +#endif // ANDROID_RUNTIME_ASSETMANAGER_H diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto index 87d302e7e6a2..53b4be498992 100644 --- a/core/proto/android/server/alarmmanagerservice.proto +++ b/core/proto/android/server/alarmmanagerservice.proto @@ -47,6 +47,8 @@ message AlarmManagerServiceProto { // Only valid if is_interactive is false. optional int64 time_until_next_non_wakeup_delivery_ms = 11; + // Can be negative if the non-wakeup alarm time is in the past (non-wakeup + // alarms aren't delivered unil the next time the device wakes up). optional int64 time_until_next_non_wakeup_alarm_ms = 12; optional int64 time_until_next_wakeup_ms = 13; optional int64 time_since_last_wakeup_ms = 14; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b04680877f89..0861e710a224 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -578,6 +578,7 @@ <!-- Added in P --> <protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" /> <protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" /> + <protected-broadcast android:name="android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE" /> <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" /> <!-- ====================================================================== --> @@ -970,6 +971,23 @@ android:description="@string/permdesc_manageOwnCalls" android:protectionLevel="normal" /> + <!-- Allows a calling app to continue a call which was started in another app. An example is a + video calling app that wants to continue a voice call on the user's mobile network.<p> + When the handover of a call from one app to another takes place, there are two devices + which are involved in the handover; the initiating and receiving devices. The initiating + device is where the request to handover the call was started, and the receiving device is + where the handover request is confirmed by the other party.<p> + This permission protects access to the + {@link android.telecom.TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)} which + the receiving side of the handover uses to accept a handover. + <p>Protection level: dangerous + --> + <permission android:name="android.permission.ACCEPT_HANDOVER" + android:permissionGroup="android.permission-group.PHONE" + android.label="@string/permlab_acceptHandover" + android:description="@string/permdesc_acceptHandovers" + android:protectionLevel="dangerous" /> + <!-- ====================================================================== --> <!-- Permissions for accessing the device microphone --> <!-- ====================================================================== --> @@ -2999,6 +3017,11 @@ <permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS" android:protectionLevel="signature|privileged|development" /> + <!-- Allows an application to control the system's display brightness + @hide --> + <permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to control VPN. <p>Not for use by third-party applications.</p> @hide --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 7e5a735236cd..66e56bf55a51 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3256,8 +3256,8 @@ <dimen name="config_buttonCornerRadius">@dimen/control_corner_material</dimen> <!-- Controls whether system buttons use all caps for text --> <bool name="config_buttonTextAllCaps">true</bool> - <!-- Name of the font family used for system buttons --> - <string name="config_fontFamilyButton">@string/font_family_button_material</string> + <!-- Name of the font family used for system surfaces where the font should use medium weight --> + <string name="config_headlineFontFamilyMedium">@string/font_family_button_material</string> <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 71e963a5bf9e..d83db87e4832 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1133,6 +1133,17 @@ <string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in order to improve the calling experience.</string> + <!-- Title of an application permission. When granted the user is giving access to a third + party app to continue a call which originated in another app. For example, the user + could be in a voice call over their carrier's mobile network, and a third party video + calling app wants to continue that voice call as a video call. --> + <string name="permlab_acceptHandover">continue a call from another app</string> + <!-- Description of an application permission. When granted the user is giving access to a + third party app to continue a call which originated in another app. For example, the user + could be in a voice call over their carrier's mobile network, and a third party video + calling app wants to continue that voice call as a video call --> + <string name="permdesc_acceptHandovers">Allows the app to continue a call which was started in another app.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readPhoneNumbers">read phone numbers</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index 189b3b7b0481..1a51c1db7c4c 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -225,7 +225,7 @@ easier. <style name="TextAppearance.DeviceDefault.SearchResult.Subtitle" parent="TextAppearance.Material.SearchResult.Subtitle"/> <style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget"/> <style name="TextAppearance.DeviceDefault.Widget.Button" parent="TextAppearance.Material.Widget.Button"> - <item name="fontFamily">@string/config_fontFamilyButton</item> + <item name="fontFamily">@string/config_headlineFontFamilyMedium</item> <item name="textAllCaps">@bool/config_buttonTextAllCaps</item> </style> <style name="TextAppearance.DeviceDefault.Widget.IconMenu.Item" parent="TextAppearance.Material.Widget.IconMenu.Item"/> diff --git a/core/tests/coretests/src/android/os/BrightnessLimit.java b/core/tests/coretests/src/android/os/BrightnessLimit.java index 43cd373ef850..fabcf3d920a9 100644 --- a/core/tests/coretests/src/android/os/BrightnessLimit.java +++ b/core/tests/coretests/src/android/os/BrightnessLimit.java @@ -16,12 +16,9 @@ package android.os; -import android.os.IPowerManager; - import android.app.Activity; +import android.hardware.display.DisplayManager; import android.os.Bundle; -import android.os.RemoteException; -import android.os.ServiceManager; import android.provider.Settings; import android.view.View; import android.view.View.OnClickListener; @@ -37,23 +34,16 @@ public class BrightnessLimit extends Activity implements OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - + setContentView(R.layout.brightness_limit); - + Button b = findViewById(R.id.go); b.setOnClickListener(this); } public void onClick(View v) { - IPowerManager power = IPowerManager.Stub.asInterface( - ServiceManager.getService("power")); - if (power != null) { - try { - power.setTemporaryScreenBrightnessSettingOverride(0); - } catch (RemoteException darn) { - - } - } + DisplayManager dm = getSystemService(DisplayManager.class); + dm.setTemporaryBrightness(0); Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, 0); } } diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java index 9893c161fec8..0d250b825d97 100644 --- a/core/tests/coretests/src/android/os/PowerManagerTest.java +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -21,9 +21,9 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; public class PowerManagerTest extends AndroidTestCase { - + private PowerManager mPm; - + /** * Setup any common data for the upcoming tests. */ @@ -32,10 +32,10 @@ public class PowerManagerTest extends AndroidTestCase { super.setUp(); mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); } - + /** * Confirm that the setup is good. - * + * * @throws Exception */ @SmallTest @@ -45,7 +45,7 @@ public class PowerManagerTest extends AndroidTestCase { /** * Confirm that we can create functional wakelocks. - * + * * @throws Exception */ @SmallTest @@ -61,22 +61,19 @@ public class PowerManagerTest extends AndroidTestCase { wl = mPm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PARTIAL_WAKE_LOCK"); doTestWakeLock(wl); - - doTestSetBacklightBrightness(); - // TODO: Some sort of functional test (maybe not in the unit test here?) + // TODO: Some sort of functional test (maybe not in the unit test here?) // that confirms that things are really happening e.g. screen power, keyboard power. } - + /** * Confirm that we can't create dysfunctional wakelocks. - * + * * @throws Exception */ @SmallTest public void testBadNewWakeLock() throws Exception { - - final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK + final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.SCREEN_DIM_WAKE_LOCK; // wrap in try because we want the error here try { @@ -86,10 +83,10 @@ public class PowerManagerTest extends AndroidTestCase { } fail("Bad WakeLock flag was not caught."); } - + /** * Apply a few tests to a wakelock to make sure it's healthy. - * + * * @param wl The wakelock to be tested. */ private void doTestWakeLock(PowerManager.WakeLock wl) { @@ -98,7 +95,7 @@ public class PowerManagerTest extends AndroidTestCase { assertTrue(wl.isHeld()); wl.release(); assertFalse(wl.isHeld()); - + // Try ref-counted acquire/release wl.setReferenceCounted(true); wl.acquire(); @@ -109,7 +106,7 @@ public class PowerManagerTest extends AndroidTestCase { assertTrue(wl.isHeld()); wl.release(); assertFalse(wl.isHeld()); - + // Try non-ref-counted wl.setReferenceCounted(false); wl.acquire(); @@ -118,24 +115,7 @@ public class PowerManagerTest extends AndroidTestCase { assertTrue(wl.isHeld()); wl.release(); assertFalse(wl.isHeld()); - + // TODO: Threaded test (needs handler) to make sure timed wakelocks work too } - - - /** - * Test that calling {@link android.os.IHardwareService#setBacklights(int)} requires - * permissions. - * <p>Tests permission: - * {@link android.Manifest.permission#DEVICE_POWER} - */ - private void doTestSetBacklightBrightness() { - try { - mPm.setBacklightBrightness(0); - fail("setBacklights did not throw SecurityException as expected"); - } catch (SecurityException e) { - // expected - } - } - } diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 08d023d03fc4..225b685b92a5 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -44,6 +44,12 @@ import java.util.Set; public class SettingsBackupTest { /** + * see {@link com.google.android.systemui.power.EnhancedEstimatesGoogleImpl} for more details + */ + public static final String HYBRID_SYSUI_BATTERY_WARNING_FLAGS = + "hybrid_sysui_battery_warning_flags"; + + /** * The following blacklists contain settings that should *not* be backed up and restored to * another device. As a general rule, anything that is not user configurable should be * blacklisted (and conversely, things that *are* user configurable *should* be backed up) @@ -114,6 +120,12 @@ public class SettingsBackupTest { Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, Settings.Global.BATTERY_STATS_CONSTANTS, Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, + Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS, + Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS, + Settings.Global.BLE_SCAN_BALANCED_WINDOW_MS, + Settings.Global.BLE_SCAN_BALANCED_INTERVAL_MS, + Settings.Global.BLE_SCAN_LOW_LATENCY_WINDOW_MS, + Settings.Global.BLE_SCAN_LOW_LATENCY_INTERVAL_MS, Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX, Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX, Settings.Global.BLUETOOTH_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX, @@ -226,6 +238,7 @@ public class SettingsBackupTest { Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, Settings.Global.HTTP_PROXY, + HYBRID_SYSUI_BATTERY_WARNING_FLAGS, Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY, Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, Settings.Global.INSTANT_APP_DEXOPT_ENABLED, diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index f6300ee20985..6f1d47d2dac2 100644 --- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -132,7 +132,7 @@ public class MeasuredParagraphTest { public void buildForStaticLayout() { MeasuredParagraph mt = null; - mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, false, null); + mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, false, false, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -147,7 +147,7 @@ public class MeasuredParagraphTest { // Recycle it MeasuredParagraph mt2 = - MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, false, mt); + MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, false, false, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml new file mode 100644 index 000000000000..e01caeedd862 --- /dev/null +++ b/core/tests/overlaytests/device/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.om.test"> + + <uses-sdk android:minSdkVersion="21" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.om.test" /> + +</manifest> diff --git a/core/tests/overlaytests/OverlayAppFiltered/Android.mk b/core/tests/overlaytests/device/OverlayAppFiltered/Android.mk index f76de7a93b2e..f76de7a93b2e 100644 --- a/core/tests/overlaytests/OverlayAppFiltered/Android.mk +++ b/core/tests/overlaytests/device/OverlayAppFiltered/Android.mk diff --git a/core/tests/overlaytests/OverlayAppFiltered/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml index 5b7950a25fbf..5b7950a25fbf 100644 --- a/core/tests/overlaytests/OverlayAppFiltered/AndroidManifest.xml +++ b/core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt index 0954cedeb5d5..0954cedeb5d5 100644 --- a/core/tests/overlaytests/OverlayAppFiltered/res/raw/lorem_ipsum.txt +++ b/core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml index 60b94eec5994..60b94eec5994 100644 --- a/core/tests/overlaytests/OverlayAppFiltered/res/values/config.xml +++ b/core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml index e2652b7e2915..e2652b7e2915 100644 --- a/core/tests/overlaytests/OverlayAppFiltered/res/xml/integer.xml +++ b/core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml diff --git a/core/tests/overlaytests/OverlayAppFirst/Android.mk b/core/tests/overlaytests/device/OverlayAppFirst/Android.mk index bf9416c279be..bf9416c279be 100644 --- a/core/tests/overlaytests/OverlayAppFirst/Android.mk +++ b/core/tests/overlaytests/device/OverlayAppFirst/Android.mk diff --git a/core/tests/overlaytests/OverlayAppFirst/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml index ec10bbcf752e..ec10bbcf752e 100644 --- a/core/tests/overlaytests/OverlayAppFirst/AndroidManifest.xml +++ b/core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml diff --git a/core/tests/overlaytests/OverlayAppFirst/res/drawable-nodpi/drawable.jpg b/core/tests/overlaytests/device/OverlayAppFirst/res/drawable-nodpi/drawable.jpg Binary files differindex 0d944d02d633..0d944d02d633 100644 --- a/core/tests/overlaytests/OverlayAppFirst/res/drawable-nodpi/drawable.jpg +++ b/core/tests/overlaytests/device/OverlayAppFirst/res/drawable-nodpi/drawable.jpg diff --git a/core/tests/overlaytests/OverlayAppFirst/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppFirst/res/raw/lorem_ipsum.txt index 756b0a3fc532..756b0a3fc532 100644 --- a/core/tests/overlaytests/OverlayAppFirst/res/raw/lorem_ipsum.txt +++ b/core/tests/overlaytests/device/OverlayAppFirst/res/raw/lorem_ipsum.txt diff --git a/core/tests/overlaytests/OverlayAppFirst/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/values-sv/config.xml index 9cdc73e27ade..9cdc73e27ade 100644 --- a/core/tests/overlaytests/OverlayAppFirst/res/values-sv/config.xml +++ b/core/tests/overlaytests/device/OverlayAppFirst/res/values-sv/config.xml diff --git a/core/tests/overlaytests/OverlayAppFirst/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/values/config.xml index 972137a3d1bf..972137a3d1bf 100644 --- a/core/tests/overlaytests/OverlayAppFirst/res/values/config.xml +++ b/core/tests/overlaytests/device/OverlayAppFirst/res/values/config.xml diff --git a/core/tests/overlaytests/OverlayAppFirst/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/xml/integer.xml index 7f628d9125f3..7f628d9125f3 100644 --- a/core/tests/overlaytests/OverlayAppFirst/res/xml/integer.xml +++ b/core/tests/overlaytests/device/OverlayAppFirst/res/xml/integer.xml diff --git a/core/tests/overlaytests/OverlayAppSecond/Android.mk b/core/tests/overlaytests/device/OverlayAppSecond/Android.mk index bb7d142d6809..bb7d142d6809 100644 --- a/core/tests/overlaytests/OverlayAppSecond/Android.mk +++ b/core/tests/overlaytests/device/OverlayAppSecond/Android.mk diff --git a/core/tests/overlaytests/OverlayAppSecond/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml index ed498637b454..ed498637b454 100644 --- a/core/tests/overlaytests/OverlayAppSecond/AndroidManifest.xml +++ b/core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml diff --git a/core/tests/overlaytests/OverlayAppSecond/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppSecond/res/raw/lorem_ipsum.txt index 613f5b63c66c..613f5b63c66c 100644 --- a/core/tests/overlaytests/OverlayAppSecond/res/raw/lorem_ipsum.txt +++ b/core/tests/overlaytests/device/OverlayAppSecond/res/raw/lorem_ipsum.txt diff --git a/core/tests/overlaytests/OverlayAppSecond/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/values-sv/config.xml index ec4b6c03e5ff..ec4b6c03e5ff 100644 --- a/core/tests/overlaytests/OverlayAppSecond/res/values-sv/config.xml +++ b/core/tests/overlaytests/device/OverlayAppSecond/res/values-sv/config.xml diff --git a/core/tests/overlaytests/OverlayAppSecond/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/values/config.xml index 8b072160ffba..8b072160ffba 100644 --- a/core/tests/overlaytests/OverlayAppSecond/res/values/config.xml +++ b/core/tests/overlaytests/device/OverlayAppSecond/res/values/config.xml diff --git a/core/tests/overlaytests/OverlayAppSecond/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/xml/integer.xml index f3370a6b8bcf..f3370a6b8bcf 100644 --- a/core/tests/overlaytests/OverlayAppSecond/res/xml/integer.xml +++ b/core/tests/overlaytests/device/OverlayAppSecond/res/xml/integer.xml diff --git a/core/tests/overlaytests/OverlayTest/Android.mk b/core/tests/overlaytests/device/OverlayTest/Android.mk index 5fe7b917102e..5fe7b917102e 100644 --- a/core/tests/overlaytests/OverlayTest/Android.mk +++ b/core/tests/overlaytests/device/OverlayTest/Android.mk diff --git a/core/tests/overlaytests/OverlayTest/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml index 9edba12ffa8f..9edba12ffa8f 100644 --- a/core/tests/overlaytests/OverlayTest/AndroidManifest.xml +++ b/core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml diff --git a/core/tests/overlaytests/OverlayTest/res/drawable-nodpi/drawable.jpg b/core/tests/overlaytests/device/OverlayTest/res/drawable-nodpi/drawable.jpg Binary files differindex a3f14f325b85..a3f14f325b85 100644 --- a/core/tests/overlaytests/OverlayTest/res/drawable-nodpi/drawable.jpg +++ b/core/tests/overlaytests/device/OverlayTest/res/drawable-nodpi/drawable.jpg diff --git a/core/tests/overlaytests/OverlayTest/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayTest/res/raw/lorem_ipsum.txt index cee7a927c5fa..cee7a927c5fa 100644 --- a/core/tests/overlaytests/OverlayTest/res/raw/lorem_ipsum.txt +++ b/core/tests/overlaytests/device/OverlayTest/res/raw/lorem_ipsum.txt diff --git a/core/tests/overlaytests/OverlayTest/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayTest/res/values-sv/config.xml index 891853edb4c5..891853edb4c5 100644 --- a/core/tests/overlaytests/OverlayTest/res/values-sv/config.xml +++ b/core/tests/overlaytests/device/OverlayTest/res/values-sv/config.xml diff --git a/core/tests/overlaytests/OverlayTest/res/values/config.xml b/core/tests/overlaytests/device/OverlayTest/res/values/config.xml index c692a2625199..c692a2625199 100644 --- a/core/tests/overlaytests/OverlayTest/res/values/config.xml +++ b/core/tests/overlaytests/device/OverlayTest/res/values/config.xml diff --git a/core/tests/overlaytests/OverlayTest/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayTest/res/xml/integer.xml index 9383daa20b6c..9383daa20b6c 100644 --- a/core/tests/overlaytests/OverlayTest/res/xml/integer.xml +++ b/core/tests/overlaytests/device/OverlayTest/res/xml/integer.xml diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java index e57c55ced046..e57c55ced046 100644 --- a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java +++ b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java index e104f5a670c1..e104f5a670c1 100644 --- a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java +++ b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java index 816a476e28cf..816a476e28cf 100644 --- a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java +++ b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java index 318cccc85461..318cccc85461 100644 --- a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java +++ b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java diff --git a/core/tests/overlaytests/OverlayTestOverlay/Android.mk b/core/tests/overlaytests/device/OverlayTestOverlay/Android.mk index ed330467f68a..ed330467f68a 100644 --- a/core/tests/overlaytests/OverlayTestOverlay/Android.mk +++ b/core/tests/overlaytests/device/OverlayTestOverlay/Android.mk diff --git a/core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml index f8b6c7b888b5..f8b6c7b888b5 100644 --- a/core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml +++ b/core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml diff --git a/core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml b/core/tests/overlaytests/device/OverlayTestOverlay/res/values/config.xml index c1e3de12059a..c1e3de12059a 100644 --- a/core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml +++ b/core/tests/overlaytests/device/OverlayTestOverlay/res/values/config.xml diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk new file mode 100644 index 000000000000..d8e1fc158fc1 --- /dev/null +++ b/core/tests/overlaytests/host/Android.mk @@ -0,0 +1,32 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := OverlayHostTests +LOCAL_JAVA_LIBRARIES := tradefed +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_TARGET_REQUIRED_MODULES := \ + OverlayHostTests_BadSignatureOverlay \ + OverlayHostTests_PlatformSignatureStaticOverlay \ + OverlayHostTests_PlatformSignatureOverlay \ + OverlayHostTests_PlatformSignatureOverlayV2 +include $(BUILD_HOST_JAVA_LIBRARY) + +# Include to build test-apps. +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/core/tests/overlaytests/host/AndroidTest.xml b/core/tests/overlaytests/host/AndroidTest.xml new file mode 100644 index 000000000000..68846238c5f1 --- /dev/null +++ b/core/tests/overlaytests/host/AndroidTest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<configuration description="Host-driven test module config for OverlayHostTests"> + <option name="test-tag" value="OverlayHostTests" /> + <option name="test-suite-tag" value="apct" /> + + <test class="com.android.tradefed.testtype.HostTest"> + <option name="class" value="com.android.server.om.hosttest.InstallOverlayTests" /> + </test> +</configuration> diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java new file mode 100644 index 000000000000..509371085055 --- /dev/null +++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.android.server.om.hosttest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class InstallOverlayTests extends BaseHostJUnit4Test { + + private static final String OVERLAY_PACKAGE_NAME = + "com.android.server.om.hosttest.signature_overlay"; + + @Test + public void failToInstallNonPlatformSignedOverlay() throws Exception { + try { + installPackage("OverlayHostTests_BadSignatureOverlay.apk"); + fail("installed a non-platform signed overlay"); + } catch (Exception e) { + // Expected. + } + assertFalse(overlayManagerContainsPackage()); + } + + @Test + public void failToInstallPlatformSignedStaticOverlay() throws Exception { + try { + installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk"); + fail("installed a static overlay"); + } catch (Exception e) { + // Expected. + } + assertFalse(overlayManagerContainsPackage()); + } + + @Test + public void succeedToInstallPlatformSignedOverlay() throws Exception { + installPackage("OverlayHostTests_PlatformSignatureOverlay.apk"); + assertTrue(overlayManagerContainsPackage()); + } + + @Test + public void succeedToInstallPlatformSignedOverlayAndUpdate() throws Exception { + installPackage("OverlayHostTests_PlatformSignatureOverlay.apk"); + assertTrue(overlayManagerContainsPackage()); + assertEquals("v1", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName()); + + installPackage("OverlayHostTests_PlatformSignatureOverlayV2.apk"); + assertTrue(overlayManagerContainsPackage()); + assertEquals("v2", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName()); + } + + private boolean overlayManagerContainsPackage() throws Exception { + return getDevice().executeShellCommand("cmd overlay list") + .contains(OVERLAY_PACKAGE_NAME); + } +} diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk new file mode 100644 index 000000000000..5c7187ead31f --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/Android.mk @@ -0,0 +1,16 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include $(call all-subdir-makefiles) + diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk new file mode 100644 index 000000000000..b051a82c29ac --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk @@ -0,0 +1,52 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +my_package_prefix := com.android.server.om.hosttest.signature_overlay + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayHostTests_BadSignatureOverlay +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad +include $(BUILD_PACKAGE) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_MANIFEST_FILE := static/AndroidManifest.xml +LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static +include $(BUILD_PACKAGE) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_CERTIFICATE := platform +LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 +LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 +include $(BUILD_PACKAGE) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlayV2 +LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_CERTIFICATE := platform +LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 +LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 +include $(BUILD_PACKAGE) + +my_package_prefix := diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..2d6843948f29 --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.om.hosttest.signature_overlay"> + <overlay android:targetPackage="android" /> +</manifest> diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy b/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml new file mode 100644 index 000000000000..139dd9653b4a --- /dev/null +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.om.hosttest.signature_overlay"> + <overlay android:targetPackage="android" android:isStatic="true" /> +</manifest> diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 2d8c71794e7d..627d5515b77a 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -22,6 +22,8 @@ import android.annotation.Nullable; import android.annotation.Size; import android.graphics.Canvas.VertexMode; import android.text.GraphicsOperations; +import android.text.MeasuredParagraph; +import android.text.MeasuredText; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; @@ -453,7 +455,8 @@ public abstract class BaseCanvas { throwIfHasHwBitmapInSwMode(paint); nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount, - x, y, isRtl, paint.getNativeInstance()); + x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */, + 0 /* measured text offset */); } public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart, @@ -483,8 +486,20 @@ public abstract class BaseCanvas { int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + long measuredTextPtr = 0; + int measuredTextOffset = 0; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + int paraIndex = mt.findParaIndex(start); + if (end <= mt.getParagraphEnd(paraIndex)) { + // Only suppor the same paragraph. + measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr(); + measuredTextOffset = start - mt.getParagraphStart(paraIndex); + } + } nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, - 0, contextLen, x, y, isRtl, paint.getNativeInstance()); + 0, contextLen, x, y, isRtl, paint.getNativeInstance(), + measuredTextPtr, measuredTextOffset); TemporaryBuffer.recycle(buf); } } @@ -623,7 +638,8 @@ public abstract class BaseCanvas { int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint); private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count, - int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint); + int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint, + long nativeMeasuredText, int measuredTextOffset); private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint); diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 02d22bec5828..3de050b5ffa5 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -920,10 +920,6 @@ public final class ImageDecoder implements AutoCloseable { Resources res = src.getResources(); byte[] np = bm.getNinePatchChunk(); if (np != null && NinePatch.isNinePatchChunk(np)) { - if (res != null) { - bm.setDensity(res.getDisplayMetrics().densityDpi); - } - Rect opticalInsets = new Rect(); bm.getOpticalInsets(opticalInsets); Rect padding = new Rect(); diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index e3740e3cf284..7ad062a6f6f9 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -163,7 +163,7 @@ public class BitmapDrawable extends Drawable { /** * Create a drawable by opening a given file path and decoding the bitmap. */ - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" }) public BitmapDrawable(Resources res, String filepath) { this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); mBitmapState.mTargetDensity = mTargetDensity; @@ -188,7 +188,7 @@ public class BitmapDrawable extends Drawable { /** * Create a drawable by decoding a bitmap from the given input stream. */ - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" }) public BitmapDrawable(Resources res, java.io.InputStream is) { this(new BitmapState(BitmapFactory.decodeStream(is)), null); mBitmapState.mTargetDensity = mTargetDensity; diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index 4571553d4d4e..41d36986dfe2 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -63,15 +63,21 @@ class RippleBackground extends RippleComponent { } } - public void setState(boolean focused, boolean hovered, boolean animateChanged) { + public void setState(boolean focused, boolean hovered, boolean pressed) { + if (!mFocused) { + focused = focused && !pressed; + } + if (!mHovered) { + hovered = hovered && !pressed; + } if (mHovered != hovered || mFocused != focused) { mHovered = hovered; mFocused = focused; - onStateChanged(animateChanged); + onStateChanged(); } } - private void onStateChanged(boolean animateChanged) { + private void onStateChanged() { float newOpacity = 0.0f; if (mHovered) newOpacity += .25f; if (mFocused) newOpacity += .75f; @@ -79,14 +85,10 @@ class RippleBackground extends RippleComponent { mAnimator.cancel(); mAnimator = null; } - if (animateChanged) { - mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity); - mAnimator.setDuration(OPACITY_DURATION); - mAnimator.setInterpolator(LINEAR_INTERPOLATOR); - mAnimator.start(); - } else { - mOpacity = newOpacity; - } + mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity); + mAnimator.setDuration(OPACITY_DURATION); + mAnimator.setInterpolator(LINEAR_INTERPOLATOR); + mAnimator.start(); } public void jumpToFinal() { diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index b883656d784a..0da61c29bd8d 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -264,7 +264,7 @@ public class RippleDrawable extends LayerDrawable { } setRippleActive(enabled && pressed); - setBackgroundActive(hovered, focused); + setBackgroundActive(hovered, focused, pressed); return changed; } @@ -280,13 +280,13 @@ public class RippleDrawable extends LayerDrawable { } } - private void setBackgroundActive(boolean hovered, boolean focused) { + private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) { if (mBackground == null && (hovered || focused)) { mBackground = new RippleBackground(this, mHotspotBounds, isBounded()); mBackground.setup(mState.mMaxRadius, mDensity); } if (mBackground != null) { - mBackground.setState(focused, hovered, true); + mBackground.setState(focused, hovered, pressed); } } @@ -878,7 +878,10 @@ public class RippleDrawable extends LayerDrawable { // Grab the color for the current state and cut the alpha channel in // half so that the ripple and background together yield full alpha. - final int color = mState.mColor.getColorForState(getState(), Color.BLACK); + int color = mState.mColor.getColorForState(getState(), Color.BLACK); + if (Color.alpha(color) > 128) { + color = (color & 0x00FFFFFF) | 0x80000000; + } final Paint p = mRipplePaint; if (mMaskColorFilter != null) { diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java index ecbf5780d67f..4129868079c7 100644 --- a/graphics/java/android/graphics/drawable/RippleForeground.java +++ b/graphics/java/android/graphics/drawable/RippleForeground.java @@ -289,6 +289,7 @@ class RippleForeground extends RippleComponent { opacity.setInterpolator(LINEAR_INTERPOLATOR); opacity.addListener(mAnimationListener); opacity.setStartDelay(computeFadeOutDelay()); + opacity.setStartValue(mOwner.getRipplePaint().getAlpha()); mPendingHwAnimators.add(opacity); invalidateSelf(); } diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 251b2e773cfb..70d52164ff74 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -145,6 +145,7 @@ cc_test { "tests/TypeWrappers_test.cpp", "tests/ZipUtils_test.cpp", ], + static_libs: ["libgmock"], target: { android: { srcs: [ @@ -171,6 +172,7 @@ cc_benchmark { // Actual benchmarks. "tests/AssetManager2_bench.cpp", + "tests/AttributeResolution_bench.cpp", "tests/SparseEntry_bench.cpp", "tests/Theme_bench.cpp", ], diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 415d3e36adf9..a558ff7ccfc1 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -36,6 +36,31 @@ namespace android { +struct FindEntryResult { + // A pointer to the resource table entry for this resource. + // If the size of the entry is > sizeof(ResTable_entry), it can be cast to + // a ResTable_map_entry and processed as a bag/map. + const ResTable_entry* entry; + + // The configuration for which the resulting entry was defined. This is already swapped to host + // endianness. + ResTable_config config; + + // The bitmask of configuration axis with which the resource value varies. + uint32_t type_flags; + + // The dynamic package ID map for the package from which this resource came from. + const DynamicRefTable* dynamic_ref_table; + + // The string pool reference to the type's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef type_string_ref; + + // The string pool reference to the entry's name. This uses a different string pool than + // the global string pool, but this is hidden from the caller. + StringPoolRef entry_string_ref; +}; + AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); } @@ -44,6 +69,7 @@ bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets bool invalidate_caches) { apk_assets_ = apk_assets; BuildDynamicRefTable(); + RebuildFilterList(); if (invalidate_caches) { InvalidateCaches(static_cast<uint32_t>(-1)); } @@ -79,7 +105,7 @@ void AssetManager2::BuildDynamicRefTable() { PackageGroup* package_group = &package_groups_[idx]; // Add the package and to the set of packages with the same ID. - package_group->packages_.push_back(package.get()); + package_group->packages_.push_back(ConfiguredPackage{package.get(), {}}); package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i)); // Add the package name -> build time ID mappings. @@ -94,7 +120,7 @@ void AssetManager2::BuildDynamicRefTable() { // Now assign the runtime IDs so that we have a build-time to runtime ID map. const auto package_groups_end = package_groups_.end(); for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) { - const std::string& package_name = iter->packages_[0]->GetPackageName(); + const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName(); for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) { iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()), iter->dynamic_ref_table.mAssignedPackageId); @@ -108,17 +134,20 @@ void AssetManager2::DumpToLog() const { std::string list; for (size_t i = 0; i < package_ids_.size(); i++) { if (package_ids_[i] != 0xff) { - base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]); + base::StringAppendF(&list, "%02x -> %d, ", (int)i, package_ids_[i]); } } LOG(INFO) << "Package ID map: " << list; - for (const auto& package_group: package_groups_) { - list = ""; - for (const auto& package : package_group.packages_) { - base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId()); - } - LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list; + for (const auto& package_group : package_groups_) { + list = ""; + for (const auto& package : package_group.packages_) { + base::StringAppendF(&list, "%s(%02x), ", package.loaded_package_->GetPackageName().c_str(), + package.loaded_package_->GetPackageId()); + } + LOG(INFO) << base::StringPrintf("PG (%02x): ", + package_group.dynamic_ref_table.mAssignedPackageId) + << list; } } @@ -157,52 +186,54 @@ void AssetManager2::SetConfiguration(const ResTable_config& configuration) { configuration_ = configuration; if (diff) { + RebuildFilterList(); InvalidateCaches(static_cast<uint32_t>(diff)); } } std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_system, - bool exclude_mipmap) { + bool exclude_mipmap) const { ATRACE_CALL(); std::set<ResTable_config> configurations; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { - if (exclude_system && package->IsSystem()) { + for (const ConfiguredPackage& package : package_group.packages_) { + if (exclude_system && package.loaded_package_->IsSystem()) { continue; } - package->CollectConfigurations(exclude_mipmap, &configurations); + package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations); } } return configurations; } std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system, - bool merge_equivalent_languages) { + bool merge_equivalent_languages) const { ATRACE_CALL(); std::set<std::string> locales; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { - if (exclude_system && package->IsSystem()) { + for (const ConfiguredPackage& package : package_group.packages_) { + if (exclude_system && package.loaded_package_->IsSystem()) { continue; } - package->CollectLocales(merge_equivalent_languages, &locales); + package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales); } } return locales; } -std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) { +std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, + Asset::AccessMode mode) const { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, mode); } std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode) { + Asset::AccessMode mode) const { const std::string new_path = "assets/" + filename; return OpenNonAsset(new_path, cookie, mode); } -std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) { +std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const { ATRACE_CALL(); std::string full_path = "assets/" + dirname; @@ -236,7 +267,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) { // is inconsistent for split APKs. std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, Asset::AccessMode mode, - ApkAssetsCookie* out_cookie) { + ApkAssetsCookie* out_cookie) const { ATRACE_CALL(); for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) { std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode); @@ -255,7 +286,8 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, - ApkAssetsCookie cookie, Asset::AccessMode mode) { + ApkAssetsCookie cookie, + Asset::AccessMode mode) const { ATRACE_CALL(); if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) { return {}; @@ -264,14 +296,13 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, } ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override, - bool stop_at_first_match, FindEntryResult* out_entry) { - ATRACE_CALL(); - + bool /*stop_at_first_match*/, + FindEntryResult* out_entry) const { // Might use this if density_override != 0. ResTable_config density_override_config; // Select our configuration or generate a density override configuration. - ResTable_config* desired_config = &configuration_; + const ResTable_config* desired_config = &configuration_; if (density_override != 0 && density_override != configuration_.density) { density_override_config = configuration_; density_override_config.density = density_override; @@ -285,53 +316,135 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri const uint32_t package_id = get_package_id(resid); const uint8_t type_idx = get_type_id(resid) - 1; - const uint16_t entry_id = get_entry_id(resid); + const uint16_t entry_idx = get_entry_id(resid); - const uint8_t idx = package_ids_[package_id]; - if (idx == 0xff) { + const uint8_t package_idx = package_ids_[package_id]; + if (package_idx == 0xff) { LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid); return kInvalidCookie; } - FindEntryResult best_entry; - ApkAssetsCookie best_cookie = kInvalidCookie; - uint32_t cumulated_flags = 0u; - - const PackageGroup& package_group = package_groups_[idx]; + const PackageGroup& package_group = package_groups_[package_idx]; const size_t package_count = package_group.packages_.size(); - FindEntryResult current_entry; - for (size_t i = 0; i < package_count; i++) { - const LoadedPackage* loaded_package = package_group.packages_[i]; - if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry)) { + + ApkAssetsCookie best_cookie = kInvalidCookie; + const LoadedPackage* best_package = nullptr; + const ResTable_type* best_type = nullptr; + const ResTable_config* best_config = nullptr; + ResTable_config best_config_copy; + uint32_t best_offset = 0u; + uint32_t type_flags = 0u; + + // If desired_config is the same as the set configuration, then we can use our filtered list + // and we don't need to match the configurations, since they already matched. + const bool use_fast_path = desired_config == &configuration_; + + for (size_t pi = 0; pi < package_count; pi++) { + const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi]; + const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_; + ApkAssetsCookie cookie = package_group.cookies_[pi]; + + // If the type IDs are offset in this package, we need to take that into account when searching + // for a type. + const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx); + if (UNLIKELY(type_spec == nullptr)) { continue; } - cumulated_flags |= current_entry.type_flags; + uint16_t local_entry_idx = entry_idx; - const ResTable_config* current_config = current_entry.config; - const ResTable_config* best_config = best_entry.config; - if (best_cookie == kInvalidCookie || - current_config->isBetterThan(*best_config, desired_config) || - (loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) { - best_entry = current_entry; - best_cookie = package_group.cookies_[i]; - if (stop_at_first_match) { - break; + // If there is an IDMAP supplied with this package, translate the entry ID. + if (type_spec->idmap_entries != nullptr) { + if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) { + // There is no mapping, so the resource is not meant to be in this overlay package. + continue; + } + } + + type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx); + + // If the package is an overlay, then even configurations that are the same MUST be chosen. + const bool package_is_overlay = loaded_package->IsOverlay(); + + const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx]; + if (use_fast_path) { + const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations; + const size_t type_count = candidate_configs.size(); + for (uint32_t i = 0; i < type_count; i++) { + const ResTable_config& this_config = candidate_configs[i]; + + // We can skip calling ResTable_config::match() because we know that all candidate + // configurations that do NOT match have been filtered-out. + if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || + (package_is_overlay && this_config.compare(*best_config) == 0)) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const ResTable_type* type_chunk = filtered_group.types[i]; + const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = type_chunk; + best_config = &this_config; + best_offset = offset; + } + } + } else { + // This is the slower path, which doesn't use the filtered list of configurations. + // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness + // and fill in any new fields that did not exist when the APK was compiled. + // Furthermore when selecting configurations we can't just record the pointer to the + // ResTable_config, we must copy it. + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config this_config; + this_config.copyFromDtoH((*iter)->config); + + if (this_config.match(*desired_config)) { + if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) || + (package_is_overlay && this_config.compare(*best_config) == 0)) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx); + if (offset == ResTable_type::NO_ENTRY) { + continue; + } + + best_cookie = cookie; + best_package = loaded_package; + best_type = *iter; + best_config_copy = this_config; + best_config = &best_config_copy; + best_offset = offset; + } + } } } } - if (best_cookie == kInvalidCookie) { + if (UNLIKELY(best_cookie == kInvalidCookie)) { return kInvalidCookie; } - *out_entry = best_entry; + const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset); + if (UNLIKELY(best_entry == nullptr)) { + return kInvalidCookie; + } + + out_entry->entry = best_entry; + out_entry->config = *best_config; + out_entry->type_flags = type_flags; + out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1); + out_entry->entry_string_ref = + StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index); out_entry->dynamic_ref_table = &package_group.dynamic_ref_table; - out_entry->type_flags = cumulated_flags; return best_cookie; } -bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { +bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const { ATRACE_CALL(); FindEntryResult entry; @@ -341,7 +454,8 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { return false; } - const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid); + const LoadedPackage* package = + apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid)); if (package == nullptr) { return false; } @@ -369,7 +483,7 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) { return true; } -bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) { +bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const { FindEntryResult entry; ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry); @@ -383,7 +497,7 @@ bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) { ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override, Res_value* out_value, ResTable_config* out_selected_config, - uint32_t* out_flags) { + uint32_t* out_flags) const { ATRACE_CALL(); FindEntryResult entry; @@ -402,7 +516,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, // Create a reference since we can't represent this complex type as a Res_value. out_value->dataType = Res_value::TYPE_REFERENCE; out_value->data = resid; - *out_selected_config = *entry.config; + *out_selected_config = entry.config; *out_flags = entry.type_flags; return cookie; } @@ -414,7 +528,7 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, // Convert the package ID to the runtime assigned package ID. entry.dynamic_ref_table->lookupResourceValue(out_value); - *out_selected_config = *entry.config; + *out_selected_config = entry.config; *out_flags = entry.type_flags; return cookie; } @@ -422,16 +536,14 @@ ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag, ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_flags, - uint32_t* out_last_reference) { + uint32_t* out_last_reference) const { ATRACE_CALL(); constexpr const int kMaxIterations = 20; for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE && in_out_value->data != 0u && iteration < kMaxIterations; iteration++) { - if (out_last_reference != nullptr) { - *out_last_reference = in_out_value->data; - } + *out_last_reference = in_out_value->data; uint32_t new_flags = 0u; cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/, in_out_value, in_out_selected_config, &new_flags); @@ -492,7 +604,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { // Attributes, arrays, etc don't have a resource id as the name. They specify // other data, which would be wrong to change via a lookup. if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, + resid); return nullptr; } } @@ -524,7 +637,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { const ResolvedBag* parent_bag = GetBag(parent_resid); if (parent_bag == nullptr) { // Failed to get the parent that should exist. - LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid); + LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, + resid); return nullptr; } @@ -543,7 +657,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { uint32_t child_key = dtohl(map_entry->name.ident); if (!is_internal_resid(child_key)) { if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, + resid); return nullptr; } } @@ -582,7 +697,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { uint32_t new_key = dtohl(map_entry->name.ident); if (!is_internal_resid(new_key)) { if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) { - LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid); + LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, + resid); return nullptr; } } @@ -638,7 +754,7 @@ static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) { uint32_t AssetManager2::GetResourceId(const std::string& resource_name, const std::string& fallback_type, - const std::string& fallback_package) { + const std::string& fallback_package) const { StringPiece package_name, type, entry; if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) { return 0u; @@ -670,7 +786,8 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, const static std::u16string kAttrPrivate16 = u"^attr-private"; for (const PackageGroup& package_group : package_groups_) { - for (const LoadedPackage* package : package_group.packages_) { + for (const ConfiguredPackage& package_impl : package_group.packages_) { + const LoadedPackage* package = package_impl.loaded_package_; if (package_name != package->GetPackageName()) { // All packages in the same group are expected to have the same package name. break; @@ -692,6 +809,32 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, return 0u; } +void AssetManager2::RebuildFilterList() { + for (PackageGroup& group : package_groups_) { + for (ConfiguredPackage& impl : group.packages_) { + // Destroy it. + impl.filtered_configs_.~ByteBucketArray(); + + // Re-create it. + new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); + + // Create the filters here. + impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) { + FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index); + const auto iter_end = spec->types + spec->type_count; + for (auto iter = spec->types; iter != iter_end; ++iter) { + ResTable_config this_config; + this_config.copyFromDtoH((*iter)->config); + if (this_config.match(configuration_)) { + group.configurations.push_back(this_config); + group.types.push_back(*iter); + } + } + }); + } + } +} + void AssetManager2::InvalidateCaches(uint32_t diff) { if (diff == 0xffffffffu) { // Everything must go. @@ -872,7 +1015,7 @@ ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value, ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_type_spec_flags, - uint32_t* out_last_ref) { + uint32_t* out_last_ref) const { if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) { uint32_t new_flags; cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags); diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp index 60e3845d98a9..f912af4f7190 100644 --- a/libs/androidfw/AttributeResolution.cpp +++ b/libs/androidfw/AttributeResolution.cpp @@ -20,13 +20,18 @@ #include <log/log.h> +#include "androidfw/AssetManager2.h" #include "androidfw/AttributeFinder.h" -#include "androidfw/ResourceTypes.h" constexpr bool kDebugStyles = false; namespace android { +// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0. +static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) { + return cookie != kInvalidCookie ? static_cast<uint32_t>(cookie + 1) : static_cast<uint32_t>(-1); +} + class XmlAttributeFinder : public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> { public: @@ -44,58 +49,53 @@ class XmlAttributeFinder }; class BagAttributeFinder - : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> { + : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> { public: - BagAttributeFinder(const ResTable::bag_entry* start, - const ResTable::bag_entry* end) - : BackTrackingAttributeFinder(start, end) {} + BagAttributeFinder(const ResolvedBag* bag) + : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr, + bag != nullptr ? bag->entries + bag->entry_count : nullptr) { + } - inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const { - return entry->map.name.ident; + inline uint32_t GetAttribute(const ResolvedBag::Entry* entry) const { + return entry->key; } }; -bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, - uint32_t def_style_res, uint32_t* src_values, - size_t src_values_length, uint32_t* attrs, - size_t attrs_length, uint32_t* out_values, - uint32_t* out_indices) { +bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, + uint32_t* src_values, size_t src_values_length, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { if (kDebugStyles) { ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme, def_style_attr, def_style_res); } - const ResTable& res = theme->getResTable(); + AssetManager2* assetmanager = theme->GetAssetManager(); ResTable_config config; Res_value value; int indices_idx = 0; // Load default style from attribute, if specified... - uint32_t def_style_bag_type_set_flags = 0; + uint32_t def_style_flags = 0u; if (def_style_attr != 0) { Res_value value; - if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) { + if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { if (value.dataType == Res_value::TYPE_REFERENCE) { def_style_res = value.data; } } } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - // Retrieve the default style bag, if requested. - const ResTable::bag_entry* def_style_start = nullptr; - uint32_t def_style_type_set_flags = 0; - ssize_t bag_off = def_style_res != 0 - ? res.getBagLocked(def_style_res, &def_style_start, - &def_style_type_set_flags) - : -1; - def_style_type_set_flags |= def_style_bag_type_set_flags; - const ResTable::bag_entry* const def_style_end = - def_style_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder def_style_attr_finder(def_style_start, def_style_end); + const ResolvedBag* default_style_bag = nullptr; + if (def_style_res != 0) { + default_style_bag = assetmanager->GetBag(def_style_res); + if (default_style_bag != nullptr) { + def_style_flags |= default_style_bag->type_spec_flags; + } + } + + BagAttributeFinder def_style_attr_finder(default_style_bag); // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. @@ -106,7 +106,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); } - ssize_t block = -1; + ApkAssetsCookie cookie = kInvalidCookie; uint32_t type_set_flags = 0; value.dataType = Res_value::TYPE_NULL; @@ -122,15 +122,14 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, value.dataType = Res_value::TYPE_ATTRIBUTE; value.data = src_values[ii]; if (kDebugStyles) { - ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, - value.data); + ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data); } } else { - const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident); - if (def_style_entry != def_style_end) { - block = def_style_entry->stringBlock; - type_set_flags = def_style_type_set_flags; - value = def_style_entry->map.value; + const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident); + if (entry != def_style_attr_finder.end()) { + cookie = entry->cookie; + type_set_flags = def_style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -140,22 +139,26 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t resid = 0; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ssize_t new_block = - theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config); - if (new_block >= 0) block = new_block; + ApkAssetsCookie new_cookie = + theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } if (kDebugStyles) { ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data); } } else if (value.data != Res_value::DATA_NULL_EMPTY) { - // If we still don't have a value for this attribute, try to find - // it in the theme! - ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags); - if (new_block >= 0) { + // If we still don't have a value for this attribute, try to find it in the theme! + ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); } - new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config); - if (new_block >= 0) block = new_block; + new_cookie = + assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } if (kDebugStyles) { ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -169,7 +172,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, } value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = -1; + cookie = kInvalidCookie; } if (kDebugStyles) { @@ -179,9 +182,7 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != -1 ? static_cast<uint32_t>(res.getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -195,90 +196,80 @@ bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, out_values += STYLE_NUM_ENTRIES; } - res.unlock(); - if (out_indices != nullptr) { out_indices[0] = indices_idx; } return true; } -void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length, +void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, + uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { if (kDebugStyles) { - ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", - theme, def_style_attr, def_style_res, xml_parser); + ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme, + def_style_attr, def_style_resid, xml_parser); } - const ResTable& res = theme->getResTable(); + AssetManager2* assetmanager = theme->GetAssetManager(); ResTable_config config; Res_value value; int indices_idx = 0; // Load default style from attribute, if specified... - uint32_t def_style_bag_type_set_flags = 0; + uint32_t def_style_flags = 0u; if (def_style_attr != 0) { Res_value value; - if (theme->getAttribute(def_style_attr, &value, - &def_style_bag_type_set_flags) >= 0) { + if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) { if (value.dataType == Res_value::TYPE_REFERENCE) { - def_style_res = value.data; + def_style_resid = value.data; } } } - // Retrieve the style class associated with the current XML tag. - int style = 0; - uint32_t style_bag_type_set_flags = 0; + // Retrieve the style resource ID associated with the current XML tag's style attribute. + uint32_t style_resid = 0u; + uint32_t style_flags = 0u; if (xml_parser != nullptr) { ssize_t idx = xml_parser->indexOfStyle(); if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) { if (value.dataType == value.TYPE_ATTRIBUTE) { - if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) { + // Resolve the attribute with out theme. + if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) { value.dataType = Res_value::TYPE_NULL; } } + if (value.dataType == value.TYPE_REFERENCE) { - style = value.data; + style_resid = value.data; } } } - // Now lock down the resource object and start pulling stuff from it. - res.lock(); - // Retrieve the default style bag, if requested. - const ResTable::bag_entry* def_style_attr_start = nullptr; - uint32_t def_style_type_set_flags = 0; - ssize_t bag_off = def_style_res != 0 - ? res.getBagLocked(def_style_res, &def_style_attr_start, - &def_style_type_set_flags) - : -1; - def_style_type_set_flags |= def_style_bag_type_set_flags; - const ResTable::bag_entry* const def_style_attr_end = - def_style_attr_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder def_style_attr_finder(def_style_attr_start, - def_style_attr_end); + const ResolvedBag* default_style_bag = nullptr; + if (def_style_resid != 0) { + default_style_bag = assetmanager->GetBag(def_style_resid); + if (default_style_bag != nullptr) { + def_style_flags |= default_style_bag->type_spec_flags; + } + } + + BagAttributeFinder def_style_attr_finder(default_style_bag); // Retrieve the style class bag, if requested. - const ResTable::bag_entry* style_attr_start = nullptr; - uint32_t style_type_set_flags = 0; - bag_off = - style != 0 - ? res.getBagLocked(style, &style_attr_start, &style_type_set_flags) - : -1; - style_type_set_flags |= style_bag_type_set_flags; - const ResTable::bag_entry* const style_attr_end = - style_attr_start + (bag_off >= 0 ? bag_off : 0); - BagAttributeFinder style_attr_finder(style_attr_start, style_attr_end); + const ResolvedBag* xml_style_bag = nullptr; + if (style_resid != 0) { + xml_style_bag = assetmanager->GetBag(style_resid); + if (xml_style_bag != nullptr) { + style_flags |= xml_style_bag->type_spec_flags; + } + } + + BagAttributeFinder xml_style_attr_finder(xml_style_bag); // Retrieve the XML attributes, if requested. - static const ssize_t kXmlBlock = 0x10000000; XmlAttributeFinder xml_attr_finder(xml_parser); - const size_t xml_attr_end = - xml_parser != nullptr ? xml_parser->getAttributeCount() : 0; // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. @@ -289,8 +280,8 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident); } - ssize_t block = kXmlBlock; - uint32_t type_set_flags = 0; + ApkAssetsCookie cookie = kInvalidCookie; + uint32_t type_set_flags = 0u; value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; @@ -302,7 +293,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s // Walk through the xml attributes looking for the requested attribute. const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident); - if (xml_attr_idx != xml_attr_end) { + if (xml_attr_idx != xml_attr_finder.end()) { // We found the attribute we were looking for. xml_parser->getAttributeValue(xml_attr_idx, &value); if (kDebugStyles) { @@ -312,12 +303,12 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the style class values looking for the requested attribute. - const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident); - if (style_attr_entry != style_attr_end) { + const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident); + if (entry != xml_style_attr_finder.end()) { // We found the attribute we were looking for. - block = style_attr_entry->stringBlock; - type_set_flags = style_type_set_flags; - value = style_attr_entry->map.value; + cookie = entry->cookie; + type_set_flags = style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data); } @@ -326,25 +317,25 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) { // Walk through the default style values looking for the requested attribute. - const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident); - if (def_style_attr_entry != def_style_attr_end) { + const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident); + if (entry != def_style_attr_finder.end()) { // We found the attribute we were looking for. - block = def_style_attr_entry->stringBlock; - type_set_flags = style_type_set_flags; - value = def_style_attr_entry->map.value; + cookie = entry->cookie; + type_set_flags = def_style_flags; + value = entry->value; if (kDebugStyles) { ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data); } } } - uint32_t resid = 0; + uint32_t resid = 0u; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - ssize_t new_block = - theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config); - if (new_block >= 0) { - block = new_block; + ApkAssetsCookie new_cookie = + theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; } if (kDebugStyles) { @@ -352,14 +343,15 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s } } else if (value.data != Res_value::DATA_NULL_EMPTY) { // If we still don't have a value for this attribute, try to find it in the theme! - ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags); - if (new_block >= 0) { + ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags); + if (new_cookie != kInvalidCookie) { if (kDebugStyles) { ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data); } - new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config); - if (new_block >= 0) { - block = new_block; + new_cookie = + assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; } if (kDebugStyles) { @@ -375,7 +367,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s } value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = kXmlBlock; + cookie = kInvalidCookie; } if (kDebugStyles) { @@ -385,9 +377,7 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != kXmlBlock ? static_cast<uint32_t>(res.getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -402,36 +392,28 @@ void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_s out_values += STYLE_NUM_ENTRIES; } - res.unlock(); - // out_indices must NOT be nullptr. out_indices[0] = indices_idx; } -bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, - uint32_t* attrs, size_t attrs_length, - uint32_t* out_values, uint32_t* out_indices) { +bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, + size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) { ResTable_config config; Res_value value; int indices_idx = 0; - // Now lock down the resource object and start pulling stuff from it. - res->lock(); - // Retrieve the XML attributes, if requested. const size_t xml_attr_count = xml_parser->getAttributeCount(); size_t ix = 0; uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix); - static const ssize_t kXmlBlock = 0x10000000; - // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. for (size_t ii = 0; ii < attrs_length; ii++) { const uint32_t cur_ident = attrs[ii]; - ssize_t block = kXmlBlock; - uint32_t type_set_flags = 0; + ApkAssetsCookie cookie = kInvalidCookie; + uint32_t type_set_flags = 0u; value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; @@ -450,28 +432,27 @@ bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, cur_xml_attr = xml_parser->getAttributeNameResID(ix); } - uint32_t resid = 0; + uint32_t resid = 0u; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. - // printf("Resolving attribute reference\n"); - ssize_t new_block = res->resolveReference(&value, block, &resid, - &type_set_flags, &config); - if (new_block >= 0) block = new_block; + ApkAssetsCookie new_cookie = + assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid); + if (new_cookie != kInvalidCookie) { + cookie = new_cookie; + } } // Deal with the special @null value -- it turns back to TYPE_NULL. if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { value.dataType = Res_value::TYPE_NULL; value.data = Res_value::DATA_NULL_UNDEFINED; - block = kXmlBlock; + cookie = kInvalidCookie; } // Write the final value back to Java. out_values[STYLE_TYPE] = value.dataType; out_values[STYLE_DATA] = value.data; - out_values[STYLE_ASSET_COOKIE] = - block != kXmlBlock ? static_cast<uint32_t>(res->getTableCookie(block)) - : static_cast<uint32_t>(-1); + out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie); out_values[STYLE_RESOURCE_ID] = resid; out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags; out_values[STYLE_DENSITY] = config.density; @@ -485,8 +466,6 @@ bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, out_values += STYLE_NUM_ENTRIES; } - res->unlock(); - if (out_indices != nullptr) { out_indices[0] = indices_idx; } diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 28548e27baf0..1d2c597c4c8c 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -44,44 +44,6 @@ namespace android { constexpr const static int kAppPackageId = 0x7f; -// Element of a TypeSpec array. See TypeSpec. -struct Type { - // The configuration for which this type defines entries. - // This is already converted to host endianness. - ResTable_config configuration; - - // Pointer to the mmapped data where entry definitions are kept. - const ResTable_type* type; -}; - -// TypeSpec is going to be immediately proceeded by -// an array of Type structs, all in the same block of memory. -struct TypeSpec { - // Pointer to the mmapped data where flags are kept. - // Flags denote whether the resource entry is public - // and under which configurations it varies. - const ResTable_typeSpec* type_spec; - - // Pointer to the mmapped data where the IDMAP mappings for this type - // exist. May be nullptr if no IDMAP exists. - const IdmapEntry_header* idmap_entries; - - // The number of types that follow this struct. - // There is a type for each configuration - // that entries are defined for. - size_t type_count; - - // Trick to easily access a variable number of Type structs - // proceeding this struct, and to ensure their alignment. - const Type types[0]; -}; - -// TypeSpecPtr points to the block of memory that holds -// a TypeSpec struct, followed by an array of Type structs. -// TypeSpecPtr is a managed pointer that knows how to delete -// itself. -using TypeSpecPtr = util::unique_cptr<TypeSpec>; - namespace { // Builder that helps accumulate Type structs and then create a single @@ -95,21 +57,22 @@ class TypeSpecPtrBuilder { } void AddType(const ResTable_type* type) { - ResTable_config config; - config.copyFromDtoH(type->config); - types_.push_back(Type{config, type}); + types_.push_back(type); } TypeSpecPtr Build() { // Check for overflow. - if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) { + using ElementType = const ResTable_type*; + if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) < + types_.size()) { return {}; } - TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type))); + TypeSpec* type_spec = + (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType))); type_spec->type_spec = header_; type_spec->idmap_entries = idmap_header_; type_spec->type_count = types_.size(); - memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type)); + memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType)); return TypeSpecPtr(type_spec); } @@ -118,7 +81,7 @@ class TypeSpecPtrBuilder { const ResTable_typeSpec* header_; const IdmapEntry_header* idmap_header_; - std::vector<Type> types_; + std::vector<const ResTable_type*> types_; }; } // namespace @@ -162,18 +125,17 @@ static bool VerifyResTableType(const ResTable_type* header) { return true; } -static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset, - size_t entry_idx) { +static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset) { // Check that the offset is aligned. if (entry_offset & 0x03) { - LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned."; + LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned."; return false; } // Check that the offset doesn't overflow. if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) { // Overflow in offset. - LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large."; + LOG(ERROR) << "Entry at offset " << entry_offset << " is too large."; return false; } @@ -181,7 +143,7 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset entry_offset += dtohl(type->entriesStart); if (entry_offset > chunk_size - sizeof(ResTable_entry)) { - LOG(ERROR) << "Entry offset at index " << entry_idx + LOG(ERROR) << "Entry at offset " << entry_offset << " is too large. No room for ResTable_entry."; return false; } @@ -191,13 +153,13 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset const size_t entry_size = dtohs(entry->size); if (entry_size < sizeof(*entry)) { - LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx + LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset << " is too small."; return false; } if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) { - LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx + LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset << " is too large."; return false; } @@ -205,7 +167,7 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset if (entry_size < sizeof(ResTable_map_entry)) { // There needs to be room for one Res_value struct. if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) { - LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx + LOG(ERROR) << "No room for Res_value after ResTable_entry at offset " << entry_offset << " for type " << (int)type->id << "."; return false; } @@ -214,12 +176,12 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size); const size_t value_size = dtohs(value->size); if (value_size < sizeof(Res_value)) { - LOG(ERROR) << "Res_value at index " << entry_idx << " is too small."; + LOG(ERROR) << "Res_value at offset " << entry_offset << " is too small."; return false; } if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) { - LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx + LOG(ERROR) << "Res_value size " << value_size << " at offset " << entry_offset << " is too large."; return false; } @@ -228,119 +190,76 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset const size_t map_entry_count = dtohl(map->count); size_t map_entries_start = entry_offset + entry_size; if (map_entries_start & 0x03) { - LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset."; + LOG(ERROR) << "Map entries at offset " << entry_offset << " start at unaligned offset."; return false; } // Each entry is sizeof(ResTable_map) big. if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) { - LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << "."; + LOG(ERROR) << "Too many map entries in ResTable_map_entry at offset " << entry_offset << "."; return false; } } return true; } -bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx, - const ResTable_config& config, FindEntryResult* out_entry) const { - const ResTable_config* best_config = nullptr; - const ResTable_type* best_type = nullptr; - uint32_t best_offset = 0; - - for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) { - const Type* type = &type_spec_ptr->types[i]; - const ResTable_type* type_chunk = type->type; - - if (type->configuration.match(config) && - (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) { - // The configuration matches and is better than the previous selection. - // Find the entry value if it exists for this configuration. - const size_t entry_count = dtohl(type_chunk->entryCount); - const size_t offsets_offset = dtohs(type_chunk->header.headerSize); - - // Check if there is the desired entry in this type. - - if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { - // This is encoded as a sparse map, so perform a binary search. - const ResTable_sparseTypeEntry* sparse_indices = - reinterpret_cast<const ResTable_sparseTypeEntry*>( - reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); - const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; - const ResTable_sparseTypeEntry* result = - std::lower_bound(sparse_indices, sparse_indices_end, entry_idx, - [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) { - return dtohs(entry.idx) < entry_idx; - }); - - if (result == sparse_indices_end || dtohs(result->idx) != entry_idx) { - // No entry found. - continue; - } +const ResTable_entry* LoadedPackage::GetEntry(const ResTable_type* type_chunk, + uint16_t entry_index) { + uint32_t entry_offset = GetEntryOffset(type_chunk, entry_index); + if (entry_offset == ResTable_type::NO_ENTRY) { + return nullptr; + } + return GetEntryFromOffset(type_chunk, entry_offset); +} - // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as - // the real offset divided by 4. - best_offset = uint32_t{dtohs(result->offset)} * 4u; - } else { - if (entry_idx >= entry_count) { - // This entry cannot be here. - continue; - } +uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) { + // The configuration matches and is better than the previous selection. + // Find the entry value if it exists for this configuration. + const size_t entry_count = dtohl(type_chunk->entryCount); + const size_t offsets_offset = dtohs(type_chunk->header.headerSize); - const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( - reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); - const uint32_t offset = dtohl(entry_offsets[entry_idx]); - if (offset == ResTable_type::NO_ENTRY) { - continue; - } - - // There is an entry for this resource, record it. - best_offset = offset; - } + // Check if there is the desired entry in this type. - best_config = &type->configuration; - best_type = type_chunk; + if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { + // This is encoded as a sparse map, so perform a binary search. + const ResTable_sparseTypeEntry* sparse_indices = + reinterpret_cast<const ResTable_sparseTypeEntry*>( + reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); + const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count; + const ResTable_sparseTypeEntry* result = + std::lower_bound(sparse_indices, sparse_indices_end, entry_index, + [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) { + return dtohs(entry.idx) < entry_idx; + }); + + if (result == sparse_indices_end || dtohs(result->idx) != entry_index) { + // No entry found. + return ResTable_type::NO_ENTRY; } - } - if (best_type == nullptr) { - return false; + // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as + // the real offset divided by 4. + return uint32_t{dtohs(result->offset)} * 4u; } - if (UNLIKELY(!VerifyResTableEntry(best_type, best_offset, entry_idx))) { - return false; + // This type is encoded as a dense array. + if (entry_index >= entry_count) { + // This entry cannot be here. + return ResTable_type::NO_ENTRY; } - const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>( - reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart)); - - const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1); - out_entry->type_flags = dtohl(flags[entry_idx]); - out_entry->entry = best_entry; - out_entry->config = best_config; - out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1); - out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index)); - return true; + const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( + reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset); + return dtohl(entry_offsets[entry_index]); } -bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, - FindEntryResult* out_entry) const { - ATRACE_CALL(); - - // If the type IDs are offset in this package, we need to take that into account when searching - // for a type. - const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_]; - if (UNLIKELY(ptr == nullptr)) { - return false; +const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk, + uint32_t offset) { + if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) { + return nullptr; } - - // If there is an IDMAP supplied with this package, translate the entry ID. - if (ptr->idmap_entries != nullptr) { - if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) { - // There is no mapping, so the resource is not meant to be in this overlay package. - return false; - } - } - return FindEntry(ptr, entry_idx, config, out_entry); + return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) + + offset + dtohl(type_chunk->entriesStart)); } void LoadedPackage::CollectConfigurations(bool exclude_mipmap, @@ -348,7 +267,7 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap, const static std::u16string kMipMap = u"mipmap"; const size_t type_count = type_specs_.size(); for (size_t i = 0; i < type_count; i++) { - const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i]; + const TypeSpecPtr& type_spec = type_specs_[i]; if (type_spec != nullptr) { if (exclude_mipmap) { const int type_idx = type_spec->type_spec->id - 1; @@ -369,8 +288,11 @@ void LoadedPackage::CollectConfigurations(bool exclude_mipmap, } } - for (size_t j = 0; j < type_spec->type_count; j++) { - out_configs->insert(type_spec->types[j].configuration); + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config config; + config.copyFromDtoH((*iter)->config); + out_configs->insert(config); } } } @@ -380,10 +302,12 @@ void LoadedPackage::CollectLocales(bool canonicalize, std::set<std::string>* out char temp_locale[RESTABLE_MAX_LOCALE_LEN]; const size_t type_count = type_specs_.size(); for (size_t i = 0; i < type_count; i++) { - const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i]; + const TypeSpecPtr& type_spec = type_specs_[i]; if (type_spec != nullptr) { - for (size_t j = 0; j < type_spec->type_count; j++) { - const ResTable_config& configuration = type_spec->types[j].configuration; + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + ResTable_config configuration; + configuration.copyFromDtoH((*iter)->config); if (configuration.locale != 0) { configuration.getBcp47Locale(temp_locale, canonicalize); std::string locale(temp_locale); @@ -411,17 +335,17 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name, return 0u; } - for (size_t ti = 0; ti < type_spec->type_count; ti++) { - const Type* type = &type_spec->types[ti]; - size_t entry_count = dtohl(type->type->entryCount); + const auto iter_end = type_spec->types + type_spec->type_count; + for (auto iter = type_spec->types; iter != iter_end; ++iter) { + const ResTable_type* type = *iter; + size_t entry_count = dtohl(type->entryCount); for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>( - reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize)); + reinterpret_cast<const uint8_t*>(type) + dtohs(type->header.headerSize)); const uint32_t offset = dtohl(entry_offsets[entry_idx]); if (offset != ResTable_type::NO_ENTRY) { - const ResTable_entry* entry = - reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) + - dtohl(type->type->entriesStart) + offset); + const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>( + reinterpret_cast<const uint8_t*>(type) + dtohl(type->entriesStart) + offset); if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) { // The package ID will be overridden by the caller (due to runtime assignment of package // IDs for shared libraries). @@ -433,8 +357,7 @@ uint32_t LoadedPackage::FindEntryByName(const std::u16string& type_name, return 0u; } -const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const { - const uint8_t package_id = get_package_id(resid); +const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const { for (const auto& loaded_package : packages_) { if (loaded_package->GetPackageId() == package_id) { return loaded_package.get(); @@ -682,26 +605,6 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, return std::move(loaded_package); } -bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config, - FindEntryResult* out_entry) const { - ATRACE_CALL(); - - const uint8_t package_id = get_package_id(resid); - const uint8_t type_id = get_type_id(resid); - const uint16_t entry_id = get_entry_id(resid); - - if (UNLIKELY(type_id == 0)) { - LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid); - return false; - } - - for (const auto& loaded_package : packages_) { - if (loaded_package->GetPackageId() == package_id) { - return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry); - } - } - return false; -} bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library) { diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index b033137b4764..ef08897d997a 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -69,6 +69,8 @@ struct ResolvedBag { Entry entries[0]; }; +struct FindEntryResult; + // AssetManager2 is the main entry point for accessing assets and resources. // AssetManager2 provides caching of resources retrieved via the underlying ApkAssets. class AssetManager2 { @@ -127,7 +129,7 @@ class AssetManager2 { // If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap' // will be excluded from the list. std::set<ResTable_config> GetResourceConfigurations(bool exclude_system = false, - bool exclude_mipmap = false); + bool exclude_mipmap = false) const; // Returns all the locales for which there are resources defined. This includes resource // locales in all the ApkAssets set for this AssetManager. @@ -136,24 +138,24 @@ class AssetManager2 { // If `merge_equivalent_languages` is set to true, resource locales will be canonicalized // and de-duped in the resulting list. std::set<std::string> GetResourceLocales(bool exclude_system = false, - bool merge_equivalent_languages = false); + bool merge_equivalent_languages = false) const; // Searches the set of APKs loaded by this AssetManager and opens the first one found located // in the assets/ directory. // `mode` controls how the file is opened. // // NOTE: The loaded APKs are searched in reverse order. - std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode); + std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const; // Opens a file within the assets/ directory of the APK specified by `cookie`. // `mode` controls how the file is opened. std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode); + Asset::AccessMode mode) const; // Opens the directory specified by `dirname`. The result is an AssetDir that is the combination // of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded. // The entries are sorted by their ASCII name. - std::unique_ptr<AssetDir> OpenDir(const std::string& dirname); + std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const; // Searches the set of APKs loaded by this AssetManager and opens the first one found. // `mode` controls how the file is opened. @@ -161,24 +163,24 @@ class AssetManager2 { // // NOTE: The loaded APKs are searched in reverse order. std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode, - ApkAssetsCookie* out_cookie = nullptr); + ApkAssetsCookie* out_cookie = nullptr) const; // Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened. // This is typically used to open a specific AndroidManifest.xml, or a binary XML file // referenced by a resource lookup with GetResource(). std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie, - Asset::AccessMode mode); + Asset::AccessMode mode) const; // Populates the `out_name` parameter with resource name information. // Utf8 strings are preferred, and only if they are unavailable are // the Utf16 variants populated. // Returns false if the resource was not found or the name was missing/corrupt. - bool GetResourceName(uint32_t resid, ResourceName* out_name); + bool GetResourceName(uint32_t resid, ResourceName* out_name) const; // Populates `out_flags` with the bitmask of configuration axis that this resource varies with. // See ResTable_config for the list of configuration axis. // Returns false if the resource was not found. - bool GetResourceFlags(uint32_t resid, uint32_t* out_flags); + bool GetResourceFlags(uint32_t resid, uint32_t* out_flags) const; // Finds the resource ID assigned to `resource_name`. // `resource_name` must be of the form '[package:][type/]entry'. @@ -186,7 +188,7 @@ class AssetManager2 { // If no type is specified in `resource_name`, then `fallback_type` is used as the type. // Returns 0x0 if no resource by that name was found. uint32_t GetResourceId(const std::string& resource_name, const std::string& fallback_type = {}, - const std::string& fallback_package = {}); + const std::string& fallback_package = {}) const; // Retrieves the best matching resource with ID `resid`. The resource value is filled into // `out_value` and the configuration for the selected value is populated in `out_selected_config`. @@ -199,7 +201,7 @@ class AssetManager2 { // this function logs if the resource was a map/bag type before returning kInvalidCookie. ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override, Res_value* out_value, ResTable_config* out_selected_config, - uint32_t* out_flags); + uint32_t* out_flags) const; // Resolves the resource reference in `in_out_value` if the data type is // Res_value::TYPE_REFERENCE. @@ -215,7 +217,7 @@ class AssetManager2 { // it was not found. ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config, uint32_t* in_out_flags, - uint32_t* out_last_reference); + uint32_t* out_last_reference) const; // Retrieves the best matching bag/map resource with ID `resid`. // This method will resolve all parent references for this bag and merge keys with the child. @@ -233,9 +235,9 @@ class AssetManager2 { std::unique_ptr<Theme> NewTheme(); template <typename Func> - void ForEachPackage(Func func) { + void ForEachPackage(Func func) const { for (const PackageGroup& package_group : package_groups_) { - func(package_group.packages_.front()->GetPackageName(), + func(package_group.packages_.front().loaded_package_->GetPackageName(), package_group.dynamic_ref_table.mAssignedPackageId); } } @@ -260,7 +262,7 @@ class AssetManager2 { // NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly // bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds. ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match, - FindEntryResult* out_entry); + FindEntryResult* out_entry) const; // Assigns package IDs to all shared library ApkAssets. // Should be called whenever the ApkAssets are changed. @@ -270,13 +272,43 @@ class AssetManager2 { // bitmask `diff`. void InvalidateCaches(uint32_t diff); + // Triggers the re-construction of lists of types that match the set configuration. + // This should always be called when mutating the AssetManager's configuration or ApkAssets set. + void RebuildFilterList(); + // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must // have a longer lifetime. std::vector<const ApkAssets*> apk_assets_; + // A collection of configurations and their associated ResTable_type that match the current + // AssetManager configuration. + struct FilteredConfigGroup { + std::vector<ResTable_config> configurations; + std::vector<const ResTable_type*> types; + }; + + // Represents an single package. + struct ConfiguredPackage { + // A pointer to the immutable, loaded package info. + const LoadedPackage* loaded_package_; + + // A mutable AssetManager-specific list of configurations that match the AssetManager's + // current configuration. This is used as an optimization to avoid checking every single + // candidate configuration when looking up resources. + ByteBucketArray<FilteredConfigGroup> filtered_configs_; + }; + + // Represents a logical package, which can be made up of many individual packages. Each package + // in a PackageGroup shares the same package name and package ID. struct PackageGroup { - std::vector<const LoadedPackage*> packages_; + // The set of packages that make-up this group. + std::vector<ConfiguredPackage> packages_; + + // The cookies associated with each package in the group. They share the same order as + // packages_. std::vector<ApkAssetsCookie> cookies_; + + // A library reference table that contains build-package ID to runtime-package ID mappings. DynamicRefTable dynamic_ref_table; }; @@ -350,7 +382,7 @@ class Theme { ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value, ResTable_config* in_out_selected_config = nullptr, uint32_t* in_out_type_spec_flags = nullptr, - uint32_t* out_last_ref = nullptr); + uint32_t* out_last_ref = nullptr) const; private: DISALLOW_COPY_AND_ASSIGN(Theme); diff --git a/libs/androidfw/include/androidfw/AttributeFinder.h b/libs/androidfw/include/androidfw/AttributeFinder.h index f281921824e7..03fad4947dfe 100644 --- a/libs/androidfw/include/androidfw/AttributeFinder.h +++ b/libs/androidfw/include/androidfw/AttributeFinder.h @@ -58,6 +58,7 @@ class BackTrackingAttributeFinder { BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end); Iterator Find(uint32_t attr); + inline Iterator end(); private: void JumpToClosestAttribute(uint32_t package_id); @@ -201,6 +202,11 @@ Iterator BackTrackingAttributeFinder<Derived, Iterator>::Find(uint32_t attr) { return end_; } +template <typename Derived, typename Iterator> +Iterator BackTrackingAttributeFinder<Derived, Iterator>::end() { + return end_; +} + } // namespace android #endif // ANDROIDFW_ATTRIBUTE_FINDER_H diff --git a/libs/androidfw/include/androidfw/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h index 69b760414846..35ef98d8c704 100644 --- a/libs/androidfw/include/androidfw/AttributeResolution.h +++ b/libs/androidfw/include/androidfw/AttributeResolution.h @@ -17,7 +17,8 @@ #ifndef ANDROIDFW_ATTRIBUTERESOLUTION_H #define ANDROIDFW_ATTRIBUTERESOLUTION_H -#include <androidfw/ResourceTypes.h> +#include "androidfw/AssetManager2.h" +#include "androidfw/ResourceTypes.h" namespace android { @@ -42,19 +43,19 @@ enum { // `out_values` must NOT be nullptr. // `out_indices` may be nullptr. -bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res, +bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_resid, uint32_t* src_values, size_t src_values_length, uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); // `out_values` must NOT be nullptr. // `out_indices` is NOT optional and must NOT be nullptr. -void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, - uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length, +void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr, + uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); // `out_values` must NOT be nullptr. // `out_indices` may be nullptr. -bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs, +bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs, size_t attrs_length, uint32_t* out_values, uint32_t* out_indices); } // namespace android diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 965e2dbd2fb2..35ae5fcd9e7b 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -41,32 +41,40 @@ class DynamicPackageEntry { int package_id = 0; }; -struct FindEntryResult { - // A pointer to the resource table entry for this resource. - // If the size of the entry is > sizeof(ResTable_entry), it can be cast to - // a ResTable_map_entry and processed as a bag/map. - const ResTable_entry* entry = nullptr; - - // The configuration for which the resulting entry was defined. - const ResTable_config* config = nullptr; - - // Stores the resulting bitmask of configuration axis with which the resource value varies. - uint32_t type_flags = 0u; - - // The dynamic package ID map for the package from which this resource came from. - const DynamicRefTable* dynamic_ref_table = nullptr; - - // The string pool reference to the type's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef type_string_ref; - - // The string pool reference to the entry's name. This uses a different string pool than - // the global string pool, but this is hidden from the caller. - StringPoolRef entry_string_ref; +// TypeSpec is going to be immediately proceeded by +// an array of Type structs, all in the same block of memory. +struct TypeSpec { + // Pointer to the mmapped data where flags are kept. + // Flags denote whether the resource entry is public + // and under which configurations it varies. + const ResTable_typeSpec* type_spec; + + // Pointer to the mmapped data where the IDMAP mappings for this type + // exist. May be nullptr if no IDMAP exists. + const IdmapEntry_header* idmap_entries; + + // The number of types that follow this struct. + // There is a type for each configuration that entries are defined for. + size_t type_count; + + // Trick to easily access a variable number of Type structs + // proceeding this struct, and to ensure their alignment. + const ResTable_type* types[0]; + + inline uint32_t GetFlagsForEntryIndex(uint16_t entry_index) const { + if (entry_index >= dtohl(type_spec->entryCount)) { + return 0u; + } + + const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec + 1); + return flags[entry_index]; + } }; -struct TypeSpec; -class LoadedArsc; +// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of +// ResTable_type pointers. +// TypeSpecPtr is a managed pointer that knows how to delete itself. +using TypeSpecPtr = util::unique_cptr<TypeSpec>; class LoadedPackage { public: @@ -76,9 +84,6 @@ class LoadedPackage { ~LoadedPackage(); - bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config, - FindEntryResult* out_entry) const; - // Finds the entry with the specified type name and entry name. The names are in UTF-16 because // the underlying ResStringPool API expects this. For now this is acceptable, but since // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change. @@ -86,6 +91,12 @@ class LoadedPackage { // for patching the correct package ID to the resource ID. uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const; + static const ResTable_entry* GetEntry(const ResTable_type* type_chunk, uint16_t entry_index); + + static uint32_t GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index); + + static const ResTable_entry* GetEntryFromOffset(const ResTable_type* type_chunk, uint32_t offset); + // Returns the string pool where type names are stored. inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; @@ -135,14 +146,32 @@ class LoadedPackage { // before being inserted into the set. This may cause some equivalent locales to de-dupe. void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const; + // type_idx is TT - 1 from 0xPPTTEEEE. + inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const { + // If the type IDs are offset in this package, we need to take that into account when searching + // for a type. + return type_specs_[type_index - type_id_offset_].get(); + } + + template <typename Func> + void ForEachTypeSpec(Func f) const { + for (size_t i = 0; i < type_specs_.size(); i++) { + const TypeSpecPtr& ptr = type_specs_[i]; + if (ptr != nullptr) { + uint8_t type_id = ptr->type_spec->id; + if (ptr->idmap_entries != nullptr) { + type_id = ptr->idmap_entries->target_type_id; + } + f(ptr.get(), type_id - 1); + } + } + } + private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); LoadedPackage(); - bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx, - const ResTable_config& config, FindEntryResult* out_entry) const; - ResStringPool type_string_pool_; ResStringPool key_string_pool_; std::string package_name_; @@ -152,7 +181,7 @@ class LoadedPackage { bool system_ = false; bool overlay_ = false; - ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_; + ByteBucketArray<TypeSpecPtr> type_specs_; std::vector<DynamicPackageEntry> dynamic_package_map_; }; @@ -180,25 +209,20 @@ class LoadedArsc { return &global_string_pool_; } - // Finds the resource with ID `resid` with the best value for configuration `config`. - // The parameter `out_entry` will be filled with the resulting resource entry. - // The resource entry can be a simple entry (ResTable_entry) or a complex bag - // (ResTable_entry_map). - bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const; + // Gets a pointer to the package with the specified package ID, or nullptr if no such package + // exists. + const LoadedPackage* GetPackageById(uint8_t package_id) const; - // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist. - const LoadedPackage* GetPackageForId(uint32_t resid) const; + // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. + inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const { + return packages_; + } // Returns true if this is a system provided resource. inline bool IsSystem() const { return system_; } - // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc. - inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const { - return packages_; - } - private: DISALLOW_COPY_AND_ASSIGN(LoadedArsc); diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h new file mode 100644 index 000000000000..64924f433245 --- /dev/null +++ b/libs/androidfw/include/androidfw/MutexGuard.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROIDFW_MUTEXGUARD_H +#define ANDROIDFW_MUTEXGUARD_H + +#include <mutex> +#include <type_traits> + +#include "android-base/macros.h" + +namespace android { + +template <typename T> +class ScopedLock; + +// Owns the guarded object and protects access to it via a mutex. +// The guarded object is inaccessible via this class. +// The mutex is locked and the object accessed via the ScopedLock<T> class. +// +// NOTE: The template parameter T should not be a raw pointer, since ownership +// is ambiguous and error-prone. Instead use an std::unique_ptr<>. +// +// Example use: +// +// Guarded<std::string> shared_string("hello"); +// { +// ScopedLock<std::string> locked_string(shared_string); +// *locked_string += " world"; +// } +// +template <typename T> +class Guarded { + static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer"); + + public: + explicit Guarded() : guarded_() { + } + + template <typename U = T> + explicit Guarded(const T& guarded, + typename std::enable_if<std::is_copy_constructible<U>::value>::type = void()) + : guarded_(guarded) { + } + + template <typename U = T> + explicit Guarded(T&& guarded, + typename std::enable_if<std::is_move_constructible<U>::value>::type = void()) + : guarded_(std::move(guarded)) { + } + + private: + friend class ScopedLock<T>; + + DISALLOW_COPY_AND_ASSIGN(Guarded); + + std::mutex lock_; + T guarded_; +}; + +template <typename T> +class ScopedLock { + public: + explicit ScopedLock(Guarded<T>& guarded) : lock_(guarded.lock_), guarded_(guarded.guarded_) { + } + + T& operator*() { + return guarded_; + } + + T* operator->() { + return &guarded_; + } + + T* get() { + return &guarded_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedLock); + + std::lock_guard<std::mutex> lock_; + T& guarded_; +}; + +} // namespace android + +#endif // ANDROIDFW_MUTEXGUARD_H diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp index 6c43a67e602f..e2b9f0040989 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -26,58 +26,56 @@ using ::android::base::unique_fd; using ::com::android::basic::R; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; namespace android { TEST(ApkAssetsTest, LoadApk) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - - const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000); - ASSERT_NE(nullptr, loaded_package); - - std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml"); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull()); + ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull()); } TEST(ApkAssetsTest, LoadApkFromFd) { const std::string path = GetTestDataPath() + "/basic/basic.apk"; unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY)); - ASSERT_GE(fd.get(), 0); + ASSERT_THAT(fd.get(), Ge(0)); std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - - const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000); - ASSERT_NE(nullptr, loaded_package); - - std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml"); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull()); + ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull()); } TEST(ApkAssetsTest, LoadApkAsSharedLibrary) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); + const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic()); loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); loaded_arsc = loaded_apk->GetLoadedArsc(); - ASSERT_NE(nullptr, loaded_arsc); - ASSERT_EQ(1u, loaded_arsc->GetPackages().size()); + ASSERT_THAT(loaded_arsc, NotNull()); + ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u)); EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic()); } @@ -86,19 +84,22 @@ TEST(ApkAssetsTest, LoadApkWithIdmap) { ResTable target_table; const std::string target_path = GetTestDataPath() + "/basic/basic.apk"; ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/)); + ASSERT_THAT(target_table.add(contents.data(), contents.size(), 0, true /*copyData*/), + Eq(NO_ERROR)); ResTable overlay_table; const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk"; ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/)); + ASSERT_THAT(overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/), + Eq(NO_ERROR)); util::unique_cptr<void> idmap_data; void* temp_data; size_t idmap_len; - ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(), - overlay_path.c_str(), &temp_data, &idmap_len)); + ASSERT_THAT(target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(), + overlay_path.c_str(), &temp_data, &idmap_len), + Eq(NO_ERROR)); idmap_data.reset(temp_data); TemporaryFile tf; @@ -108,37 +109,30 @@ TEST(ApkAssetsTest, LoadApkWithIdmap) { // Open something so that the destructor of TemporaryFile closes a valid fd. tf.fd = open("/dev/null", O_WRONLY); - std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path); - ASSERT_NE(nullptr, loaded_overlay_apk); + ASSERT_THAT(ApkAssets::LoadOverlay(tf.path), NotNull()); } TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); - { - std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER); - ASSERT_NE(nullptr, assets); - } + { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); } - { - std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER); - ASSERT_NE(nullptr, assets); - } + { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); } } TEST(ApkAssetsTest, OpenUncompressedAssetFd) { std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); - ASSERT_NE(nullptr, loaded_apk); + ASSERT_THAT(loaded_apk, NotNull()); auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN); - ASSERT_NE(nullptr, asset); + ASSERT_THAT(asset, NotNull()); off64_t start, length; unique_fd fd(asset->openFileDescriptor(&start, &length)); - EXPECT_GE(fd.get(), 0); + ASSERT_THAT(fd.get(), Ge(0)); lseek64(fd.get(), start, SEEK_SET); @@ -146,7 +140,7 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) { buffer.resize(length); ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length)); - EXPECT_EQ("This should be uncompressed.\n\n", buffer); + EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n")); } } // namespace android diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp index 85e8f25394e9..437e14772964 100644 --- a/libs/androidfw/tests/AssetManager2_bench.cpp +++ b/libs/androidfw/tests/AssetManager2_bench.cpp @@ -81,17 +81,18 @@ static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld); -static void BM_AssetManagerGetResource(benchmark::State& state) { - GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, - basic::R::integer::number1, state); +static void BM_AssetManagerGetResource(benchmark::State& state, uint32_t resid) { + GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, state); } -BENCHMARK(BM_AssetManagerGetResource); +BENCHMARK_CAPTURE(BM_AssetManagerGetResource, number1, basic::R::integer::number1); +BENCHMARK_CAPTURE(BM_AssetManagerGetResource, deep_ref, basic::R::integer::deep_ref); -static void BM_AssetManagerGetResourceOld(benchmark::State& state) { - GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, - basic::R::integer::number1, state); +static void BM_AssetManagerGetResourceOld(benchmark::State& state, uint32_t resid) { + GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, + state); } -BENCHMARK(BM_AssetManagerGetResourceOld); +BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, number1, basic::R::integer::number1); +BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, deep_ref, basic::R::integer::deep_ref); static void BM_AssetManagerGetLibraryResource(benchmark::State& state) { GetResourceBenchmark( @@ -196,7 +197,7 @@ BENCHMARK(BM_AssetManagerGetResourceLocales); static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { AssetManager assets; if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/, - false /*isSystemAssets*/)) { + true /*isSystemAssets*/)) { state.SkipWithError("Failed to load assets"); return; } @@ -211,4 +212,44 @@ static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { } BENCHMARK(BM_AssetManagerGetResourceLocalesOld); +static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) { + std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath); + if (apk == nullptr) { + state.SkipWithError("Failed to load assets"); + return; + } + + AssetManager2 assets; + assets.SetApkAssets({apk.get()}); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + + while (state.KeepRunning()) { + config.sdkVersion = ~config.sdkVersion; + assets.SetConfiguration(config); + } +} +BENCHMARK(BM_AssetManagerSetConfigurationFramework); + +static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state) { + AssetManager assets; + if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/, + true /*isSystemAssets*/)) { + state.SkipWithError("Failed to load assets"); + return; + } + + const ResTable& table = assets.getResources(true); + + ResTable_config config; + memset(&config, 0, sizeof(config)); + + while (state.KeepRunning()) { + config.sdkVersion = ~config.sdkVersion; + assets.setConfiguration(config); + } +} +BENCHMARK(BM_AssetManagerSetConfigurationFrameworkOld); + } // namespace android diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp new file mode 100644 index 000000000000..fa300c50218a --- /dev/null +++ b/libs/androidfw/tests/AttributeResolution_bench.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "benchmark/benchmark.h" + +//#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" +#include "androidfw/AssetManager.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/AttributeResolution.h" +#include "androidfw/ResourceTypes.h" + +#include "BenchmarkHelpers.h" +#include "data/basic/R.h" +#include "data/styles/R.h" + +namespace app = com::android::app; +namespace basic = com::android::basic; + +namespace android { + +constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk"; +constexpr const static uint32_t Theme_Material_Light = 0x01030237u; + +static void BM_ApplyStyle(benchmark::State& state) { + std::unique_ptr<const ApkAssets> styles_apk = + ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); + if (styles_apk == nullptr) { + state.SkipWithError("failed to load assets"); + return; + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets({styles_apk.get()}); + + std::unique_ptr<Asset> asset = + assetmanager.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER); + if (asset == nullptr) { + state.SkipWithError("failed to load layout"); + return; + } + + ResXMLTree xml_tree; + if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) { + state.SkipWithError("corrupt xml layout"); + return; + } + + // Skip to the first tag. + while (xml_tree.next() != ResXMLParser::START_TAG) { + } + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + theme->ApplyStyle(app::R::style::StyleTwo); + + std::array<uint32_t, 6> attrs{{app::R::attr::attr_one, app::R::attr::attr_two, + app::R::attr::attr_three, app::R::attr::attr_four, + app::R::attr::attr_five, app::R::attr::attr_empty}}; + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + + while (state.KeepRunning()) { + ApplyStyle(theme.get(), &xml_tree, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(), + attrs.size(), values.data(), indices.data()); + } +} +BENCHMARK(BM_ApplyStyle); + +static void BM_ApplyStyleFramework(benchmark::State& state) { + std::unique_ptr<const ApkAssets> framework_apk = ApkAssets::Load(kFrameworkPath); + if (framework_apk == nullptr) { + state.SkipWithError("failed to load framework assets"); + return; + } + + std::unique_ptr<const ApkAssets> basic_apk = + ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); + if (basic_apk == nullptr) { + state.SkipWithError("failed to load assets"); + return; + } + + AssetManager2 assetmanager; + assetmanager.SetApkAssets({framework_apk.get(), basic_apk.get()}); + + ResTable_config device_config; + memset(&device_config, 0, sizeof(device_config)); + device_config.language[0] = 'e'; + device_config.language[1] = 'n'; + device_config.country[0] = 'U'; + device_config.country[1] = 'S'; + device_config.orientation = ResTable_config::ORIENTATION_PORT; + device_config.smallestScreenWidthDp = 700; + device_config.screenWidthDp = 700; + device_config.screenHeightDp = 1024; + device_config.sdkVersion = 27; + + Res_value value; + ResTable_config config; + uint32_t flags = 0u; + ApkAssetsCookie cookie = + assetmanager.GetResource(basic::R::layout::layoutt, false /*may_be_bag*/, + 0u /*density_override*/, &value, &config, &flags); + if (cookie == kInvalidCookie) { + state.SkipWithError("failed to find R.layout.layout"); + return; + } + + size_t len = 0u; + const char* layout_path = + assetmanager.GetStringPoolForCookie(cookie)->string8At(value.data, &len); + if (layout_path == nullptr || len == 0u) { + state.SkipWithError("failed to lookup layout path"); + return; + } + + std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset( + StringPiece(layout_path, len).to_string(), cookie, Asset::ACCESS_BUFFER); + if (asset == nullptr) { + state.SkipWithError("failed to load layout"); + return; + } + + ResXMLTree xml_tree; + if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) { + state.SkipWithError("corrupt xml layout"); + return; + } + + // Skip to the first tag. + while (xml_tree.next() != ResXMLParser::START_TAG) { + } + + std::unique_ptr<Theme> theme = assetmanager.NewTheme(); + theme->ApplyStyle(Theme_Material_Light); + + std::array<uint32_t, 92> attrs{ + {0x0101000e, 0x01010034, 0x01010095, 0x01010096, 0x01010097, 0x01010098, 0x01010099, + 0x0101009a, 0x0101009b, 0x010100ab, 0x010100af, 0x010100b0, 0x010100b1, 0x0101011f, + 0x01010120, 0x0101013f, 0x01010140, 0x0101014e, 0x0101014f, 0x01010150, 0x01010151, + 0x01010152, 0x01010153, 0x01010154, 0x01010155, 0x01010156, 0x01010157, 0x01010158, + 0x01010159, 0x0101015a, 0x0101015b, 0x0101015c, 0x0101015d, 0x0101015e, 0x0101015f, + 0x01010160, 0x01010161, 0x01010162, 0x01010163, 0x01010164, 0x01010165, 0x01010166, + 0x01010167, 0x01010168, 0x01010169, 0x0101016a, 0x0101016b, 0x0101016c, 0x0101016d, + 0x0101016e, 0x0101016f, 0x01010170, 0x01010171, 0x01010217, 0x01010218, 0x0101021d, + 0x01010220, 0x01010223, 0x01010224, 0x01010264, 0x01010265, 0x01010266, 0x010102c5, + 0x010102c6, 0x010102c7, 0x01010314, 0x01010315, 0x01010316, 0x0101035e, 0x0101035f, + 0x01010362, 0x01010374, 0x0101038c, 0x01010392, 0x01010393, 0x010103ac, 0x0101045d, + 0x010104b6, 0x010104b7, 0x010104d6, 0x010104d7, 0x010104dd, 0x010104de, 0x010104df, + 0x01010535, 0x01010536, 0x01010537, 0x01010538, 0x01010546, 0x01010567, 0x011100c9, + 0x011100ca}}; + + std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; + std::array<uint32_t, attrs.size() + 1> indices; + while (state.KeepRunning()) { + ApplyStyle(theme.get(), &xml_tree, 0x01010084u /*def_style_attr*/, 0u /*def_style_res*/, + attrs.data(), attrs.size(), values.data(), indices.data()); + } +} +BENCHMARK(BM_ApplyStyleFramework); + +} // namespace android diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp index 2d73ce8f8ee3..cc3053798e7b 100644 --- a/libs/androidfw/tests/AttributeResolution_test.cpp +++ b/libs/androidfw/tests/AttributeResolution_test.cpp @@ -21,6 +21,7 @@ #include "android-base/file.h" #include "android-base/logging.h" #include "android-base/macros.h" +#include "androidfw/AssetManager2.h" #include "TestHelpers.h" #include "data/styles/R.h" @@ -32,15 +33,14 @@ namespace android { class AttributeResolutionTest : public ::testing::Test { public: virtual void SetUp() override { - std::string contents; - ASSERT_TRUE(ReadFileFromZipToString( - GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents)); - ASSERT_EQ(NO_ERROR, table_.add(contents.data(), contents.size(), - 1 /*cookie*/, true /*copyData*/)); + styles_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk"); + ASSERT_NE(nullptr, styles_assets_); + assetmanager_.SetApkAssets({styles_assets_.get()}); } protected: - ResTable table_; + std::unique_ptr<const ApkAssets> styles_assets_; + AssetManager2 assetmanager_; }; class AttributeResolutionXmlTest : public AttributeResolutionTest { @@ -48,13 +48,12 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest { virtual void SetUp() override { AttributeResolutionTest::SetUp(); - std::string contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", - "res/layout/layout.xml", &contents)); + std::unique_ptr<Asset> asset = + assetmanager_.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER); + ASSERT_NE(nullptr, asset); - ASSERT_EQ(NO_ERROR, xml_parser_.setTo(contents.data(), contents.size(), - true /*copyData*/)); + ASSERT_EQ(NO_ERROR, + xml_parser_.setTo(asset->getBuffer(true), asset->getLength(), true /*copyData*/)); // Skip to the first tag. while (xml_parser_.next() != ResXMLParser::START_TAG) { @@ -66,14 +65,14 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest { }; TEST_F(AttributeResolutionTest, Theme) { - ResTable::Theme theme(table_); - ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo)); + std::unique_ptr<Theme> theme = assetmanager_.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo)); std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; - ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/, + ASSERT_TRUE(ResolveAttrs(theme.get(), 0u /*def_style_attr*/, 0u /*def_style_res*/, nullptr /*src_values*/, 0 /*src_values_length*/, attrs.data(), attrs.size(), values.data(), nullptr /*out_indices*/)); @@ -126,8 +125,8 @@ TEST_F(AttributeResolutionXmlTest, XmlParser) { R::attr::attr_four, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; - ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs.data(), attrs.size(), values.data(), - nullptr /*out_indices*/)); + ASSERT_TRUE(RetrieveAttributes(&assetmanager_, &xml_parser_, attrs.data(), attrs.size(), + values.data(), nullptr /*out_indices*/)); uint32_t* values_cursor = values.data(); EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]); @@ -171,15 +170,15 @@ TEST_F(AttributeResolutionXmlTest, XmlParser) { } TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) { - ResTable::Theme theme(table_); - ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo)); + std::unique_ptr<Theme> theme = assetmanager_.NewTheme(); + ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo)); std::array<uint32_t, 6> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three, R::attr::attr_four, R::attr::attr_five, R::attr::attr_empty}}; std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values; std::array<uint32_t, attrs.size() + 1> indices; - ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs.data(), + ApplyStyle(theme.get(), &xml_parser_, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(), attrs.size(), values.data(), indices.data()); const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC; diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp index 7149beef797f..faddfe599af4 100644 --- a/libs/androidfw/tests/BenchmarkHelpers.cpp +++ b/libs/androidfw/tests/BenchmarkHelpers.cpp @@ -33,19 +33,21 @@ void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTab } } + // Make sure to force creation of the ResTable first, or else the configuration doesn't get set. + const ResTable& table = assetmanager.getResources(true); if (config != nullptr) { assetmanager.setConfiguration(*config); } - const ResTable& table = assetmanager.getResources(true); - Res_value value; ResTable_config selected_config; uint32_t flags; + uint32_t last_ref = 0u; while (state.KeepRunning()) { - table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, - &selected_config); + ssize_t block = table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags, + &selected_config); + table.resolveReference(&value, block, &last_ref, &flags, &selected_config); } } @@ -72,10 +74,12 @@ void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_ Res_value value; ResTable_config selected_config; uint32_t flags; + uint32_t last_id = 0u; while (state.KeepRunning()) { - assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value, - &selected_config, &flags); + ApkAssetsCookie cookie = assetmanager.GetResource( + resid, false /* may_be_bag */, 0u /* density_override */, &value, &selected_config, &flags); + assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_id); } } diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index 37ddafb14fd3..bedebd66cb2f 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -16,6 +16,8 @@ #include "androidfw/LoadedArsc.h" +#include "androidfw/ResourceUtils.h" + #include "TestHelpers.h" #include "data/basic/R.h" #include "data/libclient/R.h" @@ -27,6 +29,13 @@ namespace basic = com::android::basic; namespace libclient = com::android::libclient; namespace sparse = com::android::sparse; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; + namespace android { TEST(LoadedArscTest, LoadSinglePackageArsc) { @@ -35,39 +44,24 @@ TEST(LoadedArscTest, LoadSinglePackageArsc) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); - - const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); - - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 24; - - FindEntryResult entry; + ASSERT_THAT(loaded_arsc, NotNull()); - ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry)); - ASSERT_NE(nullptr, entry.entry); -} + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(app::R::string::string_one)); + ASSERT_THAT(package, NotNull()); + EXPECT_THAT(package->GetPackageName(), StrEq("com.android.app")); + EXPECT_THAT(package->GetPackageId(), Eq(0x7f)); -TEST(LoadedArscTest, FindDefaultEntry) { - std::string contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents)); + const uint8_t type_index = get_type_id(app::R::string::string_one) - 1; + const uint16_t entry_index = get_entry_id(app::R::string::string_one); - std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); - desired_config.language[0] = 'd'; - desired_config.language[1] = 'e'; - - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry)); - ASSERT_NE(nullptr, entry.entry); + const ResTable_type* type = type_spec->types[0]; + ASSERT_THAT(type, NotNull()); + ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull()); } TEST(LoadedArscTest, LoadSparseEntryApp) { @@ -76,15 +70,22 @@ TEST(LoadedArscTest, LoadSparseEntryApp) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9)); + ASSERT_THAT(package, NotNull()); - ResTable_config config; - memset(&config, 0, sizeof(config)); - config.sdkVersion = 26; + const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1; + const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9); - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(sparse::R::integer::foo_9, config, &entry)); - ASSERT_NE(nullptr, entry.entry); + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + + const ResTable_type* type = type_spec->types[0]; + ASSERT_THAT(type, NotNull()); + ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull()); } TEST(LoadedArscTest, LoadSharedLibrary) { @@ -93,14 +94,13 @@ TEST(LoadedArscTest, LoadSharedLibrary) { &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_TRUE(packages[0]->IsDynamic()); - EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName()); - EXPECT_EQ(0, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.lib_one")); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0)); const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); @@ -114,25 +114,23 @@ TEST(LoadedArscTest, LoadAppLinkedAgainstSharedLibrary) { "resources.arsc", &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_FALSE(packages[0]->IsDynamic()); - EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.libclient")); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f)); const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap(); // The library has two dependencies. - ASSERT_EQ(2u, dynamic_pkg_map.size()); + ASSERT_THAT(dynamic_pkg_map, SizeIs(2u)); + EXPECT_THAT(dynamic_pkg_map[0].package_name, StrEq("com.android.lib_one")); + EXPECT_THAT(dynamic_pkg_map[0].package_id, Eq(0x02)); - EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name); - EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id); - - EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name); - EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id); + EXPECT_THAT(dynamic_pkg_map[1].package_name, StrEq("com.android.lib_two")); + EXPECT_THAT(dynamic_pkg_map[1].package_id, Eq(0x03)); } TEST(LoadedArscTest, LoadAppAsSharedLibrary) { @@ -143,13 +141,12 @@ TEST(LoadedArscTest, LoadAppAsSharedLibrary) { std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/, true /*load_as_shared_library*/); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); - ASSERT_EQ(1u, packages.size()); - + ASSERT_THAT(packages, SizeIs(1u)); EXPECT_TRUE(packages[0]->IsDynamic()); - EXPECT_EQ(0x7f, packages[0]->GetPackageId()); + EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f)); } TEST(LoadedArscTest, LoadFeatureSplit) { @@ -157,21 +154,27 @@ TEST(LoadedArscTest, LoadFeatureSplit) { ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc", &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); - ASSERT_NE(nullptr, loaded_arsc); + ASSERT_THAT(loaded_arsc, NotNull()); - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); + const LoadedPackage* package = + loaded_arsc->GetPackageById(get_package_id(basic::R::string::test3)); + ASSERT_THAT(package, NotNull()); - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry)); + uint8_t type_index = get_type_id(basic::R::string::test3) - 1; + uint8_t entry_index = get_entry_id(basic::R::string::test3); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); size_t len; - const char16_t* type_name16 = entry.type_string_ref.string16(&len); - ASSERT_NE(nullptr, type_name16); - ASSERT_NE(0u, len); + const char16_t* type_name16 = + package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1, &len); + ASSERT_THAT(type_name16, NotNull()); + EXPECT_THAT(util::Utf16ToUtf8(StringPiece16(type_name16, len)), StrEq("string")); - std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len)); - EXPECT_EQ(std::string("string"), type_name); + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], entry_index), NotNull()); } class MockLoadedIdmap : public LoadedIdmap { @@ -199,23 +202,33 @@ class MockLoadedIdmap : public LoadedIdmap { }; TEST(LoadedArscTest, LoadOverlay) { - std::string contents, overlay_contents; - ASSERT_TRUE( - ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents)); + std::string contents; ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc", - &overlay_contents)); + &contents)); MockLoadedIdmap loaded_idmap; std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap); - ASSERT_NE(nullptr, loaded_arsc); - - ResTable_config desired_config; - memset(&desired_config, 0, sizeof(desired_config)); - - FindEntryResult entry; - ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry)); + LoadedArsc::Load(StringPiece(contents), &loaded_idmap); + ASSERT_THAT(loaded_arsc, NotNull()); + + const LoadedPackage* package = loaded_arsc->GetPackageById(0x08u); + ASSERT_THAT(package, NotNull()); + + const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0x03u - 1); + ASSERT_THAT(type_spec, NotNull()); + ASSERT_THAT(type_spec->type_count, Ge(1u)); + ASSERT_THAT(type_spec->types[0], NotNull()); + + // The entry being overlaid doesn't exist at the original entry index. + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0001u), IsNull()); + + // Since this is an overlay, the actual entry ID must be mapped. + ASSERT_THAT(type_spec->idmap_entries, NotNull()); + uint16_t target_entry_id = 0u; + ASSERT_TRUE(LoadedIdmap::Lookup(type_spec->idmap_entries, 0x0001u, &target_entry_id)); + ASSERT_THAT(target_entry_id, Eq(0x0u)); + ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull()); } // structs with size fields (like Res_value, ResTable_entry) should be diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h index 43a995536d89..df0c642f4565 100644 --- a/libs/androidfw/tests/TestHelpers.h +++ b/libs/androidfw/tests/TestHelpers.h @@ -20,6 +20,7 @@ #include <string> #include "androidfw/ResourceTypes.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "CommonHelpers.h" diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h index 94a2a14ced87..b7e814fea079 100644 --- a/libs/androidfw/tests/data/basic/R.h +++ b/libs/androidfw/tests/data/basic/R.h @@ -34,6 +34,7 @@ struct R { struct layout { enum : uint32_t { main = 0x7f020000, + layoutt = 0x7f020001, }; }; @@ -55,6 +56,7 @@ struct R { number2 = 0x7f040001, ref1 = 0x7f040002, ref2 = 0x7f040003, + deep_ref = 0x7f040004, // From feature number3 = 0x80030000, diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk Binary files differindex 18ef75e91ded..1733b6a16546 100644 --- a/libs/androidfw/tests/data/basic/basic.apk +++ b/libs/androidfw/tests/data/basic/basic.apk diff --git a/libs/androidfw/tests/data/basic/res/layout/layout.xml b/libs/androidfw/tests/data/basic/res/layout/layout.xml new file mode 100644 index 000000000000..045ede454bca --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/layout/layout.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<Button xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/ok" + android:layout_width="0sp" + android:layout_height="fill_parent" + android:layout_weight="1" + android:layout_marginStart="2dip" + android:layout_marginEnd="2dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + android:text="@android:string/ok" />
\ No newline at end of file diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml index 6c474596b5cd..b3435629265b 100644 --- a/libs/androidfw/tests/data/basic/res/values/values.xml +++ b/libs/androidfw/tests/data/basic/res/values/values.xml @@ -22,6 +22,7 @@ <attr name="attr2" format="reference|integer" /> <public type="layout" name="main" id="0x7f020000" /> + <public type="layout" name="layout" id="0x7f020001" /> <public type="string" name="test1" id="0x7f030000" /> <string name="test1">test1</string> @@ -43,6 +44,18 @@ <public type="integer" name="ref2" id="0x7f040003" /> <integer name="ref2">12000</integer> + <public type="integer" name="deep_ref" id="0x7f040004" /> + <integer name="deep_ref">@integer/deep_ref_1</integer> + <integer name="deep_ref_1">@integer/deep_ref_2</integer> + <integer name="deep_ref_2">@integer/deep_ref_3</integer> + <integer name="deep_ref_3">@integer/deep_ref_4</integer> + <integer name="deep_ref_4">@integer/deep_ref_5</integer> + <integer name="deep_ref_5">@integer/deep_ref_6</integer> + <integer name="deep_ref_6">@integer/deep_ref_7</integer> + <integer name="deep_ref_7">@integer/deep_ref_8</integer> + <integer name="deep_ref_8">@integer/deep_ref_9</integer> + <integer name="deep_ref_9">100</integer> + <public type="style" name="Theme1" id="0x7f050000" /> <style name="Theme1"> <item name="com.android.basic:attr1">100</item> diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 4243e7eeef8c..6cd283a9063c 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -61,6 +61,8 @@ bool Properties::disableVsync = false; bool Properties::skpCaptureEnabled = false; bool Properties::enableRTAnimations = true; +bool Properties::runningInEmulator = false; + static int property_get_int(const char* key, int defaultValue) { char buf[PROPERTY_VALUE_MAX] = { '\0', @@ -135,6 +137,8 @@ bool Properties::load() { skpCaptureEnabled = property_get_bool("ro.debuggable", false) && property_get_bool(PROPERTY_CAPTURE_SKP_ENABLED, false); + runningInEmulator = property_get_bool(PROPERTY_QEMU_KERNEL, false); + return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) || (prevDebugStencilClip != debugStencilClip); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index af4b694fe86a..179b97bf6d9f 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -180,6 +180,11 @@ enum DebugLevel { */ #define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename" +/** + * Property for whether this is running in the emulator. + */ +#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -261,6 +266,8 @@ public: // Used for testing only to change the render pipeline. static void overrideRenderPipelineType(RenderPipelineType); + static bool runningInEmulator; + private: static ProfileType sProfileType; static bool sDisableProfileBars; diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 75e414e4b3e7..284fd836745c 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -158,12 +158,13 @@ private: void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount, float x, float y, minikin::Bidi bidiFlags, const Paint& origPaint, - const Typeface* typeface) { + const Typeface* typeface, minikin::MeasuredText* mt, int mtOffset) { // minikin may modify the original paint Paint paint(origPaint); minikin::Layout layout = - MinikinUtils::doLayout(&paint, bidiFlags, typeface, text, start, count, contextCount); + MinikinUtils::doLayout(&paint, bidiFlags, typeface, text, start, count, contextCount, + mt, mtOffset); x += MinikinUtils::xOffsetForTextAlign(&paint, layout); @@ -211,7 +212,8 @@ void Canvas::drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiF const Typeface* typeface) { Paint paintCopy(paint); minikin::Layout layout = - MinikinUtils::doLayout(&paintCopy, bidiFlags, typeface, text, 0, count, count); + MinikinUtils::doLayout(&paintCopy, bidiFlags, typeface, text, 0, count, count, nullptr, + 0); hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path); // Set align to left for drawing, as we don't want individual diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index cae4542b18e8..3ddf1c48b83f 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -34,6 +34,7 @@ class SkVertices; namespace minikin { class Layout; +class MeasuredText; enum class Bidi : uint8_t; } @@ -260,7 +261,8 @@ public: * and delegating the final draw to virtual drawGlyphs method. */ void drawText(const uint16_t* text, int start, int count, int contextCount, float x, float y, - minikin::Bidi bidiFlags, const Paint& origPaint, const Typeface* typeface); + minikin::Bidi bidiFlags, const Paint& origPaint, const Typeface* typeface, + minikin::MeasuredText* mt, int mtOffset); void drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiFlags, const SkPath& path, float hOffset, float vOffset, const Paint& paint, diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index bad766c7d5ef..ba877d395c8f 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -20,6 +20,7 @@ #include <log/log.h> +#include <minikin/MeasuredText.h> #include "Paint.h" #include "SkPathMeasure.h" #include "Typeface.h" @@ -49,11 +50,24 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, size_t start, - size_t count, size_t bufSize) { + size_t count, size_t bufSize, minikin::MeasuredText* mt, + int mtOffset) { minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); + const auto& fc = Typeface::resolveDefault(typeface)->fFontCollection; minikin::Layout layout; - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint, - Typeface::resolveDefault(typeface)->fFontCollection); + + if (mt == nullptr) { + layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint, fc); + return layout; + } + + if (mt->buildLayout(minikin::U16StringPiece(buf, bufSize), + minikin::Range(start, start + count), + minikinPaint, fc, bidiFlags, mtOffset, &layout)) { + return layout; + } + + layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint, fc); return layout; } @@ -64,7 +78,7 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface); return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinPaint, resolvedTypeface->fFontCollection, advances, - nullptr /* extent */, nullptr /* overhangs */); + nullptr /* extent */); } bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) { diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index 7036cbee79f9..124fe4f81fbe 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -29,6 +29,11 @@ #include "MinikinSkia.h" #include "Paint.h" #include "Typeface.h" +#include <log/log.h> + +namespace minikin { +class MeasuredText; +} // namespace minikin namespace android { @@ -39,7 +44,8 @@ public: ANDROID_API static minikin::Layout doLayout(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, - size_t start, size_t count, size_t bufSize); + size_t start, size_t count, size_t bufSize, + minikin::MeasuredText* mt, int mtOffset); ANDROID_API static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index c7a30141803d..2fa56f613144 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -19,6 +19,7 @@ #include <log/log.h> #include <thread> #include "FileBlobCache.h" +#include "Properties.h" #include "utils/TraceUtils.h" namespace android { @@ -43,7 +44,11 @@ ShaderCache& ShaderCache::get() { void ShaderCache::initShaderDiskCache() { ATRACE_NAME("initShaderDiskCache"); std::lock_guard<std::mutex> lock(mMutex); - if (mFilename.length() > 0) { + + // Emulators can switch between different renders either as part of config + // or snapshot migration. Also, program binaries may not work well on some + // desktop / laptop GPUs. Thus, disable the shader disk cache for emulator builds. + if (!Properties::runningInEmulator && mFilename.length() > 0) { mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename)); mInitialized = true; } diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 4a0d6ee19978..51cf772e8691 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -125,7 +125,7 @@ void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint SkPaint glyphPaint(paint); glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, - glyphPaint, nullptr); + glyphPaint, nullptr, nullptr /* measured text */, 0 /* measured text offset */); } void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index b4316ba9b1df..dcd37dafd495 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -793,7 +793,7 @@ public class AudioSystem public static native int getPrimaryOutputFrameCount(); public static native int getOutputLatency(int stream); - public static native int setLowRamDevice(boolean isLowRamDevice); + public static native int setLowRamDevice(boolean isLowRamDevice, long totalMemory); public static native int checkAudioFlinger(); public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation); diff --git a/media/java/android/media/DataSourceDesc.java b/media/java/android/media/DataSourceDesc.java new file mode 100644 index 000000000000..73fad7ad4bf3 --- /dev/null +++ b/media/java/android/media/DataSourceDesc.java @@ -0,0 +1,465 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.HttpCookie; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Structure for data source descriptor. + * + * Used by {@link MediaPlayer2#setDataSource(DataSourceDesc)} + * to set data source for playback. + * + * <p>Users should use {@link Builder} to change {@link DataSourceDesc}. + * + */ +public final class DataSourceDesc { + /* No data source has been set yet */ + public static final int TYPE_NONE = 0; + /* data source is type of MediaDataSource */ + public static final int TYPE_CALLBACK = 1; + /* data source is type of FileDescriptor */ + public static final int TYPE_FD = 2; + /* data source is type of Uri */ + public static final int TYPE_URI = 3; + + // intentionally less than long.MAX_VALUE + public static final long LONG_MAX = 0x7ffffffffffffffL; + + private int mType = TYPE_NONE; + + private Media2DataSource mMedia2DataSource; + + private FileDescriptor mFD; + private long mFDOffset = 0; + private long mFDLength = LONG_MAX; + + private Uri mUri; + private Map<String, String> mUriHeader; + private List<HttpCookie> mUriCookies; + private Context mUriContext; + + private long mId = 0; + private long mStartPositionMs = 0; + private long mEndPositionMs = LONG_MAX; + + private DataSourceDesc() { + } + + /** + * Return the Id of data source. + * @return the Id of data source + */ + public long getId() { + return mId; + } + + /** + * Return the position in milliseconds at which the playback will start. + * @return the position in milliseconds at which the playback will start + */ + public long getStartPosition() { + return mStartPositionMs; + } + + /** + * Return the position in milliseconds at which the playback will end. + * -1 means ending at the end of source content. + * @return the position in milliseconds at which the playback will end + */ + public long getEndPosition() { + return mEndPositionMs; + } + + /** + * Return the type of data source. + * @return the type of data source + */ + public int getType() { + return mType; + } + + /** + * Return the Media2DataSource of this data source. + * It's meaningful only when {@code getType} returns {@link #TYPE_CALLBACK}. + * @return the Media2DataSource of this data source + */ + public Media2DataSource getMedia2DataSource() { + return mMedia2DataSource; + } + + /** + * Return the FileDescriptor of this data source. + * It's meaningful only when {@code getType} returns {@link #TYPE_FD}. + * @return the FileDescriptor of this data source + */ + public FileDescriptor getFileDescriptor() { + return mFD; + } + + /** + * Return the offset associated with the FileDescriptor of this data source. + * It's meaningful only when {@code getType} returns {@link #TYPE_FD} and it has + * been set by the {@link Builder}. + * @return the offset associated with the FileDescriptor of this data source + */ + public long getFileDescriptorOffset() { + return mFDOffset; + } + + /** + * Return the content length associated with the FileDescriptor of this data source. + * It's meaningful only when {@code getType} returns {@link #TYPE_FD}. + * -1 means same as the length of source content. + * @return the content length associated with the FileDescriptor of this data source + */ + public long getFileDescriptorLength() { + return mFDLength; + } + + /** + * Return the Uri of this data source. + * It's meaningful only when {@code getType} returns {@link #TYPE_URI}. + * @return the Uri of this data source + */ + public Uri getUri() { + return mUri; + } + + /** + * Return the Uri headers of this data source. + * It's meaningful only when {@code getType} returns {@link #TYPE_URI}. + * @return the Uri headers of this data source + */ + public Map<String, String> getUriHeaders() { + if (mUriHeader == null) { + return null; + } + return new HashMap<String, String>(mUriHeader); + } + + /** + * Return the Uri cookies of this data source. + * It's meaningful only when {@code getType} returns {@link #TYPE_URI}. + * @return the Uri cookies of this data source + */ + public List<HttpCookie> getUriCookies() { + if (mUriCookies == null) { + return null; + } + return new ArrayList<HttpCookie>(mUriCookies); + } + + /** + * Return the Context used for resolving the Uri of this data source. + * It's meaningful only when {@code getType} returns {@link #TYPE_URI}. + * @return the Context used for resolving the Uri of this data source + */ + public Context getUriContext() { + return mUriContext; + } + + /** + * Builder class for {@link DataSourceDesc} objects. + * <p> Here is an example where <code>Builder</code> is used to define the + * {@link DataSourceDesc} to be used by a {@link MediaPlayer2} instance: + * + * <pre class="prettyprint"> + * DataSourceDesc oldDSD = mediaplayer2.getDataSourceDesc(); + * DataSourceDesc newDSD = new DataSourceDesc.Builder(oldDSD) + * .setStartPosition(1000) + * .setEndPosition(15000) + * .build(); + * mediaplayer2.setDataSourceDesc(newDSD); + * </pre> + */ + public static class Builder { + private int mType = TYPE_NONE; + + private Media2DataSource mMedia2DataSource; + + private FileDescriptor mFD; + private long mFDOffset = 0; + private long mFDLength = LONG_MAX; + + private Uri mUri; + private Map<String, String> mUriHeader; + private List<HttpCookie> mUriCookies; + private Context mUriContext; + + private long mId = 0; + private long mStartPositionMs = 0; + private long mEndPositionMs = LONG_MAX; + + /** + * Constructs a new Builder with the defaults. + */ + public Builder() { + } + + /** + * Constructs a new Builder from a given {@link DataSourceDesc} instance + * @param dsd the {@link DataSourceDesc} object whose data will be reused + * in the new Builder. + */ + public Builder(DataSourceDesc dsd) { + mType = dsd.mType; + mMedia2DataSource = dsd.mMedia2DataSource; + mFD = dsd.mFD; + mFDOffset = dsd.mFDOffset; + mFDLength = dsd.mFDLength; + mUri = dsd.mUri; + mUriHeader = dsd.mUriHeader; + mUriCookies = dsd.mUriCookies; + mUriContext = dsd.mUriContext; + + mId = dsd.mId; + mStartPositionMs = dsd.mStartPositionMs; + mEndPositionMs = dsd.mEndPositionMs; + } + + /** + * Combines all of the fields that have been set and return a new + * {@link DataSourceDesc} object. <code>IllegalStateException</code> will be + * thrown if there is conflict between fields. + * + * @return a new {@link DataSourceDesc} object + */ + public DataSourceDesc build() { + if (mType != TYPE_CALLBACK + && mType != TYPE_FD + && mType != TYPE_URI) { + throw new IllegalStateException("Illegal type: " + mType); + } + if (mStartPositionMs > mEndPositionMs) { + throw new IllegalStateException("Illegal start/end position: " + + mStartPositionMs + " : " + mEndPositionMs); + } + + DataSourceDesc dsd = new DataSourceDesc(); + dsd.mType = mType; + dsd.mMedia2DataSource = mMedia2DataSource; + dsd.mFD = mFD; + dsd.mFDOffset = mFDOffset; + dsd.mFDLength = mFDLength; + dsd.mUri = mUri; + dsd.mUriHeader = mUriHeader; + dsd.mUriCookies = mUriCookies; + dsd.mUriContext = mUriContext; + + dsd.mId = mId; + dsd.mStartPositionMs = mStartPositionMs; + dsd.mEndPositionMs = mEndPositionMs; + + return dsd; + } + + /** + * Sets the Id of this data source. + * + * @param id the Id of this data source + * @return the same Builder instance. + */ + public Builder setId(long id) { + mId = id; + return this; + } + + /** + * Sets the start position in milliseconds at which the playback will start. + * Any negative number is treated as 0. + * + * @param position the start position in milliseconds at which the playback will start + * @return the same Builder instance. + * + */ + public Builder setStartPosition(long position) { + if (position < 0) { + position = 0; + } + mStartPositionMs = position; + return this; + } + + /** + * Sets the end position in milliseconds at which the playback will end. + * Any negative number is treated as maximum length of the data source. + * + * @param position the end position in milliseconds at which the playback will end + * @return the same Builder instance. + */ + public Builder setEndPosition(long position) { + if (position < 0) { + position = LONG_MAX; + } + mEndPositionMs = position; + return this; + } + + /** + * Sets the data source (Media2DataSource) to use. + * + * @param m2ds the Media2DataSource for the media you want to play + * @return the same Builder instance. + * @throws NullPointerException if m2ds is null. + */ + public Builder setDataSource(Media2DataSource m2ds) { + Preconditions.checkNotNull(m2ds); + resetDataSource(); + mType = TYPE_CALLBACK; + mMedia2DataSource = m2ds; + return this; + } + + /** + * Sets the data source (FileDescriptor) to use. The FileDescriptor must be + * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility + * to close the file descriptor after the source has been used. + * + * @param fd the FileDescriptor for the file you want to play + * @return the same Builder instance. + * @throws NullPointerException if fd is null. + */ + public Builder setDataSource(FileDescriptor fd) { + Preconditions.checkNotNull(fd); + resetDataSource(); + mType = TYPE_FD; + mFD = fd; + return this; + } + + /** + * Sets the data source (FileDescriptor) to use. The FileDescriptor must be + * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility + * to close the file descriptor after the source has been used. + * + * Any negative number for offset is treated as 0. + * Any negative number for length is treated as maximum length of the data source. + * + * @param fd the FileDescriptor for the file you want to play + * @param offset the offset into the file where the data to be played starts, in bytes + * @param length the length in bytes of the data to be played + * @return the same Builder instance. + * @throws NullPointerException if fd is null. + */ + public Builder setDataSource(FileDescriptor fd, long offset, long length) { + Preconditions.checkNotNull(fd); + if (offset < 0) { + offset = 0; + } + if (length < 0) { + length = LONG_MAX; + } + resetDataSource(); + mType = TYPE_FD; + mFD = fd; + mFDOffset = offset; + mFDLength = length; + return this; + } + + /** + * Sets the data source as a content Uri. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play + * @return the same Builder instance. + * @throws NullPointerException if context or uri is null. + */ + public Builder setDataSource(@NonNull Context context, @NonNull Uri uri) { + Preconditions.checkNotNull(context, "context cannot be null"); + Preconditions.checkNotNull(uri, "uri cannot be null"); + resetDataSource(); + mType = TYPE_URI; + mUri = uri; + mUriContext = context; + return this; + } + + /** + * Sets the data source as a content Uri. + * + * To provide cookies for the subsequent HTTP requests, you can install your own default + * cookie handler and use other variants of setDataSource APIs instead. Alternatively, you + * can use this API to pass the cookies as a list of HttpCookie. If the app has not + * installed a CookieHandler already, {@link MediaPlayer2} will create a CookieManager + * and populates its CookieStore with the provided cookies when this data source is passed + * to {@link MediaPlayer2}. If the app has installed its own handler already, the handler + * is required to be of CookieManager type such that {@link MediaPlayer2} can update the + * manager’s CookieStore. + * + * <p><strong>Note</strong> that the cross domain redirection is allowed by default, + * but that can be changed with key/value pairs through the headers parameter with + * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to + * disallow or allow cross domain redirection. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play + * @param headers the headers to be sent together with the request for the data + * The headers must not include cookies. Instead, use the cookies param. + * @param cookies the cookies to be sent together with the request + * @return the same Builder instance. + * @throws NullPointerException if context or uri is null. + */ + public Builder setDataSource(@NonNull Context context, @NonNull Uri uri, + @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) { + Preconditions.checkNotNull(uri); + resetDataSource(); + mType = TYPE_URI; + mUri = uri; + if (headers != null) { + mUriHeader = new HashMap<String, String>(headers); + } + if (cookies != null) { + mUriCookies = new ArrayList<HttpCookie>(cookies); + } + mUriContext = context; + return this; + } + + private void resetDataSource() { + mType = TYPE_NONE; + mMedia2DataSource = null; + mFD = null; + mFDOffset = 0; + mFDLength = LONG_MAX; + mUri = null; + mUriHeader = null; + mUriCookies = null; + mUriContext = null; + } + } +} diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl index 078b611bae4c..b10a40bbb0b0 100644 --- a/media/java/android/media/IMediaSession2.aidl +++ b/media/java/android/media/IMediaSession2.aidl @@ -16,7 +16,6 @@ package android.media; -import android.media.session.PlaybackState; import android.media.IMediaSession2Callback; import android.os.Bundle; @@ -44,7 +43,7 @@ interface IMediaSession2 { ////////////////////////////////////////////////////////////////////////////////////////////// oneway void sendCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args); - PlaybackState getPlaybackState(); + Bundle getPlaybackState(); ////////////////////////////////////////////////////////////////////////////////////////////// // Get library service specific diff --git a/media/java/android/media/IMediaSession2Callback.aidl b/media/java/android/media/IMediaSession2Callback.aidl index 1664e01235d6..eb02fa7a6bd2 100644 --- a/media/java/android/media/IMediaSession2Callback.aidl +++ b/media/java/android/media/IMediaSession2Callback.aidl @@ -29,7 +29,7 @@ import android.media.IMediaSession2; * @hide */ oneway interface IMediaSession2Callback { - void onPlaybackStateChanged(in PlaybackState state); + void onPlaybackStateChanged(in Bundle state); /** * Called only when the controller is created with service's token. diff --git a/media/java/android/media/Media2DataSource.java b/media/java/android/media/Media2DataSource.java new file mode 100644 index 000000000000..8ee4a705b446 --- /dev/null +++ b/media/java/android/media/Media2DataSource.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.media; + +import java.io.Closeable; +import java.io.IOException; + +/** + * For supplying media data to the framework. Implement this if your app has + * special requirements for the way media data is obtained. + * + * <p class="note">Methods of this interface may be called on multiple different + * threads. There will be a thread synchronization point between each call to ensure that + * modifications to the state of your Media2DataSource are visible to future calls. This means + * you don't need to do your own synchronization unless you're modifying the + * Media2DataSource from another thread while it's being used by the framework.</p> + * + */ +public abstract class Media2DataSource implements Closeable { + /** + * Called to request data from the given position. + * + * Implementations should should write up to {@code size} bytes into + * {@code buffer}, and return the number of bytes written. + * + * Return {@code 0} if size is zero (thus no bytes are read). + * + * Return {@code -1} to indicate that end of stream is reached. + * + * @param position the position in the data source to read from. + * @param buffer the buffer to read the data into. + * @param offset the offset within buffer to read the data into. + * @param size the number of bytes to read. + * @throws IOException on fatal errors. + * @return the number of bytes read, or -1 if there was an error. + */ + public abstract int readAt(long position, byte[] buffer, int offset, int size) + throws IOException; + + /** + * Called to get the size of the data source. + * + * @throws IOException on fatal errors + * @return the size of data source in bytes, or -1 if the size is unknown. + */ + public abstract long getSize() throws IOException; +} diff --git a/media/java/android/media/Media2HTTPConnection.java b/media/java/android/media/Media2HTTPConnection.java new file mode 100644 index 000000000000..0d7825a0853f --- /dev/null +++ b/media/java/android/media/Media2HTTPConnection.java @@ -0,0 +1,385 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.net.NetworkUtils; +import android.os.StrictMode; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.Proxy; +import java.net.URL; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.NoRouteToHostException; +import java.net.ProtocolException; +import java.net.UnknownServiceException; +import java.util.HashMap; +import java.util.Map; + +import static android.media.MediaPlayer2.MEDIA_ERROR_UNSUPPORTED; + +/** @hide */ +public class Media2HTTPConnection { + private static final String TAG = "Media2HTTPConnection"; + private static final boolean VERBOSE = false; + + // connection timeout - 30 sec + private static final int CONNECT_TIMEOUT_MS = 30 * 1000; + + private long mCurrentOffset = -1; + private URL mURL = null; + private Map<String, String> mHeaders = null; + private HttpURLConnection mConnection = null; + private long mTotalSize = -1; + private InputStream mInputStream = null; + + private boolean mAllowCrossDomainRedirect = true; + private boolean mAllowCrossProtocolRedirect = true; + + // from com.squareup.okhttp.internal.http + private final static int HTTP_TEMP_REDIRECT = 307; + private final static int MAX_REDIRECTS = 20; + + public Media2HTTPConnection() { + CookieHandler cookieHandler = CookieHandler.getDefault(); + if (cookieHandler == null) { + Log.w(TAG, "Media2HTTPConnection: Unexpected. No CookieHandler found."); + } + } + + public boolean connect(String uri, String headers) { + if (VERBOSE) { + Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers); + } + + try { + disconnect(); + mAllowCrossDomainRedirect = true; + mURL = new URL(uri); + mHeaders = convertHeaderStringToMap(headers); + } catch (MalformedURLException e) { + return false; + } + + return true; + } + + private boolean parseBoolean(String val) { + try { + return Long.parseLong(val) != 0; + } catch (NumberFormatException e) { + return "true".equalsIgnoreCase(val) || + "yes".equalsIgnoreCase(val); + } + } + + /* returns true iff header is internal */ + private boolean filterOutInternalHeaders(String key, String val) { + if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) { + mAllowCrossDomainRedirect = parseBoolean(val); + // cross-protocol redirects are also controlled by this flag + mAllowCrossProtocolRedirect = mAllowCrossDomainRedirect; + } else { + return false; + } + return true; + } + + private Map<String, String> convertHeaderStringToMap(String headers) { + HashMap<String, String> map = new HashMap<String, String>(); + + String[] pairs = headers.split("\r\n"); + for (String pair : pairs) { + int colonPos = pair.indexOf(":"); + if (colonPos >= 0) { + String key = pair.substring(0, colonPos); + String val = pair.substring(colonPos + 1); + + if (!filterOutInternalHeaders(key, val)) { + map.put(key, val); + } + } + } + + return map; + } + + public void disconnect() { + teardownConnection(); + mHeaders = null; + mURL = null; + } + + private void teardownConnection() { + if (mConnection != null) { + if (mInputStream != null) { + try { + mInputStream.close(); + } catch (IOException e) { + } + mInputStream = null; + } + + mConnection.disconnect(); + mConnection = null; + + mCurrentOffset = -1; + } + } + + private static final boolean isLocalHost(URL url) { + if (url == null) { + return false; + } + + String host = url.getHost(); + + if (host == null) { + return false; + } + + try { + if (host.equalsIgnoreCase("localhost")) { + return true; + } + if (NetworkUtils.numericToInetAddress(host).isLoopbackAddress()) { + return true; + } + } catch (IllegalArgumentException iex) { + } + return false; + } + + private void seekTo(long offset) throws IOException { + teardownConnection(); + + try { + int response; + int redirectCount = 0; + + URL url = mURL; + + // do not use any proxy for localhost (127.0.0.1) + boolean noProxy = isLocalHost(url); + + while (true) { + if (noProxy) { + mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY); + } else { + mConnection = (HttpURLConnection)url.openConnection(); + } + mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS); + + // handle redirects ourselves if we do not allow cross-domain redirect + mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect); + + if (mHeaders != null) { + for (Map.Entry<String, String> entry : mHeaders.entrySet()) { + mConnection.setRequestProperty( + entry.getKey(), entry.getValue()); + } + } + + if (offset > 0) { + mConnection.setRequestProperty( + "Range", "bytes=" + offset + "-"); + } + + response = mConnection.getResponseCode(); + if (response != HttpURLConnection.HTTP_MULT_CHOICE && + response != HttpURLConnection.HTTP_MOVED_PERM && + response != HttpURLConnection.HTTP_MOVED_TEMP && + response != HttpURLConnection.HTTP_SEE_OTHER && + response != HTTP_TEMP_REDIRECT) { + // not a redirect, or redirect handled by HttpURLConnection + break; + } + + if (++redirectCount > MAX_REDIRECTS) { + throw new NoRouteToHostException("Too many redirects: " + redirectCount); + } + + String method = mConnection.getRequestMethod(); + if (response == HTTP_TEMP_REDIRECT && + !method.equals("GET") && !method.equals("HEAD")) { + // "If the 307 status code is received in response to a + // request other than GET or HEAD, the user agent MUST NOT + // automatically redirect the request" + throw new NoRouteToHostException("Invalid redirect"); + } + String location = mConnection.getHeaderField("Location"); + if (location == null) { + throw new NoRouteToHostException("Invalid redirect"); + } + url = new URL(mURL /* TRICKY: don't use url! */, location); + if (!url.getProtocol().equals("https") && + !url.getProtocol().equals("http")) { + throw new NoRouteToHostException("Unsupported protocol redirect"); + } + boolean sameProtocol = mURL.getProtocol().equals(url.getProtocol()); + if (!mAllowCrossProtocolRedirect && !sameProtocol) { + throw new NoRouteToHostException("Cross-protocol redirects are disallowed"); + } + boolean sameHost = mURL.getHost().equals(url.getHost()); + if (!mAllowCrossDomainRedirect && !sameHost) { + throw new NoRouteToHostException("Cross-domain redirects are disallowed"); + } + + if (response != HTTP_TEMP_REDIRECT) { + // update effective URL, unless it is a Temporary Redirect + mURL = url; + } + } + + if (mAllowCrossDomainRedirect) { + // remember the current, potentially redirected URL if redirects + // were handled by HttpURLConnection + mURL = mConnection.getURL(); + } + + if (response == HttpURLConnection.HTTP_PARTIAL) { + // Partial content, we cannot just use getContentLength + // because what we want is not just the length of the range + // returned but the size of the full content if available. + + String contentRange = + mConnection.getHeaderField("Content-Range"); + + mTotalSize = -1; + if (contentRange != null) { + // format is "bytes xxx-yyy/zzz + // where "zzz" is the total number of bytes of the + // content or '*' if unknown. + + int lastSlashPos = contentRange.lastIndexOf('/'); + if (lastSlashPos >= 0) { + String total = + contentRange.substring(lastSlashPos + 1); + + try { + mTotalSize = Long.parseLong(total); + } catch (NumberFormatException e) { + } + } + } + } else if (response != HttpURLConnection.HTTP_OK) { + throw new IOException(); + } else { + mTotalSize = mConnection.getContentLength(); + } + + if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) { + // Some servers simply ignore "Range" requests and serve + // data from the start of the content. + throw new ProtocolException(); + } + + mInputStream = + new BufferedInputStream(mConnection.getInputStream()); + + mCurrentOffset = offset; + } catch (IOException e) { + mTotalSize = -1; + teardownConnection(); + mCurrentOffset = -1; + + throw e; + } + } + + public int readAt(long offset, byte[] data, int size) { + StrictMode.ThreadPolicy policy = + new StrictMode.ThreadPolicy.Builder().permitAll().build(); + + StrictMode.setThreadPolicy(policy); + + try { + if (offset != mCurrentOffset) { + seekTo(offset); + } + + int n = mInputStream.read(data, 0, size); + + if (n == -1) { + // InputStream signals EOS using a -1 result, our semantics + // are to return a 0-length read. + n = 0; + } + + mCurrentOffset += n; + + if (VERBOSE) { + Log.d(TAG, "readAt " + offset + " / " + size + " => " + n); + } + + return n; + } catch (ProtocolException e) { + Log.w(TAG, "readAt " + offset + " / " + size + " => " + e); + return MEDIA_ERROR_UNSUPPORTED; + } catch (NoRouteToHostException e) { + Log.w(TAG, "readAt " + offset + " / " + size + " => " + e); + return MEDIA_ERROR_UNSUPPORTED; + } catch (UnknownServiceException e) { + Log.w(TAG, "readAt " + offset + " / " + size + " => " + e); + return MEDIA_ERROR_UNSUPPORTED; + } catch (IOException e) { + if (VERBOSE) { + Log.d(TAG, "readAt " + offset + " / " + size + " => -1"); + } + return -1; + } catch (Exception e) { + if (VERBOSE) { + Log.d(TAG, "unknown exception " + e); + Log.d(TAG, "readAt " + offset + " / " + size + " => -1"); + } + return -1; + } + } + + public long getSize() { + if (mConnection == null) { + try { + seekTo(0); + } catch (IOException e) { + return -1; + } + } + + return mTotalSize; + } + + public String getMIMEType() { + if (mConnection == null) { + try { + seekTo(0); + } catch (IOException e) { + return "application/octet-stream"; + } + } + + return mConnection.getContentType(); + } + + public String getUri() { + return mURL.toString(); + } +} diff --git a/media/java/android/media/Media2HTTPService.java b/media/java/android/media/Media2HTTPService.java new file mode 100644 index 000000000000..957acecab13a --- /dev/null +++ b/media/java/android/media/Media2HTTPService.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.util.Log; + +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookieStore; +import java.net.HttpCookie; +import java.util.List; + +/** @hide */ +public class Media2HTTPService { + private static final String TAG = "Media2HTTPService"; + private List<HttpCookie> mCookies; + private Boolean mCookieStoreInitialized = new Boolean(false); + + public Media2HTTPService(List<HttpCookie> cookies) { + mCookies = cookies; + Log.v(TAG, "Media2HTTPService(" + this + "): Cookies: " + cookies); + } + + public Media2HTTPConnection makeHTTPConnection() { + + synchronized (mCookieStoreInitialized) { + // Only need to do it once for all connections + if ( !mCookieStoreInitialized ) { + CookieHandler cookieHandler = CookieHandler.getDefault(); + if (cookieHandler == null) { + cookieHandler = new CookieManager(); + CookieHandler.setDefault(cookieHandler); + Log.v(TAG, "makeHTTPConnection: CookieManager created: " + cookieHandler); + } else { + Log.v(TAG, "makeHTTPConnection: CookieHandler (" + cookieHandler + ") exists."); + } + + // Applying the bootstrapping cookies + if ( mCookies != null ) { + if ( cookieHandler instanceof CookieManager ) { + CookieManager cookieManager = (CookieManager)cookieHandler; + CookieStore store = cookieManager.getCookieStore(); + for ( HttpCookie cookie : mCookies ) { + try { + store.add(null, cookie); + } catch ( Exception e ) { + Log.v(TAG, "makeHTTPConnection: CookieStore.add" + e); + } + //for extended debugging when needed + //Log.v(TAG, "MediaHTTPConnection adding Cookie[" + cookie.getName() + + // "]: " + cookie); + } + } else { + Log.w(TAG, "makeHTTPConnection: The installed CookieHandler is not a " + + "CookieManager. Can’t add the provided cookies to the cookie " + + "store."); + } + } // mCookies + + mCookieStoreInitialized = true; + + Log.v(TAG, "makeHTTPConnection(" + this + "): cookieHandler: " + cookieHandler + + " Cookies: " + mCookies); + } // mCookieStoreInitialized + } // synchronized + + return new Media2HTTPConnection(); + } + + /* package private */ static Media2HTTPService createHTTPService(String path) { + return createHTTPService(path, null); + } + + // when cookies are provided + static Media2HTTPService createHTTPService(String path, List<HttpCookie> cookies) { + if (path.startsWith("http://") || path.startsWith("https://")) { + return (new Media2HTTPService(cookies)); + } else if (path.startsWith("widevine://")) { + Log.d(TAG, "Widevine classic is no longer supported"); + } + + return null; + } +} diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java index 33377bc69668..be4be3fc5e61 100644 --- a/media/java/android/media/MediaBrowser2.java +++ b/media/java/android/media/MediaBrowser2.java @@ -99,14 +99,14 @@ public class MediaBrowser2 extends MediaController2 { @Nullable Bundle options, @Nullable List<MediaItem2> result) { } } - public MediaBrowser2(Context context, SessionToken token, BrowserCallback callback, + public MediaBrowser2(Context context, SessionToken2 token, BrowserCallback callback, Executor executor) { super(context, token, callback, executor); mProvider = (MediaBrowser2Provider) getProvider(); } @Override - MediaBrowser2Provider createProvider(Context context, SessionToken token, + MediaBrowser2Provider createProvider(Context context, SessionToken2 token, ControllerCallback callback, Executor executor) { return ApiLoader.getProvider(context) .createMediaBrowser2(this, context, token, (BrowserCallback) callback, executor); diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index f41e33f7c102..44d909972e37 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -2639,7 +2639,8 @@ public final class MediaCodecInfo { /** * Returns the supported range of quality values. * - * @hide + * Quality is implementation-specific. As a general rule, a higher quality + * setting results in a better image quality and a lower compression ratio. */ public Range<Integer> getQualityRange() { return mQualityRange; @@ -2751,7 +2752,7 @@ public final class MediaCodecInfo { } if (info.containsKey("feature-bitrate-modes")) { for (String mode: info.getString("feature-bitrate-modes").split(",")) { - mBitControl |= parseBitrateMode(mode); + mBitControl |= (1 << parseBitrateMode(mode)); } } diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index dca102706645..d669bc1292a1 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -54,7 +54,7 @@ import java.util.concurrent.Executor; * <p> * A controller can be created through token from {@link MediaSessionManager} if you hold the * signature|privileged permission "android.permission.MEDIA_CONTENT_CONTROL" permission or are - * an enabled notification listener or by getting a {@link SessionToken} directly the + * an enabled notification listener or by getting a {@link SessionToken2} directly the * the session owner. * <p> * MediaController2 objects are thread-safe. @@ -234,7 +234,7 @@ public class MediaController2 implements AutoCloseable { private final MediaController2Provider mProvider; /** - * Create a {@link MediaController2} from the {@link SessionToken}. This connects to the session + * Create a {@link MediaController2} from the {@link SessionToken2}. This connects to the session * and may wake up the service if it's not available. * * @param context Context @@ -243,7 +243,7 @@ public class MediaController2 implements AutoCloseable { * @param executor executor to run callbacks on. */ // TODO(jaewan): Put @CallbackExecutor to the constructor. - public MediaController2(@NonNull Context context, @NonNull SessionToken token, + public MediaController2(@NonNull Context context, @NonNull SessionToken2 token, @NonNull ControllerCallback callback, @NonNull Executor executor) { super(); @@ -256,7 +256,7 @@ public class MediaController2 implements AutoCloseable { } MediaController2Provider createProvider(@NonNull Context context, - @NonNull SessionToken token, @NonNull ControllerCallback callback, + @NonNull SessionToken2 token, @NonNull ControllerCallback callback, @NonNull Executor executor) { return ApiLoader.getProvider(context) .createMediaController2(this, context, token, callback, executor); @@ -281,7 +281,8 @@ public class MediaController2 implements AutoCloseable { /** * @return token */ - public @NonNull SessionToken getSessionToken() { + public @NonNull + SessionToken2 getSessionToken() { return mProvider.getSessionToken_impl(); } diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index c6496ebba8bf..e9128e4c827d 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -601,8 +601,6 @@ public final class MediaFormat { * codec specific, but lower values generally result in more efficient * (smaller-sized) encoding. * - * @hide - * * @see MediaCodecInfo.EncoderCapabilities#getQualityRange() */ public static final String KEY_QUALITY = "quality"; diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java index b98936e6c870..d7e43ec9962a 100644 --- a/media/java/android/media/MediaLibraryService2.java +++ b/media/java/android/media/MediaLibraryService2.java @@ -16,6 +16,7 @@ package android.media; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; @@ -30,6 +31,7 @@ import android.os.Bundle; import android.service.media.MediaBrowserService.BrowserRoot; import java.util.List; +import java.util.concurrent.Executor; /** * Base class for media library services. @@ -67,20 +69,21 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { private final MediaLibrarySessionProvider mProvider; MediaLibrarySession(Context context, MediaPlayerBase player, String id, - SessionCallback callback, VolumeProvider volumeProvider, + Executor callbackExecutor, SessionCallback callback, VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity) { - super(context, player, id, callback, volumeProvider, ratingType, sessionActivity); + super(context, player, id, callbackExecutor, callback, volumeProvider, ratingType, + sessionActivity); mProvider = (MediaLibrarySessionProvider) getProvider(); } @Override MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id, - SessionCallback callback, VolumeProvider volumeProvider, int ratingType, - PendingIntent sessionActivity) { + Executor callbackExecutor, SessionCallback callback, VolumeProvider volumeProvider, + int ratingType, PendingIntent sessionActivity) { return ApiLoader.getProvider(context) .createMediaLibraryService2MediaLibrarySession(this, context, player, id, - (MediaLibrarySessionCallback) callback, volumeProvider, ratingType, - sessionActivity); + callbackExecutor, (MediaLibrarySessionCallback) callback, + volumeProvider, ratingType, sessionActivity); } /** @@ -206,23 +209,25 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { extends BuilderBase<MediaLibrarySessionBuilder, MediaLibrarySessionCallback> { public MediaLibrarySessionBuilder( @NonNull Context context, @NonNull MediaPlayerBase player, + @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull MediaLibrarySessionCallback callback) { super(context, player); - setSessionCallback(callback); + setSessionCallback(callbackExecutor, callback); } @Override public MediaLibrarySessionBuilder setSessionCallback( + @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull MediaLibrarySessionCallback callback) { if (callback == null) { throw new IllegalArgumentException("MediaLibrarySessionCallback cannot be null"); } - return super.setSessionCallback(callback); + return super.setSessionCallback(callbackExecutor, callback); } @Override public MediaLibrarySession build() throws IllegalStateException { - return new MediaLibrarySession(mContext, mPlayer, mId, mCallback, + return new MediaLibrarySession(mContext, mPlayer, mId, mCallbackExecutor, mCallback, mVolumeProvider, mRatingType, mSessionActivity); } } diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java new file mode 100644 index 000000000000..d36df845cc2e --- /dev/null +++ b/media/java/android/media/MediaPlayer2.java @@ -0,0 +1,2476 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.net.Uri; +import android.os.Handler; +import android.os.Parcel; +import android.os.PersistableBundle; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.media.MediaDrm; +import android.media.MediaFormat; +import android.media.MediaPlayer2Impl; +import android.media.MediaTimeProvider; +import android.media.PlaybackParams; +import android.media.SubtitleController; +import android.media.SubtitleController.Anchor; +import android.media.SubtitleData; +import android.media.SubtitleTrack.RenderingWidget; +import android.media.SyncParams; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.lang.AutoCloseable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetSocketAddress; +import java.util.concurrent.Executor; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + + +/** + * MediaPlayer2 class can be used to control playback + * of audio/video files and streams. An example on how to use the methods in + * this class can be found in {@link android.widget.VideoView}. + * + * <p>Topics covered here are: + * <ol> + * <li><a href="#StateDiagram">State Diagram</a> + * <li><a href="#Valid_and_Invalid_States">Valid and Invalid States</a> + * <li><a href="#Permissions">Permissions</a> + * <li><a href="#Callbacks">Register informational and error callbacks</a> + * </ol> + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about how to use MediaPlayer2, read the + * <a href="{@docRoot}guide/topics/media/mediaplayer.html">Media Playback</a> developer guide.</p> + * </div> + * + * <a name="StateDiagram"></a> + * <h3>State Diagram</h3> + * + * <p>Playback control of audio/video files and streams is managed as a state + * machine. The following diagram shows the life cycle and the states of a + * MediaPlayer2 object driven by the supported playback control operations. + * The ovals represent the states a MediaPlayer2 object may reside + * in. The arcs represent the playback control operations that drive the object + * state transition. There are two types of arcs. The arcs with a single arrow + * head represent synchronous method calls, while those with + * a double arrow head represent asynchronous method calls.</p> + * + * <p><img src="../../../images/mediaplayer_state_diagram.gif" + * alt="MediaPlayer State diagram" + * border="0" /></p> + * + * <p>From this state diagram, one can see that a MediaPlayer2 object has the + * following states:</p> + * <ul> + * <li>When a MediaPlayer2 object is just created using <code>new</code> or + * after {@link #reset()} is called, it is in the <em>Idle</em> state; and after + * {@link #close()} is called, it is in the <em>End</em> state. Between these + * two states is the life cycle of the MediaPlayer2 object. + * <ul> + * <li>There is a subtle but important difference between a newly constructed + * MediaPlayer2 object and the MediaPlayer2 object after {@link #reset()} + * is called. It is a programming error to invoke methods such + * as {@link #getCurrentPosition()}, + * {@link #getDuration()}, {@link #getVideoHeight()}, + * {@link #getVideoWidth()}, {@link #setAudioAttributes(AudioAttributes)}, + * {@link #setVolume(float, float)}, {@link #pause()}, {@link #play()}, + * {@link #seekTo(long, int)} or + * {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these + * methods is called right after a MediaPlayer2 object is constructed, + * the user supplied callback method OnErrorListener.onError() won't be + * called by the internal player engine and the object state remains + * unchanged; but if these methods are called right after {@link #reset()}, + * the user supplied callback method OnErrorListener.onError() will be + * invoked by the internal player engine and the object will be + * transfered to the <em>Error</em> state. </li> + * <li>It is also recommended that once + * a MediaPlayer2 object is no longer being used, call {@link #close()} immediately + * so that resources used by the internal player engine associated with the + * MediaPlayer2 object can be released immediately. Resource may include + * singleton resources such as hardware acceleration components and + * failure to call {@link #close()} may cause subsequent instances of + * MediaPlayer2 objects to fallback to software implementations or fail + * altogether. Once the MediaPlayer2 + * object is in the <em>End</em> state, it can no longer be used and + * there is no way to bring it back to any other state. </li> + * <li>Furthermore, + * the MediaPlayer2 objects created using <code>new</code> is in the + * <em>Idle</em> state. + * </li> + * </ul> + * </li> + * <li>In general, some playback control operation may fail due to various + * reasons, such as unsupported audio/video format, poorly interleaved + * audio/video, resolution too high, streaming timeout, and the like. + * Thus, error reporting and recovery is an important concern under + * these circumstances. Sometimes, due to programming errors, invoking a playback + * control operation in an invalid state may also occur. Under all these + * error conditions, the internal player engine invokes a user supplied + * EventCallback.onError() method if an EventCallback has been + * registered beforehand via + * {@link #registerEventCallback(Executor, EventCallback)}. + * <ul> + * <li>It is important to note that once an error occurs, the + * MediaPlayer2 object enters the <em>Error</em> state (except as noted + * above), even if an error listener has not been registered by the application.</li> + * <li>In order to reuse a MediaPlayer2 object that is in the <em> + * Error</em> state and recover from the error, + * {@link #reset()} can be called to restore the object to its <em>Idle</em> + * state.</li> + * <li>It is good programming practice to have your application + * register a OnErrorListener to look out for error notifications from + * the internal player engine.</li> + * <li>IllegalStateException is + * thrown to prevent programming errors such as calling + * {@link #prepareAsync()}, {@link #setDataSource(DataSourceDesc)}, or + * {@code setPlaylist} methods in an invalid state. </li> + * </ul> + * </li> + * <li>Calling + * {@link #setDataSource(DataSourceDesc)}, or + * {@code setPlaylist} transfers a + * MediaPlayer2 object in the <em>Idle</em> state to the + * <em>Initialized</em> state. + * <ul> + * <li>An IllegalStateException is thrown if + * setDataSource() or setPlaylist() is called in any other state.</li> + * <li>It is good programming + * practice to always look out for <code>IllegalArgumentException</code> + * and <code>IOException</code> that may be thrown from + * <code>setDataSource</code> and <code>setPlaylist</code> methods.</li> + * </ul> + * </li> + * <li>A MediaPlayer2 object must first enter the <em>Prepared</em> state + * before playback can be started. + * <ul> + * <li>There are an asynchronous way that the <em>Prepared</em> state can be reached: + * a call to {@link #prepareAsync()} (asynchronous) which + * first transfers the object to the <em>Preparing</em> state after the + * call returns (which occurs almost right way) while the internal + * player engine continues working on the rest of preparation work + * until the preparation work completes. When the preparation completes, + * the internal player engine then calls a user supplied callback method, + * onInfo() of the EventCallback interface with {@link #MEDIA_INFO_PREPARED}, if an + * EventCallback is registered beforehand via + * {@link #registerEventCallback(Executor, EventCallback)}.</li> + * <li>It is important to note that + * the <em>Preparing</em> state is a transient state, and the behavior + * of calling any method with side effect while a MediaPlayer2 object is + * in the <em>Preparing</em> state is undefined.</li> + * <li>An IllegalStateException is + * thrown if {@link #prepareAsync()} is called in + * any other state.</li> + * <li>While in the <em>Prepared</em> state, properties + * such as audio/sound volume, screenOnWhilePlaying, looping can be + * adjusted by invoking the corresponding set methods.</li> + * </ul> + * </li> + * <li>To start the playback, {@link #play()} must be called. After + * {@link #play()} returns successfully, the MediaPlayer2 object is in the + * <em>Started</em> state. {@link #isPlaying()} can be called to test + * whether the MediaPlayer2 object is in the <em>Started</em> state. + * <ul> + * <li>While in the <em>Started</em> state, the internal player engine calls + * a user supplied EventCallback.onBufferingUpdate() callback + * method if an EventCallback has been registered beforehand + * via {@link #registerEventCallback(Executor, EventCallback)}. + * This callback allows applications to keep track of the buffering status + * while streaming audio/video.</li> + * <li>Calling {@link #play()} has not effect + * on a MediaPlayer2 object that is already in the <em>Started</em> state.</li> + * </ul> + * </li> + * <li>Playback can be paused and stopped, and the current playback position + * can be adjusted. Playback can be paused via {@link #pause()}. When the call to + * {@link #pause()} returns, the MediaPlayer2 object enters the + * <em>Paused</em> state. Note that the transition from the <em>Started</em> + * state to the <em>Paused</em> state and vice versa happens + * asynchronously in the player engine. It may take some time before + * the state is updated in calls to {@link #isPlaying()}, and it can be + * a number of seconds in the case of streamed content. + * <ul> + * <li>Calling {@link #play()} to resume playback for a paused + * MediaPlayer2 object, and the resumed playback + * position is the same as where it was paused. When the call to + * {@link #play()} returns, the paused MediaPlayer2 object goes back to + * the <em>Started</em> state.</li> + * <li>Calling {@link #pause()} has no effect on + * a MediaPlayer2 object that is already in the <em>Paused</em> state.</li> + * </ul> + * </li> + * <li>The playback position can be adjusted with a call to + * {@link #seekTo(long, int)}. + * <ul> + * <li>Although the asynchronuous {@link #seekTo(long, int)} + * call returns right away, the actual seek operation may take a while to + * finish, especially for audio/video being streamed. When the actual + * seek operation completes, the internal player engine calls a user + * supplied EventCallback.onInfo() with {@link #MEDIA_INFO_COMPLETE_CALL_SEEK} + * if an EventCallback has been registered beforehand via + * {@link #registerEventCallback(Executor, EventCallback)}.</li> + * <li>Please + * note that {@link #seekTo(long, int)} can also be called in the other states, + * such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted + * </em> state. When {@link #seekTo(long, int)} is called in those states, + * one video frame will be displayed if the stream has video and the requested + * position is valid. + * </li> + * <li>Furthermore, the actual current playback position + * can be retrieved with a call to {@link #getCurrentPosition()}, which + * is helpful for applications such as a Music player that need to keep + * track of the playback progress.</li> + * </ul> + * </li> + * <li>When the playback reaches the end of stream, the playback completes. + * <ul> + * <li>If the looping mode was being set to one of the values of + * {@link #LOOPING_MODE_FULL}, {@link #LOOPING_MODE_SINGLE} or + * {@link #LOOPING_MODE_SHUFFLE} with + * {@link #setLoopingMode(int)}, the MediaPlayer2 object shall remain in + * the <em>Started</em> state.</li> + * <li>If the looping mode was set to <var>false + * </var>, the player engine calls a user supplied callback method, + * EventCallback.onCompletion(), if an EventCallback is registered + * beforehand via {@link #registerEventCallback(Executor, EventCallback)}. + * The invoke of the callback signals that the object is now in the <em> + * PlaybackCompleted</em> state.</li> + * <li>While in the <em>PlaybackCompleted</em> + * state, calling {@link #play()} can restart the playback from the + * beginning of the audio/video source.</li> + * </ul> + * + * + * <a name="Valid_and_Invalid_States"></a> + * <h3>Valid and invalid states</h3> + * + * <table border="0" cellspacing="0" cellpadding="0"> + * <tr><td>Method Name </p></td> + * <td>Valid Sates </p></td> + * <td>Invalid States </p></td> + * <td>Comments </p></td></tr> + * <tr><td>attachAuxEffect </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> + * <td>{Idle, Error} </p></td> + * <td>This method must be called after setDataSource or setPlaylist. + * Calling it does not change the object state. </p></td></tr> + * <tr><td>getAudioSessionId </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>getCurrentPosition </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted} </p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change the + * state. Calling this method in an invalid state transfers the object + * to the <em>Error</em> state. </p></td></tr> + * <tr><td>getDuration </p></td> + * <td>{Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> + * <td>{Idle, Initialized, Error} </p></td> + * <td>Successful invoke of this method in a valid state does not change the + * state. Calling this method in an invalid state transfers the object + * to the <em>Error</em> state. </p></td></tr> + * <tr><td>getVideoHeight </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change the + * state. Calling this method in an invalid state transfers the object + * to the <em>Error</em> state. </p></td></tr> + * <tr><td>getVideoWidth </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change + * the state. Calling this method in an invalid state transfers the + * object to the <em>Error</em> state. </p></td></tr> + * <tr><td>isPlaying </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change + * the state. Calling this method in an invalid state transfers the + * object to the <em>Error</em> state. </p></td></tr> + * <tr><td>pause </p></td> + * <td>{Started, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Prepared, Stopped, Error}</p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Paused</em> state. Calling this method in an + * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>prepareAsync </p></td> + * <td>{Initialized, Stopped} </p></td> + * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Preparing</em> state. Calling this method in an + * invalid state throws an IllegalStateException.</p></td></tr> + * <tr><td>release </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>After {@link #close()}, the object is no longer available. </p></td></tr> + * <tr><td>reset </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted, Error}</p></td> + * <td>{}</p></td> + * <td>After {@link #reset()}, the object is like being just created.</p></td></tr> + * <tr><td>seekTo </p></td> + * <td>{Prepared, Started, Paused, PlaybackCompleted} </p></td> + * <td>{Idle, Initialized, Stopped, Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change + * the state. Calling this method in an invalid state transfers the + * object to the <em>Error</em> state. </p></td></tr> + * <tr><td>setAudioAttributes </p></td> + * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method does not change the state. In order for the + * target audio attributes type to become effective, this method must be called before + * prepareAsync().</p></td></tr> + * <tr><td>setAudioSessionId </p></td> + * <td>{Idle} </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, + * Error} </p></td> + * <td>This method must be called in idle state as the audio session ID must be known before + * calling setDataSource or setPlaylist. Calling it does not change the object + * state. </p></td></tr> + * <tr><td>setAudioStreamType (deprecated)</p></td> + * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method does not change the state. In order for the + * target audio stream type to become effective, this method must be called before + * prepareAsync().</p></td></tr> + * <tr><td>setAuxEffectSendLevel </p></td> + * <td>any</p></td> + * <td>{} </p></td> + * <td>Calling this method does not change the object state. </p></td></tr> + * <tr><td>setDataSource </p></td> + * <td>{Idle} </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, + * Error} </p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Initialized</em> state. Calling this method in an + * invalid state throws an IllegalStateException.</p></td></tr> + * <tr><td>setPlaylist </p></td> + * <td>{Idle} </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, + * Error} </p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Initialized</em> state. Calling this method in an + * invalid state throws an IllegalStateException.</p></td></tr> + * <tr><td>setDisplay </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>setSurface </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>setLoopingMode </p></td> + * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change + * the state. Calling this method in an + * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>isLooping </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>registerDrmEventCallback </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>registerEventCallback </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>setPlaybackParams</p></td> + * <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td> + * <td>{Idle, Stopped} </p></td> + * <td>This method will change state in some cases, depending on when it's called. + * </p></td></tr> + * <tr><td>setVolume </p></td> + * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method does not change the state. + * <tr><td>play </p></td> + * <td>{Prepared, Started, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Stopped, Error}</p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Started</em> state. Calling this method in an + * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>stop </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Stopped</em> state. Calling this method in an + * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>getTrackInfo </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>selectTrack </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>deselectTrack </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * + * </table> + * + * <a name="Permissions"></a> + * <h3>Permissions</h3> + * <p>One may need to declare a corresponding WAKE_LOCK permission {@link + * android.R.styleable#AndroidManifestUsesPermission <uses-permission>} + * element. + * + * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission + * when used with network-based content. + * + * <a name="Callbacks"></a> + * <h3>Callbacks</h3> + * <p>Applications may want to register for informational and error + * events in order to be informed of some internal state update and + * possible runtime errors during playback or streaming. Registration for + * these events is done by properly setting the appropriate listeners (via calls + * to + * {@link #registerEventCallback(Executor, EventCallback)}, + * {@link #registerDrmEventCallback(Executor, DrmEventCallback)}). + * In order to receive the respective callback + * associated with these listeners, applications are required to create + * MediaPlayer2 objects on a thread with its own Looper running (main UI + * thread by default has a Looper running). + * + */ +public abstract class MediaPlayer2 implements SubtitleController.Listener + , AudioRouting + , AutoCloseable +{ + /** + Constant to retrieve only the new metadata since the last + call. + // FIXME: unhide. + // FIXME: add link to getMetadata(boolean, boolean) + {@hide} + */ + public static final boolean METADATA_UPDATE_ONLY = true; + + /** + Constant to retrieve all the metadata. + // FIXME: unhide. + // FIXME: add link to getMetadata(boolean, boolean) + {@hide} + */ + public static final boolean METADATA_ALL = false; + + /** + Constant to enable the metadata filter during retrieval. + // FIXME: unhide. + // FIXME: add link to getMetadata(boolean, boolean) + {@hide} + */ + public static final boolean APPLY_METADATA_FILTER = true; + + /** + Constant to disable the metadata filter during retrieval. + // FIXME: unhide. + // FIXME: add link to getMetadata(boolean, boolean) + {@hide} + */ + public static final boolean BYPASS_METADATA_FILTER = false; + + /** + * Create a MediaPlayer2 object. + * + * @return A MediaPlayer2 object created + */ + public static final MediaPlayer2 create() { + // TODO: load MediaUpdate APK + return new MediaPlayer2Impl(); + } + + /** + * @hide + */ + // add hidden empty constructor so it doesn't show in SDK + public MediaPlayer2() { } + + /** + * Create a request parcel which can be routed to the native media + * player using {@link #invoke(Parcel, Parcel)}. The Parcel + * returned has the proper InterfaceToken set. The caller should + * not overwrite that token, i.e it can only append data to the + * Parcel. + * + * @return A parcel suitable to hold a request for the native + * player. + * {@hide} + */ + public Parcel newRequest() { + return null; + } + + /** + * Invoke a generic method on the native player using opaque + * parcels for the request and reply. Both payloads' format is a + * convention between the java caller and the native player. + * Must be called after setDataSource or setPlaylist to make sure a native player + * exists. On failure, a RuntimeException is thrown. + * + * @param request Parcel with the data for the extension. The + * caller must use {@link #newRequest()} to get one. + * + * @param reply Output parcel with the data returned by the + * native player. + * {@hide} + */ + public void invoke(Parcel request, Parcel reply) { } + + /** + * Sets the {@link SurfaceHolder} to use for displaying the video + * portion of the media. + * + * Either a surface holder or surface must be set if a display or video sink + * is needed. Not calling this method or {@link #setSurface(Surface)} + * when playing back a video will result in only the audio track being played. + * A null surface holder or surface will result in only the audio track being + * played. + * + * @param sh the SurfaceHolder to use for video display + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released. + * @hide + */ + public abstract void setDisplay(SurfaceHolder sh); + + /** + * Sets the {@link Surface} to be used as the sink for the video portion of + * the media. Setting a + * Surface will un-set any Surface or SurfaceHolder that was previously set. + * A null surface will result in only the audio track being played. + * + * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps + * returned from {@link SurfaceTexture#getTimestamp()} will have an + * unspecified zero point. These timestamps cannot be directly compared + * between different media sources, different instances of the same media + * source, or multiple runs of the same program. The timestamp is normally + * monotonically increasing and is unaffected by time-of-day adjustments, + * but it is reset when the position is set. + * + * @param surface The {@link Surface} to be used for the video portion of + * the media. + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released. + */ + public abstract void setSurface(Surface surface); + + /* Do not change these video scaling mode values below without updating + * their counterparts in system/window.h! Please do not forget to update + * {@link #isVideoScalingModeSupported} when new video scaling modes + * are added. + */ + /** + * Specifies a video scaling mode. The content is stretched to the + * surface rendering area. When the surface has the same aspect ratio + * as the content, the aspect ratio of the content is maintained; + * otherwise, the aspect ratio of the content is not maintained when video + * is being rendered. + * There is no content cropping with this video scaling mode. + */ + public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; + + /** + * Specifies a video scaling mode. The content is scaled, maintaining + * its aspect ratio. The whole surface area is always used. When the + * aspect ratio of the content is the same as the surface, no content + * is cropped; otherwise, content is cropped to fit the surface. + * @hide + */ + public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; + + /** + * Sets video scaling mode. To make the target video scaling mode + * effective during playback, this method must be called after + * data source is set. If not called, the default video + * scaling mode is {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}. + * + * <p> The supported video scaling modes are: + * <ul> + * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT} + * </ul> + * + * @param mode target video scaling mode. Must be one of the supported + * video scaling modes; otherwise, IllegalArgumentException will be thrown. + * + * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT + * @hide + */ + public void setVideoScalingMode(int mode) { } + + /** + * Discards all pending commands. + */ + public abstract void clearPendingCommands(); + + /** + * Sets the data source as described by a DataSourceDesc. + * + * @param dsd the descriptor of data source you want to play + * @throws IllegalStateException if it is called in an invalid state + * @throws NullPointerException if dsd is null + */ + public abstract void setDataSource(@NonNull DataSourceDesc dsd) throws IOException; + + /** + * Gets the current data source as described by a DataSourceDesc. + * + * @return the current DataSourceDesc + */ + public abstract DataSourceDesc getCurrentDataSource(); + + /** + * Sets the play list. + * + * If startIndex falls outside play list range, it will be clamped to the nearest index + * in the play list. + * + * @param pl the play list of data source you want to play + * @param startIndex the index of the DataSourceDesc in the play list you want to play first + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if pl is null or empty, or pl contains null DataSourceDesc + */ + public abstract void setPlaylist(@NonNull List<DataSourceDesc> pl, int startIndex) + throws IOException; + + /** + * Gets a copy of the play list. + * + * @return a copy of the play list used by {@link MediaPlayer2} + */ + public abstract List<DataSourceDesc> getPlaylist(); + + /** + * Sets the index of current DataSourceDesc in the play list to be played. + * + * @param index the index of DataSourceDesc in the play list you want to play + * @throws IllegalArgumentException if the play list is null + * @throws NullPointerException if index is outside play list range + */ + public abstract void setCurrentPlaylistItem(int index); + + /** + * Sets the index of next-to-be-played DataSourceDesc in the play list. + * + * @param index the index of next-to-be-played DataSourceDesc in the play list + * @throws IllegalArgumentException if the play list is null + * @throws NullPointerException if index is outside play list range + */ + public abstract void setNextPlaylistItem(int index); + + /** + * Gets the current index of play list. + * + * @return the index of the current DataSourceDesc in the play list + */ + public abstract int getCurrentPlaylistItemIndex(); + + /** + * Specifies a playback looping mode. The source will not be played in looping mode. + */ + public static final int LOOPING_MODE_NONE = 0; + /** + * Specifies a playback looping mode. The full list of source will be played in looping mode, + * and in the order specified in the play list. + */ + public static final int LOOPING_MODE_FULL = 1; + /** + * Specifies a playback looping mode. The current DataSourceDesc will be played in looping mode. + */ + public static final int LOOPING_MODE_SINGLE = 2; + /** + * Specifies a playback looping mode. The full list of source will be played in looping mode, + * and in a random order. + */ + public static final int LOOPING_MODE_SHUFFLE = 3; + + /** @hide */ + @IntDef( + value = { + LOOPING_MODE_NONE, + LOOPING_MODE_FULL, + LOOPING_MODE_SINGLE, + LOOPING_MODE_SHUFFLE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LoopingMode {} + + /** + * Sets the looping mode of the play list. + * The mode shall be one of {@link #LOOPING_MODE_NONE}, {@link #LOOPING_MODE_FULL}, + * {@link #LOOPING_MODE_SINGLE}, {@link #LOOPING_MODE_SHUFFLE}. + * + * @param mode the mode in which the play list will be played + * @throws IllegalArgumentException if mode is not supported + */ + public abstract void setLoopingMode(@LoopingMode int mode); + + /** + * Gets the looping mode of play list. + * + * @return the looping mode of the play list + */ + public abstract int getLoopingMode(); + + /** + * Moves the DataSourceDesc at indexFrom in the play list to indexTo. + * + * @throws IllegalArgumentException if the play list is null + * @throws IndexOutOfBoundsException if indexFrom or indexTo is outside play list range + */ + public abstract void movePlaylistItem(int indexFrom, int indexTo); + + /** + * Removes the DataSourceDesc at index in the play list. + * + * If index is same as the current index of the play list, current DataSourceDesc + * will be stopped and playback moves to next source in the list. + * + * @return the removed DataSourceDesc at index in the play list + * @throws IllegalArgumentException if the play list is null + * @throws IndexOutOfBoundsException if index is outside play list range + */ + public abstract DataSourceDesc removePlaylistItem(int index); + + /** + * Inserts the DataSourceDesc to the play list at position index. + * + * This will not change the DataSourceDesc currently being played. + * If index is less than or equal to the current index of the play list, + * the current index of the play list will be incremented correspondingly. + * + * @param index the index you want to add dsd to the play list + * @param dsd the descriptor of data source you want to add to the play list + * @throws IndexOutOfBoundsException if index is outside play list range + * @throws NullPointerException if dsd is null + */ + public abstract void addPlaylistItem(int index, DataSourceDesc dsd); + + /** + * replaces the DataSourceDesc at index in the play list with given dsd. + * + * When index is same as the current index of the play list, the current source + * will be stopped and the new source will be played, except that if new + * and old source only differ on end position and current media position is + * smaller then the new end position. + * + * This will not change the DataSourceDesc currently being played. + * If index is less than or equal to the current index of the play list, + * the current index of the play list will be incremented correspondingly. + * + * @param index the index you want to add dsd to the play list + * @param dsd the descriptor of data source you want to add to the play list + * @throws IndexOutOfBoundsException if index is outside play list range + * @throws NullPointerException if dsd is null + */ + public abstract DataSourceDesc editPlaylistItem(int index, DataSourceDesc dsd); + + /** + * Prepares the player for playback, synchronously. + * + * After setting the datasource and the display surface, you need to either + * call prepare() or prepareAsync(). For files, it is OK to call prepare(), + * which blocks until MediaPlayer2 is ready for playback. + * + * @throws IOException if source can not be accessed + * @throws IllegalStateException if it is called in an invalid state + * @hide + */ + public void prepare() throws IOException { } + + /** + * Prepares the player for playback, asynchronously. + * + * After setting the datasource and the display surface, you need to + * call prepareAsync(). + * + * @throws IllegalStateException if it is called in an invalid state + */ + public abstract void prepareAsync(); + + /** + * Starts or resumes playback. If playback had previously been paused, + * playback will continue from where it was paused. If playback had + * been stopped, or never started before, playback will start at the + * beginning. + * + * @throws IllegalStateException if it is called in an invalid state + */ + public abstract void play(); + + /** + * Stops playback after playback has been started or paused. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + * @hide + */ + public void stop() { } + + /** + * Pauses playback. Call play() to resume. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + */ + public abstract void pause(); + + //-------------------------------------------------------------------------- + // Explicit Routing + //-------------------- + + /** + * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route + * the output from this MediaPlayer2. + * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source. + * If deviceInfo is null, default routing is restored. + * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and + * does not correspond to a valid audio device. + */ + @Override + public abstract boolean setPreferredDevice(AudioDeviceInfo deviceInfo); + + /** + * Returns the selected output specified by {@link #setPreferredDevice}. Note that this + * is not guaranteed to correspond to the actual device being used for playback. + */ + @Override + public abstract AudioDeviceInfo getPreferredDevice(); + + /** + * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2 + * Note: The query is only valid if the MediaPlayer2 is currently playing. + * If the player is not playing, the returned device can be null or correspond to previously + * selected device when the player was last active. + */ + @Override + public abstract AudioDeviceInfo getRoutedDevice(); + + /** + * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing + * changes on this MediaPlayer2. + * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive + * notifications of rerouting events. + * @param handler Specifies the {@link Handler} object for the thread on which to execute + * the callback. If <code>null</code>, the handler on the main looper will be used. + */ + @Override + public abstract void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener, + Handler handler); + + /** + * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added + * to receive rerouting notifications. + * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface + * to remove. + */ + @Override + public abstract void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener); + + /** + * Set the low-level power management behavior for this MediaPlayer2. + * + * <p>This function has the MediaPlayer2 access the low-level power manager + * service to control the device's power usage while playing is occurring. + * The parameter is a combination of {@link android.os.PowerManager} wake flags. + * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK} + * permission. + * By default, no attempt is made to keep the device awake during playback. + * + * @param context the Context to use + * @param mode the power/wake mode to set + * @see android.os.PowerManager + * @hide + */ + public abstract void setWakeMode(Context context, int mode); + + /** + * Control whether we should use the attached SurfaceHolder to keep the + * screen on while video playback is occurring. This is the preferred + * method over {@link #setWakeMode} where possible, since it doesn't + * require that the application have permission for low-level wake lock + * access. + * + * @param screenOn Supply true to keep the screen on, false to allow it + * to turn off. + * @hide + */ + public abstract void setScreenOnWhilePlaying(boolean screenOn); + + /** + * Returns the width of the video. + * + * @return the width of the video, or 0 if there is no video, + * no display surface was set, or the width has not been determined + * yet. The {@code EventCallback} can be registered via + * {@link #registerEventCallback(Executor, EventCallback)} to provide a + * notification {@code EventCallback.onVideoSizeChanged} when the width is available. + */ + public abstract int getVideoWidth(); + + /** + * Returns the height of the video. + * + * @return the height of the video, or 0 if there is no video, + * no display surface was set, or the height has not been determined + * yet. The {@code EventCallback} can be registered via + * {@link #registerEventCallback(Executor, EventCallback)} to provide a + * notification {@code EventCallback.onVideoSizeChanged} when the height is available. + */ + public abstract int getVideoHeight(); + + /** + * Return Metrics data about the current player. + * + * @return a {@link PersistableBundle} containing the set of attributes and values + * available for the media being handled by this instance of MediaPlayer2 + * The attributes are descibed in {@link MetricsConstants}. + * + * Additional vendor-specific fields may also be present in + * the return value. + */ + public abstract PersistableBundle getMetrics(); + + /** + * Checks whether the MediaPlayer2 is playing. + * + * @return true if currently playing, false otherwise + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released. + */ + public abstract boolean isPlaying(); + + /** + * Gets the current buffering management params used by the source component. + * Calling it only after {@code setDataSource} has been called. + * Each type of data source might have different set of default params. + * + * @return the current buffering management params used by the source component. + * @throws IllegalStateException if the internal player engine has not been + * initialized, or {@code setDataSource} has not been called. + * @hide + */ + @NonNull + public BufferingParams getBufferingParams() { + return new BufferingParams.Builder().build(); + } + + /** + * Sets buffering management params. + * The object sets its internal BufferingParams to the input, except that the input is + * invalid or not supported. + * Call it only after {@code setDataSource} has been called. + * The input is a hint to MediaPlayer2. + * + * @param params the buffering management params. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released, or {@code setDataSource} has not been called. + * @throws IllegalArgumentException if params is invalid or not supported. + * @hide + */ + public void setBufferingParams(@NonNull BufferingParams params) { } + + /** + * Change playback speed of audio by resampling the audio. + * <p> + * Specifies resampling as audio mode for variable rate playback, i.e., + * resample the waveform based on the requested playback rate to get + * a new waveform, and play back the new waveform at the original sampling + * frequency. + * When rate is larger than 1.0, pitch becomes higher. + * When rate is smaller than 1.0, pitch becomes lower. + * + * @hide + */ + public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; + + /** + * Change playback speed of audio without changing its pitch. + * <p> + * Specifies time stretching as audio mode for variable rate playback. + * Time stretching changes the duration of the audio samples without + * affecting its pitch. + * <p> + * This mode is only supported for a limited range of playback speed factors, + * e.g. between 1/2x and 2x. + * + * @hide + */ + public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; + + /** + * Change playback speed of audio without changing its pitch, and + * possibly mute audio if time stretching is not supported for the playback + * speed. + * <p> + * Try to keep audio pitch when changing the playback rate, but allow the + * system to determine how to change audio playback if the rate is out + * of range. + * + * @hide + */ + public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; + + /** @hide */ + @IntDef( + value = { + PLAYBACK_RATE_AUDIO_MODE_DEFAULT, + PLAYBACK_RATE_AUDIO_MODE_STRETCH, + PLAYBACK_RATE_AUDIO_MODE_RESAMPLE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PlaybackRateAudioMode {} + + /** + * Sets playback rate and audio mode. + * + * @param rate the ratio between desired playback rate and normal one. + * @param audioMode audio playback mode. Must be one of the supported + * audio modes. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + * @throws IllegalArgumentException if audioMode is not supported. + * + * @hide + */ + @NonNull + public PlaybackParams easyPlaybackParams(float rate, @PlaybackRateAudioMode int audioMode) { + return new PlaybackParams(); + } + + /** + * Sets playback rate using {@link PlaybackParams}. The object sets its internal + * PlaybackParams to the input, except that the object remembers previous speed + * when input speed is zero. This allows the object to resume at previous speed + * when play() is called. Calling it before the object is prepared does not change + * the object state. After the object is prepared, calling it with zero speed is + * equivalent to calling pause(). After the object is prepared, calling it with + * non-zero speed is equivalent to calling play(). + * + * @param params the playback params. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released. + * @throws IllegalArgumentException if params is not supported. + */ + public abstract void setPlaybackParams(@NonNull PlaybackParams params); + + /** + * Gets the playback params, containing the current playback rate. + * + * @return the playback params. + * @throws IllegalStateException if the internal player engine has not been + * initialized. + */ + @NonNull + public abstract PlaybackParams getPlaybackParams(); + + /** + * Sets A/V sync mode. + * + * @param params the A/V sync params to apply + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + * @throws IllegalArgumentException if params are not supported. + */ + public abstract void setSyncParams(@NonNull SyncParams params); + + /** + * Gets the A/V sync mode. + * + * @return the A/V sync params + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + */ + @NonNull + public abstract SyncParams getSyncParams(); + + /** + * Seek modes used in method seekTo(long, int) to move media position + * to a specified location. + * + * Do not change these mode values without updating their counterparts + * in include/media/IMediaSource.h! + */ + /** + * This mode is used with {@link #seekTo(long, int)} to move media position to + * a sync (or key) frame associated with a data source that is located + * right before or at the given time. + * + * @see #seekTo(long, int) + */ + public static final int SEEK_PREVIOUS_SYNC = 0x00; + /** + * This mode is used with {@link #seekTo(long, int)} to move media position to + * a sync (or key) frame associated with a data source that is located + * right after or at the given time. + * + * @see #seekTo(long, int) + */ + public static final int SEEK_NEXT_SYNC = 0x01; + /** + * This mode is used with {@link #seekTo(long, int)} to move media position to + * a sync (or key) frame associated with a data source that is located + * closest to (in time) or at the given time. + * + * @see #seekTo(long, int) + */ + public static final int SEEK_CLOSEST_SYNC = 0x02; + /** + * This mode is used with {@link #seekTo(long, int)} to move media position to + * a frame (not necessarily a key frame) associated with a data source that + * is located closest to or at the given time. + * + * @see #seekTo(long, int) + */ + public static final int SEEK_CLOSEST = 0x03; + + /** @hide */ + @IntDef( + value = { + SEEK_PREVIOUS_SYNC, + SEEK_NEXT_SYNC, + SEEK_CLOSEST_SYNC, + SEEK_CLOSEST, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SeekMode {} + + /** + * Moves the media to specified time position by considering the given mode. + * <p> + * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user. + * There is at most one active seekTo processed at any time. If there is a to-be-completed + * seekTo, new seekTo requests will be queued in such a way that only the last request + * is kept. When current seekTo is completed, the queued request will be processed if + * that request is different from just-finished seekTo operation, i.e., the requested + * position or mode is different. + * + * @param msec the offset in milliseconds from the start to seek to. + * When seeking to the given time position, there is no guarantee that the data source + * has a frame located at the position. When this happens, a frame nearby will be rendered. + * If msec is negative, time position zero will be used. + * If msec is larger than duration, duration will be used. + * @param mode the mode indicating where exactly to seek to. + * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame + * that has a timestamp earlier than or the same as msec. Use + * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame + * that has a timestamp later than or the same as msec. Use + * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame + * that has a timestamp closest to or the same as msec. Use + * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may + * or may not be a sync frame but is closest to or the same as msec. + * {@link #SEEK_CLOSEST} often has larger performance overhead compared + * to the other options if there is no sync frame located at msec. + * @throws IllegalStateException if the internal player engine has not been + * initialized + * @throws IllegalArgumentException if the mode is invalid. + */ + public abstract void seekTo(long msec, @SeekMode int mode); + + /** + * Get current playback position as a {@link MediaTimestamp}. + * <p> + * The MediaTimestamp represents how the media time correlates to the system time in + * a linear fashion using an anchor and a clock rate. During regular playback, the media + * time moves fairly constantly (though the anchor frame may be rebased to a current + * system time, the linear correlation stays steady). Therefore, this method does not + * need to be called often. + * <p> + * To help users get current playback position, this method always anchors the timestamp + * to the current {@link System#nanoTime system time}, so + * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position. + * + * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp + * is available, e.g. because the media player has not been initialized. + * + * @see MediaTimestamp + */ + @Nullable + public abstract MediaTimestamp getTimestamp(); + + /** + * Gets the current playback position. + * + * @return the current position in milliseconds + */ + public abstract int getCurrentPosition(); + + /** + * Gets the duration of the file. + * + * @return the duration in milliseconds, if no duration is available + * (for example, if streaming live content), -1 is returned. + */ + public abstract int getDuration(); + + /** + * Gets the media metadata. + * + * @param update_only controls whether the full set of available + * metadata is returned or just the set that changed since the + * last call. See {@see #METADATA_UPDATE_ONLY} and {@see + * #METADATA_ALL}. + * + * @param apply_filter if true only metadata that matches the + * filter is returned. See {@see #APPLY_METADATA_FILTER} and {@see + * #BYPASS_METADATA_FILTER}. + * + * @return The metadata, possibly empty. null if an error occured. + // FIXME: unhide. + * {@hide} + */ + public Metadata getMetadata(final boolean update_only, + final boolean apply_filter) { + return null; + } + + /** + * Set a filter for the metadata update notification and update + * retrieval. The caller provides 2 set of metadata keys, allowed + * and blocked. The blocked set always takes precedence over the + * allowed one. + * Metadata.MATCH_ALL and Metadata.MATCH_NONE are 2 sets available as + * shorthands to allow/block all or no metadata. + * + * By default, there is no filter set. + * + * @param allow Is the set of metadata the client is interested + * in receiving new notifications for. + * @param block Is the set of metadata the client is not interested + * in receiving new notifications for. + * @return The call status code. + * + // FIXME: unhide. + * {@hide} + */ + public int setMetadataFilter(Set<Integer> allow, Set<Integer> block) { + return 0; + } + + /** + * Set the MediaPlayer2 to start when this MediaPlayer2 finishes playback + * (i.e. reaches the end of the stream). + * The media framework will attempt to transition from this player to + * the next as seamlessly as possible. The next player can be set at + * any time before completion, but shall be after setDataSource has been + * called successfully. The next player must be prepared by the + * app, and the application should not call play() on it. + * The next MediaPlayer2 must be different from 'this'. An exception + * will be thrown if next == this. + * The application may call setNextMediaPlayer(null) to indicate no + * next player should be started at the end of playback. + * If the current player is looping, it will keep looping and the next + * player will not be started. + * + * @param next the player to start after this one completes playback. + * + * @hide + */ + public void setNextMediaPlayer(MediaPlayer2 next) { } + + /** + * Resets the MediaPlayer2 to its uninitialized state. After calling + * this method, you will have to initialize it again by setting the + * data source and calling prepareAsync(). + */ + public abstract void reset(); + + /** + * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be + * notified when the presentation time reaches (becomes greater than or equal to) + * the value specified. + * + * @param mediaTimeUs presentation time to get timed event callback at + * @hide + */ + public void notifyAt(long mediaTimeUs) { } + + /** + * Sets the audio attributes for this MediaPlayer2. + * See {@link AudioAttributes} for how to build and configure an instance of this class. + * You must call this method before {@link #prepareAsync()} in order + * for the audio attributes to become effective thereafter. + * @param attributes a non-null set of audio attributes + * @throws IllegalArgumentException if the attributes are null or invalid. + */ + public abstract void setAudioAttributes(AudioAttributes attributes); + + /** + * Sets the player to be looping or non-looping. + * + * @param looping whether to loop or not + * @hide + */ + public void setLooping(boolean looping) { } + + /** + * Checks whether the MediaPlayer2 is looping or non-looping. + * + * @return true if the MediaPlayer2 is currently looping, false otherwise + * @hide + */ + public boolean isLooping() { + return false; + } + + /** + * Sets the volume on this player. + * This API is recommended for balancing the output of audio streams + * within an application. Unless you are writing an application to + * control user settings, this API should be used in preference to + * {@link AudioManager#setStreamVolume(int, int, int)} which sets the volume of ALL streams of + * a particular type. Note that the passed volume values are raw scalars in range 0.0 to 1.0. + * UI controls should be scaled logarithmically. + * + * @param leftVolume left volume scalar + * @param rightVolume right volume scalar + */ + /* + * FIXME: Merge this into javadoc comment above when setVolume(float) is not @hide. + * The single parameter form below is preferred if the channel volumes don't need + * to be set independently. + */ + public abstract void setVolume(float leftVolume, float rightVolume); + + /** + * Similar, excepts sets volume of all channels to same value. + * @hide + */ + public void setVolume(float volume) { } + + /** + * Sets the audio session ID. + * + * @param sessionId the audio session ID. + * The audio session ID is a system wide unique identifier for the audio stream played by + * this MediaPlayer2 instance. + * The primary use of the audio session ID is to associate audio effects to a particular + * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect, + * this effect will be applied only to the audio content of media players within the same + * audio session and not to the output mix. + * When created, a MediaPlayer2 instance automatically generates its own audio session ID. + * However, it is possible to force this player to be part of an already existing audio session + * by calling this method. + * This method must be called before one of the overloaded <code> setDataSource </code> methods. + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if the sessionId is invalid. + */ + public abstract void setAudioSessionId(int sessionId); + + /** + * Returns the audio session ID. + * + * @return the audio session ID. {@see #setAudioSessionId(int)} + * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed. + */ + public abstract int getAudioSessionId(); + + /** + * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation + * effect which can be applied on any sound source that directs a certain amount of its + * energy to this effect. This amount is defined by setAuxEffectSendLevel(). + * See {@link #setAuxEffectSendLevel(float)}. + * <p>After creating an auxiliary effect (e.g. + * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with + * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method + * to attach the player to the effect. + * <p>To detach the effect from the player, call this method with a null effect id. + * <p>This method must be called after one of the overloaded <code> setDataSource </code> + * methods. + * @param effectId system wide unique id of the effect to attach + */ + public abstract void attachAuxEffect(int effectId); + + + /** + * Sets the send level of the player to the attached auxiliary effect. + * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0. + * <p>By default the send level is 0, so even if an effect is attached to the player + * this method must be called for the effect to be applied. + * <p>Note that the passed level value is a raw scalar. UI controls should be scaled + * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, + * so an appropriate conversion from linear UI input x to level is: + * x == 0 -> level = 0 + * 0 < x <= R -> level = 10^(72*(x-R)/20/R) + * @param level send level scalar + */ + public abstract void setAuxEffectSendLevel(float level); + + /** + * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata. + * + * @see android.media.MediaPlayer2#getTrackInfo + */ + public abstract static class TrackInfo { + /** + * Gets the track type. + * @return TrackType which indicates if the track is video, audio, timed text. + */ + public abstract int getTrackType(); + + /** + * Gets the language code of the track. + * @return a language code in either way of ISO-639-1 or ISO-639-2. + * When the language is unknown or could not be determined, + * ISO-639-2 language code, "und", is returned. + */ + public abstract String getLanguage(); + + /** + * Gets the {@link MediaFormat} of the track. If the format is + * unknown or could not be determined, null is returned. + */ + public abstract MediaFormat getFormat(); + + public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; + public static final int MEDIA_TRACK_TYPE_VIDEO = 1; + public static final int MEDIA_TRACK_TYPE_AUDIO = 2; + + /** @hide */ + public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; + + public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; + public static final int MEDIA_TRACK_TYPE_METADATA = 5; + + @Override + public abstract String toString(); + }; + + /** + * Returns a List of track information. + * + * @return List of track info. The total number of tracks is the array length. + * Must be called again if an external timed text source has been added after + * addTimedTextSource method is called. + * @throws IllegalStateException if it is called in an invalid state. + */ + public abstract List<TrackInfo> getTrackInfo(); + + /* Do not change these values without updating their counterparts + * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp! + */ + /** + * MIME type for SubRip (SRT) container. Used in addTimedTextSource APIs. + * @hide + */ + public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip"; + + /** + * MIME type for WebVTT subtitle data. + * @hide + */ + public static final String MEDIA_MIMETYPE_TEXT_VTT = "text/vtt"; + + /** + * MIME type for CEA-608 closed caption data. + * @hide + */ + public static final String MEDIA_MIMETYPE_TEXT_CEA_608 = "text/cea-608"; + + /** + * MIME type for CEA-708 closed caption data. + * @hide + */ + public static final String MEDIA_MIMETYPE_TEXT_CEA_708 = "text/cea-708"; + + /** @hide */ + public void setSubtitleAnchor( + SubtitleController controller, + SubtitleController.Anchor anchor) { } + + /** @hide */ + @Override + public void onSubtitleTrackSelected(SubtitleTrack track) { } + + /** @hide */ + public void addSubtitleSource(InputStream is, MediaFormat format) { } + + /* TODO: Limit the total number of external timed text source to a reasonable number. + */ + /** + * Adds an external timed text source file. + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external timed text source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param path The file path of external timed text source file. + * @param mimeType The mime type of the file. Must be one of the mime types listed above. + * @throws IOException if the file cannot be accessed or is corrupted. + * @throws IllegalArgumentException if the mimeType is not supported. + * @throws IllegalStateException if called in an invalid state. + * @hide + */ + public void addTimedTextSource(String path, String mimeType) throws IOException { } + + /** + * Adds an external timed text source file (Uri). + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external timed text source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play + * @param mimeType The mime type of the file. Must be one of the mime types listed above. + * @throws IOException if the file cannot be accessed or is corrupted. + * @throws IllegalArgumentException if the mimeType is not supported. + * @throws IllegalStateException if called in an invalid state. + * @hide + */ + public void addTimedTextSource(Context context, Uri uri, String mimeType) throws IOException { } + + /** + * Adds an external timed text source file (FileDescriptor). + * + * It is the caller's responsibility to close the file descriptor. + * It is safe to do so as soon as this call returns. + * + * Currently supported format is SubRip. Note that a single external timed text source may + * contain multiple tracks in it. One can find the total number of available tracks + * using {@link #getTrackInfo()} to see what additional tracks become available + * after this method call. + * + * @param fd the FileDescriptor for the file you want to play + * @param mimeType The mime type of the file. Must be one of the mime types listed above. + * @throws IllegalArgumentException if the mimeType is not supported. + * @throws IllegalStateException if called in an invalid state. + * @hide + */ + public void addTimedTextSource(FileDescriptor fd, String mimeType) { } + + /** + * Adds an external timed text file (FileDescriptor). + * + * It is the caller's responsibility to close the file descriptor. + * It is safe to do so as soon as this call returns. + * + * Currently supported format is SubRip. Note that a single external timed text source may + * contain multiple tracks in it. One can find the total number of available tracks + * using {@link #getTrackInfo()} to see what additional tracks become available + * after this method call. + * + * @param fd the FileDescriptor for the file you want to play + * @param offset the offset into the file where the data to be played starts, in bytes + * @param length the length in bytes of the data to be played + * @param mime The mime type of the file. Must be one of the mime types listed above. + * @throws IllegalArgumentException if the mimeType is not supported. + * @throws IllegalStateException if called in an invalid state. + * @hide + */ + public abstract void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime); + + /** + * Returns the index of the audio, video, or subtitle track currently selected for playback, + * The return value is an index into the array returned by {@link #getTrackInfo()}, and can + * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}. + * + * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, + * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or + * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} + * @return index of the audio, video, or subtitle track currently selected for playback; + * a negative integer is returned when there is no selected track for {@code trackType} or + * when {@code trackType} is not one of audio, video, or subtitle. + * @throws IllegalStateException if called after {@link #close()} + * + * @see #getTrackInfo() + * @see #selectTrack(int) + * @see #deselectTrack(int) + */ + public abstract int getSelectedTrack(int trackType); + + /** + * Selects a track. + * <p> + * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception. + * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately. + * If a MediaPlayer2 is not in Started state, it just marks the track to be played. + * </p> + * <p> + * In any valid state, if it is called multiple times on the same type of track (ie. Video, + * Audio, Timed Text), the most recent one will be chosen. + * </p> + * <p> + * The first audio and video tracks are selected by default if available, even though + * this method is not called. However, no timed text track will be selected until + * this function is called. + * </p> + * <p> + * Currently, only timed text tracks or audio tracks can be selected via this method. + * In addition, the support for selecting an audio track at runtime is pretty limited + * in that an audio track can only be selected in the <em>Prepared</em> state. + * </p> + * @param index the index of the track to be selected. The valid range of the index + * is 0..total number of track - 1. The total number of tracks as well as the type of + * each individual track can be found by calling {@link #getTrackInfo()} method. + * @throws IllegalStateException if called in an invalid state. + * + * @see android.media.MediaPlayer2#getTrackInfo + */ + public abstract void selectTrack(int index); + + /** + * Deselect a track. + * <p> + * Currently, the track must be a timed text track and no audio or video tracks can be + * deselected. If the timed text track identified by index has not been + * selected before, it throws an exception. + * </p> + * @param index the index of the track to be deselected. The valid range of the index + * is 0..total number of tracks - 1. The total number of tracks as well as the type of + * each individual track can be found by calling {@link #getTrackInfo()} method. + * @throws IllegalStateException if called in an invalid state. + * + * @see android.media.MediaPlayer2#getTrackInfo + */ + public abstract void deselectTrack(int index); + + /** + * Sets the target UDP re-transmit endpoint for the low level player. + * Generally, the address portion of the endpoint is an IP multicast + * address, although a unicast address would be equally valid. When a valid + * retransmit endpoint has been set, the media player will not decode and + * render the media presentation locally. Instead, the player will attempt + * to re-multiplex its media data using the Android@Home RTP profile and + * re-transmit to the target endpoint. Receiver devices (which may be + * either the same as the transmitting device or different devices) may + * instantiate, prepare, and start a receiver player using a setDataSource + * URL of the form... + * + * aahRX://<multicastIP>:<port> + * + * to receive, decode and render the re-transmitted content. + * + * setRetransmitEndpoint may only be called before setDataSource has been + * called; while the player is in the Idle state. + * + * @param endpoint the address and UDP port of the re-transmission target or + * null if no re-transmission is to be performed. + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if the retransmit endpoint is supplied, + * but invalid. + * + * {@hide} pending API council + */ + public void setRetransmitEndpoint(InetSocketAddress endpoint) { } + + /** + * Releases the resources held by this {@code MediaPlayer2} object. + * + * It is considered good practice to call this method when you're + * done using the MediaPlayer2. In particular, whenever an Activity + * of an application is paused (its onPause() method is called), + * or stopped (its onStop() method is called), this method should be + * invoked to release the MediaPlayer2 object, unless the application + * has a special need to keep the object around. In addition to + * unnecessary resources (such as memory and instances of codecs) + * being held, failure to call this method immediately if a + * MediaPlayer2 object is no longer needed may also lead to + * continuous battery consumption for mobile devices, and playback + * failure for other applications if no multiple instances of the + * same codec are supported on a device. Even if multiple instances + * of the same codec are supported, some performance degradation + * may be expected when unnecessary multiple instances are used + * at the same time. + * + * {@code close()} may be safely called after a prior {@code close()}. + * This class implements the Java {@code AutoCloseable} interface and + * may be used with try-with-resources. + */ + @Override + public abstract void close(); + + /** @hide */ + public MediaTimeProvider getMediaTimeProvider() { + return null; + } + + /** + * Interface definition for callbacks to be invoked when the player has the corresponding + * events. + */ + public abstract static class EventCallback { + /** + * Called to update status in buffering a media source received through + * progressive downloading. The received buffering percentage + * indicates how much of the content has been buffered or played. + * For example a buffering update of 80 percent when half the content + * has already been played indicates that the next 30 percent of the + * content to play has been buffered. + * + * @param mp the MediaPlayer2 the update pertains to + * @param srcId the Id of this data source + * @param percent the percentage (0-100) of the content + * that has been buffered or played thus far + */ + public void onBufferingUpdate(MediaPlayer2 mp, long srcId, int percent) { } + + /** + * Called to indicate the video size + * + * The video size (width and height) could be 0 if there was no video, + * no display surface was set, or the value was not determined yet. + * + * @param mp the MediaPlayer2 associated with this callback + * @param srcId the Id of this data source + * @param width the width of the video + * @param height the height of the video + */ + public void onVideoSizeChanged(MediaPlayer2 mp, long srcId, int width, int height) { } + + /** + * Called to indicate an avaliable timed text + * + * @param mp the MediaPlayer2 associated with this callback + * @param srcId the Id of this data source + * @param text the timed text sample which contains the text + * needed to be displayed and the display format. + * @hide + */ + public void onTimedText(MediaPlayer2 mp, long srcId, TimedText text) { } + + /** + * Called to indicate avaliable timed metadata + * <p> + * This method will be called as timed metadata is extracted from the media, + * in the same order as it occurs in the media. The timing of this event is + * not controlled by the associated timestamp. + * <p> + * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates + * {@link TimedMetaData}. + * + * @see MediaPlayer2#selectTrack(int) + * @see MediaPlayer2.OnTimedMetaDataAvailableListener + * @see TimedMetaData + * + * @param mp the MediaPlayer2 associated with this callback + * @param srcId the Id of this data source + * @param data the timed metadata sample associated with this event + */ + public void onTimedMetaDataAvailable(MediaPlayer2 mp, long srcId, TimedMetaData data) { } + + /** + * Called to indicate an error. + * + * @param mp the MediaPlayer2 the error pertains to + * @param srcId the Id of this data source + * @param what the type of error that has occurred: + * <ul> + * <li>{@link #MEDIA_ERROR_UNKNOWN} + * </ul> + * @param extra an extra code, specific to the error. Typically + * implementation dependent. + * <ul> + * <li>{@link #MEDIA_ERROR_IO} + * <li>{@link #MEDIA_ERROR_MALFORMED} + * <li>{@link #MEDIA_ERROR_UNSUPPORTED} + * <li>{@link #MEDIA_ERROR_TIMED_OUT} + * <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error. + * </ul> + */ + public void onError(MediaPlayer2 mp, long srcId, int what, int extra) { } + + /** + * Called to indicate an info or a warning. + * + * @param mp the MediaPlayer2 the info pertains to. + * @param srcId the Id of this data source + * @param what the type of info or warning. + * <ul> + * <li>{@link #MEDIA_INFO_UNKNOWN} + * <li>{@link #MEDIA_INFO_STARTED_AS_NEXT} + * <li>{@link #MEDIA_INFO_VIDEO_RENDERING_START} + * <li>{@link #MEDIA_INFO_AUDIO_RENDERING_START} + * <li>{@link #MEDIA_INFO_PLAYBACK_COMPLETE} + * <li>{@link #MEDIA_INFO_PLAYLIST_END} + * <li>{@link #MEDIA_INFO_PREPARED} + * <li>{@link #MEDIA_INFO_COMPLETE_CALL_PLAY} + * <li>{@link #MEDIA_INFO_COMPLETE_CALL_PAUSE} + * <li>{@link #MEDIA_INFO_COMPLETE_CALL_SEEK} + * <li>{@link #MEDIA_INFO_VIDEO_TRACK_LAGGING} + * <li>{@link #MEDIA_INFO_BUFFERING_START} + * <li>{@link #MEDIA_INFO_BUFFERING_END} + * <li><code>MEDIA_INFO_NETWORK_BANDWIDTH (703)</code> - + * bandwidth information is available (as <code>extra</code> kbps) + * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING} + * <li>{@link #MEDIA_INFO_NOT_SEEKABLE} + * <li>{@link #MEDIA_INFO_METADATA_UPDATE} + * <li>{@link #MEDIA_INFO_UNSUPPORTED_SUBTITLE} + * <li>{@link #MEDIA_INFO_SUBTITLE_TIMED_OUT} + * </ul> + * @param extra an extra code, specific to the info. Typically + * implementation dependent. + */ + public void onInfo(MediaPlayer2 mp, long srcId, int what, int extra) { } + } + + /** + * Register a callback to be invoked when the media source is ready + * for playback. + * + * @param eventCallback the callback that will be run + * @param executor the executor through which the callback should be invoked + */ + public abstract void registerEventCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull EventCallback eventCallback); + + /** + * Unregisters an {@link EventCallback}. + * + * @param callback an {@link EventCallback} to unregister + */ + public abstract void unregisterEventCallback(EventCallback callback); + + /** + * Interface definition of a callback to be invoked when a + * track has data available. + * + * @hide + */ + public interface OnSubtitleDataListener + { + public void onSubtitleData(MediaPlayer2 mp, SubtitleData data); + } + + /** + * Register a callback to be invoked when a track has data available. + * + * @param listener the callback that will be run + * + * @hide + */ + public void setOnSubtitleDataListener(OnSubtitleDataListener listener) { } + + + /* Do not change these values without updating their counterparts + * in include/media/mediaplayer2.h! + */ + /** Unspecified media player error. + * @see android.media.MediaPlayer2.EventCallback.onError + */ + public static final int MEDIA_ERROR_UNKNOWN = 1; + + /** The video is streamed and its container is not valid for progressive + * playback i.e the video's index (e.g moov atom) is not at the start of the + * file. + * @see android.media.MediaPlayer2.EventCallback.onError + */ + public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; + + /** File or network related operation errors. */ + public static final int MEDIA_ERROR_IO = -1004; + /** Bitstream is not conforming to the related coding standard or file spec. */ + public static final int MEDIA_ERROR_MALFORMED = -1007; + /** Bitstream is conforming to the related coding standard or file spec, but + * the media framework does not support the feature. */ + public static final int MEDIA_ERROR_UNSUPPORTED = -1010; + /** Some operation takes too long to complete, usually more than 3-5 seconds. */ + public static final int MEDIA_ERROR_TIMED_OUT = -110; + + /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in + * system/core/include/utils/Errors.h + * @see android.media.MediaPlayer2.EventCallback.onError + * @hide + */ + public static final int MEDIA_ERROR_SYSTEM = -2147483648; + + + /* Do not change these values without updating their counterparts + * in include/media/mediaplayer2.h! + */ + /** Unspecified media player info. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_UNKNOWN = 1; + + /** The player switched to this datas source because it is the + * next-to-be-played in the play list. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; + + /** The player just pushed the very first video frame for rendering. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; + + /** The player just rendered the very first audio sample. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4; + + /** The player just completed the playback of this data source. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_PLAYBACK_COMPLETE = 5; + + /** The player just completed the playback of the full play list. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_PLAYLIST_END = 6; + + /** The player just prepared a data source. + * This also serves as call completion notification for {@link #prepareAsync()}. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_PREPARED = 100; + + /** The player just completed a call {@link #play()}. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_COMPLETE_CALL_PLAY = 101; + + /** The player just completed a call {@link #pause()}. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_COMPLETE_CALL_PAUSE = 102; + + /** The player just completed a call {@link #seekTo(long, int)}. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_COMPLETE_CALL_SEEK = 103; + + /** The video is too complex for the decoder: it can't decode frames fast + * enough. Possibly only the audio plays fine at this stage. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; + + /** MediaPlayer2 is temporarily pausing playback internally in order to + * buffer more data. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_BUFFERING_START = 701; + + /** MediaPlayer2 is resuming playback after filling buffers. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_BUFFERING_END = 702; + + /** Estimated network bandwidth information (kbps) is available; currently this event fires + * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END} + * when playing network files. + * @see android.media.MediaPlayer2.EventCallback.onInfo + * @hide + */ + public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703; + + /** Bad interleaving means that a media has been improperly interleaved or + * not interleaved at all, e.g has all the video samples first then all the + * audio ones. Video is playing but a lot of disk seeks may be happening. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; + + /** The media cannot be seeked (e.g live stream) + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_NOT_SEEKABLE = 801; + + /** A new set of metadata is available. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_METADATA_UPDATE = 802; + + /** A new set of external-only metadata is available. Used by + * JAVA framework to avoid triggering track scanning. + * @hide + */ + public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803; + + /** Informs that audio is not playing. Note that playback of the video + * is not interrupted. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; + + /** Informs that video is not playing. Note that playback of the audio + * is not interrupted. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; + + /** Failed to handle timed text track properly. + * @see android.media.MediaPlayer2.EventCallback.onInfo + * + * {@hide} + */ + public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; + + /** Subtitle track was not supported by the media framework. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; + + /** Reading the subtitle track takes too long. + * @see android.media.MediaPlayer2.EventCallback.onInfo + */ + public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; + + + // Modular DRM begin + + /** + * Interface definition of a callback to be invoked when the app + * can do DRM configuration (get/set properties) before the session + * is opened. This facilitates configuration of the properties, like + * 'securityLevel', which has to be set after DRM scheme creation but + * before the DRM session is opened. + * + * The only allowed DRM calls in this listener are {@code getDrmPropertyString} + * and {@code setDrmPropertyString}. + */ + public interface OnDrmConfigHelper + { + /** + * Called to give the app the opportunity to configure DRM before the session is created + * + * @param mp the {@code MediaPlayer2} associated with this callback + */ + public void onDrmConfig(MediaPlayer2 mp); + } + + /** + * Register a callback to be invoked for configuration of the DRM object before + * the session is created. + * The callback will be invoked synchronously during the execution + * of {@link #prepareDrm(UUID uuid)}. + * + * @param listener the callback that will be run + */ + public abstract void setOnDrmConfigHelper(OnDrmConfigHelper listener); + + /** + * Interface definition for callbacks to be invoked when the player has the corresponding + * DRM events. + */ + public abstract static class DrmEventCallback { + /** + * Called to indicate DRM info is available + * + * @param mp the {@code MediaPlayer2} associated with this callback + * @param drmInfo DRM info of the source including PSSH, and subset + * of crypto schemes supported by this device + */ + public void onDrmInfo(MediaPlayer2 mp, DrmInfo drmInfo) { } + + /** + * Called to notify the client that {@code prepareDrm} is finished and ready for key request/response. + * + * @param mp the {@code MediaPlayer2} associated with this callback + * @param status the result of DRM preparation which can be + * {@link #PREPARE_DRM_STATUS_SUCCESS}, + * {@link #PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR}, + * {@link #PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR}, or + * {@link #PREPARE_DRM_STATUS_PREPARATION_ERROR}. + */ + public void onDrmPrepared(MediaPlayer2 mp, @PrepareDrmStatusCode int status) { } + + } + + /** + * Register a callback to be invoked when the media source is ready + * for playback. + * + * @param eventCallback the callback that will be run + * @param executor the executor through which the callback should be invoked + */ + public abstract void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull DrmEventCallback eventCallback); + + /** + * Unregisters a {@link DrmEventCallback}. + * + * @param callback a {@link DrmEventCallback} to unregister + */ + public abstract void unregisterDrmEventCallback(DrmEventCallback callback); + + /** + * The status codes for {@link DrmEventCallback#onDrmPrepared} listener. + * <p> + * + * DRM preparation has succeeded. + */ + public static final int PREPARE_DRM_STATUS_SUCCESS = 0; + + /** + * The device required DRM provisioning but couldn't reach the provisioning server. + */ + public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; + + /** + * The device required DRM provisioning but the provisioning server denied the request. + */ + public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; + + /** + * The DRM preparation has failed . + */ + public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; + + + /** @hide */ + @IntDef({ + PREPARE_DRM_STATUS_SUCCESS, + PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR, + PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR, + PREPARE_DRM_STATUS_PREPARATION_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PrepareDrmStatusCode {} + + /** + * Retrieves the DRM Info associated with the current source + * + * @throws IllegalStateException if called before being prepared + */ + public abstract DrmInfo getDrmInfo(); + + /** + * Prepares the DRM for the current source + * <p> + * If {@code OnDrmConfigHelper} is registered, it will be called during + * preparation to allow configuration of the DRM properties before opening the + * DRM session. Note that the callback is called synchronously in the thread that called + * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString} + * and {@code setDrmPropertyString} calls and refrain from any lengthy operation. + * <p> + * If the device has not been provisioned before, this call also provisions the device + * which involves accessing the provisioning server and can take a variable time to + * complete depending on the network connectivity. + * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking + * mode by launching the provisioning in the background and returning. The listener + * will be called when provisioning and preparation has finished. If a + * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning + * and preparation has finished, i.e., runs in blocking mode. + * <p> + * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM + * session being ready. The application should not make any assumption about its call + * sequence (e.g., before or after prepareDrm returns), or the thread context that will + * execute the listener (unless the listener is registered with a handler thread). + * <p> + * + * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved + * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}. + * + * @throws IllegalStateException if called before being prepared or the DRM was + * prepared already + * @throws UnsupportedSchemeException if the crypto scheme is not supported + * @throws ResourceBusyException if required DRM resources are in use + * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a + * network error + * @throws ProvisioningServerErrorException if provisioning is required but failed due to + * the request denied by the provisioning server + */ + public abstract void prepareDrm(@NonNull UUID uuid) + throws UnsupportedSchemeException, ResourceBusyException, + ProvisioningNetworkErrorException, ProvisioningServerErrorException; + + /** + * Releases the DRM session + * <p> + * The player has to have an active DRM session and be in stopped, or prepared + * state before this call is made. + * A {@code reset()} call will release the DRM session implicitly. + * + * @throws NoDrmSchemeException if there is no active DRM session to release + */ + public abstract void releaseDrm() throws NoDrmSchemeException; + + /** + * A key request/response exchange occurs between the app and a license server + * to obtain or release keys used to decrypt encrypted content. + * <p> + * getKeyRequest() is used to obtain an opaque key request byte array that is + * delivered to the license server. The opaque key request byte array is returned + * in KeyRequest.data. The recommended URL to deliver the key request to is + * returned in KeyRequest.defaultUrl. + * <p> + * After the app has received the key request response from the server, + * it should deliver to the response to the DRM engine plugin using the method + * {@link #provideKeyResponse}. + * + * @param keySetId is the key-set identifier of the offline keys being released when keyType is + * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when + * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. + * + * @param initData is the container-specific initialization data when the keyType is + * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is + * interpreted based on the mime type provided in the mimeType parameter. It could + * contain, for example, the content ID, key ID or other data obtained from the content + * metadata that is required in generating the key request. + * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null. + * + * @param mimeType identifies the mime type of the content + * + * @param keyType specifies the type of the request. The request may be to acquire + * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content + * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired + * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId. + * + * @param optionalParameters are included in the key request message to + * allow a client application to provide additional message parameters to the server. + * This may be {@code null} if no additional parameters are to be sent. + * + * @throws NoDrmSchemeException if there is no active DRM session + */ + @NonNull + public abstract MediaDrm.KeyRequest getKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData, + @Nullable String mimeType, @MediaDrm.KeyType int keyType, + @Nullable Map<String, String> optionalParameters) + throws NoDrmSchemeException; + + /** + * A key response is received from the license server by the app, then it is + * provided to the DRM engine plugin using provideKeyResponse. When the + * response is for an offline key request, a key-set identifier is returned that + * can be used to later restore the keys to a new session with the method + * {@ link # restoreKeys}. + * When the response is for a streaming or release request, null is returned. + * + * @param keySetId When the response is for a release request, keySetId identifies + * the saved key associated with the release request (i.e., the same keySetId + * passed to the earlier {@ link # getKeyRequest} call. It MUST be null when the + * response is for either streaming or offline key requests. + * + * @param response the byte array response from the server + * + * @throws NoDrmSchemeException if there is no active DRM session + * @throws DeniedByServerException if the response indicates that the + * server rejected the request + */ + public abstract byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) + throws NoDrmSchemeException, DeniedByServerException; + + /** + * Restore persisted offline keys into a new session. keySetId identifies the + * keys to load, obtained from a prior call to {@link #provideKeyResponse}. + * + * @param keySetId identifies the saved key set to restore + */ + public abstract void restoreKeys(@NonNull byte[] keySetId) + throws NoDrmSchemeException; + + /** + * Read a DRM engine plugin String property value, given the property name string. + * <p> + * @param propertyName the property name + * + * Standard fields names are: + * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, + * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} + */ + @NonNull + public abstract String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName) + throws NoDrmSchemeException; + + /** + * Set a DRM engine plugin String property value. + * <p> + * @param propertyName the property name + * @param value the property value + * + * Standard fields names are: + * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, + * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} + */ + public abstract void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName, + @NonNull String value) + throws NoDrmSchemeException; + + /** + * Encapsulates the DRM properties of the source. + */ + public abstract static class DrmInfo { + /** + * Returns the PSSH info of the data source for each supported DRM scheme. + */ + public abstract Map<UUID, byte[]> getPssh(); + + /** + * Returns the intersection of the data source and the device DRM schemes. + * It effectively identifies the subset of the source's DRM schemes which + * are supported by the device too. + */ + public abstract List<UUID> getSupportedSchemes(); + }; // DrmInfo + + /** + * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm(). + * Extends MediaDrm.MediaDrmException + */ + public abstract static class NoDrmSchemeException extends MediaDrmException { + protected NoDrmSchemeException(String detailMessage) { + super(detailMessage); + } + } + + /** + * Thrown when the device requires DRM provisioning but the provisioning attempt has + * failed due to a network error (Internet reachability, timeout, etc.). + * Extends MediaDrm.MediaDrmException + */ + public abstract static class ProvisioningNetworkErrorException extends MediaDrmException { + protected ProvisioningNetworkErrorException(String detailMessage) { + super(detailMessage); + } + } + + /** + * Thrown when the device requires DRM provisioning but the provisioning attempt has + * failed due to the provisioning server denying the request. + * Extends MediaDrm.MediaDrmException + */ + public abstract static class ProvisioningServerErrorException extends MediaDrmException { + protected ProvisioningServerErrorException(String detailMessage) { + super(detailMessage); + } + } + + public static final class MetricsConstants { + private MetricsConstants() {} + + /** + * Key to extract the MIME type of the video track + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is a String. + */ + public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime"; + + /** + * Key to extract the codec being used to decode the video track + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is a String. + */ + public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + + /** + * Key to extract the width (in pixels) of the video track + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is an integer. + */ + public static final String WIDTH = "android.media.mediaplayer.width"; + + /** + * Key to extract the height (in pixels) of the video track + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is an integer. + */ + public static final String HEIGHT = "android.media.mediaplayer.height"; + + /** + * Key to extract the count of video frames played + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is an integer. + */ + public static final String FRAMES = "android.media.mediaplayer.frames"; + + /** + * Key to extract the count of video frames dropped + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is an integer. + */ + public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + + /** + * Key to extract the MIME type of the audio track + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is a String. + */ + public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime"; + + /** + * Key to extract the codec being used to decode the audio track + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is a String. + */ + public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + + /** + * Key to extract the duration (in milliseconds) of the + * media being played + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is a long. + */ + public static final String DURATION = "android.media.mediaplayer.durationMs"; + + /** + * Key to extract the playing time (in milliseconds) of the + * media being played + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is a long. + */ + public static final String PLAYING = "android.media.mediaplayer.playingMs"; + + /** + * Key to extract the count of errors encountered while + * playing the media + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is an integer. + */ + public static final String ERRORS = "android.media.mediaplayer.err"; + + /** + * Key to extract an (optional) error code detected while + * playing the media + * from the {@link MediaPlayer2#getMetrics} return value. + * The value is an integer. + */ + public static final String ERROR_CODE = "android.media.mediaplayer.errcode"; + + } +} diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java new file mode 100644 index 000000000000..86a285cccaf9 --- /dev/null +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -0,0 +1,4899 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import android.os.Process; +import android.os.PowerManager; +import android.os.SystemProperties; +import android.provider.Settings; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.Log; +import android.util.Pair; +import android.util.ArrayMap; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.widget.VideoView; +import android.graphics.SurfaceTexture; +import android.media.AudioManager; +import android.media.MediaDrm; +import android.media.MediaFormat; +import android.media.MediaPlayer2; +import android.media.MediaTimeProvider; +import android.media.PlaybackParams; +import android.media.SubtitleController; +import android.media.SubtitleController.Anchor; +import android.media.SubtitleData; +import android.media.SubtitleTrack.RenderingWidget; +import android.media.SyncParams; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import dalvik.system.CloseGuard; + +import libcore.io.IoBridge; +import libcore.io.Streams; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.AutoCloseable; +import java.lang.Runnable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.HttpCookie; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.URL; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.concurrent.Executor; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.UUID; +import java.util.Vector; + + +/** + * MediaPlayer2 class can be used to control playback + * of audio/video files and streams. An example on how to use the methods in + * this class can be found in {@link android.widget.VideoView}. + * + * <p>Topics covered here are: + * <ol> + * <li><a href="#StateDiagram">State Diagram</a> + * <li><a href="#Valid_and_Invalid_States">Valid and Invalid States</a> + * <li><a href="#Permissions">Permissions</a> + * <li><a href="#Callbacks">Register informational and error callbacks</a> + * </ol> + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about how to use MediaPlayer2, read the + * <a href="{@docRoot}guide/topics/media/mediaplayer.html">Media Playback</a> developer guide.</p> + * </div> + * + * <a name="StateDiagram"></a> + * <h3>State Diagram</h3> + * + * <p>Playback control of audio/video files and streams is managed as a state + * machine. The following diagram shows the life cycle and the states of a + * MediaPlayer2 object driven by the supported playback control operations. + * The ovals represent the states a MediaPlayer2 object may reside + * in. The arcs represent the playback control operations that drive the object + * state transition. There are two types of arcs. The arcs with a single arrow + * head represent synchronous method calls, while those with + * a double arrow head represent asynchronous method calls.</p> + * + * <p><img src="../../../images/mediaplayer_state_diagram.gif" + * alt="MediaPlayer State diagram" + * border="0" /></p> + * + * <p>From this state diagram, one can see that a MediaPlayer2 object has the + * following states:</p> + * <ul> + * <li>When a MediaPlayer2 object is just created using <code>new</code> or + * after {@link #reset()} is called, it is in the <em>Idle</em> state; and after + * {@link #close()} is called, it is in the <em>End</em> state. Between these + * two states is the life cycle of the MediaPlayer2 object. + * <ul> + * <li>There is a subtle but important difference between a newly constructed + * MediaPlayer2 object and the MediaPlayer2 object after {@link #reset()} + * is called. It is a programming error to invoke methods such + * as {@link #getCurrentPosition()}, + * {@link #getDuration()}, {@link #getVideoHeight()}, + * {@link #getVideoWidth()}, {@link #setAudioAttributes(AudioAttributes)}, + * {@link #setLooping(boolean)}, + * {@link #setVolume(float, float)}, {@link #pause()}, {@link #play()}, + * {@link #seekTo(long, int)}, {@link #prepare()} or + * {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these + * methods is called right after a MediaPlayer2 object is constructed, + * the user supplied callback method OnErrorListener.onError() won't be + * called by the internal player engine and the object state remains + * unchanged; but if these methods are called right after {@link #reset()}, + * the user supplied callback method OnErrorListener.onError() will be + * invoked by the internal player engine and the object will be + * transfered to the <em>Error</em> state. </li> + * <li>It is also recommended that once + * a MediaPlayer2 object is no longer being used, call {@link #close()} immediately + * so that resources used by the internal player engine associated with the + * MediaPlayer2 object can be released immediately. Resource may include + * singleton resources such as hardware acceleration components and + * failure to call {@link #close()} may cause subsequent instances of + * MediaPlayer2 objects to fallback to software implementations or fail + * altogether. Once the MediaPlayer2 + * object is in the <em>End</em> state, it can no longer be used and + * there is no way to bring it back to any other state. </li> + * <li>Furthermore, + * the MediaPlayer2 objects created using <code>new</code> is in the + * <em>Idle</em> state. + * </li> + * </ul> + * </li> + * <li>In general, some playback control operation may fail due to various + * reasons, such as unsupported audio/video format, poorly interleaved + * audio/video, resolution too high, streaming timeout, and the like. + * Thus, error reporting and recovery is an important concern under + * these circumstances. Sometimes, due to programming errors, invoking a playback + * control operation in an invalid state may also occur. Under all these + * error conditions, the internal player engine invokes a user supplied + * EventCallback.onError() method if an EventCallback has been + * registered beforehand via + * {@link #registerEventCallback(Executor, EventCallback)}. + * <ul> + * <li>It is important to note that once an error occurs, the + * MediaPlayer2 object enters the <em>Error</em> state (except as noted + * above), even if an error listener has not been registered by the application.</li> + * <li>In order to reuse a MediaPlayer2 object that is in the <em> + * Error</em> state and recover from the error, + * {@link #reset()} can be called to restore the object to its <em>Idle</em> + * state.</li> + * <li>It is good programming practice to have your application + * register a OnErrorListener to look out for error notifications from + * the internal player engine.</li> + * <li>IllegalStateException is + * thrown to prevent programming errors such as calling {@link #prepare()}, + * {@link #prepareAsync()}, {@link #setDataSource(DataSourceDesc)}, or + * {@code setPlaylist} methods in an invalid state. </li> + * </ul> + * </li> + * <li>Calling + * {@link #setDataSource(DataSourceDesc)}, or + * {@code setPlaylist} transfers a + * MediaPlayer2 object in the <em>Idle</em> state to the + * <em>Initialized</em> state. + * <ul> + * <li>An IllegalStateException is thrown if + * setDataSource() or setPlaylist() is called in any other state.</li> + * <li>It is good programming + * practice to always look out for <code>IllegalArgumentException</code> + * and <code>IOException</code> that may be thrown from + * <code>setDataSource</code> and <code>setPlaylist</code> methods.</li> + * </ul> + * </li> + * <li>A MediaPlayer2 object must first enter the <em>Prepared</em> state + * before playback can be started. + * <ul> + * <li>There are two ways (synchronous vs. + * asynchronous) that the <em>Prepared</em> state can be reached: + * either a call to {@link #prepare()} (synchronous) which + * transfers the object to the <em>Prepared</em> state once the method call + * returns, or a call to {@link #prepareAsync()} (asynchronous) which + * first transfers the object to the <em>Preparing</em> state after the + * call returns (which occurs almost right way) while the internal + * player engine continues working on the rest of preparation work + * until the preparation work completes. When the preparation completes or when {@link #prepare()} call returns, + * the internal player engine then calls a user supplied callback method, + * onPrepared() of the EventCallback interface, if an + * EventCallback is registered beforehand via {@link + * #registerEventCallback(Executor, EventCallback)}.</li> + * <li>It is important to note that + * the <em>Preparing</em> state is a transient state, and the behavior + * of calling any method with side effect while a MediaPlayer2 object is + * in the <em>Preparing</em> state is undefined.</li> + * <li>An IllegalStateException is + * thrown if {@link #prepare()} or {@link #prepareAsync()} is called in + * any other state.</li> + * <li>While in the <em>Prepared</em> state, properties + * such as audio/sound volume, screenOnWhilePlaying, looping can be + * adjusted by invoking the corresponding set methods.</li> + * </ul> + * </li> + * <li>To start the playback, {@link #play()} must be called. After + * {@link #play()} returns successfully, the MediaPlayer2 object is in the + * <em>Started</em> state. {@link #isPlaying()} can be called to test + * whether the MediaPlayer2 object is in the <em>Started</em> state. + * <ul> + * <li>While in the <em>Started</em> state, the internal player engine calls + * a user supplied EventCallback.onBufferingUpdate() callback + * method if an EventCallback has been registered beforehand + * via {@link #registerEventCallback(Executor, EventCallback)}. + * This callback allows applications to keep track of the buffering status + * while streaming audio/video.</li> + * <li>Calling {@link #play()} has not effect + * on a MediaPlayer2 object that is already in the <em>Started</em> state.</li> + * </ul> + * </li> + * <li>Playback can be paused and stopped, and the current playback position + * can be adjusted. Playback can be paused via {@link #pause()}. When the call to + * {@link #pause()} returns, the MediaPlayer2 object enters the + * <em>Paused</em> state. Note that the transition from the <em>Started</em> + * state to the <em>Paused</em> state and vice versa happens + * asynchronously in the player engine. It may take some time before + * the state is updated in calls to {@link #isPlaying()}, and it can be + * a number of seconds in the case of streamed content. + * <ul> + * <li>Calling {@link #play()} to resume playback for a paused + * MediaPlayer2 object, and the resumed playback + * position is the same as where it was paused. When the call to + * {@link #play()} returns, the paused MediaPlayer2 object goes back to + * the <em>Started</em> state.</li> + * <li>Calling {@link #pause()} has no effect on + * a MediaPlayer2 object that is already in the <em>Paused</em> state.</li> + * </ul> + * </li> + * <li>The playback position can be adjusted with a call to + * {@link #seekTo(long, int)}. + * <ul> + * <li>Although the asynchronuous {@link #seekTo(long, int)} + * call returns right away, the actual seek operation may take a while to + * finish, especially for audio/video being streamed. When the actual + * seek operation completes, the internal player engine calls a user + * supplied EventCallback.onSeekComplete() if an EventCallback + * has been registered beforehand via + * {@link #registerEventCallback(Executor, EventCallback)}.</li> + * <li>Please + * note that {@link #seekTo(long, int)} can also be called in the other states, + * such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted + * </em> state. When {@link #seekTo(long, int)} is called in those states, + * one video frame will be displayed if the stream has video and the requested + * position is valid. + * </li> + * <li>Furthermore, the actual current playback position + * can be retrieved with a call to {@link #getCurrentPosition()}, which + * is helpful for applications such as a Music player that need to keep + * track of the playback progress.</li> + * </ul> + * </li> + * <li>When the playback reaches the end of stream, the playback completes. + * <ul> + * <li>If the looping mode was being set to <var>true</var>with + * {@link #setLooping(boolean)}, the MediaPlayer2 object shall remain in + * the <em>Started</em> state.</li> + * <li>If the looping mode was set to <var>false + * </var>, the player engine calls a user supplied callback method, + * EventCallback.onCompletion(), if an EventCallback is registered + * beforehand via {@link #registerEventCallback(Executor, EventCallback)}. + * The invoke of the callback signals that the object is now in the <em> + * PlaybackCompleted</em> state.</li> + * <li>While in the <em>PlaybackCompleted</em> + * state, calling {@link #play()} can restart the playback from the + * beginning of the audio/video source.</li> + * </ul> + * + * + * <a name="Valid_and_Invalid_States"></a> + * <h3>Valid and invalid states</h3> + * + * <table border="0" cellspacing="0" cellpadding="0"> + * <tr><td>Method Name </p></td> + * <td>Valid Sates </p></td> + * <td>Invalid States </p></td> + * <td>Comments </p></td></tr> + * <tr><td>attachAuxEffect </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> + * <td>{Idle, Error} </p></td> + * <td>This method must be called after setDataSource or setPlaylist. + * Calling it does not change the object state. </p></td></tr> + * <tr><td>getAudioSessionId </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>getCurrentPosition </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted} </p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change the + * state. Calling this method in an invalid state transfers the object + * to the <em>Error</em> state. </p></td></tr> + * <tr><td>getDuration </p></td> + * <td>{Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> + * <td>{Idle, Initialized, Error} </p></td> + * <td>Successful invoke of this method in a valid state does not change the + * state. Calling this method in an invalid state transfers the object + * to the <em>Error</em> state. </p></td></tr> + * <tr><td>getVideoHeight </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change the + * state. Calling this method in an invalid state transfers the object + * to the <em>Error</em> state. </p></td></tr> + * <tr><td>getVideoWidth </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change + * the state. Calling this method in an invalid state transfers the + * object to the <em>Error</em> state. </p></td></tr> + * <tr><td>isPlaying </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change + * the state. Calling this method in an invalid state transfers the + * object to the <em>Error</em> state. </p></td></tr> + * <tr><td>pause </p></td> + * <td>{Started, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Prepared, Stopped, Error}</p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Paused</em> state. Calling this method in an + * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>prepare </p></td> + * <td>{Initialized, Stopped} </p></td> + * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Prepared</em> state. Calling this method in an + * invalid state throws an IllegalStateException.</p></td></tr> + * <tr><td>prepareAsync </p></td> + * <td>{Initialized, Stopped} </p></td> + * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Preparing</em> state. Calling this method in an + * invalid state throws an IllegalStateException.</p></td></tr> + * <tr><td>release </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>After {@link #close()}, the object is no longer available. </p></td></tr> + * <tr><td>reset </p></td> + * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, + * PlaybackCompleted, Error}</p></td> + * <td>{}</p></td> + * <td>After {@link #reset()}, the object is like being just created.</p></td></tr> + * <tr><td>seekTo </p></td> + * <td>{Prepared, Started, Paused, PlaybackCompleted} </p></td> + * <td>{Idle, Initialized, Stopped, Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change + * the state. Calling this method in an invalid state transfers the + * object to the <em>Error</em> state. </p></td></tr> + * <tr><td>setAudioAttributes </p></td> + * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method does not change the state. In order for the + * target audio attributes type to become effective, this method must be called before + * prepare() or prepareAsync().</p></td></tr> + * <tr><td>setAudioSessionId </p></td> + * <td>{Idle} </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, + * Error} </p></td> + * <td>This method must be called in idle state as the audio session ID must be known before + * calling setDataSource or setPlaylist. Calling it does not change the object + * state. </p></td></tr> + * <tr><td>setAudioStreamType (deprecated)</p></td> + * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method does not change the state. In order for the + * target audio stream type to become effective, this method must be called before + * prepare() or prepareAsync().</p></td></tr> + * <tr><td>setAuxEffectSendLevel </p></td> + * <td>any</p></td> + * <td>{} </p></td> + * <td>Calling this method does not change the object state. </p></td></tr> + * <tr><td>setDataSource </p></td> + * <td>{Idle} </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, + * Error} </p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Initialized</em> state. Calling this method in an + * invalid state throws an IllegalStateException.</p></td></tr> + * <tr><td>setPlaylist </p></td> + * <td>{Idle} </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, + * Error} </p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Initialized</em> state. Calling this method in an + * invalid state throws an IllegalStateException.</p></td></tr> + * <tr><td>setDisplay </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>setSurface </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>setVideoScalingMode </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> + * <td>{Idle, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>setLooping </p></td> + * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method in a valid state does not change + * the state. Calling this method in an + * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>isLooping </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>registerDrmEventCallback </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>registerEventCallback </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>setPlaybackParams</p></td> + * <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td> + * <td>{Idle, Stopped} </p></td> + * <td>This method will change state in some cases, depending on when it's called. + * </p></td></tr> + * <tr><td>setScreenOnWhilePlaying</></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> + * <tr><td>setVolume </p></td> + * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, + * PlaybackCompleted}</p></td> + * <td>{Error}</p></td> + * <td>Successful invoke of this method does not change the state. + * <tr><td>setWakeMode </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state.</p></td></tr> + * <tr><td>start </p></td> + * <td>{Prepared, Started, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Stopped, Error}</p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Started</em> state. Calling this method in an + * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>stop </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method in a valid state transfers the + * object to the <em>Stopped</em> state. Calling this method in an + * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>getTrackInfo </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>addTimedTextSource </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>selectTrack </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>deselectTrack </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * + * </table> + * + * <a name="Permissions"></a> + * <h3>Permissions</h3> + * <p>One may need to declare a corresponding WAKE_LOCK permission {@link + * android.R.styleable#AndroidManifestUsesPermission <uses-permission>} + * element. + * + * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission + * when used with network-based content. + * + * <a name="Callbacks"></a> + * <h3>Callbacks</h3> + * <p>Applications may want to register for informational and error + * events in order to be informed of some internal state update and + * possible runtime errors during playback or streaming. Registration for + * these events is done by properly setting the appropriate listeners (via calls + * to + * {@link #registerEventCallback(Executor, EventCallback)}, + * {@link #registerDrmEventCallback(Executor, DrmEventCallback)}). + * In order to receive the respective callback + * associated with these listeners, applications are required to create + * MediaPlayer2 objects on a thread with its own Looper running (main UI + * thread by default has a Looper running). + * + * @hide + */ +public final class MediaPlayer2Impl extends MediaPlayer2 { + static { + System.loadLibrary("media2_jni"); + native_init(); + } + + private final static String TAG = "MediaPlayer2Impl"; + + private long mNativeContext; // accessed by native methods + private long mNativeSurfaceTexture; // accessed by native methods + private int mListenerContext; // accessed by native methods + private SurfaceHolder mSurfaceHolder; + private EventHandler mEventHandler; + private PowerManager.WakeLock mWakeLock = null; + private boolean mScreenOnWhilePlaying; + private boolean mStayAwake; + private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE; + private int mUsage = -1; + private boolean mBypassInterruptionPolicy; + private final CloseGuard mGuard = CloseGuard.get(); + + private List<DataSourceDesc> mPlaylist; + private int mPLCurrentIndex = 0; + private int mPLNextIndex = -1; + private int mLoopingMode = LOOPING_MODE_NONE; + + // Modular DRM + private UUID mDrmUUID; + private final Object mDrmLock = new Object(); + private DrmInfoImpl mDrmInfoImpl; + private MediaDrm mDrmObj; + private byte[] mDrmSessionId; + private boolean mDrmInfoResolved; + private boolean mActiveDrmScheme; + private boolean mDrmConfigAllowed; + private boolean mDrmProvisioningInProgress; + private boolean mPrepareDrmInProgress; + private ProvisioningThread mDrmProvisioningThread; + + /** + * Default constructor. + * <p>When done with the MediaPlayer2Impl, you should call {@link #close()}, + * to free the resources. If not released, too many MediaPlayer2Impl instances may + * result in an exception.</p> + */ + public MediaPlayer2Impl() { + Looper looper; + if ((looper = Looper.myLooper()) != null) { + mEventHandler = new EventHandler(this, looper); + } else if ((looper = Looper.getMainLooper()) != null) { + mEventHandler = new EventHandler(this, looper); + } else { + mEventHandler = null; + } + + mTimeProvider = new TimeProvider(this); + mOpenSubtitleSources = new Vector<InputStream>(); + mGuard.open("close"); + + /* Native setup requires a weak reference to our object. + * It's easier to create it here than in C++. + */ + native_setup(new WeakReference<MediaPlayer2Impl>(this)); + } + + /* + * Update the MediaPlayer2Impl SurfaceTexture. + * Call after setting a new display surface. + */ + private native void _setVideoSurface(Surface surface); + + /* Do not change these values (starting with INVOKE_ID) without updating + * their counterparts in include/media/mediaplayer2.h! + */ + private static final int INVOKE_ID_GET_TRACK_INFO = 1; + private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2; + private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3; + private static final int INVOKE_ID_SELECT_TRACK = 4; + private static final int INVOKE_ID_DESELECT_TRACK = 5; + private static final int INVOKE_ID_SET_VIDEO_SCALE_MODE = 6; + private static final int INVOKE_ID_GET_SELECTED_TRACK = 7; + + /** + * Create a request parcel which can be routed to the native media + * player using {@link #invoke(Parcel, Parcel)}. The Parcel + * returned has the proper InterfaceToken set. The caller should + * not overwrite that token, i.e it can only append data to the + * Parcel. + * + * @return A parcel suitable to hold a request for the native + * player. + * {@hide} + */ + @Override + public Parcel newRequest() { + Parcel parcel = Parcel.obtain(); + return parcel; + } + + /** + * Invoke a generic method on the native player using opaque + * parcels for the request and reply. Both payloads' format is a + * convention between the java caller and the native player. + * Must be called after setDataSource or setPlaylist to make sure a native player + * exists. On failure, a RuntimeException is thrown. + * + * @param request Parcel with the data for the extension. The + * caller must use {@link #newRequest()} to get one. + * + * @param reply Output parcel with the data returned by the + * native player. + * {@hide} + */ + @Override + public void invoke(Parcel request, Parcel reply) { + int retcode = native_invoke(request, reply); + reply.setDataPosition(0); + if (retcode != 0) { + throw new RuntimeException("failure code: " + retcode); + } + } + + /** + * Sets the {@link SurfaceHolder} to use for displaying the video + * portion of the media. + * + * Either a surface holder or surface must be set if a display or video sink + * is needed. Not calling this method or {@link #setSurface(Surface)} + * when playing back a video will result in only the audio track being played. + * A null surface holder or surface will result in only the audio track being + * played. + * + * @param sh the SurfaceHolder to use for video display + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released. + * @hide + */ + @Override + public void setDisplay(SurfaceHolder sh) { + mSurfaceHolder = sh; + Surface surface; + if (sh != null) { + surface = sh.getSurface(); + } else { + surface = null; + } + _setVideoSurface(surface); + updateSurfaceScreenOn(); + } + + /** + * Sets the {@link Surface} to be used as the sink for the video portion of + * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but + * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a + * Surface will un-set any Surface or SurfaceHolder that was previously set. + * A null surface will result in only the audio track being played. + * + * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps + * returned from {@link SurfaceTexture#getTimestamp()} will have an + * unspecified zero point. These timestamps cannot be directly compared + * between different media sources, different instances of the same media + * source, or multiple runs of the same program. The timestamp is normally + * monotonically increasing and is unaffected by time-of-day adjustments, + * but it is reset when the position is set. + * + * @param surface The {@link Surface} to be used for the video portion of + * the media. + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released. + */ + @Override + public void setSurface(Surface surface) { + if (mScreenOnWhilePlaying && surface != null) { + Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); + } + mSurfaceHolder = null; + _setVideoSurface(surface); + updateSurfaceScreenOn(); + } + + /** + * Sets video scaling mode. To make the target video scaling mode + * effective during playback, this method must be called after + * data source is set. If not called, the default video + * scaling mode is {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}. + * + * <p> The supported video scaling modes are: + * <ul> + * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT} + * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} + * </ul> + * + * @param mode target video scaling mode. Must be one of the supported + * video scaling modes; otherwise, IllegalArgumentException will be thrown. + * + * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT + * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING + * @hide + */ + @Override + public void setVideoScalingMode(int mode) { + if (!isVideoScalingModeSupported(mode)) { + final String msg = "Scaling mode " + mode + " is not supported"; + throw new IllegalArgumentException(msg); + } + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE); + request.writeInt(mode); + invoke(request, reply); + } finally { + request.recycle(); + reply.recycle(); + } + } + + /** + * Discards all pending commands. + */ + @Override + public void clearPendingCommands() { + } + + /** + * Sets the data source as described by a DataSourceDesc. + * + * @param dsd the descriptor of data source you want to play + * @throws IllegalStateException if it is called in an invalid state + * @throws NullPointerException if dsd is null + */ + @Override + public void setDataSource(@NonNull DataSourceDesc dsd) throws IOException { + Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); + mPlaylist = Collections.synchronizedList(new ArrayList<DataSourceDesc>(1)); + mPlaylist.add(dsd); + mPLCurrentIndex = 0; + setDataSourcePriv(dsd); + } + + /** + * Gets the current data source as described by a DataSourceDesc. + * + * @return the current DataSourceDesc + */ + @Override + public DataSourceDesc getCurrentDataSource() { + if (mPlaylist == null) { + return null; + } + return mPlaylist.get(mPLCurrentIndex); + } + + /** + * Sets the play list. + * + * If startIndex falls outside play list range, it will be clamped to the nearest index + * in the play list. + * + * @param pl the play list of data source you want to play + * @param startIndex the index of the DataSourceDesc in the play list you want to play first + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if pl is null or empty, or pl contains null DataSourceDesc + */ + @Override + public void setPlaylist(@NonNull List<DataSourceDesc> pl, int startIndex) + throws IOException { + if (pl == null || pl.size() == 0) { + throw new IllegalArgumentException("play list cannot be null or empty."); + } + HashSet ids = new HashSet(pl.size()); + for (DataSourceDesc dsd : pl) { + if (dsd == null) { + throw new IllegalArgumentException("DataSourceDesc in play list cannot be null."); + } + if (ids.add(dsd.getId()) == false) { + throw new IllegalArgumentException("DataSourceDesc Id in play list should be unique."); + } + } + + if (startIndex < 0) { + startIndex = 0; + } else if (startIndex >= pl.size()) { + startIndex = pl.size() - 1; + } + + mPlaylist = Collections.synchronizedList(new ArrayList(pl)); + mPLCurrentIndex = startIndex; + setDataSourcePriv(mPlaylist.get(startIndex)); + // TODO: handle the preparation of next source in the play list. + // It should be processed after current source is prepared. + } + + /** + * Gets a copy of the play list. + * + * @return a copy of the play list used by {@link MediaPlayer2} + */ + @Override + public List<DataSourceDesc> getPlaylist() { + if (mPlaylist == null) { + return null; + } + return new ArrayList(mPlaylist); + } + + /** + * Sets the index of current DataSourceDesc in the play list to be played. + * + * @param index the index of DataSourceDesc in the play list you want to play + * @throws IllegalArgumentException if the play list is null + * @throws NullPointerException if index is outside play list range + */ + @Override + public void setCurrentPlaylistItem(int index) { + if (mPlaylist == null) { + throw new IllegalArgumentException("play list has not been set yet."); + } + if (index < 0 || index >= mPlaylist.size()) { + throw new IndexOutOfBoundsException("index is out of play list range."); + } + + if (index == mPLCurrentIndex) { + return; + } + + // TODO: in playing state, stop current source and start to play source of index. + mPLCurrentIndex = index; + } + + /** + * Sets the index of next-to-be-played DataSourceDesc in the play list. + * + * @param index the index of next-to-be-played DataSourceDesc in the play list + * @throws IllegalArgumentException if the play list is null + * @throws NullPointerException if index is outside play list range + */ + @Override + public void setNextPlaylistItem(int index) { + if (mPlaylist == null) { + throw new IllegalArgumentException("play list has not been set yet."); + } + if (index < 0 || index >= mPlaylist.size()) { + throw new IndexOutOfBoundsException("index is out of play list range."); + } + + if (index == mPLNextIndex) { + return; + } + + // TODO: prepare the new next-to-be-played DataSourceDesc + mPLNextIndex = index; + } + + /** + * Gets the current index of play list. + * + * @return the index of the current DataSourceDesc in the play list + */ + @Override + public int getCurrentPlaylistItemIndex() { + return mPLCurrentIndex; + } + + /** + * Sets the looping mode of the play list. + * The mode shall be one of {@link #LOOPING_MODE_NONE}, {@link #LOOPING_MODE_FULL}, + * {@link #LOOPING_MODE_SINGLE}, {@link #LOOPING_MODE_SHUFFLE}. + * + * @param mode the mode in which the play list will be played + * @throws IllegalArgumentException if mode is not supported + */ + @Override + public void setLoopingMode(@LoopingMode int mode) { + if (mode != LOOPING_MODE_NONE + && mode != LOOPING_MODE_FULL + && mode != LOOPING_MODE_SINGLE + && mode != LOOPING_MODE_SHUFFLE) { + throw new IllegalArgumentException("mode is not supported."); + } + mLoopingMode = mode; + if (mPlaylist == null) { + return; + } + + // TODO: handle the new mode if necessary. + } + + /** + * Gets the looping mode of play list. + * + * @return the looping mode of the play list + */ + @Override + public int getLoopingMode() { + return mPLCurrentIndex; + } + + /** + * Moves the DataSourceDesc at indexFrom in the play list to indexTo. + * + * @throws IllegalArgumentException if the play list is null + * @throws IndexOutOfBoundsException if indexFrom or indexTo is outside play list range + */ + @Override + public void movePlaylistItem(int indexFrom, int indexTo) { + if (mPlaylist == null) { + throw new IllegalArgumentException("play list has not been set yet."); + } + // TODO: move the DataSourceDesc from indexFrom to indexTo. + } + + /** + * Removes the DataSourceDesc at index in the play list. + * + * If index is same as the current index of the play list, current DataSourceDesc + * will be stopped and playback moves to next source in the list. + * + * @return the removed DataSourceDesc at index in the play list + * @throws IllegalArgumentException if the play list is null + * @throws IndexOutOfBoundsException if index is outside play list range + */ + @Override + public DataSourceDesc removePlaylistItem(int index) { + if (mPlaylist == null) { + throw new IllegalArgumentException("play list has not been set yet."); + } + + DataSourceDesc oldDsd = mPlaylist.remove(index); + // TODO: if index == mPLCurrentIndex, stop current source and move to next one. + // if index == mPLNextIndex, prepare the new next-to-be-played source. + return oldDsd; + } + + /** + * Inserts the DataSourceDesc to the play list at position index. + * + * This will not change the DataSourceDesc currently being played. + * If index is less than or equal to the current index of the play list, + * the current index of the play list will be incremented correspondingly. + * + * @param index the index you want to add dsd to the play list + * @param dsd the descriptor of data source you want to add to the play list + * @throws IndexOutOfBoundsException if index is outside play list range + * @throws NullPointerException if dsd is null + */ + @Override + public void addPlaylistItem(int index, DataSourceDesc dsd) { + Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); + + if (mPlaylist == null) { + if (index == 0) { + mPlaylist = Collections.synchronizedList(new ArrayList<DataSourceDesc>()); + mPlaylist.add(dsd); + mPLCurrentIndex = 0; + return; + } + throw new IllegalArgumentException("index should be 0 for first DataSourceDesc."); + } + + long id = dsd.getId(); + for (DataSourceDesc pldsd : mPlaylist) { + if (id == pldsd.getId()) { + throw new IllegalArgumentException("Id of dsd already exists in the play list."); + } + } + + mPlaylist.add(index, dsd); + if (index <= mPLCurrentIndex) { + ++mPLCurrentIndex; + } + } + + /** + * replaces the DataSourceDesc at index in the play list with given dsd. + * + * When index is same as the current index of the play list, the current source + * will be stopped and the new source will be played, except that if new + * and old source only differ on end position and current media position is + * smaller then the new end position. + * + * This will not change the DataSourceDesc currently being played. + * If index is less than or equal to the current index of the play list, + * the current index of the play list will be incremented correspondingly. + * + * @param index the index you want to add dsd to the play list + * @param dsd the descriptor of data source you want to add to the play list + * @throws IndexOutOfBoundsException if index is outside play list range + * @throws NullPointerException if dsd is null + */ + @Override + public DataSourceDesc editPlaylistItem(int index, DataSourceDesc dsd) { + Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); + Preconditions.checkNotNull(mPlaylist, "the play list cannot be null"); + + long id = dsd.getId(); + for (int i = 0; i < mPlaylist.size(); ++i) { + if (i == index) { + continue; + } + if (id == mPlaylist.get(i).getId()) { + throw new IllegalArgumentException("Id of dsd already exists in the play list."); + } + } + + // TODO: if needed, stop playback of current source, and start new dsd. + DataSourceDesc oldDsd = mPlaylist.set(index, dsd); + return mPlaylist.set(index, dsd); + } + + private void setDataSourcePriv(@NonNull DataSourceDesc dsd) throws IOException { + Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null"); + + switch (dsd.getType()) { + case DataSourceDesc.TYPE_CALLBACK: + setDataSourcePriv(dsd.getId(), + dsd.getMedia2DataSource()); + break; + + case DataSourceDesc.TYPE_FD: + setDataSourcePriv(dsd.getId(), + dsd.getFileDescriptor(), + dsd.getFileDescriptorOffset(), + dsd.getFileDescriptorLength()); + break; + + case DataSourceDesc.TYPE_URI: + setDataSourcePriv(dsd.getId(), + dsd.getUriContext(), + dsd.getUri(), + dsd.getUriHeaders(), + dsd.getUriCookies()); + break; + + default: + break; + } + } + + /** + * To provide cookies for the subsequent HTTP requests, you can install your own default cookie + * handler and use other variants of setDataSource APIs instead. Alternatively, you can use + * this API to pass the cookies as a list of HttpCookie. If the app has not installed + * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with + * the provided cookies. If the app has installed its own handler already, this API requires the + * handler to be of CookieManager type such that the API can update the manager’s CookieStore. + * + * <p><strong>Note</strong> that the cross domain redirection is allowed by default, + * but that can be changed with key/value pairs through the headers parameter with + * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to + * disallow or allow cross domain redirection. + * + * @throws IllegalArgumentException if cookies are provided and the installed handler is not + * a CookieManager + * @throws IllegalStateException if it is called in an invalid state + * @throws NullPointerException if context or uri is null + * @throws IOException if uri has a file scheme and an I/O error occurs + */ + private void setDataSourcePriv(long srcId, @NonNull Context context, @NonNull Uri uri, + @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) + throws IOException { + if (context == null) { + throw new NullPointerException("context param can not be null."); + } + + if (uri == null) { + throw new NullPointerException("uri param can not be null."); + } + + if (cookies != null) { + CookieHandler cookieHandler = CookieHandler.getDefault(); + if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) { + throw new IllegalArgumentException("The cookie handler has to be of CookieManager " + + "type when cookies are provided."); + } + } + + // The context and URI usually belong to the calling user. Get a resolver for that user + // and strip out the userId from the URI if present. + final ContentResolver resolver = context.getContentResolver(); + final String scheme = uri.getScheme(); + final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()); + if (ContentResolver.SCHEME_FILE.equals(scheme)) { + setDataSourcePriv(srcId, uri.getPath(), null, null); + return; + } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) + && Settings.AUTHORITY.equals(authority)) { + // Try cached ringtone first since the actual provider may not be + // encryption aware, or it may be stored on CE media storage + final int type = RingtoneManager.getDefaultType(uri); + final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId()); + final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); + if (attemptDataSource(srcId, resolver, cacheUri)) { + return; + } else if (attemptDataSource(srcId, resolver, actualUri)) { + return; + } else { + setDataSourcePriv(srcId, uri.toString(), headers, cookies); + } + } else { + // Try requested Uri locally first, or fallback to media server + if (attemptDataSource(srcId, resolver, uri)) { + return; + } else { + setDataSourcePriv(srcId, uri.toString(), headers, cookies); + } + } + } + + private boolean attemptDataSource(long srcId, ContentResolver resolver, Uri uri) { + try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) { + if (afd.getDeclaredLength() < 0) { + setDataSourcePriv(srcId, afd.getFileDescriptor(), 0, DataSourceDesc.LONG_MAX); + } else { + setDataSourcePriv(srcId, + afd.getFileDescriptor(), + afd.getStartOffset(), + afd.getDeclaredLength()); + } + return true; + } catch (NullPointerException | SecurityException | IOException ex) { + Log.w(TAG, "Couldn't open " + uri + ": " + ex); + return false; + } + } + + private void setDataSourcePriv( + long srcId, String path, Map<String, String> headers, List<HttpCookie> cookies) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException + { + String[] keys = null; + String[] values = null; + + if (headers != null) { + keys = new String[headers.size()]; + values = new String[headers.size()]; + + int i = 0; + for (Map.Entry<String, String> entry: headers.entrySet()) { + keys[i] = entry.getKey(); + values[i] = entry.getValue(); + ++i; + } + } + setDataSourcePriv(srcId, path, keys, values, cookies); + } + + private void setDataSourcePriv(long srcId, String path, String[] keys, String[] values, + List<HttpCookie> cookies) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { + final Uri uri = Uri.parse(path); + final String scheme = uri.getScheme(); + if ("file".equals(scheme)) { + path = uri.getPath(); + } else if (scheme != null) { + // handle non-file sources + nativeSetDataSource( + Media2HTTPService.createHTTPService(path, cookies), + path, + keys, + values); + return; + } + + final File file = new File(path); + if (file.exists()) { + FileInputStream is = new FileInputStream(file); + FileDescriptor fd = is.getFD(); + setDataSourcePriv(srcId, fd, 0, DataSourceDesc.LONG_MAX); + is.close(); + } else { + throw new IOException("setDataSourcePriv failed."); + } + } + + private native void nativeSetDataSource( + Media2HTTPService httpService, String path, String[] keys, String[] values) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; + + /** + * Sets the data source (FileDescriptor) to use. The FileDescriptor must be + * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility + * to close the file descriptor. It is safe to do so as soon as this call returns. + * + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if fd is not a valid FileDescriptor + * @throws IOException if fd can not be read + */ + private void setDataSourcePriv(long srcId, FileDescriptor fd, long offset, long length) + throws IOException { + _setDataSource(fd, offset, length); + } + + private native void _setDataSource(FileDescriptor fd, long offset, long length) + throws IOException; + + /** + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if dataSource is not a valid Media2DataSource + */ + private void setDataSourcePriv(long srcId, Media2DataSource dataSource) { + _setDataSource(dataSource); + } + + private native void _setDataSource(Media2DataSource dataSource); + + /** + * Prepares the player for playback, synchronously. + * + * After setting the datasource and the display surface, you need to either + * call prepare() or prepareAsync(). For files, it is OK to call prepare(), + * which blocks until MediaPlayer2 is ready for playback. + * + * @throws IOException if source can not be accessed + * @throws IllegalStateException if it is called in an invalid state + * @hide + */ + @Override + public void prepare() throws IOException { + _prepare(); + scanInternalSubtitleTracks(); + + // DrmInfo, if any, has been resolved by now. + synchronized (mDrmLock) { + mDrmInfoResolved = true; + } + } + + private native void _prepare() throws IOException, IllegalStateException; + + /** + * Prepares the player for playback, asynchronously. + * + * After setting the datasource and the display surface, you need to either + * call prepare() or prepareAsync(). For streams, you should call prepareAsync(), + * which returns immediately, rather than blocking until enough data has been + * buffered. + * + * @throws IllegalStateException if it is called in an invalid state + */ + @Override + public native void prepareAsync(); + + /** + * Starts or resumes playback. If playback had previously been paused, + * playback will continue from where it was paused. If playback had + * been stopped, or never started before, playback will start at the + * beginning. + * + * @throws IllegalStateException if it is called in an invalid state + */ + @Override + public void play() { + stayAwake(true); + _start(); + } + + private native void _start() throws IllegalStateException; + + + private int getAudioStreamType() { + if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { + mStreamType = _getAudioStreamType(); + } + return mStreamType; + } + + private native int _getAudioStreamType() throws IllegalStateException; + + /** + * Stops playback after playback has been started or paused. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + * #hide + */ + @Override + public void stop() { + stayAwake(false); + _stop(); + } + + private native void _stop() throws IllegalStateException; + + /** + * Pauses playback. Call play() to resume. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + */ + @Override + public void pause() { + stayAwake(false); + _pause(); + } + + private native void _pause() throws IllegalStateException; + + //-------------------------------------------------------------------------- + // Explicit Routing + //-------------------- + private AudioDeviceInfo mPreferredDevice = null; + + /** + * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route + * the output from this MediaPlayer2. + * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source. + * If deviceInfo is null, default routing is restored. + * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and + * does not correspond to a valid audio device. + */ + @Override + public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) { + if (deviceInfo != null && !deviceInfo.isSink()) { + return false; + } + int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0; + boolean status = native_setOutputDevice(preferredDeviceId); + if (status == true) { + synchronized (this) { + mPreferredDevice = deviceInfo; + } + } + return status; + } + + /** + * Returns the selected output specified by {@link #setPreferredDevice}. Note that this + * is not guaranteed to correspond to the actual device being used for playback. + */ + @Override + public AudioDeviceInfo getPreferredDevice() { + synchronized (this) { + return mPreferredDevice; + } + } + + /** + * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2 + * Note: The query is only valid if the MediaPlayer2 is currently playing. + * If the player is not playing, the returned device can be null or correspond to previously + * selected device when the player was last active. + */ + @Override + public AudioDeviceInfo getRoutedDevice() { + int deviceId = native_getRoutedDeviceId(); + if (deviceId == 0) { + return null; + } + AudioDeviceInfo[] devices = + AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); + for (int i = 0; i < devices.length; i++) { + if (devices[i].getId() == deviceId) { + return devices[i]; + } + } + return null; + } + + /* + * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler. + */ + private void enableNativeRoutingCallbacksLocked(boolean enabled) { + if (mRoutingChangeListeners.size() == 0) { + native_enableDeviceCallback(enabled); + } + } + + /** + * The list of AudioRouting.OnRoutingChangedListener interfaces added (with + * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)} + * by an app to receive (re)routing notifications. + */ + @GuardedBy("mRoutingChangeListeners") + private ArrayMap<AudioRouting.OnRoutingChangedListener, + NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>(); + + /** + * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing + * changes on this MediaPlayer2. + * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive + * notifications of rerouting events. + * @param handler Specifies the {@link Handler} object for the thread on which to execute + * the callback. If <code>null</code>, the handler on the main looper will be used. + */ + @Override + public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener, + Handler handler) { + synchronized (mRoutingChangeListeners) { + if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { + enableNativeRoutingCallbacksLocked(true); + mRoutingChangeListeners.put( + listener, new NativeRoutingEventHandlerDelegate(this, listener, + handler != null ? handler : mEventHandler)); + } + } + } + + /** + * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added + * to receive rerouting notifications. + * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface + * to remove. + */ + @Override + public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) { + synchronized (mRoutingChangeListeners) { + if (mRoutingChangeListeners.containsKey(listener)) { + mRoutingChangeListeners.remove(listener); + enableNativeRoutingCallbacksLocked(false); + } + } + } + + private native final boolean native_setOutputDevice(int deviceId); + private native final int native_getRoutedDeviceId(); + private native final void native_enableDeviceCallback(boolean enabled); + + /** + * Set the low-level power management behavior for this MediaPlayer2. This + * can be used when the MediaPlayer2 is not playing through a SurfaceHolder + * set with {@link #setDisplay(SurfaceHolder)} and thus can use the + * high-level {@link #setScreenOnWhilePlaying(boolean)} feature. + * + * <p>This function has the MediaPlayer2 access the low-level power manager + * service to control the device's power usage while playing is occurring. + * The parameter is a combination of {@link android.os.PowerManager} wake flags. + * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK} + * permission. + * By default, no attempt is made to keep the device awake during playback. + * + * @param context the Context to use + * @param mode the power/wake mode to set + * @see android.os.PowerManager + * @hide + */ + @Override + public void setWakeMode(Context context, int mode) { + boolean washeld = false; + + /* Disable persistant wakelocks in media player based on property */ + if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) { + Log.w(TAG, "IGNORING setWakeMode " + mode); + return; + } + + if (mWakeLock != null) { + if (mWakeLock.isHeld()) { + washeld = true; + mWakeLock.release(); + } + mWakeLock = null; + } + + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer2Impl.class.getName()); + mWakeLock.setReferenceCounted(false); + if (washeld) { + mWakeLock.acquire(); + } + } + + /** + * Control whether we should use the attached SurfaceHolder to keep the + * screen on while video playback is occurring. This is the preferred + * method over {@link #setWakeMode} where possible, since it doesn't + * require that the application have permission for low-level wake lock + * access. + * + * @param screenOn Supply true to keep the screen on, false to allow it + * to turn off. + * @hide + */ + @Override + public void setScreenOnWhilePlaying(boolean screenOn) { + if (mScreenOnWhilePlaying != screenOn) { + if (screenOn && mSurfaceHolder == null) { + Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder"); + } + mScreenOnWhilePlaying = screenOn; + updateSurfaceScreenOn(); + } + } + + private void stayAwake(boolean awake) { + if (mWakeLock != null) { + if (awake && !mWakeLock.isHeld()) { + mWakeLock.acquire(); + } else if (!awake && mWakeLock.isHeld()) { + mWakeLock.release(); + } + } + mStayAwake = awake; + updateSurfaceScreenOn(); + } + + private void updateSurfaceScreenOn() { + if (mSurfaceHolder != null) { + mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake); + } + } + + /** + * Returns the width of the video. + * + * @return the width of the video, or 0 if there is no video, + * no display surface was set, or the width has not been determined + * yet. The {@code EventCallback} can be registered via + * {@link #registerEventCallback(Executor, EventCallback)} to provide a + * notification {@code EventCallback.onVideoSizeChanged} when the width is available. + */ + @Override + public native int getVideoWidth(); + + /** + * Returns the height of the video. + * + * @return the height of the video, or 0 if there is no video, + * no display surface was set, or the height has not been determined + * yet. The {@code EventCallback} can be registered via + * {@link #registerEventCallback(Executor, EventCallback)} to provide a + * notification {@code EventCallback.onVideoSizeChanged} when the height is available. + */ + @Override + public native int getVideoHeight(); + + /** + * Return Metrics data about the current player. + * + * @return a {@link PersistableBundle} containing the set of attributes and values + * available for the media being handled by this instance of MediaPlayer2 + * The attributes are descibed in {@link MetricsConstants}. + * + * Additional vendor-specific fields may also be present in + * the return value. + */ + @Override + public PersistableBundle getMetrics() { + PersistableBundle bundle = native_getMetrics(); + return bundle; + } + + private native PersistableBundle native_getMetrics(); + + /** + * Checks whether the MediaPlayer2 is playing. + * + * @return true if currently playing, false otherwise + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released. + */ + @Override + public native boolean isPlaying(); + + /** + * Gets the current buffering management params used by the source component. + * Calling it only after {@code setDataSource} has been called. + * Each type of data source might have different set of default params. + * + * @return the current buffering management params used by the source component. + * @throws IllegalStateException if the internal player engine has not been + * initialized, or {@code setDataSource} has not been called. + * @hide + */ + @Override + @NonNull + public native BufferingParams getBufferingParams(); + + /** + * Sets buffering management params. + * The object sets its internal BufferingParams to the input, except that the input is + * invalid or not supported. + * Call it only after {@code setDataSource} has been called. + * The input is a hint to MediaPlayer2. + * + * @param params the buffering management params. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released, or {@code setDataSource} has not been called. + * @throws IllegalArgumentException if params is invalid or not supported. + * @hide + */ + @Override + public native void setBufferingParams(@NonNull BufferingParams params); + + /** + * Sets playback rate and audio mode. + * + * @param rate the ratio between desired playback rate and normal one. + * @param audioMode audio playback mode. Must be one of the supported + * audio modes. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + * @throws IllegalArgumentException if audioMode is not supported. + * + * @hide + */ + @Override + @NonNull + public PlaybackParams easyPlaybackParams(float rate, @PlaybackRateAudioMode int audioMode) { + PlaybackParams params = new PlaybackParams(); + params.allowDefaults(); + switch (audioMode) { + case PLAYBACK_RATE_AUDIO_MODE_DEFAULT: + params.setSpeed(rate).setPitch(1.0f); + break; + case PLAYBACK_RATE_AUDIO_MODE_STRETCH: + params.setSpeed(rate).setPitch(1.0f) + .setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL); + break; + case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE: + params.setSpeed(rate).setPitch(rate); + break; + default: + final String msg = "Audio playback mode " + audioMode + " is not supported"; + throw new IllegalArgumentException(msg); + } + return params; + } + + /** + * Sets playback rate using {@link PlaybackParams}. The object sets its internal + * PlaybackParams to the input, except that the object remembers previous speed + * when input speed is zero. This allows the object to resume at previous speed + * when play() is called. Calling it before the object is prepared does not change + * the object state. After the object is prepared, calling it with zero speed is + * equivalent to calling pause(). After the object is prepared, calling it with + * non-zero speed is equivalent to calling play(). + * + * @param params the playback params. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized or has been released. + * @throws IllegalArgumentException if params is not supported. + */ + @Override + public native void setPlaybackParams(@NonNull PlaybackParams params); + + /** + * Gets the playback params, containing the current playback rate. + * + * @return the playback params. + * @throws IllegalStateException if the internal player engine has not been + * initialized. + */ + @Override + @NonNull + public native PlaybackParams getPlaybackParams(); + + /** + * Sets A/V sync mode. + * + * @param params the A/V sync params to apply + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + * @throws IllegalArgumentException if params are not supported. + */ + @Override + public native void setSyncParams(@NonNull SyncParams params); + + /** + * Gets the A/V sync mode. + * + * @return the A/V sync params + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + */ + @Override + @NonNull + public native SyncParams getSyncParams(); + + private native final void _seekTo(long msec, int mode); + + /** + * Moves the media to specified time position by considering the given mode. + * <p> + * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user. + * There is at most one active seekTo processed at any time. If there is a to-be-completed + * seekTo, new seekTo requests will be queued in such a way that only the last request + * is kept. When current seekTo is completed, the queued request will be processed if + * that request is different from just-finished seekTo operation, i.e., the requested + * position or mode is different. + * + * @param msec the offset in milliseconds from the start to seek to. + * When seeking to the given time position, there is no guarantee that the data source + * has a frame located at the position. When this happens, a frame nearby will be rendered. + * If msec is negative, time position zero will be used. + * If msec is larger than duration, duration will be used. + * @param mode the mode indicating where exactly to seek to. + * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame + * that has a timestamp earlier than or the same as msec. Use + * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame + * that has a timestamp later than or the same as msec. Use + * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame + * that has a timestamp closest to or the same as msec. Use + * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may + * or may not be a sync frame but is closest to or the same as msec. + * {@link #SEEK_CLOSEST} often has larger performance overhead compared + * to the other options if there is no sync frame located at msec. + * @throws IllegalStateException if the internal player engine has not been + * initialized + * @throws IllegalArgumentException if the mode is invalid. + */ + @Override + public void seekTo(long msec, @SeekMode int mode) { + if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) { + final String msg = "Illegal seek mode: " + mode; + throw new IllegalArgumentException(msg); + } + // TODO: pass long to native, instead of truncating here. + if (msec > Integer.MAX_VALUE) { + Log.w(TAG, "seekTo offset " + msec + " is too large, cap to " + Integer.MAX_VALUE); + msec = Integer.MAX_VALUE; + } else if (msec < Integer.MIN_VALUE) { + Log.w(TAG, "seekTo offset " + msec + " is too small, cap to " + Integer.MIN_VALUE); + msec = Integer.MIN_VALUE; + } + _seekTo(msec, mode); + } + + /** + * Get current playback position as a {@link MediaTimestamp}. + * <p> + * The MediaTimestamp represents how the media time correlates to the system time in + * a linear fashion using an anchor and a clock rate. During regular playback, the media + * time moves fairly constantly (though the anchor frame may be rebased to a current + * system time, the linear correlation stays steady). Therefore, this method does not + * need to be called often. + * <p> + * To help users get current playback position, this method always anchors the timestamp + * to the current {@link System#nanoTime system time}, so + * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position. + * + * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp + * is available, e.g. because the media player has not been initialized. + * + * @see MediaTimestamp + */ + @Override + @Nullable + public MediaTimestamp getTimestamp() + { + try { + // TODO: get the timestamp from native side + return new MediaTimestamp( + getCurrentPosition() * 1000L, + System.nanoTime(), + isPlaying() ? getPlaybackParams().getSpeed() : 0.f); + } catch (IllegalStateException e) { + return null; + } + } + + /** + * Gets the current playback position. + * + * @return the current position in milliseconds + */ + @Override + public native int getCurrentPosition(); + + /** + * Gets the duration of the file. + * + * @return the duration in milliseconds, if no duration is available + * (for example, if streaming live content), -1 is returned. + */ + @Override + public native int getDuration(); + + /** + * Gets the media metadata. + * + * @param update_only controls whether the full set of available + * metadata is returned or just the set that changed since the + * last call. See {@see #METADATA_UPDATE_ONLY} and {@see + * #METADATA_ALL}. + * + * @param apply_filter if true only metadata that matches the + * filter is returned. See {@see #APPLY_METADATA_FILTER} and {@see + * #BYPASS_METADATA_FILTER}. + * + * @return The metadata, possibly empty. null if an error occured. + // FIXME: unhide. + * {@hide} + */ + @Override + public Metadata getMetadata(final boolean update_only, + final boolean apply_filter) { + Parcel reply = Parcel.obtain(); + Metadata data = new Metadata(); + + if (!native_getMetadata(update_only, apply_filter, reply)) { + reply.recycle(); + return null; + } + + // Metadata takes over the parcel, don't recycle it unless + // there is an error. + if (!data.parse(reply)) { + reply.recycle(); + return null; + } + return data; + } + + /** + * Set a filter for the metadata update notification and update + * retrieval. The caller provides 2 set of metadata keys, allowed + * and blocked. The blocked set always takes precedence over the + * allowed one. + * Metadata.MATCH_ALL and Metadata.MATCH_NONE are 2 sets available as + * shorthands to allow/block all or no metadata. + * + * By default, there is no filter set. + * + * @param allow Is the set of metadata the client is interested + * in receiving new notifications for. + * @param block Is the set of metadata the client is not interested + * in receiving new notifications for. + * @return The call status code. + * + // FIXME: unhide. + * {@hide} + */ + @Override + public int setMetadataFilter(Set<Integer> allow, Set<Integer> block) { + // Do our serialization manually instead of calling + // Parcel.writeArray since the sets are made of the same type + // we avoid paying the price of calling writeValue (used by + // writeArray) which burns an extra int per element to encode + // the type. + Parcel request = newRequest(); + + // The parcel starts already with an interface token. There + // are 2 filters. Each one starts with a 4bytes number to + // store the len followed by a number of int (4 bytes as well) + // representing the metadata type. + int capacity = request.dataSize() + 4 * (1 + allow.size() + 1 + block.size()); + + if (request.dataCapacity() < capacity) { + request.setDataCapacity(capacity); + } + + request.writeInt(allow.size()); + for(Integer t: allow) { + request.writeInt(t); + } + request.writeInt(block.size()); + for(Integer t: block) { + request.writeInt(t); + } + return native_setMetadataFilter(request); + } + + /** + * Set the MediaPlayer2 to start when this MediaPlayer2 finishes playback + * (i.e. reaches the end of the stream). + * The media framework will attempt to transition from this player to + * the next as seamlessly as possible. The next player can be set at + * any time before completion, but shall be after setDataSource has been + * called successfully. The next player must be prepared by the + * app, and the application should not call play() on it. + * The next MediaPlayer2 must be different from 'this'. An exception + * will be thrown if next == this. + * The application may call setNextMediaPlayer(null) to indicate no + * next player should be started at the end of playback. + * If the current player is looping, it will keep looping and the next + * player will not be started. + * + * @param next the player to start after this one completes playback. + * + * @hide + */ + @Override + public native void setNextMediaPlayer(MediaPlayer2 next); + + /** + * Resets the MediaPlayer2 to its uninitialized state. After calling + * this method, you will have to initialize it again by setting the + * data source and calling prepare(). + */ + @Override + public void reset() { + mSelectedSubtitleTrackIndex = -1; + synchronized(mOpenSubtitleSources) { + for (final InputStream is: mOpenSubtitleSources) { + try { + is.close(); + } catch (IOException e) { + } + } + mOpenSubtitleSources.clear(); + } + if (mSubtitleController != null) { + mSubtitleController.reset(); + } + if (mTimeProvider != null) { + mTimeProvider.close(); + mTimeProvider = null; + } + + stayAwake(false); + _reset(); + // make sure none of the listeners get called anymore + if (mEventHandler != null) { + mEventHandler.removeCallbacksAndMessages(null); + } + + synchronized (mIndexTrackPairs) { + mIndexTrackPairs.clear(); + mInbandTrackIndices.clear(); + }; + + resetDrmState(); + } + + private native void _reset(); + + /** + * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be + * notified when the presentation time reaches (becomes greater than or equal to) + * the value specified. + * + * @param mediaTimeUs presentation time to get timed event callback at + * @hide + */ + @Override + public void notifyAt(long mediaTimeUs) { + _notifyAt(mediaTimeUs); + } + + private native void _notifyAt(long mediaTimeUs); + + // Keep KEY_PARAMETER_* in sync with include/media/mediaplayer2.h + private final static int KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400; + /** + * Sets the parameter indicated by key. + * @param key key indicates the parameter to be set. + * @param value value of the parameter to be set. + * @return true if the parameter is set successfully, false otherwise + * {@hide} + */ + private native boolean setParameter(int key, Parcel value); + + /** + * Sets the audio attributes for this MediaPlayer2. + * See {@link AudioAttributes} for how to build and configure an instance of this class. + * You must call this method before {@link #prepare()} or {@link #prepareAsync()} in order + * for the audio attributes to become effective thereafter. + * @param attributes a non-null set of audio attributes + * @throws IllegalArgumentException if the attributes are null or invalid. + */ + @Override + public void setAudioAttributes(AudioAttributes attributes) { + if (attributes == null) { + final String msg = "Cannot set AudioAttributes to null"; + throw new IllegalArgumentException(msg); + } + mUsage = attributes.getUsage(); + mBypassInterruptionPolicy = (attributes.getAllFlags() + & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0; + Parcel pattributes = Parcel.obtain(); + attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS); + setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes); + pattributes.recycle(); + } + + /** + * Sets the player to be looping or non-looping. + * + * @param looping whether to loop or not + * @hide + */ + @Override + public native void setLooping(boolean looping); + + /** + * Checks whether the MediaPlayer2 is looping or non-looping. + * + * @return true if the MediaPlayer2 is currently looping, false otherwise + * @hide + */ + @Override + public native boolean isLooping(); + + /** + * Sets the volume on this player. + * This API is recommended for balancing the output of audio streams + * within an application. Unless you are writing an application to + * control user settings, this API should be used in preference to + * {@link AudioManager#setStreamVolume(int, int, int)} which sets the volume of ALL streams of + * a particular type. Note that the passed volume values are raw scalars in range 0.0 to 1.0. + * UI controls should be scaled logarithmically. + * + * @param leftVolume left volume scalar + * @param rightVolume right volume scalar + */ + /* + * FIXME: Merge this into javadoc comment above when setVolume(float) is not @hide. + * The single parameter form below is preferred if the channel volumes don't need + * to be set independently. + */ + @Override + public void setVolume(float leftVolume, float rightVolume) { + _setVolume(leftVolume, rightVolume); + } + + private native void _setVolume(float leftVolume, float rightVolume); + + /** + * Similar, excepts sets volume of all channels to same value. + * @hide + */ + @Override + public void setVolume(float volume) { + setVolume(volume, volume); + } + + /** + * Sets the audio session ID. + * + * @param sessionId the audio session ID. + * The audio session ID is a system wide unique identifier for the audio stream played by + * this MediaPlayer2 instance. + * The primary use of the audio session ID is to associate audio effects to a particular + * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect, + * this effect will be applied only to the audio content of media players within the same + * audio session and not to the output mix. + * When created, a MediaPlayer2 instance automatically generates its own audio session ID. + * However, it is possible to force this player to be part of an already existing audio session + * by calling this method. + * This method must be called before one of the overloaded <code> setDataSource </code> methods. + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if the sessionId is invalid. + */ + @Override + public native void setAudioSessionId(int sessionId); + + /** + * Returns the audio session ID. + * + * @return the audio session ID. {@see #setAudioSessionId(int)} + * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed. + */ + @Override + public native int getAudioSessionId(); + + /** + * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation + * effect which can be applied on any sound source that directs a certain amount of its + * energy to this effect. This amount is defined by setAuxEffectSendLevel(). + * See {@link #setAuxEffectSendLevel(float)}. + * <p>After creating an auxiliary effect (e.g. + * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with + * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method + * to attach the player to the effect. + * <p>To detach the effect from the player, call this method with a null effect id. + * <p>This method must be called after one of the overloaded <code> setDataSource </code> + * methods. + * @param effectId system wide unique id of the effect to attach + */ + @Override + public native void attachAuxEffect(int effectId); + + + /** + * Sets the send level of the player to the attached auxiliary effect. + * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0. + * <p>By default the send level is 0, so even if an effect is attached to the player + * this method must be called for the effect to be applied. + * <p>Note that the passed level value is a raw scalar. UI controls should be scaled + * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, + * so an appropriate conversion from linear UI input x to level is: + * x == 0 -> level = 0 + * 0 < x <= R -> level = 10^(72*(x-R)/20/R) + * @param level send level scalar + */ + @Override + public void setAuxEffectSendLevel(float level) { + _setAuxEffectSendLevel(level); + } + + private native void _setAuxEffectSendLevel(float level); + + /* + * @param request Parcel destinated to the media player. + * @param reply[out] Parcel that will contain the reply. + * @return The status code. + */ + private native final int native_invoke(Parcel request, Parcel reply); + + + /* + * @param update_only If true fetch only the set of metadata that have + * changed since the last invocation of getMetadata. + * The set is built using the unfiltered + * notifications the native player sent to the + * MediaPlayer2Manager during that period of + * time. If false, all the metadatas are considered. + * @param apply_filter If true, once the metadata set has been built based on + * the value update_only, the current filter is applied. + * @param reply[out] On return contains the serialized + * metadata. Valid only if the call was successful. + * @return The status code. + */ + private native final boolean native_getMetadata(boolean update_only, + boolean apply_filter, + Parcel reply); + + /* + * @param request Parcel with the 2 serialized lists of allowed + * metadata types followed by the one to be + * dropped. Each list starts with an integer + * indicating the number of metadata type elements. + * @return The status code. + */ + private native final int native_setMetadataFilter(Parcel request); + + private static native final void native_init(); + private native final void native_setup(Object mediaplayer2_this); + private native final void native_finalize(); + + /** + * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata. + * + * @see android.media.MediaPlayer2#getTrackInfo + */ + public static final class TrackInfoImpl extends TrackInfo { + /** + * Gets the track type. + * @return TrackType which indicates if the track is video, audio, timed text. + */ + @Override + public int getTrackType() { + return mTrackType; + } + + /** + * Gets the language code of the track. + * @return a language code in either way of ISO-639-1 or ISO-639-2. + * When the language is unknown or could not be determined, + * ISO-639-2 language code, "und", is returned. + */ + @Override + public String getLanguage() { + String language = mFormat.getString(MediaFormat.KEY_LANGUAGE); + return language == null ? "und" : language; + } + + /** + * Gets the {@link MediaFormat} of the track. If the format is + * unknown or could not be determined, null is returned. + */ + @Override + public MediaFormat getFormat() { + if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT + || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + return mFormat; + } + return null; + } + + final int mTrackType; + final MediaFormat mFormat; + + TrackInfoImpl(Parcel in) { + mTrackType = in.readInt(); + // TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat + // even for audio/video tracks, meaning we only set the mime and language. + String mime = in.readString(); + String language = in.readString(); + mFormat = MediaFormat.createSubtitleFormat(mime, language); + + if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt()); + mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt()); + mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt()); + } + } + + /** @hide */ + TrackInfoImpl(int type, MediaFormat format) { + mTrackType = type; + mFormat = format; + } + + /** + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link android.os.Parcelable#PARCELABLE_WRITE_RETURN_VALUE}. + */ + /* package private */ void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mTrackType); + dest.writeString(getLanguage()); + + if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { + dest.writeString(mFormat.getString(MediaFormat.KEY_MIME)); + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)); + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)); + dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)); + } + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder(128); + out.append(getClass().getName()); + out.append('{'); + switch (mTrackType) { + case MEDIA_TRACK_TYPE_VIDEO: + out.append("VIDEO"); + break; + case MEDIA_TRACK_TYPE_AUDIO: + out.append("AUDIO"); + break; + case MEDIA_TRACK_TYPE_TIMEDTEXT: + out.append("TIMEDTEXT"); + break; + case MEDIA_TRACK_TYPE_SUBTITLE: + out.append("SUBTITLE"); + break; + default: + out.append("UNKNOWN"); + break; + } + out.append(", " + mFormat.toString()); + out.append("}"); + return out.toString(); + } + + /** + * Used to read a TrackInfoImpl from a Parcel. + */ + /* package private */ static final Parcelable.Creator<TrackInfoImpl> CREATOR + = new Parcelable.Creator<TrackInfoImpl>() { + @Override + public TrackInfoImpl createFromParcel(Parcel in) { + return new TrackInfoImpl(in); + } + + @Override + public TrackInfoImpl[] newArray(int size) { + return new TrackInfoImpl[size]; + } + }; + + }; + + // We would like domain specific classes with more informative names than the `first` and `second` + // in generic Pair, but we would also like to avoid creating new/trivial classes. As a compromise + // we document the meanings of `first` and `second` here: + // + // Pair.first - inband track index; non-null iff representing an inband track. + // Pair.second - a SubtitleTrack registered with mSubtitleController; non-null iff representing + // an inband subtitle track or any out-of-band track (subtitle or timedtext). + private Vector<Pair<Integer, SubtitleTrack>> mIndexTrackPairs = new Vector<>(); + private BitSet mInbandTrackIndices = new BitSet(); + + /** + * Returns a List of track information. + * + * @return List of track info. The total number of tracks is the array length. + * Must be called again if an external timed text source has been added after + * addTimedTextSource method is called. + * @throws IllegalStateException if it is called in an invalid state. + */ + @Override + public List<TrackInfo> getTrackInfo() { + TrackInfoImpl trackInfo[] = getInbandTrackInfoImpl(); + // add out-of-band tracks + synchronized (mIndexTrackPairs) { + TrackInfoImpl allTrackInfo[] = new TrackInfoImpl[mIndexTrackPairs.size()]; + for (int i = 0; i < allTrackInfo.length; i++) { + Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); + if (p.first != null) { + // inband track + allTrackInfo[i] = trackInfo[p.first]; + } else { + SubtitleTrack track = p.second; + allTrackInfo[i] = new TrackInfoImpl(track.getTrackType(), track.getFormat()); + } + } + return Arrays.asList(allTrackInfo); + } + } + + private TrackInfoImpl[] getInbandTrackInfoImpl() throws IllegalStateException { + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + request.writeInt(INVOKE_ID_GET_TRACK_INFO); + invoke(request, reply); + TrackInfoImpl trackInfo[] = reply.createTypedArray(TrackInfoImpl.CREATOR); + return trackInfo; + } finally { + request.recycle(); + reply.recycle(); + } + } + + /* + * A helper function to check if the mime type is supported by media framework. + */ + private static boolean availableMimeTypeForExternalSource(String mimeType) { + if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) { + return true; + } + return false; + } + + private SubtitleController mSubtitleController; + + /** @hide */ + @Override + public void setSubtitleAnchor( + SubtitleController controller, + SubtitleController.Anchor anchor) { + // TODO: create SubtitleController in MediaPlayer2 + mSubtitleController = controller; + mSubtitleController.setAnchor(anchor); + } + + /** + * The private version of setSubtitleAnchor is used internally to set mSubtitleController if + * necessary when clients don't provide their own SubtitleControllers using the public version + * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one). + */ + private synchronized void setSubtitleAnchor() { + if ((mSubtitleController == null) && (ActivityThread.currentApplication() != null)) { + final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread"); + thread.start(); + Handler handler = new Handler(thread.getLooper()); + handler.post(new Runnable() { + @Override + public void run() { + Context context = ActivityThread.currentApplication(); + mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer2Impl.this); + mSubtitleController.setAnchor(new Anchor() { + @Override + public void setSubtitleWidget(RenderingWidget subtitleWidget) { + } + + @Override + public Looper getSubtitleLooper() { + return Looper.getMainLooper(); + } + }); + thread.getLooper().quitSafely(); + } + }); + try { + thread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.w(TAG, "failed to join SetSubtitleAnchorThread"); + } + } + } + + private int mSelectedSubtitleTrackIndex = -1; + private Vector<InputStream> mOpenSubtitleSources; + + private OnSubtitleDataListener mSubtitleDataListener = new OnSubtitleDataListener() { + @Override + public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) { + int index = data.getTrackIndex(); + synchronized (mIndexTrackPairs) { + for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) { + if (p.first != null && p.first == index && p.second != null) { + // inband subtitle track that owns data + SubtitleTrack track = p.second; + track.onData(data); + } + } + } + } + }; + + /** @hide */ + @Override + public void onSubtitleTrackSelected(SubtitleTrack track) { + if (mSelectedSubtitleTrackIndex >= 0) { + try { + selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false); + } catch (IllegalStateException e) { + } + mSelectedSubtitleTrackIndex = -1; + } + setOnSubtitleDataListener(null); + if (track == null) { + return; + } + + synchronized (mIndexTrackPairs) { + for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) { + if (p.first != null && p.second == track) { + // inband subtitle track that is selected + mSelectedSubtitleTrackIndex = p.first; + break; + } + } + } + + if (mSelectedSubtitleTrackIndex >= 0) { + try { + selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true); + } catch (IllegalStateException e) { + } + setOnSubtitleDataListener(mSubtitleDataListener); + } + // no need to select out-of-band tracks + } + + /** @hide */ + @Override + public void addSubtitleSource(InputStream is, MediaFormat format) + throws IllegalStateException + { + final InputStream fIs = is; + final MediaFormat fFormat = format; + + if (is != null) { + // Ensure all input streams are closed. It is also a handy + // way to implement timeouts in the future. + synchronized(mOpenSubtitleSources) { + mOpenSubtitleSources.add(is); + } + } else { + Log.w(TAG, "addSubtitleSource called with null InputStream"); + } + + getMediaTimeProvider(); + + // process each subtitle in its own thread + final HandlerThread thread = new HandlerThread("SubtitleReadThread", + Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); + thread.start(); + Handler handler = new Handler(thread.getLooper()); + handler.post(new Runnable() { + private int addTrack() { + if (fIs == null || mSubtitleController == null) { + return MEDIA_INFO_UNSUPPORTED_SUBTITLE; + } + + SubtitleTrack track = mSubtitleController.addTrack(fFormat); + if (track == null) { + return MEDIA_INFO_UNSUPPORTED_SUBTITLE; + } + + // TODO: do the conversion in the subtitle track + Scanner scanner = new Scanner(fIs, "UTF-8"); + String contents = scanner.useDelimiter("\\A").next(); + synchronized(mOpenSubtitleSources) { + mOpenSubtitleSources.remove(fIs); + } + scanner.close(); + synchronized (mIndexTrackPairs) { + mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track)); + } + Handler h = mTimeProvider.mEventHandler; + int what = TimeProvider.NOTIFY; + int arg1 = TimeProvider.NOTIFY_TRACK_DATA; + Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, contents.getBytes()); + Message m = h.obtainMessage(what, arg1, 0, trackData); + h.sendMessage(m); + return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; + } + + public void run() { + int res = addTrack(); + if (mEventHandler != null) { + Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); + mEventHandler.sendMessage(m); + } + thread.getLooper().quitSafely(); + } + }); + } + + private void scanInternalSubtitleTracks() { + setSubtitleAnchor(); + + populateInbandTracks(); + + if (mSubtitleController != null) { + mSubtitleController.selectDefaultTrack(); + } + } + + private void populateInbandTracks() { + TrackInfoImpl[] tracks = getInbandTrackInfoImpl(); + synchronized (mIndexTrackPairs) { + for (int i = 0; i < tracks.length; i++) { + if (mInbandTrackIndices.get(i)) { + continue; + } else { + mInbandTrackIndices.set(i); + } + + // newly appeared inband track + if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) { + SubtitleTrack track = mSubtitleController.addTrack( + tracks[i].getFormat()); + mIndexTrackPairs.add(Pair.create(i, track)); + } else { + mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(i, null)); + } + } + } + } + + /* TODO: Limit the total number of external timed text source to a reasonable number. + */ + /** + * Adds an external timed text source file. + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external timed text source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param path The file path of external timed text source file. + * @param mimeType The mime type of the file. Must be one of the mime types listed above. + * @throws IOException if the file cannot be accessed or is corrupted. + * @throws IllegalArgumentException if the mimeType is not supported. + * @throws IllegalStateException if called in an invalid state. + * @hide + */ + @Override + public void addTimedTextSource(String path, String mimeType) + throws IOException { + if (!availableMimeTypeForExternalSource(mimeType)) { + final String msg = "Illegal mimeType for timed text source: " + mimeType; + throw new IllegalArgumentException(msg); + } + + File file = new File(path); + if (file.exists()) { + FileInputStream is = new FileInputStream(file); + FileDescriptor fd = is.getFD(); + addTimedTextSource(fd, mimeType); + is.close(); + } else { + // We do not support the case where the path is not a file. + throw new IOException(path); + } + } + + + /** + * Adds an external timed text source file (Uri). + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external timed text source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play + * @param mimeType The mime type of the file. Must be one of the mime types listed above. + * @throws IOException if the file cannot be accessed or is corrupted. + * @throws IllegalArgumentException if the mimeType is not supported. + * @throws IllegalStateException if called in an invalid state. + * @hide + */ + @Override + public void addTimedTextSource(Context context, Uri uri, String mimeType) + throws IOException { + String scheme = uri.getScheme(); + if(scheme == null || scheme.equals("file")) { + addTimedTextSource(uri.getPath(), mimeType); + return; + } + + AssetFileDescriptor fd = null; + try { + ContentResolver resolver = context.getContentResolver(); + fd = resolver.openAssetFileDescriptor(uri, "r"); + if (fd == null) { + return; + } + addTimedTextSource(fd.getFileDescriptor(), mimeType); + return; + } catch (SecurityException ex) { + } catch (IOException ex) { + } finally { + if (fd != null) { + fd.close(); + } + } + } + + /** + * Adds an external timed text source file (FileDescriptor). + * + * It is the caller's responsibility to close the file descriptor. + * It is safe to do so as soon as this call returns. + * + * Currently supported format is SubRip. Note that a single external timed text source may + * contain multiple tracks in it. One can find the total number of available tracks + * using {@link #getTrackInfo()} to see what additional tracks become available + * after this method call. + * + * @param fd the FileDescriptor for the file you want to play + * @param mimeType The mime type of the file. Must be one of the mime types listed above. + * @throws IllegalArgumentException if the mimeType is not supported. + * @throws IllegalStateException if called in an invalid state. + * @hide + */ + @Override + public void addTimedTextSource(FileDescriptor fd, String mimeType) { + // intentionally less than LONG_MAX + addTimedTextSource(fd, 0, 0x7ffffffffffffffL, mimeType); + } + + /** + * Adds an external timed text file (FileDescriptor). + * + * It is the caller's responsibility to close the file descriptor. + * It is safe to do so as soon as this call returns. + * + * Currently supported format is SubRip. Note that a single external timed text source may + * contain multiple tracks in it. One can find the total number of available tracks + * using {@link #getTrackInfo()} to see what additional tracks become available + * after this method call. + * + * @param fd the FileDescriptor for the file you want to play + * @param offset the offset into the file where the data to be played starts, in bytes + * @param length the length in bytes of the data to be played + * @param mime The mime type of the file. Must be one of the mime types listed above. + * @throws IllegalArgumentException if the mimeType is not supported. + * @throws IllegalStateException if called in an invalid state. + * @hide + */ + @Override + public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime) { + if (!availableMimeTypeForExternalSource(mime)) { + throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mime); + } + + final FileDescriptor dupedFd; + try { + dupedFd = Os.dup(fd); + } catch (ErrnoException ex) { + Log.e(TAG, ex.getMessage(), ex); + throw new RuntimeException(ex); + } + + final MediaFormat fFormat = new MediaFormat(); + fFormat.setString(MediaFormat.KEY_MIME, mime); + fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1); + + // A MediaPlayer2 created by a VideoView should already have its mSubtitleController set. + if (mSubtitleController == null) { + setSubtitleAnchor(); + } + + if (!mSubtitleController.hasRendererFor(fFormat)) { + // test and add not atomic + Context context = ActivityThread.currentApplication(); + mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler)); + } + final SubtitleTrack track = mSubtitleController.addTrack(fFormat); + synchronized (mIndexTrackPairs) { + mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track)); + } + + getMediaTimeProvider(); + + final long offset2 = offset; + final long length2 = length; + final HandlerThread thread = new HandlerThread( + "TimedTextReadThread", + Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); + thread.start(); + Handler handler = new Handler(thread.getLooper()); + handler.post(new Runnable() { + private int addTrack() { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + Os.lseek(dupedFd, offset2, OsConstants.SEEK_SET); + byte[] buffer = new byte[4096]; + for (long total = 0; total < length2;) { + int bytesToRead = (int) Math.min(buffer.length, length2 - total); + int bytes = IoBridge.read(dupedFd, buffer, 0, bytesToRead); + if (bytes < 0) { + break; + } else { + bos.write(buffer, 0, bytes); + total += bytes; + } + } + Handler h = mTimeProvider.mEventHandler; + int what = TimeProvider.NOTIFY; + int arg1 = TimeProvider.NOTIFY_TRACK_DATA; + Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, bos.toByteArray()); + Message m = h.obtainMessage(what, arg1, 0, trackData); + h.sendMessage(m); + return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + return MEDIA_INFO_TIMED_TEXT_ERROR; + } finally { + try { + Os.close(dupedFd); + } catch (ErrnoException e) { + Log.e(TAG, e.getMessage(), e); + } + } + } + + public void run() { + int res = addTrack(); + if (mEventHandler != null) { + Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); + mEventHandler.sendMessage(m); + } + thread.getLooper().quitSafely(); + } + }); + } + + /** + * Returns the index of the audio, video, or subtitle track currently selected for playback, + * The return value is an index into the array returned by {@link #getTrackInfo()}, and can + * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}. + * + * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO}, + * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or + * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE} + * @return index of the audio, video, or subtitle track currently selected for playback; + * a negative integer is returned when there is no selected track for {@code trackType} or + * when {@code trackType} is not one of audio, video, or subtitle. + * @throws IllegalStateException if called after {@link #close()} + * + * @see #getTrackInfo() + * @see #selectTrack(int) + * @see #deselectTrack(int) + */ + @Override + public int getSelectedTrack(int trackType) { + if (mSubtitleController != null + && (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE + || trackType == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT)) { + SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack(); + if (subtitleTrack != null) { + synchronized (mIndexTrackPairs) { + for (int i = 0; i < mIndexTrackPairs.size(); i++) { + Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); + if (p.second == subtitleTrack && subtitleTrack.getTrackType() == trackType) { + return i; + } + } + } + } + } + + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + request.writeInt(INVOKE_ID_GET_SELECTED_TRACK); + request.writeInt(trackType); + invoke(request, reply); + int inbandTrackIndex = reply.readInt(); + synchronized (mIndexTrackPairs) { + for (int i = 0; i < mIndexTrackPairs.size(); i++) { + Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i); + if (p.first != null && p.first == inbandTrackIndex) { + return i; + } + } + } + return -1; + } finally { + request.recycle(); + reply.recycle(); + } + } + + /** + * Selects a track. + * <p> + * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception. + * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately. + * If a MediaPlayer2 is not in Started state, it just marks the track to be played. + * </p> + * <p> + * In any valid state, if it is called multiple times on the same type of track (ie. Video, + * Audio, Timed Text), the most recent one will be chosen. + * </p> + * <p> + * The first audio and video tracks are selected by default if available, even though + * this method is not called. However, no timed text track will be selected until + * this function is called. + * </p> + * <p> + * Currently, only timed text tracks or audio tracks can be selected via this method. + * In addition, the support for selecting an audio track at runtime is pretty limited + * in that an audio track can only be selected in the <em>Prepared</em> state. + * </p> + * @param index the index of the track to be selected. The valid range of the index + * is 0..total number of track - 1. The total number of tracks as well as the type of + * each individual track can be found by calling {@link #getTrackInfo()} method. + * @throws IllegalStateException if called in an invalid state. + * + * @see android.media.MediaPlayer2#getTrackInfo + */ + @Override + public void selectTrack(int index) { + selectOrDeselectTrack(index, true /* select */); + } + + /** + * Deselect a track. + * <p> + * Currently, the track must be a timed text track and no audio or video tracks can be + * deselected. If the timed text track identified by index has not been + * selected before, it throws an exception. + * </p> + * @param index the index of the track to be deselected. The valid range of the index + * is 0..total number of tracks - 1. The total number of tracks as well as the type of + * each individual track can be found by calling {@link #getTrackInfo()} method. + * @throws IllegalStateException if called in an invalid state. + * + * @see android.media.MediaPlayer2#getTrackInfo + */ + @Override + public void deselectTrack(int index) { + selectOrDeselectTrack(index, false /* select */); + } + + private void selectOrDeselectTrack(int index, boolean select) + throws IllegalStateException { + // handle subtitle track through subtitle controller + populateInbandTracks(); + + Pair<Integer,SubtitleTrack> p = null; + try { + p = mIndexTrackPairs.get(index); + } catch (ArrayIndexOutOfBoundsException e) { + // ignore bad index + return; + } + + SubtitleTrack track = p.second; + if (track == null) { + // inband (de)select + selectOrDeselectInbandTrack(p.first, select); + return; + } + + if (mSubtitleController == null) { + return; + } + + if (!select) { + // out-of-band deselect + if (mSubtitleController.getSelectedTrack() == track) { + mSubtitleController.selectTrack(null); + } else { + Log.w(TAG, "trying to deselect track that was not selected"); + } + return; + } + + // out-of-band select + if (track.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) { + int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); + synchronized (mIndexTrackPairs) { + if (ttIndex >= 0 && ttIndex < mIndexTrackPairs.size()) { + Pair<Integer,SubtitleTrack> p2 = mIndexTrackPairs.get(ttIndex); + if (p2.first != null && p2.second == null) { + // deselect inband counterpart + selectOrDeselectInbandTrack(p2.first, false); + } + } + } + } + mSubtitleController.selectTrack(track); + } + + private void selectOrDeselectInbandTrack(int index, boolean select) + throws IllegalStateException { + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + request.writeInt(select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK); + request.writeInt(index); + invoke(request, reply); + } finally { + request.recycle(); + reply.recycle(); + } + } + + /** + * Sets the target UDP re-transmit endpoint for the low level player. + * Generally, the address portion of the endpoint is an IP multicast + * address, although a unicast address would be equally valid. When a valid + * retransmit endpoint has been set, the media player will not decode and + * render the media presentation locally. Instead, the player will attempt + * to re-multiplex its media data using the Android@Home RTP profile and + * re-transmit to the target endpoint. Receiver devices (which may be + * either the same as the transmitting device or different devices) may + * instantiate, prepare, and start a receiver player using a setDataSource + * URL of the form... + * + * aahRX://<multicastIP>:<port> + * + * to receive, decode and render the re-transmitted content. + * + * setRetransmitEndpoint may only be called before setDataSource has been + * called; while the player is in the Idle state. + * + * @param endpoint the address and UDP port of the re-transmission target or + * null if no re-transmission is to be performed. + * @throws IllegalStateException if it is called in an invalid state + * @throws IllegalArgumentException if the retransmit endpoint is supplied, + * but invalid. + * + * {@hide} pending API council + */ + @Override + public void setRetransmitEndpoint(InetSocketAddress endpoint) + throws IllegalStateException, IllegalArgumentException + { + String addrString = null; + int port = 0; + + if (null != endpoint) { + addrString = endpoint.getAddress().getHostAddress(); + port = endpoint.getPort(); + } + + int ret = native_setRetransmitEndpoint(addrString, port); + if (ret != 0) { + throw new IllegalArgumentException("Illegal re-transmit endpoint; native ret " + ret); + } + } + + private native final int native_setRetransmitEndpoint(String addrString, int port); + + /** + * Releases the resources held by this {@code MediaPlayer2} object. + * + * It is considered good practice to call this method when you're + * done using the MediaPlayer2. In particular, whenever an Activity + * of an application is paused (its onPause() method is called), + * or stopped (its onStop() method is called), this method should be + * invoked to release the MediaPlayer2 object, unless the application + * has a special need to keep the object around. In addition to + * unnecessary resources (such as memory and instances of codecs) + * being held, failure to call this method immediately if a + * MediaPlayer2 object is no longer needed may also lead to + * continuous battery consumption for mobile devices, and playback + * failure for other applications if no multiple instances of the + * same codec are supported on a device. Even if multiple instances + * of the same codec are supported, some performance degradation + * may be expected when unnecessary multiple instances are used + * at the same time. + * + * {@code close()} may be safely called after a prior {@code close()}. + * This class implements the Java {@code AutoCloseable} interface and + * may be used with try-with-resources. + */ + @Override + public void close() { + synchronized (mGuard) { + release(); + } + } + + // Have to declare protected for finalize() since it is protected + // in the base class Object. + @Override + protected void finalize() throws Throwable { + if (mGuard != null) { + mGuard.warnIfOpen(); + } + + close(); + native_finalize(); + } + + private void release() { + stayAwake(false); + updateSurfaceScreenOn(); + synchronized (mEventCbLock) { + mEventCb = null; + mEventExec = null; + } + if (mTimeProvider != null) { + mTimeProvider.close(); + mTimeProvider = null; + } + mOnSubtitleDataListener = null; + + // Modular DRM clean up + mOnDrmConfigHelper = null; + synchronized (mDrmEventCbLock) { + mDrmEventCb = null; + mDrmEventExec = null; + } + resetDrmState(); + + _release(); + } + + private native void _release(); + + /* Do not change these values without updating their counterparts + * in include/media/mediaplayer2.h! + */ + private static final int MEDIA_NOP = 0; // interface test message + private static final int MEDIA_PREPARED = 1; + private static final int MEDIA_PLAYBACK_COMPLETE = 2; + private static final int MEDIA_BUFFERING_UPDATE = 3; + private static final int MEDIA_SEEK_COMPLETE = 4; + private static final int MEDIA_SET_VIDEO_SIZE = 5; + private static final int MEDIA_STARTED = 6; + private static final int MEDIA_PAUSED = 7; + private static final int MEDIA_STOPPED = 8; + private static final int MEDIA_SKIPPED = 9; + private static final int MEDIA_NOTIFY_TIME = 98; + private static final int MEDIA_TIMED_TEXT = 99; + private static final int MEDIA_ERROR = 100; + private static final int MEDIA_INFO = 200; + private static final int MEDIA_SUBTITLE_DATA = 201; + private static final int MEDIA_META_DATA = 202; + private static final int MEDIA_DRM_INFO = 210; + private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000; + + private TimeProvider mTimeProvider; + + /** @hide */ + @Override + public MediaTimeProvider getMediaTimeProvider() { + if (mTimeProvider == null) { + mTimeProvider = new TimeProvider(this); + } + return mTimeProvider; + } + + private class EventHandler extends Handler { + private MediaPlayer2Impl mMediaPlayer; + + public EventHandler(MediaPlayer2Impl mp, Looper looper) { + super(looper); + mMediaPlayer = mp; + } + + @Override + public void handleMessage(Message msg) { + if (mMediaPlayer.mNativeContext == 0) { + Log.w(TAG, "mediaplayer2 went away with unhandled events"); + return; + } + final Executor eventExec; + final EventCallback eventCb; + synchronized (mEventCbLock) { + eventExec = mEventExec; + eventCb = mEventCb; + } + final Executor drmEventExec; + final DrmEventCallback drmEventCb; + synchronized (mDrmEventCbLock) { + drmEventExec = mDrmEventExec; + drmEventCb = mDrmEventCb; + } + switch(msg.what) { + case MEDIA_PREPARED: + try { + scanInternalSubtitleTracks(); + } catch (RuntimeException e) { + // send error message instead of crashing; + // send error message instead of inlining a call to onError + // to avoid code duplication. + Message msg2 = obtainMessage( + MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); + sendMessage(msg2); + } + + if (eventCb != null && eventExec != null) { + eventExec.execute(() -> eventCb.onInfo( + mMediaPlayer, 0, MEDIA_INFO_PREPARED, 0)); + } + return; + + case MEDIA_DRM_INFO: + Log.v(TAG, "MEDIA_DRM_INFO " + mDrmEventCb); + + if (msg.obj == null) { + Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL"); + } else if (msg.obj instanceof Parcel) { + if (drmEventExec != null && drmEventCb != null) { + // The parcel was parsed already in postEventFromNative + final DrmInfoImpl drmInfo; + + synchronized (mDrmLock) { + if (mDrmInfoImpl != null) { + drmInfo = mDrmInfoImpl.makeCopy(); + } else { + drmInfo = null; + } + } + + // notifying the client outside the lock + if (drmInfo != null) { + drmEventExec.execute(() -> drmEventCb.onDrmInfo(mMediaPlayer, drmInfo)); + } + } + } else { + Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj); + } + return; + + case MEDIA_PLAYBACK_COMPLETE: + if (eventCb != null && eventExec != null) { + eventExec.execute(() -> eventCb.onInfo( + mMediaPlayer, 0, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); + } + stayAwake(false); + return; + + case MEDIA_STOPPED: + { + TimeProvider timeProvider = mTimeProvider; + if (timeProvider != null) { + timeProvider.onStopped(); + } + } + break; + + case MEDIA_STARTED: + case MEDIA_PAUSED: + { + TimeProvider timeProvider = mTimeProvider; + if (timeProvider != null) { + timeProvider.onPaused(msg.what == MEDIA_PAUSED); + } + } + break; + + case MEDIA_BUFFERING_UPDATE: + if (eventCb != null && eventExec != null) { + final int percent = msg.arg1; + eventExec.execute(() -> eventCb.onBufferingUpdate(mMediaPlayer, 0, percent)); + } + return; + + case MEDIA_SEEK_COMPLETE: + if (eventCb != null && eventExec != null) { + eventExec.execute(() -> eventCb.onInfo( + mMediaPlayer, 0, MEDIA_INFO_COMPLETE_CALL_SEEK, 0)); + } + // fall through + + case MEDIA_SKIPPED: + { + TimeProvider timeProvider = mTimeProvider; + if (timeProvider != null) { + timeProvider.onSeekComplete(mMediaPlayer); + } + } + return; + + case MEDIA_SET_VIDEO_SIZE: + if (eventCb != null && eventExec != null) { + final int width = msg.arg1; + final int height = msg.arg2; + eventExec.execute(() -> eventCb.onVideoSizeChanged( + mMediaPlayer, 0, width, height)); + } + return; + + case MEDIA_ERROR: + Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); + if (eventCb != null && eventExec != null) { + final int what = msg.arg1; + final int extra = msg.arg2; + eventExec.execute(() -> eventCb.onError(mMediaPlayer, 0, what, extra)); + eventExec.execute(() -> eventCb.onInfo( + mMediaPlayer, 0, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); + } + stayAwake(false); + return; + + case MEDIA_INFO: + switch (msg.arg1) { + case MEDIA_INFO_VIDEO_TRACK_LAGGING: + Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); + break; + case MEDIA_INFO_METADATA_UPDATE: + try { + scanInternalSubtitleTracks(); + } catch (RuntimeException e) { + Message msg2 = obtainMessage( + MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); + sendMessage(msg2); + } + // fall through + + case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: + msg.arg1 = MEDIA_INFO_METADATA_UPDATE; + // update default track selection + if (mSubtitleController != null) { + mSubtitleController.selectDefaultTrack(); + } + break; + case MEDIA_INFO_BUFFERING_START: + case MEDIA_INFO_BUFFERING_END: + TimeProvider timeProvider = mTimeProvider; + if (timeProvider != null) { + timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START); + } + break; + } + + if (eventCb != null && eventExec != null) { + final int what = msg.arg1; + final int extra = msg.arg2; + eventExec.execute(() -> eventCb.onInfo(mMediaPlayer, 0, what, extra)); + } + // No real default action so far. + return; + + case MEDIA_NOTIFY_TIME: + TimeProvider timeProvider = mTimeProvider; + if (timeProvider != null) { + timeProvider.onNotifyTime(); + } + return; + + case MEDIA_TIMED_TEXT: + if (eventCb == null || eventExec == null) { + return; + } + if (msg.obj == null) { + eventExec.execute(() -> eventCb.onTimedText(mMediaPlayer, 0, null)); + } else { + if (msg.obj instanceof Parcel) { + Parcel parcel = (Parcel)msg.obj; + TimedText text = new TimedText(parcel); + parcel.recycle(); + eventExec.execute(() -> eventCb.onTimedText(mMediaPlayer, 0, text)); + } + } + return; + + case MEDIA_SUBTITLE_DATA: + OnSubtitleDataListener onSubtitleDataListener = mOnSubtitleDataListener; + if (onSubtitleDataListener == null) { + return; + } + if (msg.obj instanceof Parcel) { + Parcel parcel = (Parcel) msg.obj; + SubtitleData data = new SubtitleData(parcel); + parcel.recycle(); + onSubtitleDataListener.onSubtitleData(mMediaPlayer, data); + } + return; + + case MEDIA_META_DATA: + if (eventCb == null || eventExec == null) { + return; + } + if (msg.obj instanceof Parcel) { + Parcel parcel = (Parcel) msg.obj; + TimedMetaData data = TimedMetaData.createTimedMetaDataFromParcel(parcel); + parcel.recycle(); + eventExec.execute(() -> eventCb.onTimedMetaDataAvailable( + mMediaPlayer, 0, data)); + } + return; + + case MEDIA_NOP: // interface test message - ignore + break; + + case MEDIA_AUDIO_ROUTING_CHANGED: + AudioManager.resetAudioPortGeneration(); + synchronized (mRoutingChangeListeners) { + for (NativeRoutingEventHandlerDelegate delegate + : mRoutingChangeListeners.values()) { + delegate.notifyClient(); + } + } + return; + + default: + Log.e(TAG, "Unknown message type " + msg.what); + return; + } + } + } + + /* + * Called from native code when an interesting event happens. This method + * just uses the EventHandler system to post the event back to the main app thread. + * We use a weak reference to the original MediaPlayer2 object so that the native + * code is safe from the object disappearing from underneath it. (This is + * the cookie passed to native_setup().) + */ + private static void postEventFromNative(Object mediaplayer2_ref, + int what, int arg1, int arg2, Object obj) + { + final MediaPlayer2Impl mp = (MediaPlayer2Impl)((WeakReference)mediaplayer2_ref).get(); + if (mp == null) { + return; + } + + switch (what) { + case MEDIA_INFO: + if (arg1 == MEDIA_INFO_STARTED_AS_NEXT) { + new Thread(new Runnable() { + @Override + public void run() { + // this acquires the wakelock if needed, and sets the client side state + mp.play(); + } + }).start(); + Thread.yield(); + } + break; + + case MEDIA_DRM_INFO: + // We need to derive mDrmInfoImpl before prepare() returns so processing it here + // before the notification is sent to EventHandler below. EventHandler runs in the + // notification looper so its handleMessage might process the event after prepare() + // has returned. + Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO"); + if (obj instanceof Parcel) { + Parcel parcel = (Parcel)obj; + DrmInfoImpl drmInfo = new DrmInfoImpl(parcel); + synchronized (mp.mDrmLock) { + mp.mDrmInfoImpl = drmInfo; + } + } else { + Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj); + } + break; + + case MEDIA_PREPARED: + // By this time, we've learned about DrmInfo's presence or absence. This is meant + // mainly for prepareAsync() use case. For prepare(), this still can run to a race + // condition b/c MediaPlayerNative releases the prepare() lock before calling notify + // so we also set mDrmInfoResolved in prepare(). + synchronized (mp.mDrmLock) { + mp.mDrmInfoResolved = true; + } + break; + + } + + if (mp.mEventHandler != null) { + Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); + mp.mEventHandler.sendMessage(m); + } + } + + private Executor mEventExec; + private EventCallback mEventCb; + private final Object mEventCbLock = new Object(); + + /** + * Register a callback to be invoked when the media source is ready + * for playback. + * + * @param eventCallback the callback that will be run + * @param executor the executor through which the callback should be invoked + */ + @Override + public void registerEventCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull EventCallback eventCallback) { + if (eventCallback == null) { + throw new IllegalArgumentException("Illegal null EventCallback"); + } + if (executor == null) { + throw new IllegalArgumentException("Illegal null Executor for the EventCallback"); + } + synchronized (mEventCbLock) { + // TODO: support multiple callbacks. + mEventExec = executor; + mEventCb = eventCallback; + } + } + + /** + * Unregisters an {@link EventCallback}. + * + * @param callback an {@link EventCallback} to unregister + */ + @Override + public void unregisterEventCallback(EventCallback callback) { + synchronized (mEventCbLock) { + if (callback == mEventCb) { + mEventExec = null; + mEventCb = null; + } + } + } + + /** + * Register a callback to be invoked when a track has data available. + * + * @param listener the callback that will be run + * + * @hide + */ + @Override + public void setOnSubtitleDataListener(OnSubtitleDataListener listener) { + mOnSubtitleDataListener = listener; + } + + private OnSubtitleDataListener mOnSubtitleDataListener; + + + // Modular DRM begin + + /** + * Register a callback to be invoked for configuration of the DRM object before + * the session is created. + * The callback will be invoked synchronously during the execution + * of {@link #prepareDrm(UUID uuid)}. + * + * @param listener the callback that will be run + */ + @Override + public void setOnDrmConfigHelper(OnDrmConfigHelper listener) + { + synchronized (mDrmLock) { + mOnDrmConfigHelper = listener; + } // synchronized + } + + private OnDrmConfigHelper mOnDrmConfigHelper; + + private Executor mDrmEventExec; + private DrmEventCallback mDrmEventCb; + private final Object mDrmEventCbLock = new Object(); + + /** + * Register a callback to be invoked when the media source is ready + * for playback. + * + * @param eventCallback the callback that will be run + * @param executor the executor through which the callback should be invoked + */ + @Override + public void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull DrmEventCallback eventCallback) { + if (eventCallback == null) { + throw new IllegalArgumentException("Illegal null EventCallback"); + } + if (executor == null) { + throw new IllegalArgumentException("Illegal null Executor for the EventCallback"); + } + synchronized (mDrmEventCbLock) { + // TODO: support multiple callbacks. + mDrmEventExec = executor; + mDrmEventCb = eventCallback; + } + } + + /** + * Unregisters a {@link DrmEventCallback}. + * + * @param callback a {@link DrmEventCallback} to unregister + */ + @Override + public void unregisterDrmEventCallback(DrmEventCallback callback) { + synchronized (mDrmEventCbLock) { + if (callback == mDrmEventCb) { + mDrmEventExec = null; + mDrmEventCb = null; + } + } + } + + + /** + * Retrieves the DRM Info associated with the current source + * + * @throws IllegalStateException if called before prepare() + */ + @Override + public DrmInfo getDrmInfo() { + DrmInfoImpl drmInfo = null; + + // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet; + // regardless below returns drmInfo anyway instead of raising an exception + synchronized (mDrmLock) { + if (!mDrmInfoResolved && mDrmInfoImpl == null) { + final String msg = "The Player has not been prepared yet"; + Log.v(TAG, msg); + throw new IllegalStateException(msg); + } + + if (mDrmInfoImpl != null) { + drmInfo = mDrmInfoImpl.makeCopy(); + } + } // synchronized + + return drmInfo; + } + + + /** + * Prepares the DRM for the current source + * <p> + * If {@code OnDrmConfigHelper} is registered, it will be called during + * preparation to allow configuration of the DRM properties before opening the + * DRM session. Note that the callback is called synchronously in the thread that called + * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString} + * and {@code setDrmPropertyString} calls and refrain from any lengthy operation. + * <p> + * If the device has not been provisioned before, this call also provisions the device + * which involves accessing the provisioning server and can take a variable time to + * complete depending on the network connectivity. + * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking + * mode by launching the provisioning in the background and returning. The listener + * will be called when provisioning and preparation has finished. If a + * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning + * and preparation has finished, i.e., runs in blocking mode. + * <p> + * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM + * session being ready. The application should not make any assumption about its call + * sequence (e.g., before or after prepareDrm returns), or the thread context that will + * execute the listener (unless the listener is registered with a handler thread). + * <p> + * + * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved + * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}. + * + * @throws IllegalStateException if called before prepare(), or the DRM was + * prepared already + * @throws UnsupportedSchemeException if the crypto scheme is not supported + * @throws ResourceBusyException if required DRM resources are in use + * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a + * network error + * @throws ProvisioningServerErrorException if provisioning is required but failed due to + * the request denied by the provisioning server + */ + @Override + public void prepareDrm(@NonNull UUID uuid) + throws UnsupportedSchemeException, ResourceBusyException, + ProvisioningNetworkErrorException, ProvisioningServerErrorException + { + Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper); + + boolean allDoneWithoutProvisioning = false; + + synchronized (mDrmLock) { + + // only allowing if tied to a protected source; might relax for releasing offline keys + if (mDrmInfoImpl == null) { + final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " + + "DRM info be retrieved before this call."; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + if (mActiveDrmScheme) { + final String msg = "prepareDrm(): Wrong usage: There is already " + + "an active DRM scheme with " + mDrmUUID; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + if (mPrepareDrmInProgress) { + final String msg = "prepareDrm(): Wrong usage: There is already " + + "a pending prepareDrm call."; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + if (mDrmProvisioningInProgress) { + final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress."; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + + // shouldn't need this; just for safeguard + cleanDrmObj(); + + mPrepareDrmInProgress = true; + + try { + // only creating the DRM object to allow pre-openSession configuration + prepareDrm_createDrmStep(uuid); + } catch (Exception e) { + Log.w(TAG, "prepareDrm(): Exception ", e); + mPrepareDrmInProgress = false; + throw e; + } + + mDrmConfigAllowed = true; + } // synchronized + + + // call the callback outside the lock + if (mOnDrmConfigHelper != null) { + mOnDrmConfigHelper.onDrmConfig(this); + } + + synchronized (mDrmLock) { + mDrmConfigAllowed = false; + boolean earlyExit = false; + + try { + prepareDrm_openSessionStep(uuid); + + mDrmUUID = uuid; + mActiveDrmScheme = true; + + allDoneWithoutProvisioning = true; + } catch (IllegalStateException e) { + final String msg = "prepareDrm(): Wrong usage: The player must be " + + "in the prepared state to call prepareDrm()."; + Log.e(TAG, msg); + earlyExit = true; + throw new IllegalStateException(msg); + } catch (NotProvisionedException e) { + Log.w(TAG, "prepareDrm: NotProvisionedException"); + + // handle provisioning internally; it'll reset mPrepareDrmInProgress + int result = HandleProvisioninig(uuid); + + // if blocking mode, we're already done; + // if non-blocking mode, we attempted to launch background provisioning + if (result != PREPARE_DRM_STATUS_SUCCESS) { + earlyExit = true; + String msg; + + switch (result) { + case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR: + msg = "prepareDrm: Provisioning was required but failed " + + "due to a network error."; + Log.e(TAG, msg); + throw new ProvisioningNetworkErrorExceptionImpl(msg); + + case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR: + msg = "prepareDrm: Provisioning was required but the request " + + "was denied by the server."; + Log.e(TAG, msg); + throw new ProvisioningServerErrorExceptionImpl(msg); + + case PREPARE_DRM_STATUS_PREPARATION_ERROR: + default: // default for safeguard + msg = "prepareDrm: Post-provisioning preparation failed."; + Log.e(TAG, msg); + throw new IllegalStateException(msg); + } + } + // nothing else to do; + // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup + } catch (Exception e) { + Log.e(TAG, "prepareDrm: Exception " + e); + earlyExit = true; + throw e; + } finally { + if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception + mPrepareDrmInProgress = false; + } + if (earlyExit) { // cleaning up object if didn't succeed + cleanDrmObj(); + } + } // finally + } // synchronized + + + // if finished successfully without provisioning, call the callback outside the lock + if (allDoneWithoutProvisioning) { + final Executor drmEventExec; + final DrmEventCallback drmEventCb; + synchronized (mDrmEventCbLock) { + drmEventExec = mDrmEventExec; + drmEventCb = mDrmEventCb; + } + if (drmEventExec != null && drmEventCb != null) { + drmEventExec.execute(() -> drmEventCb.onDrmPrepared( + this, PREPARE_DRM_STATUS_SUCCESS)); + } + } + + } + + + private native void _releaseDrm(); + + /** + * Releases the DRM session + * <p> + * The player has to have an active DRM session and be in stopped, or prepared + * state before this call is made. + * A {@code reset()} call will release the DRM session implicitly. + * + * @throws NoDrmSchemeException if there is no active DRM session to release + */ + @Override + public void releaseDrm() + throws NoDrmSchemeException + { + Log.v(TAG, "releaseDrm:"); + + synchronized (mDrmLock) { + if (!mActiveDrmScheme) { + Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); + throw new NoDrmSchemeExceptionImpl("releaseDrm: No active DRM scheme to release."); + } + + try { + // we don't have the player's state in this layer. The below call raises + // exception if we're in a non-stopped/prepared state. + + // for cleaning native/mediaserver crypto object + _releaseDrm(); + + // for cleaning client-side MediaDrm object; only called if above has succeeded + cleanDrmObj(); + + mActiveDrmScheme = false; + } catch (IllegalStateException e) { + Log.w(TAG, "releaseDrm: Exception ", e); + throw new IllegalStateException("releaseDrm: The player is not in a valid state."); + } catch (Exception e) { + Log.e(TAG, "releaseDrm: Exception ", e); + } + } // synchronized + } + + + /** + * A key request/response exchange occurs between the app and a license server + * to obtain or release keys used to decrypt encrypted content. + * <p> + * getKeyRequest() is used to obtain an opaque key request byte array that is + * delivered to the license server. The opaque key request byte array is returned + * in KeyRequest.data. The recommended URL to deliver the key request to is + * returned in KeyRequest.defaultUrl. + * <p> + * After the app has received the key request response from the server, + * it should deliver to the response to the DRM engine plugin using the method + * {@link #provideKeyResponse}. + * + * @param keySetId is the key-set identifier of the offline keys being released when keyType is + * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when + * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. + * + * @param initData is the container-specific initialization data when the keyType is + * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is + * interpreted based on the mime type provided in the mimeType parameter. It could + * contain, for example, the content ID, key ID or other data obtained from the content + * metadata that is required in generating the key request. + * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null. + * + * @param mimeType identifies the mime type of the content + * + * @param keyType specifies the type of the request. The request may be to acquire + * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content + * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired + * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId. + * + * @param optionalParameters are included in the key request message to + * allow a client application to provide additional message parameters to the server. + * This may be {@code null} if no additional parameters are to be sent. + * + * @throws NoDrmSchemeException if there is no active DRM session + */ + @Override + @NonNull + public MediaDrm.KeyRequest getKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData, + @Nullable String mimeType, @MediaDrm.KeyType int keyType, + @Nullable Map<String, String> optionalParameters) + throws NoDrmSchemeException + { + Log.v(TAG, "getKeyRequest: " + + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType + + " keyType: " + keyType + " optionalParameters: " + optionalParameters); + + synchronized (mDrmLock) { + if (!mActiveDrmScheme) { + Log.e(TAG, "getKeyRequest NoDrmSchemeException"); + throw new NoDrmSchemeExceptionImpl("getKeyRequest: Has to set a DRM scheme first."); + } + + try { + byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ? + mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE + keySetId; // keySetId for KEY_TYPE_RELEASE + + HashMap<String, String> hmapOptionalParameters = + (optionalParameters != null) ? + new HashMap<String, String>(optionalParameters) : + null; + + MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType, + keyType, hmapOptionalParameters); + Log.v(TAG, "getKeyRequest: --> request: " + request); + + return request; + + } catch (NotProvisionedException e) { + Log.w(TAG, "getKeyRequest NotProvisionedException: " + + "Unexpected. Shouldn't have reached here."); + throw new IllegalStateException("getKeyRequest: Unexpected provisioning error."); + } catch (Exception e) { + Log.w(TAG, "getKeyRequest Exception " + e); + throw e; + } + + } // synchronized + } + + + /** + * A key response is received from the license server by the app, then it is + * provided to the DRM engine plugin using provideKeyResponse. When the + * response is for an offline key request, a key-set identifier is returned that + * can be used to later restore the keys to a new session with the method + * {@ link # restoreKeys}. + * When the response is for a streaming or release request, null is returned. + * + * @param keySetId When the response is for a release request, keySetId identifies + * the saved key associated with the release request (i.e., the same keySetId + * passed to the earlier {@ link # getKeyRequest} call. It MUST be null when the + * response is for either streaming or offline key requests. + * + * @param response the byte array response from the server + * + * @throws NoDrmSchemeException if there is no active DRM session + * @throws DeniedByServerException if the response indicates that the + * server rejected the request + */ + @Override + public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response) + throws NoDrmSchemeException, DeniedByServerException + { + Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response); + + synchronized (mDrmLock) { + + if (!mActiveDrmScheme) { + Log.e(TAG, "getKeyRequest NoDrmSchemeException"); + throw new NoDrmSchemeExceptionImpl("getKeyRequest: Has to set a DRM scheme first."); + } + + try { + byte[] scope = (keySetId == null) ? + mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE + keySetId; // keySetId for KEY_TYPE_RELEASE + + byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response); + + Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response + + " --> " + keySetResult); + + + return keySetResult; + + } catch (NotProvisionedException e) { + Log.w(TAG, "provideKeyResponse NotProvisionedException: " + + "Unexpected. Shouldn't have reached here."); + throw new IllegalStateException("provideKeyResponse: " + + "Unexpected provisioning error."); + } catch (Exception e) { + Log.w(TAG, "provideKeyResponse Exception " + e); + throw e; + } + } // synchronized + } + + + /** + * Restore persisted offline keys into a new session. keySetId identifies the + * keys to load, obtained from a prior call to {@link #provideKeyResponse}. + * + * @param keySetId identifies the saved key set to restore + */ + @Override + public void restoreKeys(@NonNull byte[] keySetId) + throws NoDrmSchemeException + { + Log.v(TAG, "restoreKeys: keySetId: " + keySetId); + + synchronized (mDrmLock) { + + if (!mActiveDrmScheme) { + Log.w(TAG, "restoreKeys NoDrmSchemeException"); + throw new NoDrmSchemeExceptionImpl("restoreKeys: Has to set a DRM scheme first."); + } + + try { + mDrmObj.restoreKeys(mDrmSessionId, keySetId); + } catch (Exception e) { + Log.w(TAG, "restoreKeys Exception " + e); + throw e; + } + + } // synchronized + } + + + /** + * Read a DRM engine plugin String property value, given the property name string. + * <p> + * @param propertyName the property name + * + * Standard fields names are: + * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, + * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} + */ + @Override + @NonNull + public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName) + throws NoDrmSchemeException + { + Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName); + + String value; + synchronized (mDrmLock) { + + if (!mActiveDrmScheme && !mDrmConfigAllowed) { + Log.w(TAG, "getDrmPropertyString NoDrmSchemeException"); + throw new NoDrmSchemeExceptionImpl("getDrmPropertyString: Has to prepareDrm() first."); + } + + try { + value = mDrmObj.getPropertyString(propertyName); + } catch (Exception e) { + Log.w(TAG, "getDrmPropertyString Exception " + e); + throw e; + } + } // synchronized + + Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value); + + return value; + } + + + /** + * Set a DRM engine plugin String property value. + * <p> + * @param propertyName the property name + * @param value the property value + * + * Standard fields names are: + * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION}, + * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS} + */ + @Override + public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName, + @NonNull String value) + throws NoDrmSchemeException + { + Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value); + + synchronized (mDrmLock) { + + if ( !mActiveDrmScheme && !mDrmConfigAllowed ) { + Log.w(TAG, "setDrmPropertyString NoDrmSchemeException"); + throw new NoDrmSchemeExceptionImpl("setDrmPropertyString: Has to prepareDrm() first."); + } + + try { + mDrmObj.setPropertyString(propertyName, value); + } catch ( Exception e ) { + Log.w(TAG, "setDrmPropertyString Exception " + e); + throw e; + } + } // synchronized + } + + /** + * Encapsulates the DRM properties of the source. + */ + public static final class DrmInfoImpl extends DrmInfo { + private Map<UUID, byte[]> mapPssh; + private UUID[] supportedSchemes; + + /** + * Returns the PSSH info of the data source for each supported DRM scheme. + */ + @Override + public Map<UUID, byte[]> getPssh() { + return mapPssh; + } + + /** + * Returns the intersection of the data source and the device DRM schemes. + * It effectively identifies the subset of the source's DRM schemes which + * are supported by the device too. + */ + @Override + public List<UUID> getSupportedSchemes() { + return Arrays.asList(supportedSchemes); + } + + private DrmInfoImpl(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) { + mapPssh = Pssh; + supportedSchemes = SupportedSchemes; + } + + private DrmInfoImpl(Parcel parcel) { + Log.v(TAG, "DrmInfoImpl(" + parcel + ") size " + parcel.dataSize()); + + int psshsize = parcel.readInt(); + byte[] pssh = new byte[psshsize]; + parcel.readByteArray(pssh); + + Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh)); + mapPssh = parsePSSH(pssh, psshsize); + Log.v(TAG, "DrmInfoImpl() PSSH: " + mapPssh); + + int supportedDRMsCount = parcel.readInt(); + supportedSchemes = new UUID[supportedDRMsCount]; + for (int i = 0; i < supportedDRMsCount; i++) { + byte[] uuid = new byte[16]; + parcel.readByteArray(uuid); + + supportedSchemes[i] = bytesToUUID(uuid); + + Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: " + + supportedSchemes[i]); + } + + Log.v(TAG, "DrmInfoImpl() Parcel psshsize: " + psshsize + + " supportedDRMsCount: " + supportedDRMsCount); + } + + private DrmInfoImpl makeCopy() { + return new DrmInfoImpl(this.mapPssh, this.supportedSchemes); + } + + private String arrToHex(byte[] bytes) { + String out = "0x"; + for (int i = 0; i < bytes.length; i++) { + out += String.format("%02x", bytes[i]); + } + + return out; + } + + private UUID bytesToUUID(byte[] uuid) { + long msb = 0, lsb = 0; + for (int i = 0; i < 8; i++) { + msb |= ( ((long)uuid[i] & 0xff) << (8 * (7 - i)) ); + lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) ); + } + + return new UUID(msb, lsb); + } + + private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) { + Map<UUID, byte[]> result = new HashMap<UUID, byte[]>(); + + final int UUID_SIZE = 16; + final int DATALEN_SIZE = 4; + + int len = psshsize; + int numentries = 0; + int i = 0; + + while (len > 0) { + if (len < UUID_SIZE) { + Log.w(TAG, String.format("parsePSSH: len is too short to parse " + + "UUID: (%d < 16) pssh: %d", len, psshsize)); + return null; + } + + byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE); + UUID uuid = bytesToUUID(subset); + i += UUID_SIZE; + len -= UUID_SIZE; + + // get data length + if (len < 4) { + Log.w(TAG, String.format("parsePSSH: len is too short to parse " + + "datalen: (%d < 4) pssh: %d", len, psshsize)); + return null; + } + + subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE); + int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ? + ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) | + ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) : + ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) | + ((subset[2] & 0xff) << 8) | (subset[3] & 0xff) ; + i += DATALEN_SIZE; + len -= DATALEN_SIZE; + + if (len < datalen) { + Log.w(TAG, String.format("parsePSSH: len is too short to parse " + + "data: (%d < %d) pssh: %d", len, datalen, psshsize)); + return null; + } + + byte[] data = Arrays.copyOfRange(pssh, i, i+datalen); + + // skip the data + i += datalen; + len -= datalen; + + Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d", + numentries, uuid, arrToHex(data), psshsize)); + numentries++; + result.put(uuid, data); + } + + return result; + } + + }; // DrmInfoImpl + + /** + * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm(). + * Extends MediaDrm.MediaDrmException + */ + public static final class NoDrmSchemeExceptionImpl extends NoDrmSchemeException { + public NoDrmSchemeExceptionImpl(String detailMessage) { + super(detailMessage); + } + } + + /** + * Thrown when the device requires DRM provisioning but the provisioning attempt has + * failed due to a network error (Internet reachability, timeout, etc.). + * Extends MediaDrm.MediaDrmException + */ + public static final class ProvisioningNetworkErrorExceptionImpl + extends ProvisioningNetworkErrorException { + public ProvisioningNetworkErrorExceptionImpl(String detailMessage) { + super(detailMessage); + } + } + + /** + * Thrown when the device requires DRM provisioning but the provisioning attempt has + * failed due to the provisioning server denying the request. + * Extends MediaDrm.MediaDrmException + */ + public static final class ProvisioningServerErrorExceptionImpl + extends ProvisioningServerErrorException { + public ProvisioningServerErrorExceptionImpl(String detailMessage) { + super(detailMessage); + } + } + + + private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId); + + // Modular DRM helpers + + private void prepareDrm_createDrmStep(@NonNull UUID uuid) + throws UnsupportedSchemeException { + Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid); + + try { + mDrmObj = new MediaDrm(uuid); + Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj); + } catch (Exception e) { // UnsupportedSchemeException + Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e); + throw e; + } + } + + private void prepareDrm_openSessionStep(@NonNull UUID uuid) + throws NotProvisionedException, ResourceBusyException { + Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid); + + // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do + // it anyway so it raises provisioning error if needed. We'd rather handle provisioning + // at prepareDrm/openSession rather than getKeyRequest/provideKeyResponse + try { + mDrmSessionId = mDrmObj.openSession(); + Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId); + + // Sending it down to native/mediaserver to create the crypto object + // This call could simply fail due to bad player state, e.g., after play(). + _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId); + Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded"); + + } catch (Exception e) { //ResourceBusyException, NotProvisionedException + Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e); + throw e; + } + + } + + private class ProvisioningThread extends Thread { + public static final int TIMEOUT_MS = 60000; + + private UUID uuid; + private String urlStr; + private Object drmLock; + private MediaPlayer2Impl mediaPlayer; + private int status; + private boolean finished; + public int status() { + return status; + } + + public ProvisioningThread initialize(MediaDrm.ProvisionRequest request, + UUID uuid, MediaPlayer2Impl mediaPlayer) { + // lock is held by the caller + drmLock = mediaPlayer.mDrmLock; + this.mediaPlayer = mediaPlayer; + + urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); + this.uuid = uuid; + + status = PREPARE_DRM_STATUS_PREPARATION_ERROR; + + Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr); + return this; + } + + public void run() { + + byte[] response = null; + boolean provisioningSucceeded = false; + try { + URL url = new URL(urlStr); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + try { + connection.setRequestMethod("POST"); + connection.setDoOutput(false); + connection.setDoInput(true); + connection.setConnectTimeout(TIMEOUT_MS); + connection.setReadTimeout(TIMEOUT_MS); + + connection.connect(); + response = Streams.readFully(connection.getInputStream()); + + Log.v(TAG, "HandleProvisioninig: Thread run: response " + + response.length + " " + response); + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; + Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url); + } finally { + connection.disconnect(); + } + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR; + Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e); + } + + if (response != null) { + try { + mDrmObj.provideProvisionResponse(response); + Log.v(TAG, "HandleProvisioninig: Thread run: " + + "provideProvisionResponse SUCCEEDED!"); + + provisioningSucceeded = true; + } catch (Exception e) { + status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR; + Log.w(TAG, "HandleProvisioninig: Thread run: " + + "provideProvisionResponse " + e); + } + } + + boolean succeeded = false; + + final Executor drmEventExec; + final DrmEventCallback drmEventCb; + synchronized (mDrmEventCbLock) { + drmEventExec = mDrmEventExec; + drmEventCb = mDrmEventCb; + } + // non-blocking mode needs the lock + if (drmEventExec != null && drmEventCb != null) { + + synchronized (drmLock) { + // continuing with prepareDrm + if (provisioningSucceeded) { + succeeded = mediaPlayer.resumePrepareDrm(uuid); + status = (succeeded) ? + PREPARE_DRM_STATUS_SUCCESS : + PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + mediaPlayer.mDrmProvisioningInProgress = false; + mediaPlayer.mPrepareDrmInProgress = false; + if (!succeeded) { + cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock + } + } // synchronized + + // calling the callback outside the lock + drmEventExec.execute(() -> drmEventCb.onDrmPrepared(mediaPlayer, status)); + } else { // blocking mode already has the lock + + // continuing with prepareDrm + if (provisioningSucceeded) { + succeeded = mediaPlayer.resumePrepareDrm(uuid); + status = (succeeded) ? + PREPARE_DRM_STATUS_SUCCESS : + PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + mediaPlayer.mDrmProvisioningInProgress = false; + mediaPlayer.mPrepareDrmInProgress = false; + if (!succeeded) { + cleanDrmObj(); // cleaning up if it hasn't gone through + } + } + + finished = true; + } // run() + + } // ProvisioningThread + + private int HandleProvisioninig(UUID uuid) { + // the lock is already held by the caller + + if (mDrmProvisioningInProgress) { + Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress"); + return PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + + MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest(); + if (provReq == null) { + Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null."); + return PREPARE_DRM_STATUS_PREPARATION_ERROR; + } + + Log.v(TAG, "HandleProvisioninig provReq " + + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl()); + + // networking in a background thread + mDrmProvisioningInProgress = true; + + mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this); + mDrmProvisioningThread.start(); + + int result; + + // non-blocking: this is not the final result + final Executor drmEventExec; + final DrmEventCallback drmEventCb; + synchronized (mDrmEventCbLock) { + drmEventExec = mDrmEventExec; + drmEventCb = mDrmEventCb; + } + if (drmEventCb != null && drmEventExec != null) { + result = PREPARE_DRM_STATUS_SUCCESS; + } else { + // if blocking mode, wait till provisioning is done + try { + mDrmProvisioningThread.join(); + } catch (Exception e) { + Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e); + } + result = mDrmProvisioningThread.status(); + // no longer need the thread + mDrmProvisioningThread = null; + } + + return result; + } + + private boolean resumePrepareDrm(UUID uuid) { + Log.v(TAG, "resumePrepareDrm: uuid: " + uuid); + + // mDrmLock is guaranteed to be held + boolean success = false; + try { + // resuming + prepareDrm_openSessionStep(uuid); + + mDrmUUID = uuid; + mActiveDrmScheme = true; + + success = true; + } catch (Exception e) { + Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e); + // mDrmObj clean up is done by the caller + } + + return success; + } + + private void resetDrmState() { + synchronized (mDrmLock) { + Log.v(TAG, "resetDrmState: " + + " mDrmInfoImpl=" + mDrmInfoImpl + + " mDrmProvisioningThread=" + mDrmProvisioningThread + + " mPrepareDrmInProgress=" + mPrepareDrmInProgress + + " mActiveDrmScheme=" + mActiveDrmScheme); + + mDrmInfoResolved = false; + mDrmInfoImpl = null; + + if (mDrmProvisioningThread != null) { + // timeout; relying on HttpUrlConnection + try { + mDrmProvisioningThread.join(); + } + catch (InterruptedException e) { + Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e); + } + mDrmProvisioningThread = null; + } + + mPrepareDrmInProgress = false; + mActiveDrmScheme = false; + + cleanDrmObj(); + } // synchronized + } + + private void cleanDrmObj() { + // the caller holds mDrmLock + Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId); + + if (mDrmSessionId != null) { + mDrmObj.closeSession(mDrmSessionId); + mDrmSessionId = null; + } + if (mDrmObj != null) { + mDrmObj.release(); + mDrmObj = null; + } + } + + private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) { + long msb = uuid.getMostSignificantBits(); + long lsb = uuid.getLeastSignificantBits(); + + byte[] uuidBytes = new byte[16]; + for (int i = 0; i < 8; ++i) { + uuidBytes[i] = (byte)(msb >>> (8 * (7 - i))); + uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i))); + } + + return uuidBytes; + } + + // Modular DRM end + + /* + * Test whether a given video scaling mode is supported. + */ + private boolean isVideoScalingModeSupported(int mode) { + return (mode == VIDEO_SCALING_MODE_SCALE_TO_FIT || + mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); + } + + /** @hide */ + static class TimeProvider implements MediaTimeProvider { + private static final String TAG = "MTP"; + private static final long MAX_NS_WITHOUT_POSITION_CHECK = 5000000000L; + private static final long MAX_EARLY_CALLBACK_US = 1000; + private static final long TIME_ADJUSTMENT_RATE = 2; /* meaning 1/2 */ + private long mLastTimeUs = 0; + private MediaPlayer2Impl mPlayer; + private boolean mPaused = true; + private boolean mStopped = true; + private boolean mBuffering; + private long mLastReportedTime; + // since we are expecting only a handful listeners per stream, there is + // no need for log(N) search performance + private MediaTimeProvider.OnMediaTimeListener mListeners[]; + private long mTimes[]; + private Handler mEventHandler; + private boolean mRefresh = false; + private boolean mPausing = false; + private boolean mSeeking = false; + private static final int NOTIFY = 1; + private static final int NOTIFY_TIME = 0; + private static final int NOTIFY_STOP = 2; + private static final int NOTIFY_SEEK = 3; + private static final int NOTIFY_TRACK_DATA = 4; + private HandlerThread mHandlerThread; + + /** @hide */ + public boolean DEBUG = false; + + public TimeProvider(MediaPlayer2Impl mp) { + mPlayer = mp; + try { + getCurrentTimeUs(true, false); + } catch (IllegalStateException e) { + // we assume starting position + mRefresh = true; + } + + Looper looper; + if ((looper = Looper.myLooper()) == null && + (looper = Looper.getMainLooper()) == null) { + // Create our own looper here in case MP was created without one + mHandlerThread = new HandlerThread("MediaPlayer2MTPEventThread", + Process.THREAD_PRIORITY_FOREGROUND); + mHandlerThread.start(); + looper = mHandlerThread.getLooper(); + } + mEventHandler = new EventHandler(looper); + + mListeners = new MediaTimeProvider.OnMediaTimeListener[0]; + mTimes = new long[0]; + mLastTimeUs = 0; + } + + private void scheduleNotification(int type, long delayUs) { + // ignore time notifications until seek is handled + if (mSeeking && type == NOTIFY_TIME) { + return; + } + + if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs); + mEventHandler.removeMessages(NOTIFY); + Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0); + mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000)); + } + + /** @hide */ + public void close() { + mEventHandler.removeMessages(NOTIFY); + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + mHandlerThread = null; + } + } + + /** @hide */ + protected void finalize() { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + } + } + + /** @hide */ + public void onNotifyTime() { + synchronized (this) { + if (DEBUG) Log.d(TAG, "onNotifyTime: "); + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + + /** @hide */ + public void onPaused(boolean paused) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "onPaused: " + paused); + if (mStopped) { // handle as seek if we were stopped + mStopped = false; + mSeeking = true; + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } else { + mPausing = paused; // special handling if player disappeared + mSeeking = false; + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + } + + /** @hide */ + public void onBuffering(boolean buffering) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "onBuffering: " + buffering); + mBuffering = buffering; + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + + /** @hide */ + public void onStopped() { + synchronized(this) { + if (DEBUG) Log.d(TAG, "onStopped"); + mPaused = true; + mStopped = true; + mSeeking = false; + mBuffering = false; + scheduleNotification(NOTIFY_STOP, 0 /* delay */); + } + } + + /** @hide */ + public void onSeekComplete(MediaPlayer2Impl mp) { + synchronized(this) { + mStopped = false; + mSeeking = true; + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } + + /** @hide */ + public void onNewPlayer() { + if (mRefresh) { + synchronized(this) { + mStopped = false; + mSeeking = true; + mBuffering = false; + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } + } + + private synchronized void notifySeek() { + mSeeking = false; + try { + long timeUs = getCurrentTimeUs(true, false); + if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs); + + for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { + if (listener == null) { + break; + } + listener.onSeek(timeUs); + } + } catch (IllegalStateException e) { + // we should not be there, but at least signal pause + if (DEBUG) Log.d(TAG, "onSeekComplete but no player"); + mPausing = true; // special handling if player disappeared + notifyTimedEvent(false /* refreshTime */); + } + } + + private synchronized void notifyTrackData(Pair<SubtitleTrack, byte[]> trackData) { + SubtitleTrack track = trackData.first; + byte[] data = trackData.second; + track.onData(data, true /* eos */, ~0 /* runID: keep forever */); + } + + private synchronized void notifyStop() { + for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) { + if (listener == null) { + break; + } + listener.onStop(); + } + } + + private int registerListener(MediaTimeProvider.OnMediaTimeListener listener) { + int i = 0; + for (; i < mListeners.length; i++) { + if (mListeners[i] == listener || mListeners[i] == null) { + break; + } + } + + // new listener + if (i >= mListeners.length) { + MediaTimeProvider.OnMediaTimeListener[] newListeners = + new MediaTimeProvider.OnMediaTimeListener[i + 1]; + long[] newTimes = new long[i + 1]; + System.arraycopy(mListeners, 0, newListeners, 0, mListeners.length); + System.arraycopy(mTimes, 0, newTimes, 0, mTimes.length); + mListeners = newListeners; + mTimes = newTimes; + } + + if (mListeners[i] == null) { + mListeners[i] = listener; + mTimes[i] = MediaTimeProvider.NO_TIME; + } + return i; + } + + public void notifyAt( + long timeUs, MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "notifyAt " + timeUs); + mTimes[registerListener(listener)] = timeUs; + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + + public void scheduleUpdate(MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + if (DEBUG) Log.d(TAG, "scheduleUpdate"); + int i = registerListener(listener); + + if (!mStopped) { + mTimes[i] = 0; + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + } + + public void cancelNotifications( + MediaTimeProvider.OnMediaTimeListener listener) { + synchronized(this) { + int i = 0; + for (; i < mListeners.length; i++) { + if (mListeners[i] == listener) { + System.arraycopy(mListeners, i + 1, + mListeners, i, mListeners.length - i - 1); + System.arraycopy(mTimes, i + 1, + mTimes, i, mTimes.length - i - 1); + mListeners[mListeners.length - 1] = null; + mTimes[mTimes.length - 1] = NO_TIME; + break; + } else if (mListeners[i] == null) { + break; + } + } + + scheduleNotification(NOTIFY_TIME, 0 /* delay */); + } + } + + private synchronized void notifyTimedEvent(boolean refreshTime) { + // figure out next callback + long nowUs; + try { + nowUs = getCurrentTimeUs(refreshTime, true); + } catch (IllegalStateException e) { + // assume we paused until new player arrives + mRefresh = true; + mPausing = true; // this ensures that call succeeds + nowUs = getCurrentTimeUs(refreshTime, true); + } + long nextTimeUs = nowUs; + + if (mSeeking) { + // skip timed-event notifications until seek is complete + return; + } + + if (DEBUG) { + StringBuilder sb = new StringBuilder(); + sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ") + .append(nowUs).append(") from {"); + boolean first = true; + for (long time: mTimes) { + if (time == NO_TIME) { + continue; + } + if (!first) sb.append(", "); + sb.append(time); + first = false; + } + sb.append("}"); + Log.d(TAG, sb.toString()); + } + + Vector<MediaTimeProvider.OnMediaTimeListener> activatedListeners = + new Vector<MediaTimeProvider.OnMediaTimeListener>(); + for (int ix = 0; ix < mTimes.length; ix++) { + if (mListeners[ix] == null) { + break; + } + if (mTimes[ix] <= NO_TIME) { + // ignore, unless we were stopped + } else if (mTimes[ix] <= nowUs + MAX_EARLY_CALLBACK_US) { + activatedListeners.add(mListeners[ix]); + if (DEBUG) Log.d(TAG, "removed"); + mTimes[ix] = NO_TIME; + } else if (nextTimeUs == nowUs || mTimes[ix] < nextTimeUs) { + nextTimeUs = mTimes[ix]; + } + } + + if (nextTimeUs > nowUs && !mPaused) { + // schedule callback at nextTimeUs + if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs); + mPlayer.notifyAt(nextTimeUs); + } else { + mEventHandler.removeMessages(NOTIFY); + // no more callbacks + } + + for (MediaTimeProvider.OnMediaTimeListener listener: activatedListeners) { + listener.onTimedEvent(nowUs); + } + } + + public long getCurrentTimeUs(boolean refreshTime, boolean monotonic) + throws IllegalStateException { + synchronized (this) { + // we always refresh the time when the paused-state changes, because + // we expect to have received the pause-change event delayed. + if (mPaused && !refreshTime) { + return mLastReportedTime; + } + + try { + mLastTimeUs = mPlayer.getCurrentPosition() * 1000L; + mPaused = !mPlayer.isPlaying() || mBuffering; + if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs); + } catch (IllegalStateException e) { + if (mPausing) { + // if we were pausing, get last estimated timestamp + mPausing = false; + if (!monotonic || mLastReportedTime < mLastTimeUs) { + mLastReportedTime = mLastTimeUs; + } + mPaused = true; + if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime); + return mLastReportedTime; + } + // TODO get time when prepared + throw e; + } + if (monotonic && mLastTimeUs < mLastReportedTime) { + /* have to adjust time */ + if (mLastReportedTime - mLastTimeUs > 1000000) { + // schedule seeked event if time jumped significantly + // TODO: do this properly by introducing an exception + mStopped = false; + mSeeking = true; + scheduleNotification(NOTIFY_SEEK, 0 /* delay */); + } + } else { + mLastReportedTime = mLastTimeUs; + } + + return mLastReportedTime; + } + } + + private class EventHandler extends Handler { + public EventHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == NOTIFY) { + switch (msg.arg1) { + case NOTIFY_TIME: + notifyTimedEvent(true /* refreshTime */); + break; + case NOTIFY_STOP: + notifyStop(); + break; + case NOTIFY_SEEK: + notifySeek(); + break; + case NOTIFY_TRACK_DATA: + notifyTrackData((Pair<SubtitleTrack, byte[]>)msg.obj); + break; + } + } + } + } + } +} diff --git a/media/java/android/media/MediaPlayerBase.java b/media/java/android/media/MediaPlayerBase.java index 980c70f8178c..d638a9f95544 100644 --- a/media/java/android/media/MediaPlayerBase.java +++ b/media/java/android/media/MediaPlayerBase.java @@ -16,49 +16,52 @@ package android.media; +import android.media.MediaSession2.PlaylistParam; import android.media.session.PlaybackState; import android.os.Handler; +import java.util.List; +import java.util.concurrent.Executor; + /** - * Tentative interface for all media players that want media session. - * APIs are named to avoid conflicts with MediaPlayer APIs. - * All calls should be asynchrounous. + * Base interfaces for all media players that want media session. * * @hide */ -// TODO(wjia) Finalize the list of MediaPlayer2, which MediaPlayerBase's APIs will be come from. public abstract class MediaPlayerBase { /** - * Listens change in {@link PlaybackState}. + * Listens change in {@link PlaybackState2}. */ public interface PlaybackListener { /** - * Called when {@link PlaybackState} for this player is changed. + * Called when {@link PlaybackState2} for this player is changed. */ - void onPlaybackChanged(PlaybackState state); + void onPlaybackChanged(PlaybackState2 state); } - // TODO(jaewan): setDataSources()? - // TODO(jaewan): Add release() or do that in stop()? - - // TODO(jaewan): Add set/getSupportedActions(). public abstract void play(); + public abstract void prepare(); public abstract void pause(); public abstract void stop(); public abstract void skipToPrevious(); public abstract void skipToNext(); + public abstract void seekTo(long pos); + public abstract void fastFoward(); + public abstract void rewind(); + + public abstract PlaybackState2 getPlaybackState(); + public abstract AudioAttributes getAudioAttributes(); - // Currently PlaybackState's error message is the content title (for testing only) - // TODO(jaewan): Add metadata support - public abstract PlaybackState getPlaybackState(); + public abstract void setPlaylist(List<MediaItem2> item, PlaylistParam param); + public abstract void setCurrentPlaylistItem(int index); /** * Add a {@link PlaybackListener} to be invoked when the playback state is changed. * + * @param executor the Handler that will receive the listener * @param listener the listener that will be run - * @param handler the Handler that will receive the listener */ - public abstract void addPlaybackListener(PlaybackListener listener, Handler handler); + public abstract void addPlaybackListener(Executor executor, PlaybackListener listener); /** * Remove previously added {@link PlaybackListener}. diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 6fdf7a8b906c..0e90040ac567 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -16,14 +16,14 @@ package android.media; +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.media.AudioAttributes; -import android.media.AudioManager; import android.media.MediaPlayerBase.PlaybackListener; import android.media.session.MediaSession; import android.media.session.MediaSession.Callback; @@ -39,8 +39,11 @@ import android.os.ResultReceiver; import android.text.TextUtils; import android.util.ArraySet; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; /** * Allows a media app to expose its transport controls and playback information in a process to @@ -461,6 +464,7 @@ public class MediaSession2 implements AutoCloseable { final Context mContext; final MediaPlayerBase mPlayer; String mId; + Executor mCallbackExecutor; C mCallback; VolumeProvider mVolumeProvider; int mRatingType; @@ -553,10 +557,19 @@ public class MediaSession2 implements AutoCloseable { /** * Set {@link SessionCallback}. * + * @param executor callback executor * @param callback session callback. * @return */ - public T setSessionCallback(@Nullable C callback) { + public T setSessionCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull C callback) { + if (executor == null) { + throw new IllegalArgumentException("executor shouldn't be null"); + } + if (callback == null) { + throw new IllegalArgumentException("callback shouldn't be null"); + } + mCallbackExecutor = executor; mCallback = callback; return (T) this; } @@ -590,7 +603,7 @@ public class MediaSession2 implements AutoCloseable { if (mCallback == null) { mCallback = new SessionCallback(); } - return new MediaSession2(mContext, mPlayer, mId, mCallback, + return new MediaSession2(mContext, mPlayer, mId, mCallbackExecutor, mCallback, mVolumeProvider, mRatingType, mSessionActivity); } } @@ -842,28 +855,79 @@ public class MediaSession2 implements AutoCloseable { * Parameter for the playlist. */ // TODO(jaewan): add fromBundle()/toBundle() - public class PlaylistParam { + public static class PlaylistParam { + /** + * @hide + */ + @IntDef({REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL, + REPEAT_MODE_GROUP}) + @Retention(RetentionPolicy.SOURCE) + public @interface RepeatMode {} + + /** + * Playback will be stopped at the end of the playing media list. + */ + public static final int REPEAT_MODE_NONE = 0; + + /** + * Playback of the current playing media item will be repeated. + */ + public static final int REPEAT_MODE_ONE = 1; + + /** + * Playing media list will be repeated. + */ + public static final int REPEAT_MODE_ALL = 2; + + /** + * Playback of the playing media group will be repeated. + * A group is a logical block of media items which is specified in the section 5.7 of the + * Bluetooth AVRCP 1.6. + */ + public static final int REPEAT_MODE_GROUP = 3; + + /** + * @hide + */ + @IntDef({SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP}) + @Retention(RetentionPolicy.SOURCE) + public @interface ShuffleMode {} + + /** + * Media list will be played in order. + */ + public static final int SHUFFLE_MODE_NONE = 0; + + /** + * Media list will be played in shuffled order. + */ + public static final int SHUFFLE_MODE_ALL = 1; + + /** + * Media group will be played in shuffled order. + * A group is a logical block of media items which is specified in the section 5.7 of the + * Bluetooth AVRCP 1.6. + */ + public static final int SHUFFLE_MODE_GROUP = 2; + + private @RepeatMode int mRepeatMode; + private @ShuffleMode int mShuffleMode; + private MediaMetadata2 mPlaylistMetadata; - // It's mixture of shuffle mode and repeat mode. Needs to be separated for avrcp 1.6 support - // PlaybackState#REPEAT_MODE_ALL - // PlaybackState#REPEAT_MODE_GROUP <- for avrcp 1.6 - // PlaybackState#REPEAT_MODE_INVALID - // PlaybackState#REPEAT_MODE_NONE - // PlaybackState#REPEAT_MODE_ONE - // PlaybackState#SHUFFLE_MODE_ALL - // PlaybackState#SHUFFLE_MODE_GROUP <- for avrcp 1.6 - // PlaybackState#SHUFFLE_MODE_INVALID - // PlaybackState#SHUFFLE_MODE_NONE - private int mLoopingMode; - - public PlaylistParam(int loopingMode, @Nullable MediaMetadata2 playlistMetadata) { - mLoopingMode = loopingMode; + public PlaylistParam(@RepeatMode int repeatMode, @ShuffleMode int shuffleMode, + @Nullable MediaMetadata2 playlistMetadata) { + mRepeatMode = repeatMode; + mShuffleMode = shuffleMode; mPlaylistMetadata = playlistMetadata; } - public int getLoopingMode() { - return mLoopingMode; + public @RepeatMode int getRepeatMode() { + return mRepeatMode; + } + + public @ShuffleMode int getShuffleMode() { + return mShuffleMode; } public MediaMetadata2 getPlaylistMetadata() { @@ -885,20 +949,20 @@ public class MediaSession2 implements AutoCloseable { * framework had to add heuristics to figure out if an app is * @hide */ - MediaSession2(Context context, MediaPlayerBase player, String id, + MediaSession2(Context context, MediaPlayerBase player, String id, Executor callbackExecutor, SessionCallback callback, VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity) { super(); - mProvider = createProvider(context, player, id, callback, + mProvider = createProvider(context, player, id, callbackExecutor, callback, volumeProvider, ratingType, sessionActivity); } MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id, - SessionCallback callback, VolumeProvider volumeProvider, int ratingType, - PendingIntent sessionActivity) { + Executor callbackExecutor, SessionCallback callback, VolumeProvider volumeProvider, + int ratingType, PendingIntent sessionActivity) { return ApiLoader.getProvider(context) - .createMediaSession2(this, context, player, id, callback, - volumeProvider, ratingType, sessionActivity); + .createMediaSession2(this, context, player, id, callbackExecutor, + callback, volumeProvider, ratingType, sessionActivity); } /** @@ -954,10 +1018,10 @@ public class MediaSession2 implements AutoCloseable { } /** - * Returns the {@link SessionToken} for creating {@link MediaController2}. + * Returns the {@link SessionToken2} for creating {@link MediaController2}. */ public @NonNull - SessionToken getToken() { + SessionToken2 getToken() { return mProvider.getToken_impl(); } diff --git a/media/java/android/media/MediaSessionService2.java b/media/java/android/media/MediaSessionService2.java index 1a12d6806c05..19814f04ec9e 100644 --- a/media/java/android/media/MediaSessionService2.java +++ b/media/java/android/media/MediaSessionService2.java @@ -179,7 +179,7 @@ public abstract class MediaSessionService2 extends Service { * @return a {@link MediaNotification}. If it's {@code null}, notification wouldn't be shown. */ // TODO(jaewan): Also add metadata - public MediaNotification onUpdateNotification(PlaybackState state) { + public MediaNotification onUpdateNotification(PlaybackState2 state) { return mProvider.onUpdateNotification_impl(state); } diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java index 3740aeab9562..46d6f45aa80f 100644 --- a/media/java/android/media/PlaybackState2.java +++ b/media/java/android/media/PlaybackState2.java @@ -17,6 +17,7 @@ package android.media; import android.annotation.IntDef; +import android.os.Bundle; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,10 +29,11 @@ import java.lang.annotation.RetentionPolicy; * @hide */ // TODO(jaewan): Move to updatable -// TODO(jaewan): Add fromBundle() toBundle() public final class PlaybackState2 { private static final String TAG = "PlaybackState2"; + private static final String KEY_STATE = "android.media.playbackstate2.state"; + // TODO(jaewan): Replace states from MediaPlayer2 /** * @hide @@ -97,7 +99,7 @@ public final class PlaybackState2 { private final long mUpdateTime; private final long mActiveItemId; - private PlaybackState2(int state, long position, long updateTime, float speed, + public PlaybackState2(int state, long position, long updateTime, float speed, long bufferedPosition, long activeItemId, CharSequence error) { mState = state; mPosition = position; @@ -129,14 +131,8 @@ public final class PlaybackState2 { * <li> {@link PlaybackState2#STATE_STOPPED}</li> * <li> {@link PlaybackState2#STATE_PLAYING}</li> * <li> {@link PlaybackState2#STATE_PAUSED}</li> - * <li> {@link PlaybackState2#STATE_FAST_FORWARDING}</li> - * <li> {@link PlaybackState2#STATE_REWINDING}</li> * <li> {@link PlaybackState2#STATE_BUFFERING}</li> * <li> {@link PlaybackState2#STATE_ERROR}</li> - * <li> {@link PlaybackState2#STATE_CONNECTING}</li> - * <li> {@link PlaybackState2#STATE_SKIPPING_TO_PREVIOUS}</li> - * <li> {@link PlaybackState2#STATE_SKIPPING_TO_NEXT}</li> - * <li> {@link PlaybackState2#STATE_SKIPPING_TO_QUEUE_ITEM}</li> * </ul> */ @State @@ -197,4 +193,24 @@ public final class PlaybackState2 { public long getCurrentPlaylistItemIndex() { return mActiveItemId; } + + /** + * @return Bundle object for this to share between processes. + */ + public Bundle toBundle() { + // TODO(jaewan): Include other variables. + Bundle bundle = new Bundle(); + bundle.putInt(KEY_STATE, mState); + return bundle; + } + + /** + * @param bundle input + * @return + */ + public static PlaybackState2 fromBundle(Bundle bundle) { + // TODO(jaewan): Include other variables. + final int state = bundle.getInt(KEY_STATE); + return new PlaybackState2(state, 0, 0, 0, 0, 0, null); + } }
\ No newline at end of file diff --git a/media/java/android/media/SessionToken.java b/media/java/android/media/SessionToken2.java index 53fc24afea2c..697a5a87a784 100644 --- a/media/java/android/media/SessionToken.java +++ b/media/java/android/media/SessionToken2.java @@ -40,7 +40,7 @@ import java.lang.annotation.RetentionPolicy; // TODO(jaewan): Unhide. SessionToken2? // TODO(jaewan): Move Token to updatable! // TODO(jaewan): Find better name for this (SessionToken or Session2Token) -public final class SessionToken { +public final class SessionToken2 { @Retention(RetentionPolicy.SOURCE) @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE}) public @interface TokenType { @@ -75,7 +75,7 @@ public final class SessionToken { */ // TODO(jaewan): UID is also needed. // TODO(jaewan): Unhide - public SessionToken(@TokenType int type, @NonNull String packageName, @NonNull String id, + public SessionToken2(@TokenType int type, @NonNull String packageName, @NonNull String id, @Nullable String serviceName, @Nullable IMediaSession2 sessionBinder) { // TODO(jaewan): Add sanity check. mType = type; @@ -102,7 +102,7 @@ public final class SessionToken { return false; if (getClass() != obj.getClass()) return false; - SessionToken other = (SessionToken) obj; + SessionToken2 other = (SessionToken2) obj; if (!mPackageName.equals(other.getPackageName()) || !mServiceName.equals(other.getServiceName()) || !mId.equals(other.getId()) @@ -168,7 +168,7 @@ public final class SessionToken { * @param bundle * @return */ - public static SessionToken fromBundle(@NonNull Bundle bundle) { + public static SessionToken2 fromBundle(@NonNull Bundle bundle) { if (bundle == null) { return null; } @@ -202,7 +202,7 @@ public final class SessionToken { // TODO(jaewan): Revisit here when we add connection callback to the session for individual // controller's permission check. With it, sessionBinder should be available // if and only if for session, not session service. - return new SessionToken(type, packageName, id, serviceName, + return new SessionToken2(type, packageName, id, serviceName, sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null); } diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 6a9f04a77aa1..81b4603ebf93 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -28,8 +28,7 @@ import android.media.IMediaSession2; import android.media.IRemoteVolumeController; import android.media.MediaSession2; import android.media.MediaSessionService2; -import android.media.SessionToken; -import android.media.session.ISessionManager; +import android.media.SessionToken2; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -342,11 +341,11 @@ public final class MediaSessionManager { * @hide */ // TODO(jaewan): System API - public SessionToken createSessionToken(@NonNull String callingPackage, @NonNull String id, + public SessionToken2 createSessionToken(@NonNull String callingPackage, @NonNull String id, @NonNull IMediaSession2 binder) { try { Bundle bundle = mService.createSessionToken(callingPackage, id, binder); - return SessionToken.fromBundle(bundle); + return SessionToken2.fromBundle(bundle); } catch (RemoteException e) { Log.wtf(TAG, "Cannot communicate with the service.", e); } @@ -354,7 +353,7 @@ public final class MediaSessionManager { } /** - * Get {@link List} of {@link SessionToken} whose sessions are active now. This list represents + * Get {@link List} of {@link SessionToken2} whose sessions are active now. This list represents * active sessions regardless of whether they're {@link MediaSession2} or * {@link MediaSessionService2}. * @@ -364,7 +363,7 @@ public final class MediaSessionManager { // TODO(jaewan): Unhide // TODO(jaewan): Protect this with permission. // TODO(jaewna): Add listener for change in lists. - public List<SessionToken> getActiveSessionTokens() { + public List<SessionToken2> getActiveSessionTokens() { try { List<Bundle> bundles = mService.getSessionTokens( /* activeSessionOnly */ true, /* sessionServiceOnly */ false); @@ -376,7 +375,7 @@ public final class MediaSessionManager { } /** - * Get {@link List} of {@link SessionToken} for {@link MediaSessionService2} regardless of their + * Get {@link List} of {@link SessionToken2} for {@link MediaSessionService2} regardless of their * activeness. This list represents media apps that support background playback. * * @return list of Tokens @@ -384,7 +383,7 @@ public final class MediaSessionManager { */ // TODO(jaewan): Unhide // TODO(jaewna): Add listener for change in lists. - public List<SessionToken> getSessionServiceTokens() { + public List<SessionToken2> getSessionServiceTokens() { try { List<Bundle> bundles = mService.getSessionTokens( /* activeSessionOnly */ false, /* sessionServiceOnly */ true); @@ -396,7 +395,7 @@ public final class MediaSessionManager { } /** - * Get all {@link SessionToken}s. This is the combined list of {@link #getActiveSessionTokens()} + * Get all {@link SessionToken2}s. This is the combined list of {@link #getActiveSessionTokens()} * and {@link #getSessionServiceTokens}. * * @return list of Tokens @@ -407,7 +406,7 @@ public final class MediaSessionManager { // TODO(jaewan): Unhide // TODO(jaewan): Protect this with permission. // TODO(jaewna): Add listener for change in lists. - public List<SessionToken> getAllSessionTokens() { + public List<SessionToken2> getAllSessionTokens() { try { List<Bundle> bundles = mService.getSessionTokens( /* activeSessionOnly */ false, /* sessionServiceOnly */ false); @@ -418,11 +417,11 @@ public final class MediaSessionManager { } } - private static List<SessionToken> toTokenList(List<Bundle> bundles) { - List<SessionToken> tokens = new ArrayList<>(); + private static List<SessionToken2> toTokenList(List<Bundle> bundles) { + List<SessionToken2> tokens = new ArrayList<>(); if (bundles != null) { for (int i = 0; i < bundles.size(); i++) { - SessionToken token = SessionToken.fromBundle(bundles.get(i)); + SessionToken2 token = SessionToken2.fromBundle(bundles.get(i)); if (token != null) { tokens.add(token); } diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java index 8f5a64310b31..c5f6b963d658 100644 --- a/media/java/android/media/update/MediaController2Provider.java +++ b/media/java/android/media/update/MediaController2Provider.java @@ -23,7 +23,7 @@ import android.media.MediaSession2.Command; import android.media.MediaSession2.PlaylistParam; import android.media.PlaybackState2; import android.media.Rating2; -import android.media.SessionToken; +import android.media.SessionToken2; import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -35,7 +35,7 @@ import java.util.List; */ public interface MediaController2Provider extends TransportControlProvider { void close_impl(); - SessionToken getSessionToken_impl(); + SessionToken2 getSessionToken_impl(); boolean isConnected_impl(); PendingIntent getSessionActivity_impl(); diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java index 511686dd93f7..2a68ad1df6ac 100644 --- a/media/java/android/media/update/MediaSession2Provider.java +++ b/media/java/android/media/update/MediaSession2Provider.java @@ -24,12 +24,9 @@ import android.media.MediaSession2.Command; import android.media.MediaSession2.CommandButton; import android.media.MediaSession2.CommandGroup; import android.media.MediaSession2.ControllerInfo; -import android.media.MediaSession2.PlaylistParam; -import android.media.SessionToken; +import android.media.SessionToken2; import android.media.VolumeProvider; -import android.media.session.PlaybackState; import android.os.Bundle; -import android.os.Handler; import android.os.ResultReceiver; import java.util.List; @@ -42,7 +39,7 @@ public interface MediaSession2Provider extends TransportControlProvider { void setPlayer_impl(MediaPlayerBase player); void setPlayer_impl(MediaPlayerBase player, VolumeProvider volumeProvider); MediaPlayerBase getPlayer_impl(); - SessionToken getToken_impl(); + SessionToken2 getToken_impl(); List<ControllerInfo> getConnectedControllers_impl(); void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout); void setAudioAttributes_impl(AudioAttributes attributes); diff --git a/media/java/android/media/update/MediaSessionService2Provider.java b/media/java/android/media/update/MediaSessionService2Provider.java index 11749153e73d..a6b462b85f06 100644 --- a/media/java/android/media/update/MediaSessionService2Provider.java +++ b/media/java/android/media/update/MediaSessionService2Provider.java @@ -19,8 +19,7 @@ package android.media.update; import android.content.Intent; import android.media.MediaSession2; import android.media.MediaSessionService2.MediaNotification; -import android.media.session.PlaybackState; -import android.os.Handler; +import android.media.PlaybackState2; import android.os.IBinder; /** @@ -28,7 +27,7 @@ import android.os.IBinder; */ public interface MediaSessionService2Provider { MediaSession2 getSession_impl(); - MediaNotification onUpdateNotification_impl(PlaybackState state); + MediaNotification onUpdateNotification_impl(PlaybackState2 state); // Service void onCreate_impl(); diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 64968d66ff8a..7c222c3cbd20 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -31,7 +31,7 @@ import android.media.MediaPlayerBase; import android.media.MediaSession2; import android.media.MediaSession2.SessionCallback; import android.media.MediaSessionService2; -import android.media.SessionToken; +import android.media.SessionToken2; import android.media.VolumeProvider; import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider; import android.media.update.MediaSession2Provider.ControllerInfoProvider; @@ -58,17 +58,17 @@ public interface StaticProvider { @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes); MediaSession2Provider createMediaSession2(MediaSession2 mediaSession2, Context context, - MediaPlayerBase player, String id, SessionCallback callback, + MediaPlayerBase player, String id, Executor callbackExecutor, SessionCallback callback, VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity); ControllerInfoProvider createMediaSession2ControllerInfoProvider( MediaSession2.ControllerInfo instance, Context context, int uid, int pid, String packageName, IMediaSession2Callback callback); MediaController2Provider createMediaController2( - MediaController2 instance, Context context, SessionToken token, + MediaController2 instance, Context context, SessionToken2 token, ControllerCallback callback, Executor executor); MediaBrowser2Provider createMediaBrowser2( - MediaBrowser2 instance, Context context, SessionToken token, + MediaBrowser2 instance, Context context, SessionToken2 token, BrowserCallback callback, Executor executor); MediaSessionService2Provider createMediaSessionService2( MediaSessionService2 instance); @@ -76,6 +76,6 @@ public interface StaticProvider { MediaLibraryService2 instance); MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession( MediaLibrarySession instance, Context context, MediaPlayerBase player, String id, - MediaLibrarySessionCallback callback, VolumeProvider volumeProvider, int ratingType, - PendingIntent sessionActivity); + Executor callbackExecutor, MediaLibrarySessionCallback callback, + VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity); } diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 597336bc5fae..4b4a255644c3 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -84,6 +84,93 @@ cc_library_shared { ], } +cc_library_shared { + name: "libmedia2_jni", + + srcs: [ + "android_media_Media2HTTPConnection.cpp", + "android_media_Media2HTTPService.cpp", + "android_media_MediaCrypto.cpp", + "android_media_Media2DataSource.cpp", + "android_media_MediaDrm.cpp", + "android_media_MediaMetricsJNI.cpp", + "android_media_MediaPlayer2.cpp", + "android_media_SyncParams.cpp", + ], + + shared_libs: [ + "android.hardware.cas@1.0", // for CasManager. VNDK??? + "android.hardware.cas.native@1.0", // CasManager. VNDK??? + "libandroid", // NDK + "libandroid_runtime", // ??? + "libaudioclient", // for use of AudioTrack, AudioSystem. to be removed + "liblog", // NDK + "libdrmframework", // for FileSource, MediaHTTP + "libgui", // for VideoFrameScheduler + "libhidlbase", // VNDK??? + "libmediandk", // NDK + "libpowermanager", // for JWakeLock. to be removed + ], + + header_libs: ["libhardware_headers"], + + static_libs: [ + "libbacktrace", + "libbase", + "libbinder", + "libc_malloc_debug_backtrace", + "libcrypto", + "libcutils", + "libdexfile", + "liblzma", + "libmedia", + "libmedia_helper", + "libmedia_player2", + "libmedia_player2_util", + "libmediadrm", + "libmediaextractor", + "libmediametrics", + "libmediautils", + "libnativehelper", + "libnetd_client", + "libstagefright_esds", + "libstagefright_foundation", + "libstagefright_httplive", + "libstagefright_id3", + "libstagefright_mpeg2support", + "libstagefright_nuplayer2", + "libstagefright_player2", + "libstagefright_rtsp", + "libstagefright_timedtext", + "libunwindstack", + "libutils", + "libutilscallstack", + "libvndksupport", + "libz", + "libziparchive", + ], + + group_static_libs: true, + + include_dirs: [ + "frameworks/base/core/jni", + "frameworks/native/include/media/openmax", + "system/media/camera/include", + ], + + export_include_dirs: ["."], + + cflags: [ + "-Wall", + "-Werror", + "-Wno-error=deprecated-declarations", + "-Wunused", + "-Wunreachable-code", + ], + + ldflags: ["-Wl,--exclude-libs=ALL"], +} + subdirs = [ "audioeffect", "soundpool", diff --git a/media/jni/android_media_Media2DataSource.cpp b/media/jni/android_media_Media2DataSource.cpp new file mode 100644 index 000000000000..bc3f6bd80cd8 --- /dev/null +++ b/media/jni/android_media_Media2DataSource.cpp @@ -0,0 +1,159 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "JMedia2DataSource-JNI" +#include <utils/Log.h> + +#include "android_media_Media2DataSource.h" + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" +#include "jni.h" +#include <nativehelper/JNIHelp.h> + +#include <drm/drm_framework_common.h> +#include <media/stagefright/foundation/ADebug.h> +#include <nativehelper/ScopedLocalRef.h> + +namespace android { + +static const size_t kBufferSize = 64 * 1024; + +JMedia2DataSource::JMedia2DataSource(JNIEnv* env, jobject source) + : mJavaObjStatus(OK), + mSizeIsCached(false), + mCachedSize(0) { + mMedia2DataSourceObj = env->NewGlobalRef(source); + CHECK(mMedia2DataSourceObj != NULL); + + ScopedLocalRef<jclass> media2DataSourceClass(env, env->GetObjectClass(mMedia2DataSourceObj)); + CHECK(media2DataSourceClass.get() != NULL); + + mReadAtMethod = env->GetMethodID(media2DataSourceClass.get(), "readAt", "(J[BII)I"); + CHECK(mReadAtMethod != NULL); + mGetSizeMethod = env->GetMethodID(media2DataSourceClass.get(), "getSize", "()J"); + CHECK(mGetSizeMethod != NULL); + mCloseMethod = env->GetMethodID(media2DataSourceClass.get(), "close", "()V"); + CHECK(mCloseMethod != NULL); + + ScopedLocalRef<jbyteArray> tmp(env, env->NewByteArray(kBufferSize)); + mByteArrayObj = (jbyteArray)env->NewGlobalRef(tmp.get()); + CHECK(mByteArrayObj != NULL); +} + +JMedia2DataSource::~JMedia2DataSource() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mMedia2DataSourceObj); + env->DeleteGlobalRef(mByteArrayObj); +} + +status_t JMedia2DataSource::initCheck() const { + return OK; +} + +ssize_t JMedia2DataSource::readAt(off64_t offset, void *data, size_t size) { + Mutex::Autolock lock(mLock); + + if (mJavaObjStatus != OK) { + return -1; + } + if (size > kBufferSize) { + size = kBufferSize; + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jint numread = env->CallIntMethod(mMedia2DataSourceObj, mReadAtMethod, + (jlong)offset, mByteArrayObj, (jint)0, (jint)size); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred in readAt()"); + LOGW_EX(env); + env->ExceptionClear(); + mJavaObjStatus = UNKNOWN_ERROR; + return -1; + } + if (numread < 0) { + if (numread != -1) { + ALOGW("An error occurred in readAt()"); + mJavaObjStatus = UNKNOWN_ERROR; + return -1; + } else { + // numread == -1 indicates EOF + return 0; + } + } + if ((size_t)numread > size) { + ALOGE("readAt read too many bytes."); + mJavaObjStatus = UNKNOWN_ERROR; + return -1; + } + + ALOGV("readAt %lld / %zu => %d.", (long long)offset, size, numread); + env->GetByteArrayRegion(mByteArrayObj, 0, numread, (jbyte*)data); + return numread; +} + +status_t JMedia2DataSource::getSize(off64_t* size) { + Mutex::Autolock lock(mLock); + + if (mJavaObjStatus != OK) { + return UNKNOWN_ERROR; + } + if (mSizeIsCached) { + *size = mCachedSize; + return OK; + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + *size = env->CallLongMethod(mMedia2DataSourceObj, mGetSizeMethod); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred in getSize()"); + LOGW_EX(env); + env->ExceptionClear(); + // After returning an error, size shouldn't be used by callers. + *size = UNKNOWN_ERROR; + mJavaObjStatus = UNKNOWN_ERROR; + return UNKNOWN_ERROR; + } + + // The minimum size should be -1, which indicates unknown size. + if (*size < 0) { + *size = -1; + } + + mCachedSize = *size; + mSizeIsCached = true; + return OK; +} + +void JMedia2DataSource::close() { + Mutex::Autolock lock(mLock); + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mMedia2DataSourceObj, mCloseMethod); + // The closed state is effectively the same as an error state. + mJavaObjStatus = UNKNOWN_ERROR; +} + +String8 JMedia2DataSource::toString() { + return String8::format("JMedia2DataSource(pid %d, uid %d)", getpid(), getuid()); +} + +String8 JMedia2DataSource::getMIMEType() const { + return String8("application/octet-stream"); +} + +} // namespace android diff --git a/media/jni/android_media_Media2DataSource.h b/media/jni/android_media_Media2DataSource.h new file mode 100644 index 000000000000..dc085f3f90d1 --- /dev/null +++ b/media/jni/android_media_Media2DataSource.h @@ -0,0 +1,70 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_MEDIA_MEDIA2DATASOURCE_H_ +#define _ANDROID_MEDIA_MEDIA2DATASOURCE_H_ + +#include "jni.h" + +#include <media/DataSource.h> +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/Mutex.h> + +namespace android { + +// The native counterpart to a Java android.media.Media2DataSource. It inherits from +// DataSource. +// +// If the java DataSource returns an error or throws an exception it +// will be considered to be in a broken state, and the only further call this +// will make is to close(). +class JMedia2DataSource : public DataSource { +public: + JMedia2DataSource(JNIEnv *env, jobject source); + virtual ~JMedia2DataSource(); + + virtual status_t initCheck() const override; + virtual ssize_t readAt(off64_t offset, void *data, size_t size) override; + virtual status_t getSize(off64_t *size) override; + + virtual String8 toString() override; + virtual String8 getMIMEType() const override; + virtual void close() override; +private: + // Protect all member variables with mLock because this object will be + // accessed on different threads. + Mutex mLock; + + // The status of the java DataSource. Set to OK unless an error occurred or + // close() was called. + status_t mJavaObjStatus; + // Only call the java getSize() once so the app can't change the size on us. + bool mSizeIsCached; + off64_t mCachedSize; + + jobject mMedia2DataSourceObj; + jmethodID mReadAtMethod; + jmethodID mGetSizeMethod; + jmethodID mCloseMethod; + jbyteArray mByteArrayObj; + + DISALLOW_EVIL_CONSTRUCTORS(JMedia2DataSource); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_MEDIA2DATASOURCE_H_ diff --git a/media/jni/android_media_Media2HTTPConnection.cpp b/media/jni/android_media_Media2HTTPConnection.cpp new file mode 100644 index 000000000000..60176e3d238e --- /dev/null +++ b/media/jni/android_media_Media2HTTPConnection.cpp @@ -0,0 +1,182 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "Media2HTTPConnection-JNI" +#include <utils/Log.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <nativehelper/ScopedLocalRef.h> + +#include "android_media_Media2HTTPConnection.h" +#include "android_util_Binder.h" + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" +#include "jni.h" +#include <nativehelper/JNIHelp.h> + +namespace android { + +static const size_t kBufferSize = 32768; + +JMedia2HTTPConnection::JMedia2HTTPConnection(JNIEnv *env, jobject thiz) { + mMedia2HTTPConnectionObj = env->NewGlobalRef(thiz); + CHECK(mMedia2HTTPConnectionObj != NULL); + + ScopedLocalRef<jclass> media2HTTPConnectionClass( + env, env->GetObjectClass(mMedia2HTTPConnectionObj)); + CHECK(media2HTTPConnectionClass.get() != NULL); + + mConnectMethod = env->GetMethodID( + media2HTTPConnectionClass.get(), + "connect", + "(Ljava/lang/String;Ljava/lang/String;)Z"); + CHECK(mConnectMethod != NULL); + + mDisconnectMethod = env->GetMethodID( + media2HTTPConnectionClass.get(), + "disconnect", + "()V"); + CHECK(mDisconnectMethod != NULL); + + mReadAtMethod = env->GetMethodID( + media2HTTPConnectionClass.get(), + "readAt", + "(J[BI)I"); + CHECK(mReadAtMethod != NULL); + + mGetSizeMethod = env->GetMethodID( + media2HTTPConnectionClass.get(), + "getSize", + "()J"); + CHECK(mGetSizeMethod != NULL); + + mGetMIMETypeMethod = env->GetMethodID( + media2HTTPConnectionClass.get(), + "getMIMEType", + "()Ljava/lang/String;"); + CHECK(mGetMIMETypeMethod != NULL); + + mGetUriMethod = env->GetMethodID( + media2HTTPConnectionClass.get(), + "getUri", + "()Ljava/lang/String;"); + CHECK(mGetUriMethod != NULL); + + ScopedLocalRef<jbyteArray> tmp( + env, env->NewByteArray(kBufferSize)); + mByteArrayObj = (jbyteArray)env->NewGlobalRef(tmp.get()); + CHECK(mByteArrayObj != NULL); +} + +JMedia2HTTPConnection::~JMedia2HTTPConnection() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mMedia2HTTPConnectionObj); + env->DeleteGlobalRef(mByteArrayObj); +} + +bool JMedia2HTTPConnection::connect( + const char *uri, const KeyedVector<String8, String8> *headers) { + String8 tmp(""); + if (headers != NULL) { + for (size_t i = 0; i < headers->size(); ++i) { + tmp.append(headers->keyAt(i)); + tmp.append(String8(": ")); + tmp.append(headers->valueAt(i)); + tmp.append(String8("\r\n")); + } + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jstring juri = env->NewStringUTF(uri); + jstring jheaders = env->NewStringUTF(tmp.string()); + + jboolean ret = + env->CallBooleanMethod(mMedia2HTTPConnectionObj, mConnectMethod, juri, jheaders); + + env->DeleteLocalRef(juri); + env->DeleteLocalRef(jheaders); + + return (bool)ret; +} + +void JMedia2HTTPConnection::disconnect() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mMedia2HTTPConnectionObj, mDisconnectMethod); +} + +ssize_t JMedia2HTTPConnection::readAt(off64_t offset, void *data, size_t size) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + + if (size > kBufferSize) { + size = kBufferSize; + } + + jint n = env->CallIntMethod( + mMedia2HTTPConnectionObj, mReadAtMethod, (jlong)offset, mByteArrayObj, (jint)size); + + if (n > 0) { + env->GetByteArrayRegion( + mByteArrayObj, + 0, + n, + (jbyte *)data); + } + + return n; +} + +off64_t JMedia2HTTPConnection::getSize() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + return (off64_t)(env->CallLongMethod(mMedia2HTTPConnectionObj, mGetSizeMethod)); +} + +status_t JMedia2HTTPConnection::getMIMEType(String8 *mimeType) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jstring jmime = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetMIMETypeMethod); + jboolean flag = env->ExceptionCheck(); + if (flag) { + env->ExceptionClear(); + return UNKNOWN_ERROR; + } + + const char *str = env->GetStringUTFChars(jmime, 0); + if (str != NULL) { + *mimeType = String8(str); + } else { + *mimeType = "application/octet-stream"; + } + env->ReleaseStringUTFChars(jmime, str); + return OK; +} + +status_t JMedia2HTTPConnection::getUri(String8 *uri) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jstring juri = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetUriMethod); + jboolean flag = env->ExceptionCheck(); + if (flag) { + env->ExceptionClear(); + return UNKNOWN_ERROR; + } + + const char *str = env->GetStringUTFChars(juri, 0); + *uri = String8(str); + env->ReleaseStringUTFChars(juri, str); + return OK; +} + +} // namespace android diff --git a/media/jni/android_media_Media2HTTPConnection.h b/media/jni/android_media_Media2HTTPConnection.h new file mode 100644 index 000000000000..14bc677f931b --- /dev/null +++ b/media/jni/android_media_Media2HTTPConnection.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_MEDIA_MEDIA2HTTPCONNECTION_H_ +#define _ANDROID_MEDIA_MEDIA2HTTPCONNECTION_H_ + +#include "jni.h" + +#include <media/MediaHTTPConnection.h> +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +struct JMedia2HTTPConnection : public MediaHTTPConnection { + JMedia2HTTPConnection(JNIEnv *env, jobject thiz); + + virtual bool connect( + const char *uri, const KeyedVector<String8, String8> *headers) override; + + virtual void disconnect() override; + virtual ssize_t readAt(off64_t offset, void *data, size_t size) override; + virtual off64_t getSize() override; + virtual status_t getMIMEType(String8 *mimeType) override; + virtual status_t getUri(String8 *uri) override; + +protected: + virtual ~JMedia2HTTPConnection(); + +private: + jobject mMedia2HTTPConnectionObj; + jmethodID mConnectMethod; + jmethodID mDisconnectMethod; + jmethodID mReadAtMethod; + jmethodID mGetSizeMethod; + jmethodID mGetMIMETypeMethod; + jmethodID mGetUriMethod; + + jbyteArray mByteArrayObj; + + DISALLOW_EVIL_CONSTRUCTORS(JMedia2HTTPConnection); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_MEDIA2HTTPCONNECTION_H_ diff --git a/media/jni/android_media_Media2HTTPService.cpp b/media/jni/android_media_Media2HTTPService.cpp new file mode 100644 index 000000000000..382f099b7932 --- /dev/null +++ b/media/jni/android_media_Media2HTTPService.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "Media2HTTPService-JNI" +#include <utils/Log.h> + +#include "android_media_Media2HTTPConnection.h" +#include "android_media_Media2HTTPService.h" + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" +#include "jni.h" +#include <nativehelper/JNIHelp.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <nativehelper/ScopedLocalRef.h> + +namespace android { + +JMedia2HTTPService::JMedia2HTTPService(JNIEnv *env, jobject thiz) { + mMedia2HTTPServiceObj = env->NewGlobalRef(thiz); + CHECK(mMedia2HTTPServiceObj != NULL); + + ScopedLocalRef<jclass> media2HTTPServiceClass(env, env->GetObjectClass(mMedia2HTTPServiceObj)); + CHECK(media2HTTPServiceClass.get() != NULL); + + mMakeHTTPConnectionMethod = env->GetMethodID( + media2HTTPServiceClass.get(), + "makeHTTPConnection", + "()Landroid/media/Media2HTTPConnection;"); + CHECK(mMakeHTTPConnectionMethod != NULL); +} + +JMedia2HTTPService::~JMedia2HTTPService() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mMedia2HTTPServiceObj); +} + +sp<MediaHTTPConnection> JMedia2HTTPService::makeHTTPConnection() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jobject media2HTTPConnectionObj = + env->CallObjectMethod(mMedia2HTTPServiceObj, mMakeHTTPConnectionMethod); + + return new JMedia2HTTPConnection(env, media2HTTPConnectionObj); +} + +} // namespace android diff --git a/media/jni/android_media_Media2HTTPService.h b/media/jni/android_media_Media2HTTPService.h new file mode 100644 index 000000000000..30d03f5309b1 --- /dev/null +++ b/media/jni/android_media_Media2HTTPService.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_MEDIA_MEDIA2HTTPSERVICE_H_ +#define _ANDROID_MEDIA_MEDIA2HTTPSERVICE_H_ + +#include "jni.h" + +#include <media/MediaHTTPService.h> +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +struct JMedia2HTTPService : public MediaHTTPService { + JMedia2HTTPService(JNIEnv *env, jobject thiz); + + virtual sp<MediaHTTPConnection> makeHTTPConnection() override; + +protected: + virtual ~JMedia2HTTPService(); + +private: + jobject mMedia2HTTPServiceObj; + + jmethodID mMakeHTTPConnectionMethod; + + DISALLOW_EVIL_CONSTRUCTORS(JMedia2HTTPService); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_MEDIA2HTTPSERVICE_H_ diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp new file mode 100644 index 000000000000..3bf0b37f407b --- /dev/null +++ b/media/jni/android_media_MediaPlayer2.cpp @@ -0,0 +1,1514 @@ +/* +** +** Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaPlayer2-JNI" +#include "utils/Log.h" + +#include <media/mediaplayer2.h> +#include <media/AudioResamplerPublic.h> +#include <media/MediaHTTPService.h> +#include <media/MediaPlayer2Interface.h> +#include <media/MediaAnalyticsItem.h> +#include <media/NdkWrapper.h> +#include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <utils/threads.h> +#include "jni.h" +#include <nativehelper/JNIHelp.h> +#include "android/native_window_jni.h" +#include "android_runtime/Log.h" +#include "utils/Errors.h" // for status_t +#include "utils/KeyedVector.h" +#include "utils/String8.h" +#include "android_media_BufferingParams.h" +#include "android_media_Media2HTTPService.h" +#include "android_media_Media2DataSource.h" +#include "android_media_MediaMetricsJNI.h" +#include "android_media_PlaybackParams.h" +#include "android_media_SyncParams.h" +#include "android_media_VolumeShaper.h" + +#include "android_os_Parcel.h" +#include "android_util_Binder.h" +#include <binder/Parcel.h> + +// Modular DRM begin +#define FIND_CLASS(var, className) \ +var = env->FindClass(className); \ +LOG_FATAL_IF(! (var), "Unable to find class " className); + +#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ +var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \ +LOG_FATAL_IF(! (var), "Unable to find method " fieldName); + +struct StateExceptionFields { + jmethodID init; + jclass classId; +}; + +static StateExceptionFields gStateExceptionFields; +// Modular DRM end + +// ---------------------------------------------------------------------------- + +using namespace android; + +using media::VolumeShaper; + +// ---------------------------------------------------------------------------- + +struct fields_t { + jfieldID context; + jfieldID surface_texture; + + jmethodID post_event; + + jmethodID proxyConfigGetHost; + jmethodID proxyConfigGetPort; + jmethodID proxyConfigGetExclusionList; +}; +static fields_t fields; + +static BufferingParams::fields_t gBufferingParamsFields; +static PlaybackParams::fields_t gPlaybackParamsFields; +static SyncParams::fields_t gSyncParamsFields; +static VolumeShaperHelper::fields_t gVolumeShaperFields; + +static Mutex sLock; + +static bool ConvertKeyValueArraysToKeyedVector( + JNIEnv *env, jobjectArray keys, jobjectArray values, + KeyedVector<String8, String8>* keyedVector) { + + int nKeyValuePairs = 0; + bool failed = false; + if (keys != NULL && values != NULL) { + nKeyValuePairs = env->GetArrayLength(keys); + failed = (nKeyValuePairs != env->GetArrayLength(values)); + } + + if (!failed) { + failed = ((keys != NULL && values == NULL) || + (keys == NULL && values != NULL)); + } + + if (failed) { + ALOGE("keys and values arrays have different length"); + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + for (int i = 0; i < nKeyValuePairs; ++i) { + // No need to check on the ArrayIndexOutOfBoundsException, since + // it won't happen here. + jstring key = (jstring) env->GetObjectArrayElement(keys, i); + jstring value = (jstring) env->GetObjectArrayElement(values, i); + + const char* keyStr = env->GetStringUTFChars(key, NULL); + if (!keyStr) { // OutOfMemoryError + return false; + } + + const char* valueStr = env->GetStringUTFChars(value, NULL); + if (!valueStr) { // OutOfMemoryError + env->ReleaseStringUTFChars(key, keyStr); + return false; + } + + keyedVector->add(String8(keyStr), String8(valueStr)); + + env->ReleaseStringUTFChars(key, keyStr); + env->ReleaseStringUTFChars(value, valueStr); + env->DeleteLocalRef(key); + env->DeleteLocalRef(value); + } + return true; +} + +// ---------------------------------------------------------------------------- +// ref-counted object for callbacks +class JNIMediaPlayer2Listener: public MediaPlayer2Listener +{ +public: + JNIMediaPlayer2Listener(JNIEnv* env, jobject thiz, jobject weak_thiz); + ~JNIMediaPlayer2Listener(); + virtual void notify(int msg, int ext1, int ext2, const Parcel *obj = NULL); +private: + JNIMediaPlayer2Listener(); + jclass mClass; // Reference to MediaPlayer2 class + jobject mObject; // Weak ref to MediaPlayer2 Java object to call on +}; + +JNIMediaPlayer2Listener::JNIMediaPlayer2Listener(JNIEnv* env, jobject thiz, jobject weak_thiz) +{ + + // Hold onto the MediaPlayer2 class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + ALOGE("Can't find android/media/MediaPlayer2Impl"); + jniThrowException(env, "java/lang/Exception", NULL); + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the MediaPlayer2 object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); +} + +JNIMediaPlayer2Listener::~JNIMediaPlayer2Listener() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNIMediaPlayer2Listener::notify(int msg, int ext1, int ext2, const Parcel *obj) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (obj && obj->dataSize() > 0) { + jobject jParcel = createJavaParcelObject(env); + if (jParcel != NULL) { + Parcel* nativeParcel = parcelForJavaObject(env, jParcel); + nativeParcel->setData(obj->data(), obj->dataSize()); + env->CallStaticVoidMethod(mClass, fields.post_event, mObject, + msg, ext1, ext2, jParcel); + env->DeleteLocalRef(jParcel); + } + } else { + env->CallStaticVoidMethod(mClass, fields.post_event, mObject, + msg, ext1, ext2, NULL); + } + if (env->ExceptionCheck()) { + ALOGW("An exception occurred while notifying an event."); + LOGW_EX(env); + env->ExceptionClear(); + } +} + +// ---------------------------------------------------------------------------- + +static sp<MediaPlayer2> getMediaPlayer(JNIEnv* env, jobject thiz) +{ + Mutex::Autolock l(sLock); + MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.context); + return sp<MediaPlayer2>(p); +} + +static sp<MediaPlayer2> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer2>& player) +{ + Mutex::Autolock l(sLock); + sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.context); + if (player.get()) { + player->incStrong((void*)setMediaPlayer); + } + if (old != 0) { + old->decStrong((void*)setMediaPlayer); + } + env->SetLongField(thiz, fields.context, (jlong)player.get()); + return old; +} + +// If exception is NULL and opStatus is not OK, this method sends an error +// event to the client application; otherwise, if exception is not NULL and +// opStatus is not OK, this method throws the given exception to the client +// application. +static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message) +{ + if (exception == NULL) { // Don't throw exception. Instead, send an event. + if (opStatus != (status_t) OK) { + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp != 0) mp->notify(MEDIA2_ERROR, opStatus, 0); + } + } else { // Throw exception! + if ( opStatus == (status_t) INVALID_OPERATION ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + } else if ( opStatus == (status_t) BAD_VALUE ) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + } else if ( opStatus == (status_t) PERMISSION_DENIED ) { + jniThrowException(env, "java/lang/SecurityException", NULL); + } else if ( opStatus != (status_t) OK ) { + if (strlen(message) > 230) { + // if the message is too long, don't bother displaying the status code + jniThrowException( env, exception, message); + } else { + char msg[256]; + // append the status code to the message + sprintf(msg, "%s: status=0x%X", message, opStatus); + jniThrowException( env, exception, msg); + } + } + } +} + +static void +android_media_MediaPlayer2_setDataSourceAndHeaders( + JNIEnv *env, jobject thiz, jobject httpServiceObj, jstring path, + jobjectArray keys, jobjectArray values) { + + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (path == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + const char *tmp = env->GetStringUTFChars(path, NULL); + if (tmp == NULL) { // Out of memory + return; + } + ALOGV("setDataSource: path %s", tmp); + + String8 pathStr(tmp); + env->ReleaseStringUTFChars(path, tmp); + tmp = NULL; + + // We build a KeyedVector out of the key and val arrays + KeyedVector<String8, String8> headersVector; + if (!ConvertKeyValueArraysToKeyedVector( + env, keys, values, &headersVector)) { + return; + } + + sp<MediaHTTPService> httpService; + if (httpServiceObj != NULL) { + httpService = new JMedia2HTTPService(env, httpServiceObj); + } + + status_t opStatus = + mp->setDataSource( + httpService, + pathStr, + headersVector.size() > 0? &headersVector : NULL); + + process_media_player_call( + env, thiz, opStatus, "java/io/IOException", + "setDataSource failed." ); +} + +static void +android_media_MediaPlayer2_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (fileDescriptor == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + ALOGV("setDataSourceFD: fd %d", fd); + process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." ); +} + +static void +android_media_MediaPlayer2_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (dataSource == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + sp<DataSource> callbackDataSource = new JMedia2DataSource(env, dataSource); + process_media_player_call(env, thiz, mp->setDataSource(callbackDataSource), "java/lang/RuntimeException", "setDataSourceCallback failed." ); +} + +static sp<ANativeWindowWrapper> +getVideoSurfaceTexture(JNIEnv* env, jobject thiz) { + ANativeWindow * const p = (ANativeWindow*)env->GetLongField(thiz, fields.surface_texture); + return new ANativeWindowWrapper(p); +} + +static void +decVideoSurfaceRef(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + return; + } + + ANativeWindow * const old_anw = (ANativeWindow*)env->GetLongField(thiz, fields.surface_texture); + if (old_anw != NULL) { + ANativeWindow_release(old_anw); + env->SetLongField(thiz, fields.surface_texture, (jlong)NULL); + } +} + +static void +setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + if (mediaPlayerMustBeAlive) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + } + return; + } + + decVideoSurfaceRef(env, thiz); + + ANativeWindow* anw = NULL; + if (jsurface) { + anw = ANativeWindow_fromSurface(env, jsurface); + if (anw == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "The surface has been released"); + return; + } + } + + env->SetLongField(thiz, fields.surface_texture, (jlong)anw); + + // This will fail if the media player has not been initialized yet. This + // can be the case if setDisplay() on MediaPlayer2Impl.java has been called + // before setDataSource(). The redundant call to setVideoSurfaceTexture() + // in prepare/prepareAsync covers for this case. + mp->setVideoSurfaceTexture(new ANativeWindowWrapper(anw)); +} + +static void +android_media_MediaPlayer2_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface) +{ + setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */); +} + +static jobject +android_media_MediaPlayer2_getBufferingParams(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + BufferingParams bp; + BufferingSettings &settings = bp.settings; + process_media_player_call( + env, thiz, mp->getBufferingSettings(&settings), + "java/lang/IllegalStateException", "unexpected error"); + ALOGV("getBufferingSettings:{%s}", settings.toString().string()); + + return bp.asJobject(env, gBufferingParamsFields); +} + +static void +android_media_MediaPlayer2_setBufferingParams(JNIEnv *env, jobject thiz, jobject params) +{ + if (params == NULL) { + return; + } + + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + BufferingParams bp; + bp.fillFromJobject(env, gBufferingParamsFields, params); + ALOGV("setBufferingParams:{%s}", bp.settings.toString().string()); + + process_media_player_call( + env, thiz, mp->setBufferingSettings(bp.settings), + "java/lang/IllegalStateException", "unexpected error"); +} + +static void +android_media_MediaPlayer2_prepare(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + // Handle the case where the display surface was set before the mp was + // initialized. We try again to make it stick. + sp<ANativeWindowWrapper> st = getVideoSurfaceTexture(env, thiz); + mp->setVideoSurfaceTexture(st); + + process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." ); +} + +static void +android_media_MediaPlayer2_prepareAsync(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + // Handle the case where the display surface was set before the mp was + // initialized. We try again to make it stick. + sp<ANativeWindowWrapper> st = getVideoSurfaceTexture(env, thiz); + mp->setVideoSurfaceTexture(st); + + process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." ); +} + +static void +android_media_MediaPlayer2_start(JNIEnv *env, jobject thiz) +{ + ALOGV("start"); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->start(), NULL, NULL ); +} + +static void +android_media_MediaPlayer2_stop(JNIEnv *env, jobject thiz) +{ + ALOGV("stop"); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->stop(), NULL, NULL ); +} + +static void +android_media_MediaPlayer2_pause(JNIEnv *env, jobject thiz) +{ + ALOGV("pause"); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->pause(), NULL, NULL ); +} + +static jboolean +android_media_MediaPlayer2_isPlaying(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return JNI_FALSE; + } + const jboolean is_playing = mp->isPlaying(); + + ALOGV("isPlaying: %d", is_playing); + return is_playing; +} + +static void +android_media_MediaPlayer2_setPlaybackParams(JNIEnv *env, jobject thiz, jobject params) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + PlaybackParams pbp; + pbp.fillFromJobject(env, gPlaybackParamsFields, params); + ALOGV("setPlaybackParams: %d:%f %d:%f %d:%u %d:%u", + pbp.speedSet, pbp.audioRate.mSpeed, + pbp.pitchSet, pbp.audioRate.mPitch, + pbp.audioFallbackModeSet, pbp.audioRate.mFallbackMode, + pbp.audioStretchModeSet, pbp.audioRate.mStretchMode); + + AudioPlaybackRate rate; + status_t err = mp->getPlaybackSettings(&rate); + if (err == OK) { + bool updatedRate = false; + if (pbp.speedSet) { + rate.mSpeed = pbp.audioRate.mSpeed; + updatedRate = true; + } + if (pbp.pitchSet) { + rate.mPitch = pbp.audioRate.mPitch; + updatedRate = true; + } + if (pbp.audioFallbackModeSet) { + rate.mFallbackMode = pbp.audioRate.mFallbackMode; + updatedRate = true; + } + if (pbp.audioStretchModeSet) { + rate.mStretchMode = pbp.audioRate.mStretchMode; + updatedRate = true; + } + if (updatedRate) { + err = mp->setPlaybackSettings(rate); + } + } + process_media_player_call( + env, thiz, err, + "java/lang/IllegalStateException", "unexpected error"); +} + +static jobject +android_media_MediaPlayer2_getPlaybackParams(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + PlaybackParams pbp; + AudioPlaybackRate &audioRate = pbp.audioRate; + process_media_player_call( + env, thiz, mp->getPlaybackSettings(&audioRate), + "java/lang/IllegalStateException", "unexpected error"); + ALOGV("getPlaybackSettings: %f %f %d %d", + audioRate.mSpeed, audioRate.mPitch, audioRate.mFallbackMode, audioRate.mStretchMode); + + pbp.speedSet = true; + pbp.pitchSet = true; + pbp.audioFallbackModeSet = true; + pbp.audioStretchModeSet = true; + + return pbp.asJobject(env, gPlaybackParamsFields); +} + +static void +android_media_MediaPlayer2_setSyncParams(JNIEnv *env, jobject thiz, jobject params) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + SyncParams scp; + scp.fillFromJobject(env, gSyncParamsFields, params); + ALOGV("setSyncParams: %d:%d %d:%d %d:%f %d:%f", + scp.syncSourceSet, scp.sync.mSource, + scp.audioAdjustModeSet, scp.sync.mAudioAdjustMode, + scp.toleranceSet, scp.sync.mTolerance, + scp.frameRateSet, scp.frameRate); + + AVSyncSettings avsync; + float videoFrameRate; + status_t err = mp->getSyncSettings(&avsync, &videoFrameRate); + if (err == OK) { + bool updatedSync = scp.frameRateSet; + if (scp.syncSourceSet) { + avsync.mSource = scp.sync.mSource; + updatedSync = true; + } + if (scp.audioAdjustModeSet) { + avsync.mAudioAdjustMode = scp.sync.mAudioAdjustMode; + updatedSync = true; + } + if (scp.toleranceSet) { + avsync.mTolerance = scp.sync.mTolerance; + updatedSync = true; + } + if (updatedSync) { + err = mp->setSyncSettings(avsync, scp.frameRateSet ? scp.frameRate : -1.f); + } + } + process_media_player_call( + env, thiz, err, + "java/lang/IllegalStateException", "unexpected error"); +} + +static jobject +android_media_MediaPlayer2_getSyncParams(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + SyncParams scp; + scp.frameRate = -1.f; + process_media_player_call( + env, thiz, mp->getSyncSettings(&scp.sync, &scp.frameRate), + "java/lang/IllegalStateException", "unexpected error"); + + ALOGV("getSyncSettings: %d %d %f %f", + scp.sync.mSource, scp.sync.mAudioAdjustMode, scp.sync.mTolerance, scp.frameRate); + + // sanity check params + if (scp.sync.mSource >= AVSYNC_SOURCE_MAX + || scp.sync.mAudioAdjustMode >= AVSYNC_AUDIO_ADJUST_MODE_MAX + || scp.sync.mTolerance < 0.f + || scp.sync.mTolerance >= AVSYNC_TOLERANCE_MAX) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + scp.syncSourceSet = true; + scp.audioAdjustModeSet = true; + scp.toleranceSet = true; + scp.frameRateSet = scp.frameRate >= 0.f; + + return scp.asJobject(env, gSyncParamsFields); +} + +static void +android_media_MediaPlayer2_seekTo(JNIEnv *env, jobject thiz, jlong msec, jint mode) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + ALOGV("seekTo: %lld(msec), mode=%d", (long long)msec, mode); + process_media_player_call( env, thiz, mp->seekTo((int)msec, (MediaPlayer2SeekMode)mode), NULL, NULL ); +} + +static void +android_media_MediaPlayer2_notifyAt(JNIEnv *env, jobject thiz, jlong mediaTimeUs) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + ALOGV("notifyAt: %lld", (long long)mediaTimeUs); + process_media_player_call( env, thiz, mp->notifyAt((int64_t)mediaTimeUs), NULL, NULL ); +} + +static jint +android_media_MediaPlayer2_getVideoWidth(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + int w; + if (0 != mp->getVideoWidth(&w)) { + ALOGE("getVideoWidth failed"); + w = 0; + } + ALOGV("getVideoWidth: %d", w); + return (jint) w; +} + +static jint +android_media_MediaPlayer2_getVideoHeight(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + int h; + if (0 != mp->getVideoHeight(&h)) { + ALOGE("getVideoHeight failed"); + h = 0; + } + ALOGV("getVideoHeight: %d", h); + return (jint) h; +} + +static jobject +android_media_MediaPlayer2_native_getMetrics(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + Parcel p; + int key = FOURCC('m','t','r','X'); + status_t status = mp->getParameter(key, &p); + if (status != OK) { + ALOGD("getMetrics() failed: %d", status); + return (jobject) NULL; + } + + p.setDataPosition(0); + MediaAnalyticsItem *item = new MediaAnalyticsItem; + item->readFromParcel(p); + jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL); + + // housekeeping + delete item; + item = NULL; + + return mybundle; +} + +static jint +android_media_MediaPlayer2_getCurrentPosition(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + int msec; + process_media_player_call( env, thiz, mp->getCurrentPosition(&msec), NULL, NULL ); + ALOGV("getCurrentPosition: %d (msec)", msec); + return (jint) msec; +} + +static jint +android_media_MediaPlayer2_getDuration(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + int msec; + process_media_player_call( env, thiz, mp->getDuration(&msec), NULL, NULL ); + ALOGV("getDuration: %d (msec)", msec); + return (jint) msec; +} + +static void +android_media_MediaPlayer2_reset(JNIEnv *env, jobject thiz) +{ + ALOGV("reset"); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->reset(), NULL, NULL ); +} + +static jint +android_media_MediaPlayer2_getAudioStreamType(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + audio_stream_type_t streamtype; + process_media_player_call( env, thiz, mp->getAudioStreamType(&streamtype), NULL, NULL ); + ALOGV("getAudioStreamType: %d (streamtype)", streamtype); + return (jint) streamtype; +} + +static jboolean +android_media_MediaPlayer2_setParameter(JNIEnv *env, jobject thiz, jint key, jobject java_request) +{ + ALOGV("setParameter: key %d", key); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return false; + } + + Parcel *request = parcelForJavaObject(env, java_request); + status_t err = mp->setParameter(key, *request); + if (err == OK) { + return true; + } else { + return false; + } +} + +static void +android_media_MediaPlayer2_setLooping(JNIEnv *env, jobject thiz, jboolean looping) +{ + ALOGV("setLooping: %d", looping); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setLooping(looping), NULL, NULL ); +} + +static jboolean +android_media_MediaPlayer2_isLooping(JNIEnv *env, jobject thiz) +{ + ALOGV("isLooping"); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return JNI_FALSE; + } + return mp->isLooping() ? JNI_TRUE : JNI_FALSE; +} + +static void +android_media_MediaPlayer2_setVolume(JNIEnv *env, jobject thiz, jfloat leftVolume, jfloat rightVolume) +{ + ALOGV("setVolume: left %f right %f", (float) leftVolume, (float) rightVolume); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setVolume((float) leftVolume, (float) rightVolume), NULL, NULL ); +} + +// Sends the request and reply parcels to the media player via the +// binder interface. +static jint +android_media_MediaPlayer2_invoke(JNIEnv *env, jobject thiz, + jobject java_request, jobject java_reply) +{ + sp<MediaPlayer2> media_player = getMediaPlayer(env, thiz); + if (media_player == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return UNKNOWN_ERROR; + } + + Parcel *request = parcelForJavaObject(env, java_request); + Parcel *reply = parcelForJavaObject(env, java_reply); + + request->setDataPosition(0); + + // Don't use process_media_player_call which use the async loop to + // report errors, instead returns the status. + return (jint) media_player->invoke(*request, reply); +} + +// Sends the new filter to the client. +static jint +android_media_MediaPlayer2_setMetadataFilter(JNIEnv *env, jobject thiz, jobject request) +{ + sp<MediaPlayer2> media_player = getMediaPlayer(env, thiz); + if (media_player == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return UNKNOWN_ERROR; + } + + Parcel *filter = parcelForJavaObject(env, request); + + if (filter == NULL ) { + jniThrowException(env, "java/lang/RuntimeException", "Filter is null"); + return UNKNOWN_ERROR; + } + + return (jint) media_player->setMetadataFilter(*filter); +} + +static jboolean +android_media_MediaPlayer2_getMetadata(JNIEnv *env, jobject thiz, jboolean update_only, + jboolean apply_filter, jobject reply) +{ + sp<MediaPlayer2> media_player = getMediaPlayer(env, thiz); + if (media_player == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return JNI_FALSE; + } + + Parcel *metadata = parcelForJavaObject(env, reply); + + if (metadata == NULL ) { + jniThrowException(env, "java/lang/RuntimeException", "Reply parcel is null"); + return JNI_FALSE; + } + + metadata->freeData(); + // On return metadata is positioned at the beginning of the + // metadata. Note however that the parcel actually starts with the + // return code so you should not rewind the parcel using + // setDataPosition(0). + if (media_player->getMetadata(update_only, apply_filter, metadata) == OK) { + return JNI_TRUE; + } else { + return JNI_FALSE; + } +} + +// This function gets some field IDs, which in turn causes class initialization. +// It is called from a static block in MediaPlayer2, which won't run until the +// first time an instance of this class is used. +static void +android_media_MediaPlayer2_native_init(JNIEnv *env) +{ + jclass clazz; + + clazz = env->FindClass("android/media/MediaPlayer2Impl"); + if (clazz == NULL) { + return; + } + + fields.context = env->GetFieldID(clazz, "mNativeContext", "J"); + if (fields.context == NULL) { + return; + } + + fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", + "(Ljava/lang/Object;IIILjava/lang/Object;)V"); + if (fields.post_event == NULL) { + return; + } + + fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J"); + if (fields.surface_texture == NULL) { + return; + } + + env->DeleteLocalRef(clazz); + + clazz = env->FindClass("android/net/ProxyInfo"); + if (clazz == NULL) { + return; + } + + fields.proxyConfigGetHost = + env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;"); + + fields.proxyConfigGetPort = + env->GetMethodID(clazz, "getPort", "()I"); + + fields.proxyConfigGetExclusionList = + env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;"); + + env->DeleteLocalRef(clazz); + + gBufferingParamsFields.init(env); + + // Modular DRM + FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException"); + if (clazz) { + GET_METHOD_ID(gStateExceptionFields.init, clazz, "<init>", "(ILjava/lang/String;)V"); + gStateExceptionFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); + + env->DeleteLocalRef(clazz); + } else { + ALOGE("JNI android_media_MediaPlayer2_native_init couldn't " + "get clazz android/media/MediaDrm$MediaDrmStateException"); + } + + gPlaybackParamsFields.init(env); + gSyncParamsFields.init(env); + gVolumeShaperFields.init(env); +} + +static void +android_media_MediaPlayer2_native_setup(JNIEnv *env, jobject thiz, jobject weak_this) +{ + ALOGV("native_setup"); + sp<MediaPlayer2> mp = new MediaPlayer2(); + if (mp == NULL) { + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return; + } + + // create new listener and give it to MediaPlayer2 + sp<JNIMediaPlayer2Listener> listener = new JNIMediaPlayer2Listener(env, thiz, weak_this); + mp->setListener(listener); + + // Stow our new C++ MediaPlayer2 in an opaque field in the Java object. + setMediaPlayer(env, thiz, mp); +} + +static void +android_media_MediaPlayer2_release(JNIEnv *env, jobject thiz) +{ + ALOGV("release"); + decVideoSurfaceRef(env, thiz); + sp<MediaPlayer2> mp = setMediaPlayer(env, thiz, 0); + if (mp != NULL) { + // this prevents native callbacks after the object is released + mp->setListener(0); + mp->disconnect(); + } +} + +static void +android_media_MediaPlayer2_native_finalize(JNIEnv *env, jobject thiz) +{ + ALOGV("native_finalize"); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp != NULL) { + ALOGW("MediaPlayer2 finalized without being released"); + } + android_media_MediaPlayer2_release(env, thiz); +} + +static void android_media_MediaPlayer2_set_audio_session_id(JNIEnv *env, jobject thiz, + jint sessionId) { + ALOGV("set_session_id(): %d", sessionId); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setAudioSessionId((audio_session_t) sessionId), NULL, + NULL); +} + +static jint android_media_MediaPlayer2_get_audio_session_id(JNIEnv *env, jobject thiz) { + ALOGV("get_session_id()"); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + return (jint) mp->getAudioSessionId(); +} + +static void +android_media_MediaPlayer2_setAuxEffectSendLevel(JNIEnv *env, jobject thiz, jfloat level) +{ + ALOGV("setAuxEffectSendLevel: level %f", level); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setAuxEffectSendLevel(level), NULL, NULL ); +} + +static void android_media_MediaPlayer2_attachAuxEffect(JNIEnv *env, jobject thiz, jint effectId) { + ALOGV("attachAuxEffect(): %d", effectId); + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->attachAuxEffect(effectId), NULL, NULL ); +} + +static jint +android_media_MediaPlayer2_setRetransmitEndpoint(JNIEnv *env, jobject thiz, + jstring addrString, jint port) { + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return INVALID_OPERATION; + } + + const char *cAddrString = NULL; + + if (NULL != addrString) { + cAddrString = env->GetStringUTFChars(addrString, NULL); + if (cAddrString == NULL) { // Out of memory + return NO_MEMORY; + } + } + ALOGV("setRetransmitEndpoint: %s:%d", + cAddrString ? cAddrString : "(null)", port); + + status_t ret; + if (cAddrString && (port > 0xFFFF)) { + ret = BAD_VALUE; + } else { + ret = mp->setRetransmitEndpoint(cAddrString, + static_cast<uint16_t>(port)); + } + + if (NULL != addrString) { + env->ReleaseStringUTFChars(addrString, cAddrString); + } + + if (ret == INVALID_OPERATION ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + } + + return (jint) ret; +} + +static void +android_media_MediaPlayer2_setNextMediaPlayer(JNIEnv *env, jobject thiz, jobject java_player) +{ + ALOGV("setNextMediaPlayer"); + sp<MediaPlayer2> thisplayer = getMediaPlayer(env, thiz); + if (thisplayer == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "This player not initialized"); + return; + } + sp<MediaPlayer2> nextplayer = (java_player == NULL) ? NULL : getMediaPlayer(env, java_player); + if (nextplayer == NULL && java_player != NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "That player not initialized"); + return; + } + + if (nextplayer == thisplayer) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Next player can't be self"); + return; + } + // tie the two players together + process_media_player_call( + env, thiz, thisplayer->setNextMediaPlayer(nextplayer), + "java/lang/IllegalArgumentException", + "setNextMediaPlayer failed." ); + ; +} + +// Pass through the arguments to the MediaServer player implementation. +static jint android_media_MediaPlayer2_applyVolumeShaper(JNIEnv *env, jobject thiz, + jobject jconfig, jobject joperation) { + // NOTE: hard code here to prevent platform issues. Must match VolumeShaper.java + const int VOLUME_SHAPER_INVALID_OPERATION = -38; + + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == nullptr) { + return (jint)VOLUME_SHAPER_INVALID_OPERATION; + } + + sp<VolumeShaper::Configuration> configuration; + sp<VolumeShaper::Operation> operation; + if (jconfig != nullptr) { + configuration = VolumeShaperHelper::convertJobjectToConfiguration( + env, gVolumeShaperFields, jconfig); + ALOGV("applyVolumeShaper configuration: %s", configuration->toString().c_str()); + } + if (joperation != nullptr) { + operation = VolumeShaperHelper::convertJobjectToOperation( + env, gVolumeShaperFields, joperation); + ALOGV("applyVolumeShaper operation: %s", operation->toString().c_str()); + } + VolumeShaper::Status status = mp->applyVolumeShaper(configuration, operation); + if (status == INVALID_OPERATION) { + status = VOLUME_SHAPER_INVALID_OPERATION; + } + return (jint)status; // if status < 0 an error, else a VolumeShaper id +} + +// Pass through the arguments to the MediaServer player implementation. +static jobject android_media_MediaPlayer2_getVolumeShaperState(JNIEnv *env, jobject thiz, + jint id) { + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == nullptr) { + return (jobject)nullptr; + } + + sp<VolumeShaper::State> state = mp->getVolumeShaperState((int)id); + if (state.get() == nullptr) { + return (jobject)nullptr; + } + return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state); +} + +///////////////////////////////////////////////////////////////////////////////////// +// Modular DRM begin + +// TODO: investigate if these can be shared with their MediaDrm counterparts +static void throwDrmStateException(JNIEnv *env, const char *msg, status_t err) +{ + ALOGE("Illegal DRM state exception: %s (%d)", msg, err); + + jobject exception = env->NewObject(gStateExceptionFields.classId, + gStateExceptionFields.init, static_cast<int>(err), + env->NewStringUTF(msg)); + env->Throw(static_cast<jthrowable>(exception)); +} + +// TODO: investigate if these can be shared with their MediaDrm counterparts +static bool throwDrmExceptionAsNecessary(JNIEnv *env, status_t err, const char *msg = NULL) +{ + const char *drmMessage = "Unknown DRM Msg"; + + switch (err) { + case ERROR_DRM_UNKNOWN: + drmMessage = "General DRM error"; + break; + case ERROR_DRM_NO_LICENSE: + drmMessage = "No license"; + break; + case ERROR_DRM_LICENSE_EXPIRED: + drmMessage = "License expired"; + break; + case ERROR_DRM_SESSION_NOT_OPENED: + drmMessage = "Session not opened"; + break; + case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED: + drmMessage = "Not initialized"; + break; + case ERROR_DRM_DECRYPT: + drmMessage = "Decrypt error"; + break; + case ERROR_DRM_CANNOT_HANDLE: + drmMessage = "Unsupported scheme or data format"; + break; + case ERROR_DRM_TAMPER_DETECTED: + drmMessage = "Invalid state"; + break; + default: + break; + } + + String8 vendorMessage; + if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) { + vendorMessage = String8::format("DRM vendor-defined error: %d", err); + drmMessage = vendorMessage.string(); + } + + if (err == BAD_VALUE) { + jniThrowException(env, "java/lang/IllegalArgumentException", msg); + return true; + } else if (err == ERROR_DRM_NOT_PROVISIONED) { + jniThrowException(env, "android/media/NotProvisionedException", msg); + return true; + } else if (err == ERROR_DRM_RESOURCE_BUSY) { + jniThrowException(env, "android/media/ResourceBusyException", msg); + return true; + } else if (err == ERROR_DRM_DEVICE_REVOKED) { + jniThrowException(env, "android/media/DeniedByServerException", msg); + return true; + } else if (err == DEAD_OBJECT) { + jniThrowException(env, "android/media/MediaDrmResetException", + "mediaserver died"); + return true; + } else if (err != OK) { + String8 errbuf; + if (drmMessage != NULL) { + if (msg == NULL) { + msg = drmMessage; + } else { + errbuf = String8::format("%s: %s", msg, drmMessage); + msg = errbuf.string(); + } + } + throwDrmStateException(env, msg, err); + return true; + } + return false; +} + +static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray) +{ + Vector<uint8_t> vector; + size_t length = env->GetArrayLength(byteArray); + vector.insertAt((size_t)0, length); + env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray()); + return vector; +} + +static void android_media_MediaPlayer2_prepareDrm(JNIEnv *env, jobject thiz, + jbyteArray uuidObj, jbyteArray drmSessionIdObj) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (uuidObj == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj); + + if (uuid.size() != 16) { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + "invalid UUID size, expected 16 bytes"); + return; + } + + Vector<uint8_t> drmSessionId = JByteArrayToVector(env, drmSessionIdObj); + + if (drmSessionId.size() == 0) { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + "empty drmSessionId"); + return; + } + + status_t err = mp->prepareDrm(uuid.array(), drmSessionId); + if (err != OK) { + if (err == INVALID_OPERATION) { + jniThrowException( + env, + "java/lang/IllegalStateException", + "The player must be in prepared state."); + } else if (err == ERROR_DRM_CANNOT_HANDLE) { + jniThrowException( + env, + "android/media/UnsupportedSchemeException", + "Failed to instantiate drm object."); + } else { + throwDrmExceptionAsNecessary(env, err, "Failed to prepare DRM scheme"); + } + } +} + +static void android_media_MediaPlayer2_releaseDrm(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + status_t err = mp->releaseDrm(); + if (err != OK) { + if (err == INVALID_OPERATION) { + jniThrowException( + env, + "java/lang/IllegalStateException", + "Can not release DRM in an active player state."); + } + } +} +// Modular DRM end +// ---------------------------------------------------------------------------- + +///////////////////////////////////////////////////////////////////////////////////// +// AudioRouting begin +static jboolean android_media_MediaPlayer2_setOutputDevice(JNIEnv *env, jobject thiz, jint device_id) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + return false; + } + return mp->setOutputDevice(device_id) == NO_ERROR; +} + +static jint android_media_MediaPlayer2_getRoutedDeviceId(JNIEnv *env, jobject thiz) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + return AUDIO_PORT_HANDLE_NONE; + } + return mp->getRoutedDeviceId(); +} + +static void android_media_MediaPlayer2_enableDeviceCallback( + JNIEnv* env, jobject thiz, jboolean enabled) +{ + sp<MediaPlayer2> mp = getMediaPlayer(env, thiz); + if (mp == NULL) { + return; + } + + status_t status = mp->enableAudioDeviceCallback(enabled); + if (status != NO_ERROR) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + ALOGE("enable device callback failed: %d", status); + } +} + +// AudioRouting end +// ---------------------------------------------------------------------------- + +static const JNINativeMethod gMethods[] = { + { + "nativeSetDataSource", + "(Landroid/media/Media2HTTPService;Ljava/lang/String;[Ljava/lang/String;" + "[Ljava/lang/String;)V", + (void *)android_media_MediaPlayer2_setDataSourceAndHeaders + }, + + {"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer2_setDataSourceFD}, + {"_setDataSource", "(Landroid/media/Media2DataSource;)V",(void *)android_media_MediaPlayer2_setDataSourceCallback }, + {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer2_setVideoSurface}, + {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer2_getBufferingParams}, + {"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams}, + {"_prepare", "()V", (void *)android_media_MediaPlayer2_prepare}, + {"prepareAsync", "()V", (void *)android_media_MediaPlayer2_prepareAsync}, + {"_start", "()V", (void *)android_media_MediaPlayer2_start}, + {"_stop", "()V", (void *)android_media_MediaPlayer2_stop}, + {"getVideoWidth", "()I", (void *)android_media_MediaPlayer2_getVideoWidth}, + {"getVideoHeight", "()I", (void *)android_media_MediaPlayer2_getVideoHeight}, + {"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaPlayer2_native_getMetrics}, + {"setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer2_setPlaybackParams}, + {"getPlaybackParams", "()Landroid/media/PlaybackParams;", (void *)android_media_MediaPlayer2_getPlaybackParams}, + {"setSyncParams", "(Landroid/media/SyncParams;)V", (void *)android_media_MediaPlayer2_setSyncParams}, + {"getSyncParams", "()Landroid/media/SyncParams;", (void *)android_media_MediaPlayer2_getSyncParams}, + {"_seekTo", "(JI)V", (void *)android_media_MediaPlayer2_seekTo}, + {"_notifyAt", "(J)V", (void *)android_media_MediaPlayer2_notifyAt}, + {"_pause", "()V", (void *)android_media_MediaPlayer2_pause}, + {"isPlaying", "()Z", (void *)android_media_MediaPlayer2_isPlaying}, + {"getCurrentPosition", "()I", (void *)android_media_MediaPlayer2_getCurrentPosition}, + {"getDuration", "()I", (void *)android_media_MediaPlayer2_getDuration}, + {"_release", "()V", (void *)android_media_MediaPlayer2_release}, + {"_reset", "()V", (void *)android_media_MediaPlayer2_reset}, + {"_getAudioStreamType", "()I", (void *)android_media_MediaPlayer2_getAudioStreamType}, + {"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer2_setParameter}, + {"setLooping", "(Z)V", (void *)android_media_MediaPlayer2_setLooping}, + {"isLooping", "()Z", (void *)android_media_MediaPlayer2_isLooping}, + {"_setVolume", "(FF)V", (void *)android_media_MediaPlayer2_setVolume}, + {"native_invoke", "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer2_invoke}, + {"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer2_setMetadataFilter}, + {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer2_getMetadata}, + {"native_init", "()V", (void *)android_media_MediaPlayer2_native_init}, + {"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer2_native_setup}, + {"native_finalize", "()V", (void *)android_media_MediaPlayer2_native_finalize}, + {"getAudioSessionId", "()I", (void *)android_media_MediaPlayer2_get_audio_session_id}, + {"setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer2_set_audio_session_id}, + {"_setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer2_setAuxEffectSendLevel}, + {"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer2_attachAuxEffect}, + {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer2_setRetransmitEndpoint}, + {"setNextMediaPlayer", "(Landroid/media/MediaPlayer2;)V", (void *)android_media_MediaPlayer2_setNextMediaPlayer}, + {"native_applyVolumeShaper", + "(Landroid/media/VolumeShaper$Configuration;Landroid/media/VolumeShaper$Operation;)I", + (void *)android_media_MediaPlayer2_applyVolumeShaper}, + {"native_getVolumeShaperState", + "(I)Landroid/media/VolumeShaper$State;", + (void *)android_media_MediaPlayer2_getVolumeShaperState}, + // Modular DRM + { "_prepareDrm", "([B[B)V", (void *)android_media_MediaPlayer2_prepareDrm }, + { "_releaseDrm", "()V", (void *)android_media_MediaPlayer2_releaseDrm }, + + // AudioRouting + {"native_setOutputDevice", "(I)Z", (void *)android_media_MediaPlayer2_setOutputDevice}, + {"native_getRoutedDeviceId", "()I", (void *)android_media_MediaPlayer2_getRoutedDeviceId}, + {"native_enableDeviceCallback", "(Z)V", (void *)android_media_MediaPlayer2_enableDeviceCallback}, +}; + +// This function only registers the native methods +static int register_android_media_MediaPlayer2Impl(JNIEnv *env) +{ + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods)); +} + +jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) +{ + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + ALOGE("ERROR: GetEnv failed\n"); + goto bail; + } + assert(env != NULL); + + if (register_android_media_MediaPlayer2Impl(env) < 0) { + ALOGE("ERROR: MediaPlayer2 native registration failed\n"); + goto bail; + } + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + +bail: + return result; +} + +// KTHXBYE diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp index 98e9a42d944d..e70d5ea0d566 100644 --- a/native/android/asset_manager.cpp +++ b/native/android/asset_manager.cpp @@ -18,9 +18,11 @@ #include <utils/Log.h> #include <android/asset_manager_jni.h> +#include <android_runtime/android_util_AssetManager.h> #include <androidfw/Asset.h> #include <androidfw/AssetDir.h> #include <androidfw/AssetManager.h> +#include <androidfw/AssetManager2.h> #include <utils/threads.h> #include "jni.h" @@ -35,21 +37,20 @@ using namespace android; // ----- struct AAssetDir { - AssetDir* mAssetDir; + std::unique_ptr<AssetDir> mAssetDir; size_t mCurFileIndex; String8 mCachedFileName; - explicit AAssetDir(AssetDir* dir) : mAssetDir(dir), mCurFileIndex(0) { } - ~AAssetDir() { delete mAssetDir; } + explicit AAssetDir(std::unique_ptr<AssetDir> dir) : + mAssetDir(std::move(dir)), mCurFileIndex(0) { } }; // ----- struct AAsset { - Asset* mAsset; + std::unique_ptr<Asset> mAsset; - explicit AAsset(Asset* asset) : mAsset(asset) { } - ~AAsset() { delete mAsset; } + explicit AAsset(std::unique_ptr<Asset> asset) : mAsset(std::move(asset)) { } }; // -------------------- Public native C API -------------------- @@ -104,19 +105,18 @@ AAsset* AAssetManager_open(AAssetManager* amgr, const char* filename, int mode) return NULL; } - AssetManager* mgr = static_cast<AssetManager*>(amgr); - Asset* asset = mgr->open(filename, amMode); - if (asset == NULL) { - return NULL; + ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr)); + std::unique_ptr<Asset> asset = locked_mgr->Open(filename, amMode); + if (asset == nullptr) { + return nullptr; } - - return new AAsset(asset); + return new AAsset(std::move(asset)); } AAssetDir* AAssetManager_openDir(AAssetManager* amgr, const char* dirName) { - AssetManager* mgr = static_cast<AssetManager*>(amgr); - return new AAssetDir(mgr->openDir(dirName)); + ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr)); + return new AAssetDir(locked_mgr->OpenDir(dirName)); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java new file mode 100644 index 000000000000..72273046ef29 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +import android.content.Context; +import android.metrics.LogMaker; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; + +/** + * {@link LogWriter} that writes data to eventlog. + */ +public class EventLogWriter implements LogWriter { + + private final MetricsLogger mMetricsLogger = new MetricsLogger(); + + public void visible(Context context, int source, int category) { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_OPEN) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source); + MetricsLogger.action(logMaker); + } + + public void hidden(Context context, int category) { + MetricsLogger.hidden(context, category); + } + + public void action(int category, int value, Pair<Integer, Object>... taggedData) { + if (taggedData == null || taggedData.length == 0) { + mMetricsLogger.action(category, value); + } else { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_ACTION) + .setSubtype(value); + for (Pair<Integer, Object> pair : taggedData) { + logMaker.addTaggedData(pair.first, pair.second); + } + mMetricsLogger.write(logMaker); + } + } + + public void action(int category, boolean value, Pair<Integer, Object>... taggedData) { + action(category, value ? 1 : 0, taggedData); + } + + public void action(Context context, int category, Pair<Integer, Object>... taggedData) { + action(context, category, "", taggedData); + } + + public void actionWithSource(Context context, int source, int category) { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_ACTION); + if (source != MetricsProto.MetricsEvent.VIEW_UNKNOWN) { + logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source); + } + MetricsLogger.action(logMaker); + } + + /** @deprecated use {@link #action(int, int, Pair[])} */ + @Deprecated + public void action(Context context, int category, int value) { + MetricsLogger.action(context, category, value); + } + + /** @deprecated use {@link #action(int, boolean, Pair[])} */ + @Deprecated + public void action(Context context, int category, boolean value) { + MetricsLogger.action(context, category, value); + } + + public void action(Context context, int category, String pkg, + Pair<Integer, Object>... taggedData) { + if (taggedData == null || taggedData.length == 0) { + MetricsLogger.action(context, category, pkg); + } else { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_ACTION) + .setPackageName(pkg); + for (Pair<Integer, Object> pair : taggedData) { + logMaker.addTaggedData(pair.first, pair.second); + } + MetricsLogger.action(logMaker); + } + } + + public void count(Context context, String name, int value) { + MetricsLogger.count(context, name, value); + } + + public void histogram(Context context, String name, int bucket) { + MetricsLogger.histogram(context, name, bucket); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java new file mode 100644 index 000000000000..dbc61c26e82e --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +public interface Instrumentable { + + int METRICS_CATEGORY_UNKNOWN = 0; + + /** + * Instrumented name for a view as defined in + * {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}. + */ + int getMetricsCategory(); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java new file mode 100644 index 000000000000..4b9f5727208d --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +import android.content.Context; +import android.util.Pair; + +/** + * Generic log writer interface. + */ +public interface LogWriter { + + /** + * Logs a visibility event when view becomes visible. + */ + void visible(Context context, int source, int category); + + /** + * Logs a visibility event when view becomes hidden. + */ + void hidden(Context context, int category); + + /** + * Logs a user action. + */ + void action(int category, int value, Pair<Integer, Object>... taggedData); + + /** + * Logs a user action. + */ + void action(int category, boolean value, Pair<Integer, Object>... taggedData); + + /** + * Logs an user action. + */ + void action(Context context, int category, Pair<Integer, Object>... taggedData); + + /** + * Logs an user action. + */ + void actionWithSource(Context context, int source, int category); + + /** + * Logs an user action. + * @deprecated use {@link #action(int, int, Pair[])} + */ + @Deprecated + void action(Context context, int category, int value); + + /** + * Logs an user action. + * @deprecated use {@link #action(int, boolean, Pair[])} + */ + @Deprecated + void action(Context context, int category, boolean value); + + /** + * Logs an user action. + */ + void action(Context context, int category, String pkg, Pair<Integer, Object>... taggedData); + + /** + * Logs a count. + */ + void count(Context context, String name, int value); + + /** + * Logs a histogram event. + */ + void histogram(Context context, String name, int bucket); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java new file mode 100644 index 000000000000..1e5b378e931c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.util.Pair; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * FeatureProvider for metrics. + */ +public class MetricsFeatureProvider { + private List<LogWriter> mLoggerWriters; + + public MetricsFeatureProvider() { + mLoggerWriters = new ArrayList<>(); + installLogWriters(); + } + + protected void installLogWriters() { + mLoggerWriters.add(new EventLogWriter()); + } + + public void visible(Context context, int source, int category) { + for (LogWriter writer : mLoggerWriters) { + writer.visible(context, source, category); + } + } + + public void hidden(Context context, int category) { + for (LogWriter writer : mLoggerWriters) { + writer.hidden(context, category); + } + } + + public void actionWithSource(Context context, int source, int category) { + for (LogWriter writer : mLoggerWriters) { + writer.actionWithSource(context, source, category); + } + } + + /** + * Logs a user action. Includes the elapsed time since the containing + * fragment has been visible. + */ + public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(category, value, + sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible())); + } + } + + /** + * Logs a user action. Includes the elapsed time since the containing + * fragment has been visible. + */ + public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(category, value, + sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible())); + } + } + + public void action(Context context, int category, Pair<Integer, Object>... taggedData) { + for (LogWriter writer : mLoggerWriters) { + writer.action(context, category, taggedData); + } + } + + /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */ + @Deprecated + public void action(Context context, int category, int value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(context, category, value); + } + } + + /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */ + @Deprecated + public void action(Context context, int category, boolean value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(context, category, value); + } + } + + public void action(Context context, int category, String pkg, + Pair<Integer, Object>... taggedData) { + for (LogWriter writer : mLoggerWriters) { + writer.action(context, category, pkg, taggedData); + } + } + + public void count(Context context, String name, int value) { + for (LogWriter writer : mLoggerWriters) { + writer.count(context, name, value); + } + } + + public void histogram(Context context, String name, int bucket) { + for (LogWriter writer : mLoggerWriters) { + writer.histogram(context, name, bucket); + } + } + + public int getMetricsCategory(Object object) { + if (object == null || !(object instanceof Instrumentable)) { + return MetricsEvent.VIEW_UNKNOWN; + } + return ((Instrumentable) object).getMetricsCategory(); + } + + public void logDashboardStartIntent(Context context, Intent intent, + int sourceMetricsCategory) { + if (intent == null) { + return; + } + final ComponentName cn = intent.getComponent(); + if (cn == null) { + final String action = intent.getAction(); + if (TextUtils.isEmpty(action)) { + // Not loggable + return; + } + action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action, + Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); + return; + } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) { + // Going to a Setting internal page, skip click logging in favor of page's own + // visibility logging. + return; + } + action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(), + Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); + } + + private Pair<Integer, Object> sinceVisibleTaggedData(long timestamp) { + return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java new file mode 100644 index 000000000000..facce4e0bcbb --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.AsyncTask; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; + +public class SharedPreferencesLogger implements SharedPreferences { + + private static final String LOG_TAG = "SharedPreferencesLogger"; + + private final String mTag; + private final Context mContext; + private final MetricsFeatureProvider mMetricsFeature; + private final Set<String> mPreferenceKeySet; + + public SharedPreferencesLogger(Context context, String tag, + MetricsFeatureProvider metricsFeature) { + mContext = context; + mTag = tag; + mMetricsFeature = metricsFeature; + mPreferenceKeySet = new ConcurrentSkipListSet<>(); + } + + @Override + public Map<String, ?> getAll() { + return null; + } + + @Override + public String getString(String key, @Nullable String defValue) { + return defValue; + } + + @Override + public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { + return defValues; + } + + @Override + public int getInt(String key, int defValue) { + return defValue; + } + + @Override + public long getLong(String key, long defValue) { + return defValue; + } + + @Override + public float getFloat(String key, float defValue) { + return defValue; + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + return defValue; + } + + @Override + public boolean contains(String key) { + return false; + } + + @Override + public Editor edit() { + return new EditorLogger(); + } + + @Override + public void registerOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + } + + @Override + public void unregisterOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + } + + private void logValue(String key, Object value) { + logValue(key, value, false /* forceLog */); + } + + private void logValue(String key, Object value, boolean forceLog) { + final String prefKey = buildPrefKey(mTag, key); + if (!forceLog && !mPreferenceKeySet.contains(prefKey)) { + // Pref key doesn't exist in set, this is initial display so we skip metrics but + // keeps track of this key. + mPreferenceKeySet.add(prefKey); + return; + } + // TODO: Remove count logging to save some resource. + mMetricsFeature.count(mContext, buildCountName(prefKey, value), 1); + + final Pair<Integer, Object> valueData; + if (value instanceof Long) { + final Long longVal = (Long) value; + final int intVal; + if (longVal > Integer.MAX_VALUE) { + intVal = Integer.MAX_VALUE; + } else if (longVal < Integer.MIN_VALUE) { + intVal = Integer.MIN_VALUE; + } else { + intVal = longVal.intValue(); + } + valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, + intVal); + } else if (value instanceof Integer) { + valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, + value); + } else if (value instanceof Boolean) { + valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, + (Boolean) value ? 1 : 0); + } else if (value instanceof Float) { + valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, + value); + } else if (value instanceof String) { + Log.d(LOG_TAG, "Tried to log string preference " + prefKey + " = " + value); + valueData = null; + } else { + Log.w(LOG_TAG, "Tried to log unloggable object" + value); + valueData = null; + } + if (valueData != null) { + // Pref key exists in set, log it's change in metrics. + mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, + Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey), + valueData); + } + } + + @VisibleForTesting + void logPackageName(String key, String value) { + final String prefKey = mTag + "/" + key; + mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, value, + Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey)); + } + + private void safeLogValue(String key, String value) { + new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value); + } + + public static String buildCountName(String prefKey, Object value) { + return prefKey + "|" + value; + } + + public static String buildPrefKey(String tag, String key) { + return tag + "/" + key; + } + + private class AsyncPackageCheck extends AsyncTask<String, Void, Void> { + @Override + protected Void doInBackground(String... params) { + String key = params[0]; + String value = params[1]; + PackageManager pm = mContext.getPackageManager(); + try { + // Check if this might be a component. + ComponentName name = ComponentName.unflattenFromString(value); + if (value != null) { + value = name.getPackageName(); + } + } catch (Exception e) { + } + try { + pm.getPackageInfo(value, PackageManager.MATCH_ANY_USER); + logPackageName(key, value); + } catch (PackageManager.NameNotFoundException e) { + // Clearly not a package, and it's unlikely this preference is in prefSet, so + // lets force log it. + logValue(key, value, true /* forceLog */); + } + return null; + } + } + + public class EditorLogger implements Editor { + @Override + public Editor putString(String key, @Nullable String value) { + safeLogValue(key, value); + return this; + } + + @Override + public Editor putStringSet(String key, @Nullable Set<String> values) { + safeLogValue(key, TextUtils.join(",", values)); + return this; + } + + @Override + public Editor putInt(String key, int value) { + logValue(key, value); + return this; + } + + @Override + public Editor putLong(String key, long value) { + logValue(key, value); + return this; + } + + @Override + public Editor putFloat(String key, float value) { + logValue(key, value); + return this; + } + + @Override + public Editor putBoolean(String key, boolean value) { + logValue(key, value); + return this; + } + + @Override + public Editor remove(String key) { + return this; + } + + @Override + public Editor clear() { + return this; + } + + @Override + public boolean commit() { + return true; + } + + @Override + public void apply() { + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java new file mode 100644 index 000000000000..79838962ef1e --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import android.os.SystemClock; +import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN; + +/** + * Logs visibility change of a fragment. + */ +public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPause { + + private static final String TAG = "VisibilityLoggerMixin"; + + private final int mMetricsCategory; + + private MetricsFeatureProvider mMetricsFeature; + private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN; + private long mVisibleTimestamp; + + /** + * The metrics category constant for logging source when a setting fragment is opened. + */ + public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics"; + + private VisibilityLoggerMixin() { + mMetricsCategory = METRICS_CATEGORY_UNKNOWN; + } + + public VisibilityLoggerMixin(int metricsCategory, MetricsFeatureProvider metricsFeature) { + mMetricsCategory = metricsCategory; + mMetricsFeature = metricsFeature; + } + + @Override + public void onResume() { + mVisibleTimestamp = SystemClock.elapsedRealtime(); + if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { + mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory); + } + } + + @Override + public void onPause() { + mVisibleTimestamp = 0; + if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { + mMetricsFeature.hidden(null /* context */, mMetricsCategory); + } + } + + /** + * Sets source metrics category for this logger. Source is the caller that opened this UI. + */ + public void setSourceMetricsCategory(Activity activity) { + if (mSourceMetricsCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN || activity == null) { + return; + } + final Intent intent = activity.getIntent(); + if (intent == null) { + return; + } + mSourceMetricsCategory = intent.getIntExtra(EXTRA_SOURCE_METRICS_CATEGORY, + MetricsProto.MetricsEvent.VIEW_UNKNOWN); + } + + /** Returns elapsed time since onResume() */ + public long elapsedTimeSinceVisible() { + if (mVisibleTimestamp == 0) { + return 0; + } + return SystemClock.elapsedRealtime() - mVisibleTimestamp; + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java new file mode 100644 index 000000000000..8bea51d1696d --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.util.Pair; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settingslib.TestConfig; +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class MetricsFeatureProviderTest { + private static int CATEGORY = 10; + private static boolean SUBTYPE_BOOLEAN = true; + private static int SUBTYPE_INTEGER = 1; + private static long ELAPSED_TIME = 1000; + + @Mock private LogWriter mockLogWriter; + @Mock private VisibilityLoggerMixin mockVisibilityLogger; + + private Context mContext; + private MetricsFeatureProvider mProvider; + + @Captor + private ArgumentCaptor<Pair> mPairCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mProvider = new MetricsFeatureProvider(); + List<LogWriter> writers = new ArrayList<>(); + writers.add(mockLogWriter); + ReflectionHelpers.setField(mProvider, "mLoggerWriters", writers); + + when(mockVisibilityLogger.elapsedTimeSinceVisible()).thenReturn(ELAPSED_TIME); + } + + @Test + public void logDashboardStartIntent_intentEmpty_shouldNotLog() { + mProvider.logDashboardStartIntent(mContext, null /* intent */, + MetricsEvent.SETTINGS_GESTURES); + + verifyNoMoreInteractions(mockLogWriter); + } + + @Test + public void logDashboardStartIntent_intentHasNoComponent_shouldLog() { + final Intent intent = new Intent(Intent.ACTION_ASSIST); + + mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES); + + verify(mockLogWriter).action( + eq(mContext), + eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK), + anyString(), + eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES))); + } + + @Test + public void logDashboardStartIntent_intentIsExternal_shouldLog() { + final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls")); + + mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES); + + verify(mockLogWriter).action( + eq(mContext), + eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK), + anyString(), + eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES))); + } + + @Test + public void action_BooleanLogsElapsedTime() { + mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_BOOLEAN); + verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_BOOLEAN), mPairCaptor.capture()); + + Pair value = mPairCaptor.getValue(); + assertThat(value.first instanceof Integer).isTrue(); + assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS); + assertThat(value.second).isEqualTo(ELAPSED_TIME); + } + + @Test + public void action_IntegerLogsElapsedTime() { + mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_INTEGER); + verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_INTEGER), mPairCaptor.capture()); + + Pair value = mPairCaptor.getValue(); + assertThat(value.first instanceof Integer).isTrue(); + assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS); + assertThat(value.second).isEqualTo(ELAPSED_TIME); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java new file mode 100644 index 000000000000..d558a645aeb7 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Pair; + +import com.android.settingslib.TestConfig; +import com.android.settingslib.SettingsLibRobolectricTestRunner; + +import com.google.common.truth.Platform; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SharedPreferenceLoggerTest { + + private static final String TEST_TAG = "tag"; + private static final String TEST_KEY = "key"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + + private ArgumentMatcher<Pair<Integer, Object>> mNamePairMatcher; + @Mock + private MetricsFeatureProvider mMetricsFeature; + private SharedPreferencesLogger mSharedPrefLogger; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG, mMetricsFeature); + mNamePairMatcher = pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, String.class); + } + + @Test + public void putInt_shouldNotLogInitialPut() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + editor.putInt(TEST_KEY, 1); + editor.putInt(TEST_KEY, 1); + editor.putInt(TEST_KEY, 1); + editor.putInt(TEST_KEY, 2); + editor.putInt(TEST_KEY, 2); + editor.putInt(TEST_KEY, 2); + editor.putInt(TEST_KEY, 2); + + verify(mMetricsFeature, times(6)).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class))); + } + + @Test + public void putBoolean_shouldNotLogInitialPut() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + editor.putBoolean(TEST_KEY, true); + editor.putBoolean(TEST_KEY, true); + editor.putBoolean(TEST_KEY, false); + editor.putBoolean(TEST_KEY, false); + editor.putBoolean(TEST_KEY, false); + + + verify(mMetricsFeature).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, true))); + verify(mMetricsFeature, times(3)).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, false))); + } + + @Test + public void putLong_shouldNotLogInitialPut() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, 2); + + verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class))); + } + + @Test + public void putLong_biggerThanIntMax_shouldLogIntMax() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + final long veryBigNumber = 500L + Integer.MAX_VALUE; + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, veryBigNumber); + + verify(mMetricsFeature).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches( + FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MAX_VALUE))); + } + + @Test + public void putLong_smallerThanIntMin_shouldLogIntMin() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + final long veryNegativeNumber = -500L + Integer.MIN_VALUE; + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, veryNegativeNumber); + + verify(mMetricsFeature).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches( + FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MIN_VALUE))); + } + + @Test + public void putFloat_shouldNotLogInitialPut() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + editor.putFloat(TEST_KEY, 1); + editor.putFloat(TEST_KEY, 1); + editor.putFloat(TEST_KEY, 1); + editor.putFloat(TEST_KEY, 1); + editor.putFloat(TEST_KEY, 2); + + verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, Float.class))); + } + + @Test + public void logPackage_shouldUseLogPackageApi() { + mSharedPrefLogger.logPackageName("key", "com.android.settings"); + verify(mMetricsFeature).action(any(Context.class), + eq(ACTION_SETTINGS_PREFERENCE_CHANGE), + eq("com.android.settings"), + any(Pair.class)); + } + + private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, Class clazz) { + return pair -> pair.first == tag && Platform.isInstanceOfType(pair.second, clazz); + } + + private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, boolean bool) { + return pair -> pair.first == tag + && Platform.isInstanceOfType(pair.second, Integer.class) + && pair.second.equals((bool ? 1 : 0)); + } + + private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, int val) { + return pair -> pair.first == tag + && Platform.isInstanceOfType(pair.second, Integer.class) + && pair.second.equals(val); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java new file mode 100644 index 000000000000..a2648861d1d8 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.settingslib.core.instrumentation; + +import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN; + +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class VisibilityLoggerMixinTest { + + @Mock + private MetricsFeatureProvider mMetricsFeature; + + private VisibilityLoggerMixin mMixin; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, mMetricsFeature); + } + + @Test + public void shouldLogVisibleOnResume() { + mMixin.onResume(); + + verify(mMetricsFeature, times(1)) + .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.VIEW_UNKNOWN), + eq(TestInstrumentable.TEST_METRIC)); + } + + @Test + public void shouldLogVisibleWithSource() { + final Intent sourceIntent = new Intent() + .putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, + MetricsProto.MetricsEvent.SETTINGS_GESTURES); + final Activity activity = mock(Activity.class); + when(activity.getIntent()).thenReturn(sourceIntent); + mMixin.setSourceMetricsCategory(activity); + mMixin.onResume(); + + verify(mMetricsFeature, times(1)) + .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.SETTINGS_GESTURES), + eq(TestInstrumentable.TEST_METRIC)); + } + + @Test + public void shouldLogHideOnPause() { + mMixin.onPause(); + + verify(mMetricsFeature, times(1)) + .hidden(nullable(Context.class), eq(TestInstrumentable.TEST_METRIC)); + } + + @Test + public void shouldNotLogIfMetricsFeatureIsNull() { + mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, null); + mMixin.onResume(); + mMixin.onPause(); + + verify(mMetricsFeature, never()) + .hidden(nullable(Context.class), anyInt()); + } + + @Test + public void shouldNotLogIfMetricsCategoryIsUnknown() { + mMixin = new VisibilityLoggerMixin(METRICS_CATEGORY_UNKNOWN, mMetricsFeature); + + mMixin.onResume(); + mMixin.onPause(); + + verify(mMetricsFeature, never()) + .hidden(nullable(Context.class), anyInt()); + } + + private final class TestInstrumentable implements Instrumentable { + + public static final int TEST_METRIC = 12345; + + @Override + public int getMetricsCategory() { + return TEST_METRIC; + } + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index cfd33a19baf9..91957e1ff05c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -30,6 +30,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; +import android.hardware.display.DisplayManager; import android.icu.util.ULocale; import android.location.LocationManager; import android.media.AudioManager; @@ -306,15 +307,7 @@ public class SettingsHelper { } private void setBrightness(int brightness) { - try { - IPowerManager power = IPowerManager.Stub.asInterface( - ServiceManager.getService("power")); - if (power != null) { - power.setTemporaryScreenBrightnessSettingOverride(brightness); - } - } catch (RemoteException doe) { - - } + mContext.getSystemService(DisplayManager.class).setTemporaryBrightness(brightness); } /* package */ byte[] getLocaleData() { diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 0b6e11bf8638..42b7213e84f0 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -72,6 +72,7 @@ <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/> <!-- Physical hardware --> <uses-permission android:name="android.permission.MANAGE_USB" /> + <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.MASTER_CLEAR" /> diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml index e0f0ed994166..cd3271fa5374 100644 --- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml +++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml @@ -13,13 +13,25 @@ See the License for the specific language governing permissions and limitations under the License. --> -<View +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/quick_qs_status_icons" android:layout_width="match_parent" android:layout_height="20dp" - android:layout_marginBottom="22dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="14dp" android:layout_below="@id/quick_status_bar_system_icons" > -</View> + <com.android.systemui.statusbar.phone.StatusIconContainer + android:id="@+id/statusIcons" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" /> + + <include layout="@layout/signal_cluster_view" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/signal_cluster_margin_start" /> + +</LinearLayout> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index cc4bc58fbf9d..da50776708b3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -29,4 +29,9 @@ interface ISystemUiProxy { */ GraphicBufferCompat screenshot(in Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean useIdentityTransform, int rotation); + + /** + * Called when the overview service has started the recents animation. + */ + void onRecentsAnimationStarted(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index c9a6ea9939f5..f9e1069cfe95 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -32,6 +32,7 @@ import android.app.AppGlobals; import android.app.IAssistDataReceiver; import android.app.WindowConfiguration.ActivityType; import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -48,7 +49,10 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.IconDrawableFactory; import android.util.Log; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; +import android.view.RemoteAnimationTarget; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -243,10 +247,9 @@ public class ActivityManagerWrapper { /** * Starts the recents activity. The caller should manage the thread on which this is called. */ - public void startRecentsActivity(AssistDataReceiverCompat assistDataReceiver, Bundle options, - ActivityOptions opts, int userId, Consumer<Boolean> resultCallback, + public void startRecentsActivity(Intent intent, AssistDataReceiver assistDataReceiver, + RecentsAnimationListener animationHandler, Consumer<Boolean> resultCallback, Handler resultCallbackHandler) { - Bundle activityOptions = opts != null ? opts.toBundle() : null; try { IAssistDataReceiver receiver = null; if (assistDataReceiver != null) { @@ -259,8 +262,24 @@ public class ActivityManagerWrapper { } }; } - ActivityManager.getService().startRecentsActivity(receiver, options, activityOptions, - userId); + IRecentsAnimationRunner runner = null; + if (animationHandler != null) { + runner = new IRecentsAnimationRunner.Stub() { + public void onAnimationStart(IRecentsAnimationController controller, + RemoteAnimationTarget[] apps) { + final RecentsAnimationControllerCompat controllerCompat = + new RecentsAnimationControllerCompat(controller); + final RemoteAnimationTargetCompat[] appsCompat = + RemoteAnimationTargetCompat.wrap(apps); + animationHandler.onAnimationStart(controllerCompat, appsCompat); + } + + public void onAnimationCanceled() { + animationHandler.onAnimationCanceled(); + } + }; + } + ActivityManager.getService().startRecentsActivity(intent, receiver, runner); if (resultCallback != null) { resultCallbackHandler.post(new Runnable() { @Override diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiverCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiver.java index cd943f62ea9b..7cd6c512b660 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiverCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/AssistDataReceiver.java @@ -22,7 +22,7 @@ import android.os.Bundle; /** * Abstract class for assist data receivers. */ -public abstract class AssistDataReceiverCompat { - public abstract void onHandleAssistData(Bundle resultData); - public abstract void onHandleAssistScreenshot(Bitmap screenshot); +public abstract class AssistDataReceiver { + public void onHandleAssistData(Bundle resultData) {} + public void onHandleAssistScreenshot(Bitmap screenshot) {} } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java index db4f988a9122..38b8ae8418af 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.systemui.pip.phone; +package com.android.systemui.shared.system; import static android.view.WindowManager.INPUT_CONSUMER_PIP; +import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import android.os.Binder; import android.os.IBinder; @@ -29,11 +30,12 @@ import android.view.InputChannel; import android.view.InputEvent; import android.view.IWindowManager; import android.view.MotionEvent; +import android.view.WindowManagerGlobal; import java.io.PrintWriter; /** - * Manages the input consumer that allows the SystemUI to control the PiP. + * Manages the input consumer that allows the SystemUI to directly receive touch input. */ public class InputConsumerController { @@ -55,12 +57,12 @@ public class InputConsumerController { } /** - * Input handler used for the PiP input consumer. Input events are batched and consumed with the + * Input handler used for the input consumer. Input events are batched and consumed with the * SurfaceFlinger vsync. */ - private final class PipInputEventReceiver extends BatchedInputEventReceiver { + private final class InputEventReceiver extends BatchedInputEventReceiver { - public PipInputEventReceiver(InputChannel inputChannel, Looper looper) { + public InputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper, Choreographer.getSfInstance()); } @@ -68,7 +70,6 @@ public class InputConsumerController { public void onInputEvent(InputEvent event, int displayId) { boolean handled = true; try { - // To be implemented for input handling over Pip windows if (mListener != null && event instanceof MotionEvent) { MotionEvent ev = (MotionEvent) event; handled = mListener.onTouchEvent(ev); @@ -81,15 +82,35 @@ public class InputConsumerController { private final IWindowManager mWindowManager; private final IBinder mToken; + private final String mName; - private PipInputEventReceiver mInputEventReceiver; + private InputEventReceiver mInputEventReceiver; private TouchListener mListener; private RegistrationListener mRegistrationListener; - public InputConsumerController(IWindowManager windowManager) { + /** + * @param name the name corresponding to the input consumer that is defined in the system. + */ + public InputConsumerController(IWindowManager windowManager, String name) { mWindowManager = windowManager; mToken = new Binder(); - registerInputConsumer(); + mName = name; + } + + /** + * @return A controller for the pip input consumer. + */ + public static InputConsumerController getPipInputConsumer() { + return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_PIP); + } + + /** + * @return A controller for the recents animation input consumer. + */ + public static InputConsumerController getRecentsAnimationInputConsumer() { + return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_RECENTS_ANIMATION); } /** @@ -125,12 +146,12 @@ public class InputConsumerController { if (mInputEventReceiver == null) { final InputChannel inputChannel = new InputChannel(); try { - mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); - mWindowManager.createInputConsumer(mToken, INPUT_CONSUMER_PIP, inputChannel); + mWindowManager.destroyInputConsumer(mName); + mWindowManager.createInputConsumer(mToken, mName, inputChannel); } catch (RemoteException e) { - Log.e(TAG, "Failed to create PIP input consumer", e); + Log.e(TAG, "Failed to create input consumer", e); } - mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper()); + mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper()); if (mRegistrationListener != null) { mRegistrationListener.onRegistrationChanged(true /* isRegistered */); } @@ -143,9 +164,9 @@ public class InputConsumerController { public void unregisterInputConsumer() { if (mInputEventReceiver != null) { try { - mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); + mWindowManager.destroyInputConsumer(mName); } catch (RemoteException e) { - Log.e(TAG, "Failed to destroy PIP input consumer", e); + Log.e(TAG, "Failed to destroy input consumer", e); } mInputEventReceiver.dispose(); mInputEventReceiver = null; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java new file mode 100644 index 000000000000..9a7abf82c56c --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shared.system; + +import android.app.ActivityManager.TaskSnapshot; +import android.os.RemoteException; +import android.util.Log; +import android.view.IRecentsAnimationController; + +import com.android.systemui.shared.recents.model.ThumbnailData; + +public class RecentsAnimationControllerCompat { + + private static final String TAG = RecentsAnimationControllerCompat.class.getSimpleName(); + + private IRecentsAnimationController mAnimationController; + + public RecentsAnimationControllerCompat(IRecentsAnimationController animationController) { + mAnimationController = animationController; + } + + public ThumbnailData screenshotTask(int taskId) { + try { + TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); + return snapshot != null ? new ThumbnailData(snapshot) : new ThumbnailData(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to screenshot task", e); + return new ThumbnailData(); + } + } + + public void setInputConsumerEnabled(boolean enabled) { + try { + mAnimationController.setInputConsumerEnabled(enabled); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set input consumer enabled state", e); + } + } + + public void finish(boolean toHome) { + try { + mAnimationController.finish(toHome); + } catch (RemoteException e) { + Log.e(TAG, "Failed to finish recents animation", e); + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java new file mode 100644 index 000000000000..bf6179d70a5e --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shared.system; + +public interface RecentsAnimationListener { + + /** + * Called when the animation into Recents can start. This call is made on the binder thread. + */ + void onAnimationStart(RecentsAnimationControllerCompat controller, + RemoteAnimationTargetCompat[] apps); + + /** + * Called when the animation into Recents was canceled. This call is made on the binder thread. + */ + void onAnimationCanceled(); +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index 244c1b990448..b6e49ae6cc2c 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -77,6 +77,15 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Binder.restoreCallingIdentity(token); } } + + public void onRecentsAnimationStarted() { + long token = Binder.clearCallingIdentity(); + try { + notifyRecentsAnimationStarted(); + } finally { + Binder.restoreCallingIdentity(token); + } + } }; private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() { @@ -214,6 +223,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + private void notifyRecentsAnimationStarted() { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onRecentsAnimationStarted(); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(TAG_OPS + " state:"); @@ -224,6 +239,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } public interface OverviewProxyListener { - void onConnectionChanged(boolean isConnected); + default void onConnectionChanged(boolean isConnected) {} + default void onRecentsAnimationStarted() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 36531bb727a4..24d0126a1494 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -16,12 +16,10 @@ package com.android.systemui.pip.phone; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.INPUT_CONSUMER_PIP; import android.app.ActivityManager; -import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; import android.content.ComponentName; import android.content.Context; @@ -43,6 +41,7 @@ import com.android.systemui.recents.events.component.ExpandPipEvent; import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.InputConsumerController; import java.io.PrintWriter; @@ -174,7 +173,8 @@ public class PipManager implements BasePipManager { } ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); - mInputConsumerController = new InputConsumerController(mWindowManager); + mInputConsumerController = InputConsumerController.getPipInputConsumer(); + mInputConsumerController.registerInputConsumer(); mMediaController = new PipMediaController(context, mActivityManager); mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController, mInputConsumerController); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index 9fb201b82d8c..26fced307bac 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -23,7 +23,6 @@ import android.app.ActivityManager.StackInfo; import android.app.ActivityOptions; import android.app.IActivityManager; import android.app.RemoteAction; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -43,6 +42,7 @@ import com.android.systemui.pip.phone.PipMediaController.ActionListener; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.component.HidePipMenuEvent; import com.android.systemui.recents.misc.ReferenceCountedTrigger; +import com.android.systemui.shared.system.InputConsumerController; import java.io.PrintWriter; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index c0fed342ef44..b25351731a35 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -46,6 +46,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.policy.PipSnapAlgorithm; import com.android.systemui.R; +import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java index 8f41a6036072..bd130f4b40f3 100644 --- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java @@ -2,7 +2,25 @@ package com.android.systemui.power; public interface EnhancedEstimates { + /** + * Returns a boolean indicating if the hybrid notification should be used. + */ boolean isHybridNotificationEnabled(); + /** + * Returns an estimate object if the feature is enabled. + */ Estimate getEstimate(); + + /** + * Returns a long indicating the amount of time remaining in milliseconds under which we will + * show a regular warning to the user. + */ + long getLowWarningThreshold(); + + /** + * Returns a long indicating the amount of time remaining in milliseconds under which we will + * show a severe warning to the user. + */ + long getSevereWarningThreshold(); } diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java index d447542588c5..5686d801bca2 100644 --- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java @@ -13,4 +13,14 @@ public class EnhancedEstimatesImpl implements EnhancedEstimates { public Estimate getEstimate() { return null; } + + @Override + public long getLowWarningThreshold() { + return 0; + } + + @Override + public long getSevereWarningThreshold() { + return 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 736286f21bfe..aa56694775fc 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -99,6 +99,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private long mWarningTriggerTimeMs; private Estimate mEstimate; + private long mLowWarningThreshold; + private long mSevereWarningThreshold; private boolean mWarning; private boolean mPlaySound; private boolean mInvalidCharger; @@ -142,11 +144,18 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { @Override public void updateEstimate(Estimate estimate) { mEstimate = estimate; - if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) { + if (estimate.estimateMillis <= mLowWarningThreshold) { mWarningTriggerTimeMs = System.currentTimeMillis(); } } + @Override + public void updateThresholds(long lowThreshold, long severeThreshold) { + mLowWarningThreshold = lowThreshold; + mSevereWarningThreshold = severeThreshold; + } + + private void updateNotification() { if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound=" + mPlaySound + " mInvalidCharger=" + mInvalidCharger); @@ -181,7 +190,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } protected void showWarningNotification() { - final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0); + final String percentage = NumberFormat.getPercentInstance() + .format((double) mBatteryLevel / 100.0); // get standard notification copy String title = mContext.getString(R.string.battery_low_title); @@ -214,7 +224,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } // Make the notification red if the percentage goes below a certain amount or the time // remaining estimate is disabled - if (mEstimate == null || mBucket < 0) { + if (mEstimate == null || mBucket < 0 + || mEstimate.estimateMillis < mSevereWarningThreshold) { nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); } nb.addAction(0, diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index c5aab601283c..b43e99be828e 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -269,6 +269,8 @@ public class PowerUI extends SystemUI { if (estimate != null) { mTimeRemaining = estimate.estimateMillis; mWarnings.updateEstimate(estimate); + mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(), + mEnhancedEstimates.getSevereWarningThreshold()); } } @@ -292,7 +294,7 @@ public class PowerUI extends SystemUI { && !isPowerSaver && (((bucket < oldBucket || oldPlugged) && bucket < 0) || (mEnhancedEstimates.isHybridNotificationEnabled() - && timeRemaining < THREE_HOURS_IN_MILLIS + && timeRemaining < mEnhancedEstimates.getLowWarningThreshold() && isHourLess(oldTimeRemaining, timeRemaining))) && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN; } @@ -306,7 +308,7 @@ public class PowerUI extends SystemUI { boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket, long timeRemaining, boolean isPowerSaver) { final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled() - && timeRemaining > THREE_HOURS_IN_MILLIS; + && timeRemaining > mEnhancedEstimates.getLowWarningThreshold(); final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0); return isPowerSaver || plugged @@ -485,6 +487,7 @@ public class PowerUI extends SystemUI { public interface WarningsUI { void update(int batteryLevel, int bucket, long screenOffTime); void updateEstimate(Estimate estimate); + void updateThresholds(long lowThreshold, long severeThreshold); void dismissLowBatteryWarning(); void showLowBatteryWarning(boolean playSound); void dismissInvalidChargerWarning(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index a97b35cba048..17ede6586402 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -39,7 +39,8 @@ import com.android.systemui.SysUiServiceProvider; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSDetail.Callback; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.SignalClusterView; +import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; @@ -56,6 +57,10 @@ public class QuickStatusBarHeader extends RelativeLayout protected QuickQSPanel mHeaderQsPanel; protected QSTileHost mHost; + private TintedIconManager mIconManager; + private TouchAnimator mAlphaAnimator; + + private View mQuickQsStatusIcons; private View mDate; @@ -71,16 +76,25 @@ public class QuickStatusBarHeader extends RelativeLayout mHeaderQsPanel = findViewById(R.id.quick_qs_panel); mDate = findViewById(R.id.date); mDate.setOnClickListener(this); + mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons); + mIconManager = new TintedIconManager(findViewById(R.id.statusIcons)); // RenderThread is doing more harm than good when touching the header (to expand quick // settings), so disable it for this view updateResources(); - // Set light text on the header icons because they will always be on a black background - int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground); Rect tintArea = new Rect(0, 0, 0, 0); + int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground); + float intensity = colorForeground == Color.WHITE ? 0 : 1; + int fillColor = fillColorForIntensity(intensity, getContext()); + + // Set light text on the header icons because they will always be on a black background applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT); + applyDarkness(id.signal_cluster, tintArea, intensity, colorForeground); + + // Set the correct tint for the status icons so they contrast + mIconManager.setTint(fillColor); BatteryMeterView battery = findViewById(R.id.battery); battery.setFillColor(Color.WHITE); @@ -96,6 +110,13 @@ public class QuickStatusBarHeader extends RelativeLayout } } + private int fillColorForIntensity(float intensity, Context context) { + if (intensity == 0) { + return context.getColor(R.color.light_mode_icon_color_dual_tone_fill); + } + return context.getColor(R.color.dark_mode_icon_color_dual_tone_fill); + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -109,6 +130,13 @@ public class QuickStatusBarHeader extends RelativeLayout } private void updateResources() { + updateAlphaAnimator(); + } + + private void updateAlphaAnimator() { + mAlphaAnimator = new TouchAnimator.Builder() + .addFloat(mQuickQsStatusIcons, "alpha", 1, 0) + .build(); } public int getCollapsedHeight() { @@ -127,6 +155,9 @@ public class QuickStatusBarHeader extends RelativeLayout } public void setExpansion(float headerExpansionFraction) { + if (mAlphaAnimator != null ) { + mAlphaAnimator.setPosition(headerExpansionFraction); + } } @Override @@ -142,6 +173,7 @@ public class QuickStatusBarHeader extends RelativeLayout @Override public void onAttachedToWindow() { SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this); + Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); } @Override @@ -149,17 +181,10 @@ public class QuickStatusBarHeader extends RelativeLayout public void onDetachedFromWindow() { setListening(false); SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this); + Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager); super.onDetachedFromWindow(); } - @Override - public void onClick(View v) { - if (v == mDate) { - Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent( - AlarmClock.ACTION_SHOW_ALARMS), 0); - } - } - public void setListening(boolean listening) { if (listening == mListening) { return; @@ -168,6 +193,14 @@ public class QuickStatusBarHeader extends RelativeLayout mListening = listening; } + @Override + public void onClick(View v) { + if(v == mDate){ + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent( + AlarmClock.ACTION_SHOW_ALARMS),0); + } + } + public void updateEverything() { post(() -> setClickable(false)); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 4ceace34befe..bba847c9a816 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -19,6 +19,8 @@ package com.android.systemui.qs.tiles; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import android.app.AlarmManager; +import android.app.AlarmManager.AlarmClockInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -27,9 +29,11 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Global; +import android.service.notification.ScheduleCalendar; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; import android.service.quicksettings.Tile; @@ -177,29 +181,27 @@ public class DndTile extends QSTileImpl<BooleanState> { state.value = newValue; state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.slash.isSlashed = !state.value; + state.label = getTileLabel(); + state.secondaryLabel = getSecondaryLabel(zen != Global.ZEN_MODE_OFF); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); switch (zen) { case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); - state.label = mContext.getString(R.string.quick_settings_dnd_priority_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd_priority_on); break; case Global.ZEN_MODE_NO_INTERRUPTIONS: state.icon = TOTAL_SILENCE; - state.label = mContext.getString(R.string.quick_settings_dnd_none_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd_none_on); break; case ZEN_MODE_ALARMS: state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); - state.label = mContext.getString(R.string.quick_settings_dnd_alarms_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd_alarms_on); break; default: state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); - state.label = mContext.getString(R.string.quick_settings_dnd_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd); break; @@ -212,6 +214,102 @@ public class DndTile extends QSTileImpl<BooleanState> { state.expandedAccessibilityClassName = Switch.class.getName(); } + /** + * Returns the secondary label to use for the given instance of do not disturb. + * - If turned on manually and end time is known, returns end time. + * - If turned on by an automatic rule, returns the automatic rule name. + * - If on due to an app, returns the app name. + * - If there's a combination of rules/apps that trigger, then shows the one that will + * last the longest if applicable. + * @return null if do not disturb is off. + */ + private String getSecondaryLabel(boolean zenOn) { + if (!zenOn) { + return null; + } + + ZenModeConfig config = mController.getConfig(); + String secondaryText = ""; + long latestEndTime = -1; + + // DND turned on by manual rule + if (config.manualRule != null) { + final Uri id = config.manualRule.conditionId; + if (config.manualRule.enabler != null) { + // app triggered manual rule + String appName = ZenModeConfig.getOwnerCaption(mContext, config.manualRule.enabler); + if (!appName.isEmpty()) { + secondaryText = appName; + } + } else { + if (id == null) { + // Do not disturb manually triggered to remain on forever until turned off + // No subtext + return null; + } else { + latestEndTime = ZenModeConfig.tryParseCountdownConditionId(id); + if (latestEndTime > 0) { + final CharSequence formattedTime = ZenModeConfig.getFormattedTime(mContext, + latestEndTime, ZenModeConfig.isToday(latestEndTime), + mContext.getUserId()); + secondaryText = mContext.getString(R.string.qs_dnd_until, formattedTime); + } + } + } + } + + // DND turned on by an automatic rule + for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) { + if (automaticRule.isAutomaticActive()) { + if (ZenModeConfig.isValidEventConditionId(automaticRule.conditionId) || + ZenModeConfig.isValidScheduleConditionId(automaticRule.conditionId)) { + // set text if automatic rule end time is the latest active rule end time + long endTime = parseAutomaticRuleEndTime(automaticRule.conditionId); + if (endTime > latestEndTime) { + latestEndTime = endTime; + secondaryText = automaticRule.name; + } + } else { + // set text if 3rd party rule + return automaticRule.name; + } + } + } + + return !secondaryText.equals("") ? secondaryText : null; + } + + private long parseAutomaticRuleEndTime(Uri id) { + if (ZenModeConfig.isValidEventConditionId(id)) { + // cannot look up end times for events + return Long.MAX_VALUE; + } + + if (ZenModeConfig.isValidScheduleConditionId(id)) { + ScheduleCalendar schedule = ZenModeConfig.toScheduleCalendar(id); + long endTimeMs = schedule.getNextChangeTime(System.currentTimeMillis()); + + // check if automatic rule will end on next alarm + if (schedule.exitAtAlarm()) { + long nextAlarm = getNextAlarm(mContext); + schedule.maybeSetNextAlarm(System.currentTimeMillis(), nextAlarm); + if (schedule.shouldExitForAlarm(endTimeMs)) { + return nextAlarm; + } + } + + return endTimeMs; + } + + return -1; + } + + private long getNextAlarm(Context context) { + final AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + final AlarmClockInfo info = alarms.getNextAlarmClock(mContext.getUserId()); + return info != null ? info.getTriggerTime() : 0; + } + @Override public int getMetricsCategory() { return MetricsEvent.QS_DND; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 99a9be38065d..ea6e174d786e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -39,7 +39,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the * nearest hour and add on the AM/PM indicator. */ - private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "H a"; + private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h a"; private ColorDisplayController mController; private boolean mIsListening; diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java index d3f997a04500..3db30fcbbe6b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java @@ -16,9 +16,11 @@ package com.android.systemui.settings; +import android.animation.ValueAnimator; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; @@ -45,11 +47,7 @@ public class BrightnessController implements ToggleSlider.Listener { private static final String TAG = "StatusBar.BrightnessController"; private static final boolean SHOW_AUTOMATIC_ICON = false; - /** - * {@link android.provider.Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ} uses the range [-1, 1]. - * Using this factor, it is converted to [0, BRIGHTNESS_ADJ_RESOLUTION] for the SeekBar. - */ - private static final float BRIGHTNESS_ADJ_RESOLUTION = 2048; + private static final int SLIDER_ANIMATION_DURATION = 3000; private static final int MSG_UPDATE_ICON = 0; private static final int MSG_UPDATE_SLIDER = 1; @@ -67,7 +65,7 @@ public class BrightnessController implements ToggleSlider.Listener { private final ImageView mIcon; private final ToggleSlider mControl; private final boolean mAutomaticAvailable; - private final IPowerManager mPower; + private final DisplayManager mDisplayManager; private final CurrentUserTracker mUserTracker; private final IVrManager mVrManager; @@ -81,6 +79,9 @@ public class BrightnessController implements ToggleSlider.Listener { private volatile boolean mIsVrModeEnabled; private boolean mListening; private boolean mExternalChange; + private boolean mControlInitialized; + + private ValueAnimator mSliderAnimator; public interface BrightnessStateChangeCallback { public void onBrightnessLevelChanged(); @@ -95,8 +96,6 @@ public class BrightnessController implements ToggleSlider.Listener { Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); private final Uri BRIGHTNESS_FOR_VR_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR); - private final Uri BRIGHTNESS_ADJ_URI = - Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ); public BrightnessObserver(Handler handler) { super(handler); @@ -114,12 +113,10 @@ public class BrightnessController implements ToggleSlider.Listener { if (BRIGHTNESS_MODE_URI.equals(uri)) { mBackgroundHandler.post(mUpdateModeRunnable); mBackgroundHandler.post(mUpdateSliderRunnable); - } else if (BRIGHTNESS_URI.equals(uri) && !mAutomatic) { + } else if (BRIGHTNESS_URI.equals(uri)) { mBackgroundHandler.post(mUpdateSliderRunnable); } else if (BRIGHTNESS_FOR_VR_URI.equals(uri)) { mBackgroundHandler.post(mUpdateSliderRunnable); - } else if (BRIGHTNESS_ADJ_URI.equals(uri) && mAutomatic) { - mBackgroundHandler.post(mUpdateSliderRunnable); } else { mBackgroundHandler.post(mUpdateModeRunnable); mBackgroundHandler.post(mUpdateSliderRunnable); @@ -141,9 +138,6 @@ public class BrightnessController implements ToggleSlider.Listener { cr.registerContentObserver( BRIGHTNESS_FOR_VR_URI, false, this, UserHandle.USER_ALL); - cr.registerContentObserver( - BRIGHTNESS_ADJ_URI, - false, this, UserHandle.USER_ALL); } public void stopObserving() { @@ -214,12 +208,6 @@ public class BrightnessController implements ToggleSlider.Listener { mHandler.obtainMessage(MSG_UPDATE_SLIDER, mMaximumBacklightForVr - mMinimumBacklightForVr, value - mMinimumBacklightForVr).sendToTarget(); - } else if (mAutomatic) { - float value = Settings.System.getFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, - UserHandle.USER_CURRENT); - mHandler.obtainMessage(MSG_UPDATE_SLIDER, (int) BRIGHTNESS_ADJ_RESOLUTION, - (int) ((value + 1) * BRIGHTNESS_ADJ_RESOLUTION / 2f)).sendToTarget(); } else { int value; value = Settings.System.getIntForUser(mContext.getContentResolver(), @@ -250,7 +238,7 @@ public class BrightnessController implements ToggleSlider.Listener { break; case MSG_UPDATE_SLIDER: mControl.setMax(msg.arg1); - mControl.setValue(msg.arg2); + animateSliderTo(msg.arg2); break; case MSG_SET_CHECKED: mControl.setChecked(msg.arg1 != 0); @@ -295,8 +283,7 @@ public class BrightnessController implements ToggleSlider.Listener { mAutomaticAvailable = context.getResources().getBoolean( com.android.internal.R.bool.config_automatic_brightness_available); - mPower = IPowerManager.Stub.asInterface(ServiceManager.getService( - Context.POWER_SERVICE)); + mDisplayManager = context.getSystemService(DisplayManager.class); mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService( Context.VR_SERVICE)); } @@ -356,6 +343,10 @@ public class BrightnessController implements ToggleSlider.Listener { updateIcon(mAutomatic); if (mExternalChange) return; + if (mSliderAnimator != null) { + mSliderAnimator.cancel(); + } + if (mIsVrModeEnabled) { final int val = value + mMinimumBacklightForVr; if (stopTracking) { @@ -371,7 +362,7 @@ public class BrightnessController implements ToggleSlider.Listener { } }); } - } else if (!mAutomatic) { + } else { final int val = value + mMinimumBacklight; if (stopTracking) { MetricsLogger.action(mContext, MetricsEvent.ACTION_BRIGHTNESS, val); @@ -386,21 +377,6 @@ public class BrightnessController implements ToggleSlider.Listener { } }); } - } else { - final float adj = value / (BRIGHTNESS_ADJ_RESOLUTION / 2f) - 1; - if (stopTracking) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_BRIGHTNESS_AUTO, value); - } - setBrightnessAdj(adj); - if (!tracking) { - AsyncTask.execute(new Runnable() { - public void run() { - Settings.System.putFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adj, - UserHandle.USER_CURRENT); - } - }); - } } for (BrightnessStateChangeCallback cb : mChangeCallbacks) { @@ -415,17 +391,11 @@ public class BrightnessController implements ToggleSlider.Listener { } private void setBrightness(int brightness) { - try { - mPower.setTemporaryScreenBrightnessSettingOverride(brightness); - } catch (RemoteException ex) { - } + mDisplayManager.setTemporaryBrightness(brightness); } private void setBrightnessAdj(float adj) { - try { - mPower.setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(adj); - } catch (RemoteException ex) { - } + mDisplayManager.setTemporaryAutoBrightnessAdjustment(adj); } private void updateIcon(boolean automatic) { @@ -442,4 +412,23 @@ public class BrightnessController implements ToggleSlider.Listener { mBackgroundHandler.post(mUpdateSliderRunnable); } } + + private void animateSliderTo(int target) { + if (!mControlInitialized) { + // Don't animate the first value since it's default state isn't meaningful to users. + mControl.setValue(target); + mControlInitialized = true; + } + if (mSliderAnimator != null && mSliderAnimator.isStarted()) { + mSliderAnimator.cancel(); + } + mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target); + mSliderAnimator.addUpdateListener((ValueAnimator animation) -> { + mExternalChange = true; + mControl.setValue((int)animation.getAnimatedValue()); + mExternalChange = false; + }); + mSliderAnimator.setDuration(SLIDER_ANIMATION_DURATION); + mSliderAnimator.start(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java index 62abf3d6c3e2..135f89d3f343 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java +++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java @@ -28,4 +28,5 @@ public interface ToggleSlider { default boolean isChecked() { return false; } void setMax(int max); void setValue(int value); + int getValue(); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java index 5b234e9c5576..07b9ec27629f 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java @@ -126,6 +126,11 @@ public class ToggleSliderView extends RelativeLayout implements ToggleSlider { } @Override + public int getValue() { + return mSlider.getProgress(); + } + + @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mMirror != null) { MotionEvent copy = ev.copy(); 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 9bef0eecaa06..445fb243923f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -104,6 +104,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private DeadZone mDeadZone; private final NavigationBarTransitions mBarTransitions; private final OverviewProxyService mOverviewProxyService; + private boolean mRecentsAnimationStarted; // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) final static boolean WORKAROUND_INVALID_LAYOUT = true; @@ -205,10 +206,18 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } } - private final OverviewProxyListener mOverviewProxyListener = isConnected -> { - setSlippery(!isConnected); - setDisabledFlags(mDisabledFlags, true); - setUpSwipeUpOnboarding(isConnected); + private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { + @Override + public void onConnectionChanged(boolean isConnected) { + setSlippery(!isConnected); + setDisabledFlags(mDisabledFlags, true); + setUpSwipeUpOnboarding(isConnected); + } + + @Override + public void onRecentsAnimationStarted() { + mRecentsAnimationStarted = true; + } }; public NavigationBarView(Context context, AttributeSet attrs) { @@ -270,12 +279,26 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (mGestureHelper.onTouchEvent(event)) { return true; } - return super.onTouchEvent(event); + return mRecentsAnimationStarted || super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { - return mGestureHelper.onInterceptTouchEvent(event); + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + mRecentsAnimationStarted = false; + } else if (action == MotionEvent.ACTION_UP) { + // If the overview proxy service has not started the recents animation then clean up + // after it to ensure that the nav bar buttons still work + if (mOverviewProxyService.getProxy() != null && !mRecentsAnimationStarted) { + try { + ActivityManager.getService().cancelRecentsAnimation(); + } catch (RemoteException e) { + Log.e(TAG, "Could not cancel recents animation"); + } + } + } + return mRecentsAnimationStarted || mGestureHelper.onInterceptTouchEvent(event); } public void abortCurrentGesture() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index fdb7f8d005b6..0a51e5a4e389 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -40,6 +40,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.power.PowerUI.WarningsUI; import com.android.systemui.statusbar.phone.StatusBar; +import java.time.Duration; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; @@ -53,18 +54,19 @@ public class PowerUITest extends SysuiTestCase { private static final boolean UNPLUGGED = false; private static final boolean POWER_SAVER_OFF = false; private static final int ABOVE_WARNING_BUCKET = 1; + private static final long ONE_HOUR_MILLIS = Duration.ofHours(1).toMillis(); public static final int BELOW_WARNING_BUCKET = -1; public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2); public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4); private HardwarePropertiesManager mHardProps; private WarningsUI mMockWarnings; private PowerUI mPowerUI; - private EnhancedEstimates mEnhacedEstimates; + private EnhancedEstimates mEnhancedEstimates; @Before public void setup() { mMockWarnings = mDependency.injectMockDependency(WarningsUI.class); - mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class); + mEnhancedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class); mHardProps = mock(HardwarePropertiesManager.class); mContext.putComponent(StatusBar.class, mock(StatusBar.class)); @@ -142,8 +144,44 @@ public class PowerUITest extends SysuiTestCase { } @Test + public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsNoShow() { + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()) + .thenReturn(Duration.ofHours(1).toMillis()); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + mPowerUI.start(); + + // unplugged device that would not show the non-hybrid notification but would show the + // hybrid but the threshold has been overriden to be too low + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsShow() { + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()) + .thenReturn(Duration.ofHours(5).toMillis()); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + mPowerUI.start(); + + // unplugged device that would not show the non-hybrid notification but would show the + // hybrid since the threshold has been overriden to be much higher + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() { - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); mPowerUI.start(); // unplugged device that would not show the non-hybrid notification but would show the @@ -157,7 +195,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() { - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); mPowerUI.start(); // unplugged device that would show the non-hybrid notification and the hybrid @@ -170,7 +210,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() { - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); mPowerUI.start(); // unplugged device that would show the non-hybrid but not the hybrid @@ -183,7 +225,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() { - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); mPowerUI.start(); // unplugged device that would show the neither due to battery level being good @@ -196,7 +240,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() { - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); mPowerUI.start(); // plugged device that would show the neither due to being plugged @@ -209,7 +255,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() { - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); mPowerUI.start(); // Unknown battery status device that would show the neither due @@ -222,7 +270,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() { - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); mPowerUI.start(); // BatterySaverEnabled device that would show the neither due to battery saver @@ -236,7 +286,10 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() { mPowerUI.start(); - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + // device that gets power saver turned on should dismiss boolean shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, @@ -247,7 +300,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() { mPowerUI.start(); - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); // device that gets plugged in should dismiss boolean shouldDismiss = @@ -259,7 +314,10 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() { mPowerUI.start(); - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + // would dismiss hybrid but not non-hybrid should not dismiss boolean shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, @@ -270,7 +328,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() { mPowerUI.start(); - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); // would dismiss non-hybrid but not hybrid should not dismiss boolean shouldDismiss = @@ -282,7 +342,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() { mPowerUI.start(); - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); // should not dismiss when both would not dismiss boolean shouldDismiss = @@ -294,7 +356,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() { mPowerUI.start(); - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); //should dismiss if both would dismiss boolean shouldDismiss = @@ -306,7 +370,9 @@ public class PowerUITest extends SysuiTestCase { @Test public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() { mPowerUI.start(); - when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false); + when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false); + when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); + when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); // would dismiss non-hybrid with hybrid disabled should dismiss boolean shouldDismiss = diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index b32be736533b..52d0e08e4e7f 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -24,8 +24,9 @@ #include <utils/misc.h> #include <inttypes.h> +#include <android-base/macros.h> #include <androidfw/Asset.h> -#include <androidfw/AssetManager.h> +#include <androidfw/AssetManager2.h> #include <androidfw/ResourceTypes.h> #include <android-base/macros.h> @@ -1664,18 +1665,22 @@ nFileA3DCreateFromAssetStream(JNIEnv *_env, jobject _this, jlong con, jlong nati static jlong nFileA3DCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path) { - AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr); + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr); if (mgr == nullptr) { return 0; } AutoJavaStringToUTF8 str(_env, _path); - Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - if (asset == nullptr) { - return 0; + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + if (asset == nullptr) { + return 0; + } } - jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset); + jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset.release()); return id; } @@ -1752,22 +1757,25 @@ static jlong nFontCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path, jfloat fontSize, jint dpi) { - AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr); + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr); if (mgr == nullptr) { return 0; } AutoJavaStringToUTF8 str(_env, _path); - Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); - if (asset == nullptr) { - return 0; + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + if (asset == nullptr) { + return 0; + } } jlong id = (jlong)(uintptr_t)rsFontCreateFromMemory((RsContext)con, str.c_str(), str.length(), fontSize, dpi, asset->getBuffer(false), asset->getLength()); - delete asset; return id; } diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 0bc95f40bfcf..52ab85c480b3 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -54,6 +54,9 @@ import android.view.ViewConfiguration; import com.android.internal.annotations.VisibleForTesting; +import java.util.ArrayDeque; +import java.util.Queue; + /** * This class handles magnification in response to touch events. * @@ -110,6 +113,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL; private static final boolean DEBUG_DETECTING = false || DEBUG_ALL; private static final boolean DEBUG_PANNING_SCALING = false || DEBUG_ALL; + private static final boolean DEBUG_EVENT_STREAM = false || DEBUG_ALL; private static final float MIN_SCALE = 2.0f; private static final float MAX_SCALE = 5.0f; @@ -141,6 +145,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; + private final Queue<MotionEvent> mDebugInputEventHistory; + private final Queue<MotionEvent> mDebugOutputEventHistory; + /** * @param context Context for resolving various magnification-related resources * @param magnificationController the {@link MagnificationController} @@ -179,11 +186,28 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { mScreenStateReceiver = null; } + mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null; + mDebugOutputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null; + transitionTo(mDetectingState); } @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (DEBUG_EVENT_STREAM) { + storeEventInto(mDebugInputEventHistory, event); + try { + onMotionEventInternal(event, rawEvent, policyFlags); + } catch (Exception e) { + throw new RuntimeException( + "Exception following input events: " + mDebugInputEventHistory, e); + } + } else { + onMotionEventInternal(event, rawEvent, policyFlags); + } + } + + private void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (DEBUG_ALL) Slog.i(LOG_TAG, "onMotionEvent(" + event + ")"); if ((!mDetectTripleTap && !mDetectShortcutTrigger) @@ -273,7 +297,27 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(), event.getFlags()); } - super.onMotionEvent(event, rawEvent, policyFlags); + if (DEBUG_EVENT_STREAM) { + storeEventInto(mDebugOutputEventHistory, event); + try { + super.onMotionEvent(event, rawEvent, policyFlags); + } catch (Exception e) { + throw new RuntimeException( + "Exception downstream following input events: " + mDebugInputEventHistory + + "\nTransformed into output events: " + mDebugOutputEventHistory, + e); + } + } else { + super.onMotionEvent(event, rawEvent, policyFlags); + } + } + + private static void storeEventInto(Queue<MotionEvent> queue, MotionEvent event) { + queue.add(MotionEvent.obtain(event)); + // Prune old events + while (!queue.isEmpty() && (event.getEventTime() - queue.peek().getEventTime() > 5000)) { + queue.remove().recycle(); + } } private PointerCoords[] getTempPointerCoordsWithMinSize(int size) { @@ -544,6 +588,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + + // Ensure that the state at the end of delegation is consistent with the last delegated + // UP/DOWN event in queue: still delegating if pointer is down, detecting otherwise switch (event.getActionMasked()) { case ACTION_UP: case ACTION_CANCEL: { @@ -551,9 +598,11 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation { } break; case ACTION_DOWN: { + transitionTo(mDelegatingState); mLastDelegatedDownEventTime = event.getDownTime(); } break; } + if (getNext() != null) { // We cache some events to see if the user wants to trigger magnification. // If no magnification is triggered we inject these events with adjusted diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 145b307728be..3bfa31a80b9d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -140,6 +140,7 @@ import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.LingerMonitor; import com.android.server.connectivity.MockableSystemProperties; +import com.android.server.connectivity.MultipathPolicyTracker; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkDiagnostics; import com.android.server.connectivity.NetworkMonitor; @@ -511,6 +512,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting final MultinetworkPolicyTracker mMultinetworkPolicyTracker; + @VisibleForTesting + final MultipathPolicyTracker mMultipathPolicyTracker; + /** * Implements support for the legacy "one network per network type" model. * @@ -894,6 +898,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mContext, mHandler, () -> rematchForAvoidBadWifiUpdate()); mMultinetworkPolicyTracker.start(); + mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler); + mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties); registerPrivateDnsSettingsCallbacks(); } @@ -1974,6 +1980,9 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(); dumpAvoidBadWifiSettings(pw); + pw.println(); + mMultipathPolicyTracker.dump(pw); + if (argsContain(args, SHORT_ARG) == false) { pw.println(); synchronized (mValidationLogs) { @@ -2891,6 +2900,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED; } + Integer networkPreference = mMultipathPolicyTracker.getMultipathPreference(network); + if (networkPreference != null) { + return networkPreference; + } + return mMultinetworkPolicyTracker.getMeteredMultipathPreference(); } @@ -2984,6 +2998,7 @@ public class ConnectivityService extends IConnectivityManager.Stub for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkMonitor.systemReady = true; } + mMultipathPolicyTracker.start(); break; } case EVENT_REVALIDATE_NETWORK: { diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index c2adbfaeb740..fe4ac6d771bc 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -65,7 +65,6 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.BitSet; import java.util.List; import libcore.io.IoUtils; @@ -596,6 +595,10 @@ public class IpSecService extends IIpSecService.Stub { return mSpi; } + public EncapSocketRecord getSocketRecord() { + return mSocket; + } + /** always guarded by IpSecService#this */ @Override public void freeUnderlyingResources() { @@ -806,9 +809,29 @@ public class IpSecService extends IIpSecService.Stub { /** always guarded by IpSecService#this */ @Override public void freeUnderlyingResources() { - // TODO: Add calls to netd + // Calls to netd // Teardown VTI // Delete global policies + try { + mSrvConfig.getNetdInstance().removeVirtualTunnelInterface(mInterfaceName); + + for (int direction : DIRECTIONS) { + int mark = (direction == IpSecManager.DIRECTION_IN) ? mIkey : mOkey; + mSrvConfig + .getNetdInstance() + .ipSecDeleteSecurityPolicy( + 0, direction, mLocalAddress, mRemoteAddress, mark, 0xffffffff); + } + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e( + TAG, + "Failed to delete VTI with interface name: " + + mInterfaceName + + " and id: " + + mResourceId); + } getResourceTracker().give(); releaseNetId(mIkey); @@ -1229,24 +1252,57 @@ public class IpSecService extends IIpSecService.Stub { final int okey = reserveNetId(); String intfName = String.format("%s%d", TUNNEL_INTERFACE_PREFIX, resourceId); - // TODO: Add calls to netd: - // Create VTI - // Add inbound/outbound global policies - // (use reqid = 0) + try { + // Calls to netd: + // Create VTI + // Add inbound/outbound global policies + // (use reqid = 0) + mSrvConfig + .getNetdInstance() + .addVirtualTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey); - userRecord.mTunnelInterfaceRecords.put( - resourceId, - new RefcountedResource<TunnelInterfaceRecord>( - new TunnelInterfaceRecord( - resourceId, - intfName, - underlyingNetwork, - localAddr, - remoteAddr, - ikey, - okey), - binder)); - return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName); + for (int direction : DIRECTIONS) { + int mark = (direction == IpSecManager.DIRECTION_OUT) ? okey : ikey; + + mSrvConfig + .getNetdInstance() + .ipSecAddSecurityPolicy( + 0, // Use 0 for reqId + direction, + "", + "", + 0, + mark, + 0xffffffff); + } + + userRecord.mTunnelInterfaceRecords.put( + resourceId, + new RefcountedResource<TunnelInterfaceRecord>( + new TunnelInterfaceRecord( + resourceId, + intfName, + underlyingNetwork, + localAddr, + remoteAddr, + ikey, + okey), + binder)); + return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName); + } catch (RemoteException e) { + // Release keys if we got an error. + releaseNetId(ikey); + releaseNetId(okey); + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + + // If we make it to here, then something has gone wrong and we couldn't create a VTI. + // Release the keys that we reserved, and return an error status. + releaseNetId(ikey); + releaseNetId(okey); + return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } /** @@ -1381,12 +1437,50 @@ public class IpSecService extends IIpSecService.Stub { } } + private void createOrUpdateTransform( + IpSecConfig c, int resourceId, SpiRecord spiRecord, EncapSocketRecord socketRecord) + throws RemoteException { + + int encapType = c.getEncapType(), encapLocalPort = 0, encapRemotePort = 0; + if (encapType != IpSecTransform.ENCAP_NONE) { + encapLocalPort = socketRecord.getPort(); + encapRemotePort = c.getEncapRemotePort(); + } + + IpSecAlgorithm auth = c.getAuthentication(); + IpSecAlgorithm crypt = c.getEncryption(); + IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(); + + mSrvConfig + .getNetdInstance() + .ipSecAddSecurityAssociation( + resourceId, + c.getMode(), + c.getSourceAddress(), + c.getDestinationAddress(), + (c.getNetwork() != null) ? c.getNetwork().netId : 0, + spiRecord.getSpi(), + c.getMarkValue(), + c.getMarkMask(), + (auth != null) ? auth.getName() : "", + (auth != null) ? auth.getKey() : new byte[] {}, + (auth != null) ? auth.getTruncationLengthBits() : 0, + (crypt != null) ? crypt.getName() : "", + (crypt != null) ? crypt.getKey() : new byte[] {}, + (crypt != null) ? crypt.getTruncationLengthBits() : 0, + (authCrypt != null) ? authCrypt.getName() : "", + (authCrypt != null) ? authCrypt.getKey() : new byte[] {}, + (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0, + encapType, + encapLocalPort, + encapRemotePort); + } + /** - * Create a transport mode transform, which represent two security associations (one in each - * direction) in the kernel. The transform will be cached by the system server and must be freed - * when no longer needed. It is possible to free one, deleting the SA from underneath sockets - * that are using it, which will result in all of those sockets becoming unable to send or - * receive data. + * Create a IPsec transform, which represents a single security association in the kernel. The + * transform will be cached by the system server and must be freed when no longer needed. It is + * possible to free one, deleting the SA from underneath sockets that are using it, which will + * result in all of those sockets becoming unable to send or receive data. */ @Override public synchronized IpSecTransformResponse createTransform(IpSecConfig c, IBinder binder) @@ -1402,58 +1496,28 @@ public class IpSecService extends IIpSecService.Stub { return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } - int encapType, encapLocalPort = 0, encapRemotePort = 0; EncapSocketRecord socketRecord = null; - encapType = c.getEncapType(); - if (encapType != IpSecTransform.ENCAP_NONE) { + if (c.getEncapType() != IpSecTransform.ENCAP_NONE) { RefcountedResource<EncapSocketRecord> refcountedSocketRecord = userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow( c.getEncapSocketResourceId()); dependencies.add(refcountedSocketRecord); - socketRecord = refcountedSocketRecord.getResource(); - encapLocalPort = socketRecord.getPort(); - encapRemotePort = c.getEncapRemotePort(); } - IpSecAlgorithm auth = c.getAuthentication(); - IpSecAlgorithm crypt = c.getEncryption(); - IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(); - RefcountedResource<SpiRecord> refcountedSpiRecord = userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId()); dependencies.add(refcountedSpiRecord); SpiRecord spiRecord = refcountedSpiRecord.getResource(); try { - mSrvConfig - .getNetdInstance() - .ipSecAddSecurityAssociation( - resourceId, - c.getMode(), - c.getSourceAddress(), - c.getDestinationAddress(), - (c.getNetwork() != null) ? c.getNetwork().netId : 0, - spiRecord.getSpi(), - c.getMarkValue(), - c.getMarkMask(), - (auth != null) ? auth.getName() : "", - (auth != null) ? auth.getKey() : new byte[] {}, - (auth != null) ? auth.getTruncationLengthBits() : 0, - (crypt != null) ? crypt.getName() : "", - (crypt != null) ? crypt.getKey() : new byte[] {}, - (crypt != null) ? crypt.getTruncationLengthBits() : 0, - (authCrypt != null) ? authCrypt.getName() : "", - (authCrypt != null) ? authCrypt.getKey() : new byte[] {}, - (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0, - encapType, - encapLocalPort, - encapRemotePort); + createOrUpdateTransform(c, resourceId, spiRecord, socketRecord); } catch (ServiceSpecificException e) { // FIXME: get the error code and throw is at an IOException from Errno Exception return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } - // Both SAs were created successfully, time to construct a record and lock it away + + // SA was created successfully, time to construct a record and lock it away userRecord.mTransformRecords.put( resourceId, new RefcountedResource<TransformRecord>( @@ -1561,14 +1625,48 @@ public class IpSecService extends IIpSecService.Stub { c.getMode() == IpSecTransform.MODE_TUNNEL, "Transform mode was not Tunnel mode; cannot be applied to a tunnel interface"); + EncapSocketRecord socketRecord = null; + if (c.getEncapType() != IpSecTransform.ENCAP_NONE) { + socketRecord = + userRecord.mEncapSocketRecords.getResourceOrThrow(c.getEncapSocketResourceId()); + } + SpiRecord spiRecord = userRecord.mSpiRecords.getResourceOrThrow(c.getSpiResourceId()); + int mark = (direction == IpSecManager.DIRECTION_IN) ? tunnelInterfaceInfo.getIkey() : tunnelInterfaceInfo.getOkey(); - // TODO: Add calls to netd: - // Update SA with tunnel mark (ikey or okey based on direction) - // If outbound, add SPI to policy + try { + c.setMarkValue(mark); + c.setMarkMask(0xffffffff); + + if (direction == IpSecManager.DIRECTION_OUT) { + // Set output mark via underlying network (output only) + c.setNetwork(tunnelInterfaceInfo.getUnderlyingNetwork()); + + // If outbound, also add SPI to the policy. + mSrvConfig + .getNetdInstance() + .ipSecUpdateSecurityPolicy( + 0, // Use 0 for reqId + direction, + "", + "", + transformInfo.getSpiRecord().getSpi(), + mark, + 0xffffffff); + } + + // Update SA with tunnel mark (ikey or okey based on direction) + createOrUpdateTransform(c, transformResourceId, spiRecord, socketRecord); + } catch (ServiceSpecificException e) { + if (e.errorCode == EINVAL) { + throw new IllegalArgumentException(e.toString()); + } else { + throw e; + } + } } @Override diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 5a4f7cab3ed6..9aa588fc823a 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -2570,7 +2570,7 @@ public class LocationManagerService extends ILocationManager.Stub { // Check INTERACT_ACROSS_USERS permission if userId is not current user id. checkInteractAcrossUsersPermission(userId); - // enable all location providers + // Enable or disable all location providers synchronized (mLock) { for(String provider : getAllProviders()) { setProviderEnabledForUser(provider, enabled, userId); @@ -2586,10 +2586,10 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public boolean isLocationEnabledForUser(int userId) { - // Check INTERACT_ACROSS_USERS permission if userId is not current user id. checkInteractAcrossUsersPermission(userId); + // If at least one location provider is enabled, return true synchronized (mLock) { for (String provider : getAllProviders()) { if (isProviderEnabledForUser(provider, userId)) { @@ -2602,7 +2602,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean isProviderEnabled(String provider) { - return isProviderEnabledForUser(provider, UserHandle.myUserId()); + return isProviderEnabledForUser(provider, UserHandle.getCallingUserId()); } /** diff --git a/services/core/java/com/android/server/MultipathPolicyTracker.java b/services/core/java/com/android/server/MultipathPolicyTracker.java new file mode 100644 index 000000000000..9e0a230b9250 --- /dev/null +++ b/services/core/java/com/android/server/MultipathPolicyTracker.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.app.usage.NetworkStatsManager; +import android.app.usage.NetworkStatsManager.UsageCallback; +import android.content.Context; +import android.net.INetworkStatsService; +import android.net.INetworkPolicyManager; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkPolicyManager; +import android.net.NetworkRequest; +import android.net.NetworkStats; +import android.net.NetworkTemplate; +import android.net.StringNetworkSpecifier; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.TelephonyManager; +import android.util.DebugUtils; +import android.util.Slog; + +import java.util.Calendar; +import java.util.concurrent.ConcurrentHashMap; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.LocalServices; +import com.android.server.net.NetworkPolicyManagerInternal; + +import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER; +import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; +import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; +import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; + +/** + * Manages multipath data budgets. + * + * Informs the return value of ConnectivityManager#getMultipathPreference() based on: + * - The user's data plan, as returned by getSubscriptionOpportunisticQuota(). + * - The amount of data usage that occurs on mobile networks while they are not the system default + * network (i.e., when the app explicitly selected such networks). + * + * Currently, quota is determined on a daily basis, from midnight to midnight local time. + * + * @hide + */ +public class MultipathPolicyTracker { + private static String TAG = MultipathPolicyTracker.class.getSimpleName(); + + private static final boolean DBG = false; + + private final Context mContext; + private final Handler mHandler; + + private ConnectivityManager mCM; + private NetworkStatsManager mStatsManager; + private NetworkPolicyManager mNPM; + private TelephonyManager mTelephonyManager; + private INetworkStatsService mStatsService; + + private NetworkCallback mMobileNetworkCallback; + private NetworkPolicyManager.Listener mPolicyListener; + + // STOPSHIP: replace this with a configurable mechanism. + private static final long DEFAULT_DAILY_MULTIPATH_QUOTA = 2_500_000; + + private volatile int mMeteredMultipathPreference; + + public MultipathPolicyTracker(Context ctx, Handler handler) { + mContext = ctx; + mHandler = handler; + // Because we are initialized by the ConnectivityService constructor, we can't touch any + // connectivity APIs. Service initialization is done in start(). + } + + public void start() { + mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + mNPM = (NetworkPolicyManager) mContext.getSystemService(Context.NETWORK_POLICY_SERVICE); + mStatsManager = (NetworkStatsManager) mContext.getSystemService( + Context.NETWORK_STATS_SERVICE); + mStatsService = INetworkStatsService.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + + registerTrackMobileCallback(); + registerNetworkPolicyListener(); + } + + public void shutdown() { + maybeUnregisterTrackMobileCallback(); + unregisterNetworkPolicyListener(); + for (MultipathTracker t : mMultipathTrackers.values()) { + t.shutdown(); + } + mMultipathTrackers.clear(); + } + + // Called on an arbitrary binder thread. + public Integer getMultipathPreference(Network network) { + MultipathTracker t = mMultipathTrackers.get(network); + if (t != null) { + return t.getMultipathPreference(); + } + return null; + } + + // Track information on mobile networks as they come and go. + class MultipathTracker { + final Network network; + final int subId; + final String subscriberId; + + private long mQuota; + /** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */ + private long mMultipathBudget; + private final NetworkTemplate mNetworkTemplate; + private final UsageCallback mUsageCallback; + + public MultipathTracker(Network network, NetworkCapabilities nc) { + this.network = network; + try { + subId = Integer.parseInt( + ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString()); + } catch (ClassCastException | NullPointerException | NumberFormatException e) { + throw new IllegalStateException(String.format( + "Can't get subId from mobile network %s (%s): %s", + network, nc, e.getMessage())); + } + + TelephonyManager tele = (TelephonyManager) mContext.getSystemService( + Context.TELEPHONY_SERVICE); + if (tele == null) { + throw new IllegalStateException(String.format("Missing TelephonyManager")); + } + tele = tele.createForSubscriptionId(subId); + if (tele == null) { + throw new IllegalStateException(String.format( + "Can't get TelephonyManager for subId %d", subId)); + } + + subscriberId = tele.getSubscriberId(); + mNetworkTemplate = new NetworkTemplate( + NetworkTemplate.MATCH_MOBILE_ALL, subscriberId, new String[] { subscriberId }, + null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL, + NetworkStats.DEFAULT_NETWORK_NO); + mUsageCallback = new UsageCallback() { + @Override + public void onThresholdReached(int networkType, String subscriberId) { + if (DBG) Slog.d(TAG, "onThresholdReached for network " + network); + mMultipathBudget = 0; + updateMultipathBudget(); + } + }; + + updateMultipathBudget(); + } + + private long getDailyNonDefaultDataUsage() { + Calendar start = Calendar.getInstance(); + Calendar end = (Calendar) start.clone(); + start.set(Calendar.HOUR_OF_DAY, 0); + start.set(Calendar.MINUTE, 0); + start.set(Calendar.SECOND, 0); + start.set(Calendar.MILLISECOND, 0); + + long bytes; + try { + // TODO: Consider using NetworkStatsManager.getSummaryForDevice instead. + bytes = mStatsService.getNetworkTotalBytes(mNetworkTemplate, + start.getTimeInMillis(), end.getTimeInMillis()); + if (DBG) Slog.w(TAG, "Non-default data usage: " + bytes); + } catch (RemoteException e) { + Slog.w(TAG, "Can't fetch daily data usage: " + e); + bytes = -1; + } catch (IllegalStateException e) { + // Bandwidth control disabled? + bytes = -1; + } + return bytes; + } + + void updateMultipathBudget() { + NetworkPolicyManagerInternal npms = LocalServices.getService( + NetworkPolicyManagerInternal.class); + long quota = npms.getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH); + if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes"); + + if (quota == 0) { + // STOPSHIP: replace this with a configurable mechanism. + quota = DEFAULT_DAILY_MULTIPATH_QUOTA; + if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes"); + } + + if (haveMultipathBudget() && quota == mQuota) { + // If we already have a usage callback pending , there's no need to re-register it + // if the quota hasn't changed. The callback will simply fire as expected when the + // budget is spent. Also: if we re-register the callback when we're below the + // UsageCallback's minimum value of 2MB, we'll overshoot the budget. + if (DBG) Slog.d(TAG, "Quota still " + quota + ", not updating."); + return; + } + mQuota = quota; + + long usage = getDailyNonDefaultDataUsage(); + long budget = Math.max(0, quota - usage); + if (budget > 0) { + if (DBG) Slog.d(TAG, "Setting callback for " + budget + + " bytes on network " + network); + registerUsageCallback(budget); + } else { + maybeUnregisterUsageCallback(); + } + } + + public int getMultipathPreference() { + if (haveMultipathBudget()) { + return MULTIPATH_PREFERENCE_HANDOVER | MULTIPATH_PREFERENCE_RELIABILITY; + } + return 0; + } + + // For debugging only. + public long getQuota() { + return mQuota; + } + + // For debugging only. + public long getMultipathBudget() { + return mMultipathBudget; + } + + private boolean haveMultipathBudget() { + return mMultipathBudget > 0; + } + + private void registerUsageCallback(long budget) { + maybeUnregisterUsageCallback(); + mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget, + mUsageCallback, mHandler); + mMultipathBudget = budget; + } + + private void maybeUnregisterUsageCallback() { + if (haveMultipathBudget()) { + if (DBG) Slog.d(TAG, "Unregistering callback, budget was " + mMultipathBudget); + mStatsManager.unregisterUsageCallback(mUsageCallback); + mMultipathBudget = 0; + } + } + + void shutdown() { + maybeUnregisterUsageCallback(); + } + } + + // Only ever updated on the handler thread. Accessed from other binder threads to retrieve + // the tracker for a specific network. + private final ConcurrentHashMap <Network, MultipathTracker> mMultipathTrackers = + new ConcurrentHashMap<>(); + + // TODO: this races with app code that might respond to onAvailable() by immediately calling + // getMultipathPreference. Fix this by adding to ConnectivityService the ability to directly + // invoke NetworkCallbacks on tightly-coupled classes such as this one which run on its + // handler thread. + private void registerTrackMobileCallback() { + final NetworkRequest request = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .addTransportType(TRANSPORT_CELLULAR) + .build(); + mMobileNetworkCallback = new ConnectivityManager.NetworkCallback() { + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { + MultipathTracker existing = mMultipathTrackers.get(network); + if (existing != null) { + existing.updateMultipathBudget(); + return; + } + + try { + mMultipathTrackers.put(network, new MultipathTracker(network, nc)); + } catch (IllegalStateException e) { + Slog.e(TAG, "Can't track mobile network " + network + ": " + e.getMessage()); + } + if (DBG) Slog.d(TAG, "Tracking mobile network " + network); + } + + @Override + public void onLost(Network network) { + MultipathTracker existing = mMultipathTrackers.get(network); + if (existing != null) { + existing.shutdown(); + mMultipathTrackers.remove(network); + } + if (DBG) Slog.d(TAG, "No longer tracking mobile network " + network); + } + }; + + mCM.registerNetworkCallback(request, mMobileNetworkCallback, mHandler); + } + + private void maybeUnregisterTrackMobileCallback() { + if (mMobileNetworkCallback != null) { + mCM.unregisterNetworkCallback(mMobileNetworkCallback); + } + mMobileNetworkCallback = null; + } + + private void registerNetworkPolicyListener() { + mPolicyListener = new NetworkPolicyManager.Listener() { + @Override + public void onMeteredIfacesChanged(String[] meteredIfaces) { + // Dispatched every time opportunistic quota is recalculated. + mHandler.post(() -> { + for (MultipathTracker t : mMultipathTrackers.values()) { + t.updateMultipathBudget(); + } + }); + } + }; + mNPM.registerListener(mPolicyListener); + } + + private void unregisterNetworkPolicyListener() { + mNPM.unregisterListener(mPolicyListener); + } + + public void dump(IndentingPrintWriter pw) { + // Do not use in production. Access to class data is only safe on the handler thrad. + pw.println("MultipathPolicyTracker:"); + pw.increaseIndent(); + for (MultipathTracker t : mMultipathTrackers.values()) { + pw.println(String.format("Network %s: quota %d, budget %d. Preference: %s", + t.network, t.getQuota(), t.getMultipathBudget(), + DebugUtils.flagsToString(ConnectivityManager.class, "MULTIPATH_PREFERENCE_", + t.getMultipathPreference()))); + } + pw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index db21ef1f9b5c..aa8d56b3f6c6 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -50,7 +51,9 @@ import android.util.proto.ProtoOutputStream; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.ConfigurationContainer; +import com.android.server.wm.DisplayWindowController; +import com.android.server.wm.WindowContainerListener; import java.io.PrintWriter; import java.util.ArrayList; @@ -58,7 +61,8 @@ import java.util.ArrayList; * Exactly one of these classes per Display in the system. Capable of holding zero or more * attached {@link ActivityStack}s. */ -class ActivityDisplay extends ConfigurationContainer<ActivityStack> { +class ActivityDisplay extends ConfigurationContainer<ActivityStack> + implements WindowContainerListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM; private static final String TAG_STACK = TAG + POSTFIX_STACK; @@ -100,6 +104,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { // Used in updating the display size private Point mTmpDisplaySize = new Point(); + private DisplayWindowController mWindowContainerController; + ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { mSupervisor = supervisor; mDisplayId = displayId; @@ -108,10 +114,15 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { throw new IllegalStateException("Display does not exist displayId=" + displayId); } mDisplay = display; + mWindowContainerController = createWindowContainerController(); updateBounds(); } + protected DisplayWindowController createWindowContainerController() { + return new DisplayWindowController(mDisplayId, this); + } + void updateBounds() { mDisplay.getSize(mTmpDisplaySize); setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); @@ -148,7 +159,10 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { private void positionChildAt(ActivityStack stack, int position) { mStacks.remove(stack); - mStacks.add(getTopInsertPosition(stack, position), stack); + final int insertPosition = getTopInsertPosition(stack, position); + mStacks.add(insertPosition, stack); + mWindowContainerController.positionChildAt(stack.getWindowContainerController(), + insertPosition); } private int getTopInsertPosition(ActivityStack stack, int candidatePosition) { @@ -651,6 +665,64 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { && (mSupervisor.mService.mRunningVoice == null); } + /** + * @return the stack currently above the home stack. Can be null if there is no home stack, or + * the home stack is already on top. + */ + ActivityStack getStackAboveHome() { + if (mHomeStack == null) { + // Skip if there is no home stack + return null; + } + + final int stackIndex = mStacks.indexOf(mHomeStack) + 1; + return (stackIndex < mStacks.size()) ? mStacks.get(stackIndex) : null; + } + + /** + * Adjusts the home stack behind the last visible stack in the display if necessary. Generally + * used in conjunction with {@link #moveHomeStackBehindStack}. + */ + void moveHomeStackBehindBottomMostVisibleStack() { + if (mHomeStack == null) { + // Skip if there is no home stack + return; + } + + // Move the home stack to the bottom to not affect the following visibility checks + positionChildAtBottom(mHomeStack); + + // Find the next position where the homes stack should be placed + final int numStacks = mStacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { + final ActivityStack stack = mStacks.get(stackNdx); + if (stack == mHomeStack) { + continue; + } + final int winMode = stack.getWindowingMode(); + final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN || + winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + if (stack.shouldBeVisible(null) && isValidWindowingMode) { + // Move the home stack to behind this stack + positionChildAt(mHomeStack, Math.max(0, stackNdx - 1)); + break; + } + } + } + + /** + * Moves the home stack behind the given {@param stack} if possible. If {@param stack} is not + * currently in the display, then then the home stack is moved to the back. Generally used in + * conjunction with {@link #moveHomeStackBehindBottomMostVisibleStack}. + */ + void moveHomeStackBehindStack(ActivityStack behindStack) { + if (behindStack == null) { + return; + } + + positionChildAt(mHomeStack, Math.max(0, mStacks.indexOf(behindStack) - 1)); + } + boolean isSleeping() { return mSleeping; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 83976154ab11..fcbcce4181e3 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -39,6 +39,7 @@ import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE; import static android.app.ActivityThread.PROC_START_SEQ_IDENT; import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE; import static android.app.AppOpsManager.OP_NONE; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; @@ -376,6 +377,7 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoUtils; import android.view.Gravity; +import android.view.IRecentsAnimationRunner; import android.view.LayoutInflater; import android.view.RemoteAnimationDefinition; import android.view.View; @@ -449,6 +451,7 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.utils.PriorityDump; import com.android.server.vr.VrManagerInternal; import com.android.server.wm.PinnedStackWindowController; +import com.android.server.wm.RecentsAnimationController; import com.android.server.wm.WindowManagerService; import dalvik.system.VMRuntime; @@ -4064,6 +4067,12 @@ public class ActivityManagerService extends IActivityManager.Stub runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES; } + if (app.info.isAllowedToUseHiddenApi()) { + // This app is allowed to use undocumented and private APIs. Set + // up its runtime with the appropriate flag. + runtimeFlags |= Zygote.DISABLE_HIDDEN_API_CHECKS; + } + String invokeWith = null; if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { // Debuggable apps may include a wrapper script with their library directory. @@ -5089,23 +5098,16 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public int startRecentsActivity(IAssistDataReceiver assistDataReceiver, Bundle options, - Bundle activityOptions, int userId) { - if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) { - String msg = "Permission Denial: startRecentsActivity() from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " not recent tasks package"; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - - final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(options); - final int recentsUid = mRecentTasks.getRecentsComponentUid(); - final ComponentName recentsComponent = mRecentTasks.getRecentsComponent(); - final String recentsPackage = recentsComponent.getPackageName(); + public void startRecentsActivity(Intent intent, IAssistDataReceiver assistDataReceiver, + IRecentsAnimationRunner recentsAnimationRunner) { + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "startRecentsActivity()"); final long origId = Binder.clearCallingIdentity(); try { synchronized (this) { + final int recentsUid = mRecentTasks.getRecentsComponentUid(); + final ComponentName recentsComponent = mRecentTasks.getRecentsComponent(); + final String recentsPackage = recentsComponent.getPackageName(); + // If provided, kick off the request for the assist data in the background before // starting the activity if (assistDataReceiver != null) { @@ -5122,17 +5124,24 @@ public class ActivityManagerService extends IActivityManager.Stub recentsUid, recentsPackage); } - final Intent intent = new Intent(); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - intent.setComponent(recentsComponent); - intent.putExtras(options); + // Start a new recents animation + final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor, + mActivityStartController, mWindowManager, mUserController); + anim.startRecentsActivity(intent, recentsAnimationRunner, recentsComponent, + recentsUid); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } - return mActivityStartController.obtainStarter(intent, "startRecentsActivity") - .setCallingUid(recentsUid) - .setCallingPackage(recentsPackage) - .setActivityOptions(safeOptions) - .setMayWait(userId) - .execute(); + @Override + public void cancelRecentsAnimation() { + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "cancelRecentsAnimation()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (this) { + mWindowManager.cancelRecentsAnimation(); } } finally { Binder.restoreCallingIdentity(origId); @@ -13268,6 +13277,9 @@ public class ActivityManagerService extends IActivityManager.Stub case ActivityManager.BUGREPORT_OPTION_TELEPHONY: extraOptions = "bugreporttelephony"; break; + case ActivityManager.BUGREPORT_OPTION_WIFI: + extraOptions = "bugreportwifi"; + break; default: throw new IllegalArgumentException("Provided bugreport type is not correct, value: " + bugreportType); @@ -13289,9 +13301,8 @@ public class ActivityManagerService extends IActivityManager.Stub * No new code should be calling it. */ @Deprecated - @Override - public void requestTelephonyBugReport(String shareTitle, String shareDescription) { - + private void requestBugReportWithDescription(String shareTitle, String shareDescription, + int bugreportType) { if (!TextUtils.isEmpty(shareTitle)) { if (shareTitle.length() > MAX_BUGREPORT_TITLE_SIZE) { String errorStr = "shareTitle should be less than " + @@ -13320,9 +13331,34 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.d(TAG, "Bugreport notification title " + shareTitle + " description " + shareDescription); - requestBugReport(ActivityManager.BUGREPORT_OPTION_TELEPHONY); + requestBugReport(bugreportType); + } + + /** + * @deprecated This method is only used by a few internal components and it will soon be + * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps). + * No new code should be calling it. + */ + @Deprecated + @Override + public void requestTelephonyBugReport(String shareTitle, String shareDescription) { + requestBugReportWithDescription(shareTitle, shareDescription, + ActivityManager.BUGREPORT_OPTION_TELEPHONY); } + /** + * @deprecated This method is only used by a few internal components and it will soon be + * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps). + * No new code should be calling it. + */ + @Deprecated + @Override + public void requestWifiBugReport(String shareTitle, String shareDescription) { + requestBugReportWithDescription(shareTitle, shareDescription, + ActivityManager.BUGREPORT_OPTION_WIFI); + } + + public static long getInputDispatchingTimeoutLocked(ActivityRecord r) { return r != null ? getInputDispatchingTimeoutLocked(r.app) : KEY_DISPATCHING_TIMEOUT; } @@ -13746,7 +13782,7 @@ public class ActivityManagerService extends IActivityManager.Stub int index = task.mActivities.lastIndexOf(r); if (index > 0) { ActivityRecord under = task.mActivities.get(index - 1); - under.returningOptions = safeOptions.getOptions(r); + under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null; } final boolean translucentChanged = r.changeWindowTranslucency(false); if (translucentChanged) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 9d06b0dbab64..172228b88eb2 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -994,12 +994,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai insertTaskAtTop(task, null); return; } - - task = topTask(); - if (task != null) { - mWindowContainerController.positionChildAtTop(task.getWindowContainerController(), - true /* includingParents */); - } } /** @@ -1024,12 +1018,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (task != null) { insertTaskAtBottom(task); return; - } else { - task = bottomTask(); - if (task != null) { - mWindowContainerController.positionChildAtBottom( - task.getWindowContainerController(), true /* includingParents */); - } } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index bfb563fd93a8..510a3fa47ec5 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -297,6 +297,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private RunningTasks mRunningTasks; final ActivityStackSupervisorHandler mHandler; + final Looper mLooper; /** Short cut */ WindowManagerService mWindowManager; @@ -581,6 +582,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D public ActivityStackSupervisor(ActivityManagerService service, Looper looper) { mService = service; + mLooper = looper; mHandler = new ActivityStackSupervisorHandler(looper); } diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 4dc30ddf4b5b..8fd754af1a0f 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -302,6 +302,7 @@ class ActivityStarter { SafeActivityOptions activityOptions; boolean ignoreTargetSecurity; boolean componentSpecified; + boolean avoidMoveToFront; ActivityRecord[] outActivity; TaskRecord inTask; String reason; @@ -356,6 +357,7 @@ class ActivityStarter { userId = 0; waitResult = null; mayWait = false; + avoidMoveToFront = false; } /** @@ -390,6 +392,7 @@ class ActivityStarter { userId = request.userId; waitResult = request.waitResult; mayWait = request.mayWait; + avoidMoveToFront = request.avoidMoveToFront; } } @@ -1485,19 +1488,23 @@ class ActivityStarter { mDoResume = false; } - if (mOptions != null && mOptions.getLaunchTaskId() != -1 - && mOptions.getTaskOverlay()) { - r.mTaskOverlay = true; - if (!mOptions.canTaskOverlayResume()) { - final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId()); - final ActivityRecord top = task != null ? task.getTopActivity() : null; - if (top != null && top.state != RESUMED) { - - // The caller specifies that we'd like to be avoided to be moved to the front, - // so be it! - mDoResume = false; - mAvoidMoveToFront = true; + if (mOptions != null) { + if (mOptions.getLaunchTaskId() != -1 && mOptions.getTaskOverlay()) { + r.mTaskOverlay = true; + if (!mOptions.canTaskOverlayResume()) { + final TaskRecord task = mSupervisor.anyTaskForIdLocked( + mOptions.getLaunchTaskId()); + final ActivityRecord top = task != null ? task.getTopActivity() : null; + if (top != null && top.state != RESUMED) { + + // The caller specifies that we'd like to be avoided to be moved to the + // front, so be it! + mDoResume = false; + mAvoidMoveToFront = true; + } } + } else if (mOptions.getAvoidMoveToFront()) { + mAvoidMoveToFront = true; } } @@ -1838,7 +1845,7 @@ class ActivityStarter { // Need to update mTargetStack because if task was moved out of it, the original stack may // be destroyed. mTargetStack = intentActivity.getStack(); - if (!mMovedToFront && mDoResume) { + if (!mAvoidMoveToFront && !mMovedToFront && mDoResume) { if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + mTargetStack + " from " + intentActivity); mTargetStack.moveToFront("intentActivityFound"); diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java new file mode 100644 index 000000000000..fe576fdaacbe --- /dev/null +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_NONE; +import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; + +import android.app.ActivityOptions; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Handler; +import android.view.IRecentsAnimationRunner; +import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks; +import com.android.server.wm.WindowManagerService; + +/** + * Manages the recents animation, including the reordering of the stacks for the transition and + * cleanup. See {@link com.android.server.wm.RecentsAnimationController}. + */ +class RecentsAnimation implements RecentsAnimationCallbacks { + private static final String TAG = RecentsAnimation.class.getSimpleName(); + + private static final int RECENTS_ANIMATION_TIMEOUT = 10 * 1000; + + private final ActivityManagerService mService; + private final ActivityStackSupervisor mStackSupervisor; + private final ActivityStartController mActivityStartController; + private final WindowManagerService mWindowManager; + private final UserController mUserController; + private final Handler mHandler; + + private final Runnable mCancelAnimationRunnable; + + // The stack to restore the home stack behind when the animation is finished + private ActivityStack mRestoreHomeBehindStack; + + RecentsAnimation(ActivityManagerService am, ActivityStackSupervisor stackSupervisor, + ActivityStartController activityStartController, WindowManagerService wm, + UserController userController) { + mService = am; + mStackSupervisor = stackSupervisor; + mActivityStartController = activityStartController; + mHandler = new Handler(mStackSupervisor.mLooper); + mWindowManager = wm; + mUserController = userController; + mCancelAnimationRunnable = () -> { + // The caller has not finished the animation in a predefined amount of time, so + // force-cancel the animation + mWindowManager.cancelRecentsAnimation(); + }; + } + + void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner, + ComponentName recentsComponent, int recentsUid) { + + // Cancel the previous recents animation if necessary + mWindowManager.cancelRecentsAnimation(); + + final boolean hasExistingHomeActivity = mStackSupervisor.getHomeActivity() != null; + if (!hasExistingHomeActivity) { + // No home activity + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setLaunchActivityType(ACTIVITY_TYPE_HOME); + opts.setAvoidMoveToFront(); + intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION); + + mActivityStartController.obtainStarter(intent, "startRecentsActivity_noHomeActivity") + .setCallingUid(recentsUid) + .setCallingPackage(recentsComponent.getPackageName()) + .setActivityOptions(SafeActivityOptions.fromBundle(opts.toBundle())) + .setMayWait(mUserController.getCurrentUserId()) + .execute(); + mWindowManager.prepareAppTransition(TRANSIT_NONE, false); + + // TODO: Maybe wait for app to draw in this particular case? + } + + final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity(); + final ActivityDisplay display = homeActivity.getDisplay(); + + // Save the initial position of the home activity stack to be restored to after the + // animation completes + mRestoreHomeBehindStack = hasExistingHomeActivity + ? display.getStackAboveHome() + : null; + + // Move the home activity into place for the animation + display.moveHomeStackBehindBottomMostVisibleStack(); + + // Mark the home activity as launch-behind to bump its visibility for the + // duration of the gesture that is driven by the recents component + homeActivity.mLaunchTaskBehind = true; + + // Fetch all the surface controls and pass them to the client to get the animation + // started + mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this, display.mDisplayId); + + // If we updated the launch-behind state, update the visibility of the activities after we + // fetch the visible tasks to be controlled by the animation + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS); + + // Post a timeout for the animation + mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT); + } + + @Override + public void onAnimationFinished(boolean moveHomeToTop) { + mHandler.removeCallbacks(mCancelAnimationRunnable); + synchronized (mService) { + if (mWindowManager.getRecentsAnimationController() == null) return; + + mWindowManager.inSurfaceTransaction(() -> { + mWindowManager.cleanupRecentsAnimation(); + + // Move the home stack to the front + final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity(); + if (homeActivity == null) { + return; + } + + // Restore the launched-behind state + homeActivity.mLaunchTaskBehind = false; + + if (moveHomeToTop) { + // Bring the home stack to the front + final ActivityStack homeStack = homeActivity.getStack(); + homeStack.mNoAnimActivities.add(homeActivity); + homeStack.moveToFront("RecentsAnimation.onAnimationFinished()"); + } else { + // Restore the home stack to its previous position + final ActivityDisplay display = homeActivity.getDisplay(); + display.moveHomeStackBehindStack(mRestoreHomeBehindStack); + } + + mWindowManager.prepareAppTransition(TRANSIT_NONE, false); + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false); + mStackSupervisor.resumeFocusedStackTopActivityLocked(); + }); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6a3ed14cee19..bedf043147de 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6635,7 +6635,19 @@ public class AudioService extends IAudioService.Stub // Inform AudioFlinger of our device's low RAM attribute private static void readAndSetLowRamDevice() { - int status = AudioSystem.setLowRamDevice(ActivityManager.isLowRamDeviceStatic()); + boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); + long totalMemory = 1024 * 1024 * 1024; // 1GB is the default if ActivityManager fails. + + try { + final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); + ActivityManager.getService().getMemoryInfo(info); + totalMemory = info.totalMem; + } catch (RemoteException e) { + Log.w(TAG, "Cannot obtain MemoryInfo from ActivityManager, assume low memory device"); + isLowRamDevice = true; + } + + final int status = AudioSystem.setLowRamDevice(isLowRamDevice, totalMemory); if (status != 0) { Log.w(TAG, "AudioFlinger informed of device's low RAM attribute; status " + status); } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index 9e1f6b85ce39..d24f9c985ac4 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -18,10 +18,10 @@ package com.android.server.connectivity; import com.android.internal.util.HexDump; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.connectivity.KeepalivePacketData; import com.android.server.connectivity.NetworkAgentInfo; import android.net.ConnectivityManager; import android.net.ConnectivityManager.PacketKeepalive; +import android.net.KeepalivePacketData; import android.net.LinkAddress; import android.net.NetworkAgent; import android.net.NetworkUtils; @@ -129,7 +129,7 @@ public class KeepaliveTracker { .append("->") .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)) .append(" interval=" + mInterval) - .append(" data=" + HexDump.toHexString(mPacket.data)) + .append(" packetData=" + HexDump.toHexString(mPacket.getPacket())) .append(" uid=").append(mUid).append(" pid=").append(mPid) .append(" ]") .toString(); @@ -172,7 +172,7 @@ public class KeepaliveTracker { } private int checkInterval() { - return mInterval >= 20 ? SUCCESS : ERROR_INVALID_INTERVAL; + return mInterval >= 10 ? SUCCESS : ERROR_INVALID_INTERVAL; } private int isValid() { diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index ff0572346cc4..be6c4a12d114 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -506,6 +506,7 @@ public class Tethering extends BaseNetworkObserver { Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); intent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(ConnectivityManager.EXTRA_PROVISION_CALLBACK, receiver); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final long ident = Binder.clearCallingIdentity(); try { mContext.startActivityAsUser(intent, UserHandle.CURRENT); diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 6c5bfc793330..e445d2715196 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -25,6 +25,7 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.BrightnessConfiguration; +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -57,8 +58,16 @@ class AutomaticBrightnessController { // the user is satisfied with the result before storing the sample. private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000; + // Timeout after which we remove the effects any user interactions might've had on the + // brightness mapping. This timeout doesn't start until we transition to a non-interactive + // display policy so that we don't reset while users are using their devices, but also so that + // we don't erroneously keep the short-term model if the device is dozing but the display is + // fully on. + private static final int SHORT_TERM_MODEL_TIMEOUT_MILLIS = 30000; + private static final int MSG_UPDATE_AMBIENT_LUX = 1; private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2; + private static final int MSG_RESET_SHORT_TERM_MODEL = 3; // Length of the ambient light horizon used to calculate the long term estimate of ambient // light. @@ -173,8 +182,9 @@ class AutomaticBrightnessController { // The last screen auto-brightness gamma. (For printing in dump() only.) private float mLastScreenAutoBrightnessGamma = 1.0f; - // Are we going to adjust brightness while dozing. - private boolean mDozing; + // The current display policy. This is useful, for example, for knowing when we're dozing, + // where the light sensor may not be available. + private int mDisplayPolicy = DisplayPowerRequest.POLICY_OFF; // True if we are collecting a brightness adjustment sample, along with some data // for the initial state of the sample. @@ -221,31 +231,72 @@ class AutomaticBrightnessController { } public int getAutomaticScreenBrightness() { - if (mDozing) { + if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) { return (int) (mScreenAutoBrightness * mDozeScaleFactor); } return mScreenAutoBrightness; } public void configure(boolean enable, @Nullable BrightnessConfiguration configuration, - float adjustment, boolean dozing, boolean userInitiatedChange) { + float brightness, float adjustment, int displayPolicy, boolean userInitiatedChange) { // While dozing, the application processor may be suspended which will prevent us from // receiving new information from the light sensor. On some devices, we may be able to // switch to a wake-up light sensor instead but for now we will simply disable the sensor // and hold onto the last computed screen auto brightness. We save the dozing flag for // debugging purposes. - mDozing = dozing; + boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE); boolean changed = setBrightnessConfiguration(configuration); - changed |= setLightSensorEnabled(enable && !dozing); - if (enable && !dozing && userInitiatedChange) { + changed |= setDisplayPolicy(displayPolicy); + if (userInitiatedChange && enable && !dozing) { + // Update the current brightness value. + changed |= setScreenBrightnessByUser(brightness); prepareBrightnessAdjustmentSample(); } changed |= setScreenAutoBrightnessAdjustment(adjustment); + changed |= setLightSensorEnabled(enable && !dozing); if (changed) { updateAutoBrightness(false /*sendUpdate*/); } } + private boolean setDisplayPolicy(int policy) { + if (mDisplayPolicy == policy) { + return false; + } + final int oldPolicy = mDisplayPolicy; + mDisplayPolicy = policy; + if (DEBUG) { + Slog.d(TAG, "Display policy transitioning from " + mDisplayPolicy + " to " + policy); + } + if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) { + mHandler.sendEmptyMessageDelayed(MSG_RESET_SHORT_TERM_MODEL, + SHORT_TERM_MODEL_TIMEOUT_MILLIS); + } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) { + mHandler.removeMessages(MSG_RESET_SHORT_TERM_MODEL); + } + return true; + } + + private static boolean isInteractivePolicy(int policy) { + return policy == DisplayPowerRequest.POLICY_BRIGHT + || policy == DisplayPowerRequest.POLICY_DIM + || policy == DisplayPowerRequest.POLICY_VR; + } + + private boolean setScreenBrightnessByUser(float brightness) { + if (!mAmbientLuxValid) { + // If we don't have a valid ambient lux then we don't have a valid brightness anyways, + // and we can't use this data to add a new control point to the short-term model. + return false; + } + mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness); + return true; + } + + private void resetShortTermModel() { + mBrightnessMapper.clearUserDataPoints(); + } + public boolean setBrightnessConfiguration(BrightnessConfiguration configuration) { return mBrightnessMapper.setBrightnessConfiguration(configuration); } @@ -280,7 +331,7 @@ class AutomaticBrightnessController { pw.println(" mScreenAutoBrightnessAdjustmentMaxGamma=" + mScreenAutoBrightnessAdjustmentMaxGamma); pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma); - pw.println(" mDozing=" + mDozing); + pw.println(" mDisplayPolicy=" + mDisplayPolicy); pw.println(); mBrightnessMapper.dump(pw); @@ -364,6 +415,10 @@ class AutomaticBrightnessController { if (DEBUG) { Slog.d(TAG, "setAmbientLux(" + lux + ")"); } + if (lux < 0) { + Slog.w(TAG, "Ambient lux was negative, ignoring and setting to 0."); + lux = 0; + } mAmbientLux = lux; mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux); mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux); @@ -647,6 +702,10 @@ class AutomaticBrightnessController { case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE: collectBrightnessAdjustmentSample(); break; + + case MSG_RESET_SHORT_TERM_MODEL: + resetShortTermModel(); + break; } } } diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index ac0e1b5c3550..436ebff0ad86 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -30,6 +30,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.Arrays; /** * A utility to map from an ambient brightness to a display's "backlight" brightness based on the @@ -42,6 +43,9 @@ public abstract class BrightnessMappingStrategy { private static final String TAG = "BrightnessMappingStrategy"; private static final boolean DEBUG = false; + private static final float LUX_GRAD_SMOOTHING = 0.25f; + private static final float MAX_GRAD = 1.0f; + @Nullable public static BrightnessMappingStrategy create(Resources resources) { float[] luxLevels = getLuxLevels(resources.getIntArray( @@ -169,11 +173,28 @@ public abstract class BrightnessMappingStrategy { public abstract float getBrightness(float lux); /** - * Gets the display's brightness in nits for the given backlight value. + * Converts the provided backlight value to nits if possible. * * Returns -1.0f if there's no available mapping for the backlight to nits. */ - public abstract float getNits(int backlight); + public abstract float convertToNits(int backlight); + + /** + * Adds a user interaction data point to the brightness mapping. + * + * Currently, we only keep track of one of these at a time to constrain what can happen to the + * curve. + */ + public abstract void addUserDataPoint(float lux, float brightness); + + /** + * Removes any short term adjustments made to the curve from user interactions. + * + * Note that this does *not* reset the mapping to its initial state, any brightness + * configurations that have been applied will continue to be in effect. This solely removes the + * effects of user interactions on the model. + */ + public abstract void clearUserDataPoints(); public abstract void dump(PrintWriter pw); @@ -183,6 +204,112 @@ public abstract class BrightnessMappingStrategy { return (float) brightness / PowerManager.BRIGHTNESS_ON; } + private static Spline createSpline(float[] x, float[] y) { + Spline spline = Spline.createSpline(x, y); + if (DEBUG) { + Slog.d(TAG, "Spline: " + spline); + for (float v = 1f; v < x[x.length - 1] * 1.25f; v *= 1.25f) { + Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v))); + } + } + return spline; + } + + private static Pair<float[], float[]> insertControlPoint( + float[] luxLevels, float[] brightnessLevels, float lux, float brightness) { + if (DEBUG) { + Slog.d(TAG, "Inserting new control point at (" + lux + ", " + brightness + ")"); + } + final int idx = findInsertionPoint(luxLevels, lux); + final float[] newLuxLevels; + final float[] newBrightnessLevels; + if (idx == luxLevels.length) { + newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1); + newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1); + newLuxLevels[idx] = lux; + newBrightnessLevels[idx] = brightness; + } else if (luxLevels[idx] == lux) { + newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length); + newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length); + newBrightnessLevels[idx] = brightness; + } else { + newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1); + System.arraycopy(newLuxLevels, idx, newLuxLevels, idx+1, luxLevels.length - idx); + newLuxLevels[idx] = lux; + newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1); + System.arraycopy(newBrightnessLevels, idx, newBrightnessLevels, idx+1, + brightnessLevels.length - idx); + newBrightnessLevels[idx] = brightness; + } + smoothCurve(newLuxLevels, newBrightnessLevels, idx); + return Pair.create(newLuxLevels, newBrightnessLevels); + } + + /** + * Returns the index of the first value that's less than or equal to {@code val}. + * + * This assumes that {@code arr} is sorted. If all values in {@code arr} are greater + * than val, then it will return the length of arr as the insertion point. + */ + private static int findInsertionPoint(float[] arr, float val) { + for (int i = 0; i < arr.length; i++) { + if (val <= arr[i]) { + return i; + } + } + return arr.length; + } + + private static void smoothCurve(float[] lux, float[] brightness, int idx) { + if (DEBUG) { + Slog.d(TAG, "smoothCurve(lux=" + Arrays.toString(lux) + + ", brightness=" + Arrays.toString(brightness) + + ", idx=" + idx + ")"); + } + float prevLux = lux[idx]; + float prevBrightness = brightness[idx]; + // Smooth curve for data points above the newly introduced point + for (int i = idx+1; i < lux.length; i++) { + float currLux = lux[i]; + float currBrightness = brightness[i]; + float maxBrightness = prevBrightness * permissibleRatio(currLux, prevLux); + float newBrightness = MathUtils.constrain( + currBrightness, prevBrightness, maxBrightness); + if (newBrightness == currBrightness) { + break; + } + prevLux = currLux; + prevBrightness = newBrightness; + brightness[i] = newBrightness; + } + + // Smooth curve for data points below the newly introduced point + prevLux = lux[idx]; + prevBrightness = brightness[idx]; + for (int i = idx-1; i >= 0; i--) { + float currLux = lux[i]; + float currBrightness = brightness[i]; + float minBrightness = prevBrightness * permissibleRatio(currLux, prevLux); + float newBrightness = MathUtils.constrain( + currBrightness, minBrightness, prevBrightness); + if (newBrightness == currBrightness) { + break; + } + prevLux = currLux; + prevBrightness = newBrightness; + brightness[i] = newBrightness; + } + if (DEBUG) { + Slog.d(TAG, "Smoothed Curve: lux=" + Arrays.toString(lux) + + ", brightness=" + Arrays.toString(brightness)); + } + } + + private static float permissibleRatio(float currLux, float prevLux) { + return MathUtils.exp(MAX_GRAD + * (MathUtils.log(currLux + LUX_GRAD_SMOOTHING) + - MathUtils.log(prevLux + LUX_GRAD_SMOOTHING))); + } /** * A {@link BrightnessMappingStrategy} that maps from ambient room brightness directly to the @@ -192,7 +319,14 @@ public abstract class BrightnessMappingStrategy { * configurations that are set are just ignored. */ private static class SimpleMappingStrategy extends BrightnessMappingStrategy { - private final Spline mSpline; + // Lux control points + private final float[] mLux; + // Brightness control points normalized to [0, 1] + private final float[] mBrightness; + + private Spline mSpline; + private float mUserLux; + private float mUserBrightness; public SimpleMappingStrategy(float[] lux, int[] brightness) { Preconditions.checkArgument(lux.length != 0 && brightness.length != 0, @@ -204,20 +338,16 @@ public abstract class BrightnessMappingStrategy { 0, Integer.MAX_VALUE, "brightness"); final int N = brightness.length; - float[] x = new float[N]; - float[] y = new float[N]; + mLux = new float[N]; + mBrightness = new float[N]; for (int i = 0; i < N; i++) { - x[i] = lux[i]; - y[i] = normalizeAbsoluteBrightness(brightness[i]); + mLux[i] = lux[i]; + mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]); } - mSpline = Spline.createSpline(x, y); - if (DEBUG) { - Slog.d(TAG, "Auto-brightness spline: " + mSpline); - for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { - Slog.d(TAG, String.format(" %7.1f: %7.1f", v, mSpline.interpolate(v))); - } - } + mSpline = createSpline(mLux, mBrightness); + mUserLux = -1; + mUserBrightness = -1; } @Override @@ -231,14 +361,36 @@ public abstract class BrightnessMappingStrategy { } @Override - public float getNits(int backlight) { + public float convertToNits(int backlight) { return -1.0f; } @Override + public void addUserDataPoint(float lux, float brightness) { + if (DEBUG){ + Slog.d(TAG, "addUserDataPoint(lux=" + lux + ", brightness=" + brightness + ")"); + } + Pair<float[], float[]> curve = insertControlPoint(mLux, mBrightness, lux, brightness); + mSpline = createSpline(curve.first, curve.second); + mUserLux = lux; + mUserBrightness = brightness; + } + + @Override + public void clearUserDataPoints() { + if (mUserLux != -1) { + mSpline = createSpline(mLux, mBrightness); + mUserLux = -1; + mUserBrightness = -1; + } + } + + @Override public void dump(PrintWriter pw) { pw.println("SimpleMappingStrategy"); pw.println(" mSpline=" + mSpline); + pw.println(" mUserLux=" + mUserLux); + pw.println(" mUserBrightness=" + mUserBrightness); } } @@ -261,12 +413,15 @@ public abstract class BrightnessMappingStrategy { // [0, 1.0]. private final Spline mNitsToBacklightSpline; + // The default brightness configuration. + private final BrightnessConfiguration mDefaultConfig; + // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to // a brightness in nits. - private final Spline mBacklightToNitsSpline; + private Spline mBacklightToNitsSpline; - // The default brightness configuration. - private final BrightnessConfiguration mDefaultConfig; + private float mUserLux; + private float mUserBrightness; public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits, int[] backlight) { @@ -279,6 +434,9 @@ public abstract class BrightnessMappingStrategy { Preconditions.checkArrayElementsInRange(backlight, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON, "backlight"); + mUserLux = -1; + mUserBrightness = -1; + // Setup the backlight spline final int N = nits.length; float[] normalizedBacklight = new float[N]; @@ -286,15 +444,8 @@ public abstract class BrightnessMappingStrategy { normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]); } - mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight); - mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits); - if (DEBUG) { - Slog.d(TAG, "Backlight spline: " + mNitsToBacklightSpline); - for (float v = 1f; v < nits[nits.length - 1] * 1.25f; v *= 1.25f) { - Slog.d(TAG, String.format( - " %7.1f: %7.1f", v, mNitsToBacklightSpline.interpolate(v))); - } - } + mNitsToBacklightSpline = createSpline(nits, normalizedBacklight); + mBacklightToNitsSpline = createSpline(normalizedBacklight, nits); mDefaultConfig = config; setBrightnessConfiguration(config); @@ -306,42 +457,59 @@ public abstract class BrightnessMappingStrategy { config = mDefaultConfig; } if (config.equals(mConfig)) { - if (DEBUG) { - Slog.d(TAG, "Tried to set an identical brightness config, ignoring"); - } return false; } Pair<float[], float[]> curve = config.getCurve(); - mBrightnessSpline = Spline.createSpline(curve.first /*lux*/, curve.second /*nits*/); - if (DEBUG) { - Slog.d(TAG, "Brightness spline: " + mBrightnessSpline); - final float[] lux = curve.first; - for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { - Slog.d(TAG, String.format( - " %7.1f: %7.1f", v, mBrightnessSpline.interpolate(v))); - } - } + mBrightnessSpline = createSpline(curve.first /*lux*/, curve.second /*nits*/); mConfig = config; return true; } @Override public float getBrightness(float lux) { - return mNitsToBacklightSpline.interpolate(mBrightnessSpline.interpolate(lux)); + float nits = mBrightnessSpline.interpolate(lux); + float backlight = mNitsToBacklightSpline.interpolate(nits); + return backlight; } @Override - public float getNits(int backlight) { + public float convertToNits(int backlight) { return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight)); } @Override + public void addUserDataPoint(float lux, float backlight) { + if (DEBUG){ + Slog.d(TAG, "addUserDataPoint(lux=" + lux + ", backlight=" + backlight + ")"); + } + float brightness = mBacklightToNitsSpline.interpolate(backlight); + Pair<float[], float[]> defaultCurve = mConfig.getCurve(); + Pair<float[], float[]> newCurve = + insertControlPoint(defaultCurve.first, defaultCurve.second, lux, brightness); + mBrightnessSpline = createSpline(newCurve.first, newCurve.second); + mUserLux = lux; + mUserBrightness = brightness; + } + + @Override + public void clearUserDataPoints() { + if (mUserLux != -1) { + Pair<float[], float[]> defaultCurve = mConfig.getCurve(); + mBrightnessSpline = createSpline(defaultCurve.first, defaultCurve.second); + mUserLux = -1; + mUserBrightness = -1; + } + } + + @Override public void dump(PrintWriter pw) { pw.println("PhysicalMappingStrategy"); pw.println(" mConfig=" + mConfig); pw.println(" mBrightnessSpline=" + mBrightnessSpline); pw.println(" mNitsToBacklightSpline=" + mNitsToBacklightSpline); + pw.println(" mUserLux=" + mUserLux); + pw.println(" mUserBrightness=" + mUserBrightness); } } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index c77ec20afa3b..0c2ff0519615 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1857,6 +1857,36 @@ public final class DisplayManagerService extends SystemService { } } + @Override // Binder call + public void setTemporaryBrightness(int brightness) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS, + "Permission required to set the display's brightness"); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + mDisplayPowerController.setTemporaryBrightness(brightness); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void setTemporaryAutoBrightnessAdjustment(float adjustment) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS, + "Permission required to set the display's auto brightness adjustment"); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + mDisplayPowerController.setTemporaryAutoBrightnessAdjustment(adjustment); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean validatePackageName(int uid, String packageName) { if (packageName != null) { String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index d2b8e5c677b4..056c3e641c4a 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -29,6 +29,7 @@ import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.ParceledListSlice; import android.content.res.Resources; +import android.database.ContentObserver; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -37,6 +38,7 @@ import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -44,6 +46,8 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.os.UserHandle; +import android.provider.Settings; import android.util.MathUtils; import android.util.Slog; import android.util.Spline; @@ -99,6 +103,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_SCREEN_ON_UNBLOCKED = 3; private static final int MSG_SCREEN_OFF_UNBLOCKED = 4; private static final int MSG_CONFIGURE_BRIGHTNESS = 5; + private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6; + private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -144,6 +150,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The display blanker. private final DisplayBlanker mBlanker; + // Tracker for brightness changes. + private final BrightnessTracker mBrightnessTracker; + + // Tracker for brightness settings changes. + private final SettingsObserver mSettingsObserver; + // The proximity sensor, or null if not available or needed. private Sensor mProximitySensor; @@ -159,6 +171,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The maximum allowed brightness. private final int mScreenBrightnessRangeMaximum; + // The default screen brightness. + private final int mScreenBrightnessDefault; + + // The default screen brightness for VR. + private final int mScreenBrightnessForVrDefault; + // True if auto-brightness should be used. private boolean mUseSoftwareAutoBrightnessConfig; @@ -298,20 +316,42 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The last brightness that was set by the user and not temporary. Set to -1 when a brightness // has yet to be recorded. - private int mLastBrightness; + private int mLastUserSetScreenBrightness; + + // The screen brightenss setting has changed but not taken effect yet. If this is different + // from the current screen brightness setting then this is coming from something other than us + // and should be considered a user interaction. + private int mPendingScreenBrightnessSetting; + + // The last observed screen brightness setting, either set by us or by the settings app on + // behalf of the user. + private int mCurrentScreenBrightnessSetting; + + // The temporary screen brightness. Typically set when a user is interacting with the + // brightness slider but hasn't settled on a choice yet. Set to -1 when there's no temporary + // brightness set. + private int mTemporaryScreenBrightness; + + // The current screen brightness while in VR mode. + private int mScreenBrightnessForVr; // The last auto brightness adjustment that was set by the user and not temporary. Set to // Float.NaN when an auto-brightness adjustment hasn't been recorded yet. - private float mLastAutoBrightnessAdjustment; + private float mAutoBrightnessAdjustment; + + // The pending auto brightness adjustment that will take effect on the next power state update. + private float mPendingAutoBrightnessAdjustment; + + // The temporary auto brightness adjustment. Typically set when a user is interacting with the + // adjustment slider but hasn't settled on a choice yet. Set to Float.NaN when there's no + // temporary adjustment set. + private float mTemporaryAutoBrightnessAdjustment; // Animators. private ObjectAnimator mColorFadeOnAnimator; private ObjectAnimator mColorFadeOffAnimator; private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator; - // Tracker for brightness changes - private final BrightnessTracker mBrightnessTracker; - /** * Creates the display power controller. */ @@ -320,6 +360,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call SensorManager sensorManager, DisplayBlanker blanker) { mHandler = new DisplayControllerHandler(handler.getLooper()); mBrightnessTracker = new BrightnessTracker(context, null); + mSettingsObserver = new SettingsObserver(mHandler); mCallbacks = callbacks; mBatteryStats = BatteryStatsService.getService(); @@ -343,6 +384,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenBrightnessRangeMaximum = clampAbsoluteBrightness(resources.getInteger( com.android.internal.R.integer.config_screenBrightnessSettingMaximum)); + mScreenBrightnessDefault = clampAbsoluteBrightness(resources.getInteger( + com.android.internal.R.integer.config_screenBrightnessSettingDefault)); + mScreenBrightnessForVrDefault = clampAbsoluteBrightness(resources.getInteger( + com.android.internal.R.integer.config_screenBrightnessForVrSettingDefault)); mUseSoftwareAutoBrightnessConfig = resources.getBoolean( com.android.internal.R.bool.config_automatic_brightness_available); @@ -429,8 +474,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - mLastBrightness = -1; - mLastAutoBrightnessAdjustment = Float.NaN; + mCurrentScreenBrightnessSetting = getScreenBrightnessSetting(); + mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); + mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); + mTemporaryScreenBrightness = -1; + mTemporaryAutoBrightnessAdjustment = Float.NaN; } /** @@ -553,10 +601,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } // Initialize all of the brightness tracking state - final float brightness = getNits(mPowerState.getScreenBrightness()); + final float brightness = convertToNits(mPowerState.getScreenBrightness()); if (brightness >= 0.0f) { mBrightnessTracker.start(brightness); } + + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS), + false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ), + false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL); } private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { @@ -586,7 +641,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Update the power state request. final boolean mustNotify; boolean mustInitialize = false; - boolean autoBrightnessAdjustmentChanged = false; synchronized (mLock) { mPendingUpdatePowerStateLocked = false; @@ -601,8 +655,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mPendingRequestChangedLocked = false; mustInitialize = true; } else if (mPendingRequestChangedLocked) { - autoBrightnessAdjustmentChanged = (mPowerRequest.screenAutoBrightnessAdjustment - != mPendingRequestLocked.screenAutoBrightnessAdjustment); mPowerRequest.copyFrom(mPendingRequestLocked); mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked; mPendingWaitForNegativeProximityLocked = false; @@ -691,6 +743,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call brightness = PowerManager.BRIGHTNESS_OFF; } + // Always use the VR brightness when in the VR state. + if (state == Display.STATE_VR) { + brightness = mScreenBrightnessForVr; + } + + if (brightness < 0 && mPowerRequest.screenBrightnessOverride > 0) { + brightness = mPowerRequest.screenBrightnessOverride; + } final boolean autoBrightnessEnabledInDoze = mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state); @@ -698,40 +758,56 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) && brightness < 0 && mAutomaticBrightnessController != null; - final boolean brightnessAdjustmentChanged = - !Float.isNaN(mLastAutoBrightnessAdjustment) - && mPowerRequest.screenAutoBrightnessAdjustment != mLastAutoBrightnessAdjustment; - final boolean brightnessChanged = mLastBrightness >= 0 - && mPowerRequest.screenBrightness != mLastBrightness; - - // Update the last set brightness values. - final boolean userInitiatedChange; - if (mPowerRequest.brightnessSetByUser && !mPowerRequest.brightnessIsTemporary) { - userInitiatedChange = autoBrightnessEnabled && brightnessAdjustmentChanged - || !autoBrightnessEnabled && brightnessChanged; - mLastBrightness = mPowerRequest.screenBrightness; - mLastAutoBrightnessAdjustment = mPowerRequest.screenAutoBrightnessAdjustment; - } else { - userInitiatedChange = false; + boolean brightnessIsTemporary = false; + + final boolean userSetBrightnessChanged = updateUserSetScreenBrightness(); + if (userSetBrightnessChanged) { + mTemporaryScreenBrightness = -1; } - // Configure auto-brightness. - if (mAutomaticBrightnessController != null) { - mAutomaticBrightnessController.configure(autoBrightnessEnabled, - mBrightnessConfiguration, mPowerRequest.screenAutoBrightnessAdjustment, - state != Display.STATE_ON, userInitiatedChange); + // Use the temporary screen brightness if there isn't an override, either from + // WindowManager or based on the display state. + if (mTemporaryScreenBrightness > 0) { + brightness = mTemporaryScreenBrightness; + brightnessIsTemporary = true; + } + + final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment(); + if (autoBrightnessAdjustmentChanged) { + mTemporaryAutoBrightnessAdjustment = Float.NaN; + } + + // Use the autobrightness adjustment override if set. + final float autoBrightnessAdjustment; + if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) { + autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment; + brightnessIsTemporary = true; + } else { + autoBrightnessAdjustment = mAutoBrightnessAdjustment; } // Apply brightness boost. - // We do this here after configuring auto-brightness so that we don't - // disable the light sensor during this temporary state. That way when - // boost ends we will be able to resume normal auto-brightness behavior - // without any delay. + // We do this here after deciding whether auto-brightness is enabled so that we don't + // disable the light sensor during this temporary state. That way when boost ends we will + // be able to resume normal auto-brightness behavior without any delay. if (mPowerRequest.boostScreenBrightness && brightness != PowerManager.BRIGHTNESS_OFF) { brightness = PowerManager.BRIGHTNESS_ON; } + // If the brightness is already set then it's been overriden by something other than the + // user, or is a temporary adjustment. + final boolean userInitiatedChange = brightness < 0 + && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged); + + // Configure auto-brightness. + if (mAutomaticBrightnessController != null) { + mAutomaticBrightnessController.configure(autoBrightnessEnabled, + mBrightnessConfiguration, + mLastUserSetScreenBrightness / (float) PowerManager.BRIGHTNESS_ON, + autoBrightnessAdjustment, mPowerRequest.policy, userInitiatedChange); + } + // Apply auto-brightness. boolean slowChange = false; if (brightness < 0) { @@ -744,6 +820,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) { slowChange = true; // slowly adapt to auto-brightness } + // Tell the rest of the system about the new brightness. Note that we do this + // before applying the low power or dim transformations so that the slider + // accurately represents the full possible range, even if they range changes what + // it means in absolute terms. + putScreenBrightnessSetting(brightness); mAppliedAutoBrightness = true; } else { mAppliedAutoBrightness = false; @@ -762,9 +843,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // provide a nominal default value for the case where auto-brightness // is not ready yet. if (brightness < 0) { - brightness = clampScreenBrightness(mPowerRequest.screenBrightness); + brightness = clampScreenBrightness(mLastUserSetScreenBrightness); } + // Apply dimming by at least some minimum amount when user activity // timeout is about to expire. if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) { @@ -833,19 +915,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final boolean isDisplayContentVisible = mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f; if (initialRampSkip || hasBrightnessBuckets - || wasOrWillBeInVr || !isDisplayContentVisible) { + || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) { animateScreenBrightness(brightness, 0); } else { animateScreenBrightness(brightness, slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast); } - final float brightnessInNits = getNits(brightness); - if (!mPowerRequest.brightnessIsTemporary && brightnessInNits >= 0.0f) { - // We only want to track changes made by the user and on devices that can actually - // map the display backlight values into a physical brightness unit. - mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiatedChange); + if (!brightnessIsTemporary) { + notifyBrightnessChanged(brightness, userInitiatedChange); } + } // Determine whether the display is ready for use in the newly requested state. @@ -913,6 +993,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call msg.sendToTarget(); } + public void setTemporaryBrightness(int brightness) { + Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS, + brightness, 0 /*unused*/); + msg.sendToTarget(); + } + + public void setTemporaryAutoBrightnessAdjustment(float adjustment) { + Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT, + Float.floatToIntBits(adjustment), 0 /*unused*/); + msg.sendToTarget(); + } + private void blockScreenOn() { if (mPendingScreenOnUnblocker == null) { Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); @@ -1304,9 +1396,79 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHandler.post(mOnStateChangedRunnable); } - private float getNits(int backlight) { + private void handleSettingsChange() { + mPendingScreenBrightnessSetting = getScreenBrightnessSetting(); + mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); + // We don't bother with a pending variable for VR screen brightness since we just + // immediately adapt to it. + mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); + sendUpdatePowerState(); + } + + private float getAutoBrightnessAdjustmentSetting() { + final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT); + return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj); + } + + private int getScreenBrightnessSetting() { + final int brightness = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, mScreenBrightnessDefault, + UserHandle.USER_CURRENT); + return clampAbsoluteBrightness(brightness); + } + + private int getScreenBrightnessForVrSetting() { + final int brightness = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FOR_VR, mScreenBrightnessForVrDefault, + UserHandle.USER_CURRENT); + return clampAbsoluteBrightness(brightness); + } + + private void putScreenBrightnessSetting(int brightness) { + mCurrentScreenBrightnessSetting = brightness; + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, brightness, + UserHandle.USER_CURRENT); + } + + private boolean updateAutoBrightnessAdjustment() { + if (Float.isNaN(mPendingAutoBrightnessAdjustment)) { + return false; + } + if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) { + return false; + } + mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment; + mPendingAutoBrightnessAdjustment = Float.NaN; + return true; + } + + private boolean updateUserSetScreenBrightness() { + if (mPendingScreenBrightnessSetting < 0) { + return false; + } + if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) { + return false; + } + mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting; + mPendingScreenBrightnessSetting = -1; + return true; + } + + private void notifyBrightnessChanged(int brightness, boolean userInitiated) { + final float brightnessInNits = convertToNits(brightness); + if (brightnessInNits >= 0.0f) { + // We only want to track changes on devices that can actually map the display backlight + // values into a physical brightness unit since the value provided by the API is in + // nits and not using the arbitrary backlight units. + mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated); + } + } + + private float convertToNits(int backlight) { if (mBrightnessMapper != null) { - return mBrightnessMapper.getNits(backlight); + return mBrightnessMapper.convertToNits(backlight); } else { return -1.0f; } @@ -1390,8 +1552,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mPendingProximityDebounceTime=" + TimeUtils.formatUptime(mPendingProximityDebounceTime)); pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity); - pw.println(" mLastBrightness=" + mLastBrightness); - pw.println(" mLastAutoBrightnessAdjustment=" + mLastAutoBrightnessAdjustment); + pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness); + pw.println(" mCurrentScreenBrightnessSetting=" + mCurrentScreenBrightnessSetting); + pw.println(" mPendingScreenBrightnessSetting=" + mPendingScreenBrightnessSetting); + pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); + pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment); pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness); pw.println(" mAppliedDimming=" + mAppliedDimming); pw.println(" mAppliedLowPower=" + mAppliedLowPower); @@ -1456,6 +1621,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return MathUtils.constrain(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); } + private static float clampAutoBrightnessAdjustment(float value) { + return MathUtils.constrain(value, -1.0f, 1.0f); + } + private final class DisplayControllerHandler extends Handler { public DisplayControllerHandler(Looper looper) { super(looper, null, true /*async*/); @@ -1488,6 +1657,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessConfiguration = (BrightnessConfiguration)msg.obj; updatePowerState(); break; + + case MSG_SET_TEMPORARY_BRIGHTNESS: + // TODO: Should we have a a timeout for the temporary brightness? + mTemporaryScreenBrightness = msg.arg1; + updatePowerState(); + break; + + case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT: + mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1); + updatePowerState(); + break; } } } @@ -1509,6 +1689,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } }; + + private final class SettingsObserver extends ContentObserver { + public SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + handleSettingsChange(); + } + } + private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener { @Override public void onScreenOn() { diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 3e1958d9edfc..b5f94b1ce384 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -45,6 +45,7 @@ import android.hardware.fingerprint.IFingerprintService; import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback; import android.hardware.fingerprint.IFingerprintServiceReceiver; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.DeadObjectException; import android.os.Environment; @@ -58,6 +59,7 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.security.KeyStore; @@ -1419,8 +1421,17 @@ public class FingerprintService extends SystemService implements IHwBinder.Death try { userId = getUserOrWorkProfileId(clientPackage, userId); if (userId != mCurrentUserId) { - final File systemDir = Environment.getUserSystemDirectory(userId); - final File fpDir = new File(systemDir, FP_DATA_DIR); + File baseDir; + if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1 + && !SystemProperties.getBoolean( + "ro.treble.supports_vendor_data", false)) { + // TODO(b/72405644) remove the override when possible. + baseDir = Environment.getUserSystemDirectory(userId); + } else { + baseDir = Environment.getDataVendorDeDirectory(userId); + } + + File fpDir = new File(baseDir, FP_DATA_DIR); if (!fpDir.exists()) { if (!fpDir.mkdir()) { Slog.v(TAG, "Cannot make directory: " + fpDir.getAbsolutePath()); @@ -1434,6 +1445,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death return; } } + daemon.setActiveGroup(userId, fpDir.getAbsolutePath()); mCurrentUserId = userId; } diff --git a/services/core/java/com/android/server/job/JobSchedulerInternal.java b/services/core/java/com/android/server/job/JobSchedulerInternal.java index c97eeaf30ab3..4e7490858fc8 100644 --- a/services/core/java/com/android/server/job/JobSchedulerInternal.java +++ b/services/core/java/com/android/server/job/JobSchedulerInternal.java @@ -59,7 +59,6 @@ public interface JobSchedulerInternal { /** * Stats about the first load after boot and the most recent save. - * STOPSHIP Remove it and the relevant code once b/64536115 is fixed. */ public class JobStorePersistStats { public int countAllJobsLoaded = -1; diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index c59c5f61bf91..824b148789e8 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -23,7 +23,7 @@ import android.content.Context; import android.media.IMediaSession2; import android.media.MediaController2; import android.media.MediaSession2; -import android.media.SessionToken; +import android.media.SessionToken2; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -96,7 +96,7 @@ class MediaSession2Record { // TODO(jaewan): also add uid for multiuser support @CallSuper public @Nullable - SessionToken createSessionToken(int sessionPid, String packageName, String id, + SessionToken2 createSessionToken(int sessionPid, String packageName, String id, IMediaSession2 sessionBinder) { if (mController != null) { if (mSessionPid != sessionPid) { @@ -130,12 +130,12 @@ class MediaSession2Record { */ MediaController2 onCreateMediaController( String packageName, String id, IMediaSession2 sessionBinder) { - SessionToken token = new SessionToken( - SessionToken.TYPE_SESSION, packageName, id, null, sessionBinder); + SessionToken2 token = new SessionToken2( + SessionToken2.TYPE_SESSION, packageName, id, null, sessionBinder); return createMediaController(token); } - final MediaController2 createMediaController(SessionToken token) { + final MediaController2 createMediaController(SessionToken2 token) { mControllerCallback = new ControllerCallback(); return new MediaController2(mContext, token, mControllerCallback, mMainExecutor); } @@ -143,7 +143,7 @@ class MediaSession2Record { /** * @return controller. Note that framework can only call oneway calls. */ - public SessionToken getToken() { + public SessionToken2 getToken() { return mController == null ? null : mController.getSessionToken(); } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index c9c7d04e6e14..c7f6014fa1b0 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -40,7 +40,7 @@ import android.media.IMediaSession2; import android.media.IRemoteVolumeController; import android.media.MediaLibraryService2; import android.media.MediaSessionService2; -import android.media.SessionToken; +import android.media.SessionToken2; import android.media.session.IActiveSessionsListener; import android.media.session.ICallback; import android.media.session.IOnMediaKeyListener; @@ -481,7 +481,7 @@ public class MediaSessionService extends SystemService implements Monitor { + serviceInfo.packageName + "/" + serviceInfo.name); } else { int type = (libraryServices.contains(services.get(i))) - ? SessionToken.TYPE_LIBRARY_SERVICE : SessionToken.TYPE_SESSION_SERVICE; + ? SessionToken2.TYPE_LIBRARY_SERVICE : SessionToken2.TYPE_SESSION_SERVICE; MediaSessionService2Record record = new MediaSessionService2Record(getContext(), mSessionDestroyedListener, type, serviceInfo.packageName, serviceInfo.name, id); @@ -1416,7 +1416,7 @@ public class MediaSessionService extends SystemService implements Monitor { int pid = Binder.getCallingPid(); MediaSession2Record record; - SessionToken token; + SessionToken2 token; // TODO(jaewan): Add sanity check for the token if calling package is from uid. synchronized (mLock) { record = getSessionRecordLocked(sessionPackage, id); @@ -1448,7 +1448,7 @@ public class MediaSessionService extends SystemService implements Monitor { boolean isActive = record.getSessionPid() != 0; if ((!activeSessionOnly && isSessionService) || (!sessionServiceOnly && isActive)) { - SessionToken token = record.getToken(); + SessionToken2 token = record.getToken(); if (token != null) { tokens.add(token.toBundle()); } else { diff --git a/services/core/java/com/android/server/media/MediaSessionService2Record.java b/services/core/java/com/android/server/media/MediaSessionService2Record.java index bd97dbce57d1..d033f552124f 100644 --- a/services/core/java/com/android/server/media/MediaSessionService2Record.java +++ b/services/core/java/com/android/server/media/MediaSessionService2Record.java @@ -19,7 +19,7 @@ package com.android.server.media; import android.content.Context; import android.media.IMediaSession2; import android.media.MediaController2; -import android.media.SessionToken; +import android.media.SessionToken2; import android.media.MediaSessionService2; /** @@ -33,7 +33,7 @@ class MediaSessionService2Record extends MediaSession2Record { private final int mType; private final String mServiceName; - private final SessionToken mToken; + private final SessionToken2 mToken; public MediaSessionService2Record(Context context, SessionDestroyedListener sessionDestroyedListener, int type, @@ -41,7 +41,7 @@ class MediaSessionService2Record extends MediaSession2Record { super(context, sessionDestroyedListener); mType = type; mServiceName = serviceName; - mToken = new SessionToken(mType, packageName, id, mServiceName, null); + mToken = new SessionToken2(mType, packageName, id, mServiceName, null); } /** @@ -51,7 +51,7 @@ class MediaSessionService2Record extends MediaSession2Record { @Override MediaController2 onCreateMediaController( String packageName, String id, IMediaSession2 sessionBinder) { - SessionToken token = new SessionToken(mType, packageName, id, mServiceName, sessionBinder); + SessionToken2 token = new SessionToken2(mType, packageName, id, mServiceName, sessionBinder); return createMediaController(token); } @@ -59,7 +59,7 @@ class MediaSessionService2Record extends MediaSession2Record { * @return token with no session binder information. */ @Override - public SessionToken getToken() { + public SessionToken2 getToken() { return mToken; } } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 321af43d9c3d..7600e81cd1c0 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -283,7 +283,18 @@ final class OverlayManagerServiceImpl { } void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) { - Slog.wtf(TAG, "onOverlayPackageRemoved called, but only pre-installed overlays supported"); + try { + final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId); + if (mSettings.remove(packageName, userId)) { + removeIdmapIfPossible(overlayInfo); + if (overlayInfo.isEnabled()) { + // Only trigger updates if the overlay was enabled. + mListener.onOverlaysChanged(overlayInfo.targetPackageName, userId); + } + } + } catch (OverlayManagerSettings.BadKeyException e) { + Slog.e(TAG, "failed to remove overlay", e); + } } OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) { diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java index 7d00423a2c41..17b38deb27c1 100644 --- a/services/core/java/com/android/server/om/OverlayManagerSettings.java +++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java @@ -104,7 +104,7 @@ final class OverlayManagerSettings { return true; } - OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) + @NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) throws BadKeyException { final int idx = select(packageName, userId); if (idx < 0) { diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index cdc79c7741e1..c04cdf66bf30 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -35,6 +35,8 @@ import com.android.server.SystemService; import dalvik.system.VMRuntime; +import java.io.FileDescriptor; + public class Installer extends SystemService { private static final String TAG = "Installer"; @@ -478,6 +480,16 @@ public class Installer extends SystemService { } } + public void installApkVerity(String filePath, FileDescriptor verityInput) + throws InstallerException { + if (!checkBeforeRemote()) return; + try { + mInstalld.installApkVerity(filePath, verityInput); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid, String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException { for (int i = 0; i < isas.length; i++) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a73ad8dc88d0..5dfd3ae4b8e8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -52,11 +52,9 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_L import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE; -import static android.content.pm.PackageManager.INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; -import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED; import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_INTERNAL; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -314,6 +312,7 @@ import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPerm import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback; import com.android.server.pm.permission.PermissionsState; import com.android.server.pm.permission.PermissionsState.PermissionState; +import com.android.server.security.VerityUtils; import com.android.server.storage.DeviceStorageMonitorInternal; import dalvik.system.CloseGuard; @@ -450,7 +449,6 @@ public class PackageManagerService extends IPackageManager.Stub static final int SCAN_NEW_INSTALL = 1<<2; static final int SCAN_UPDATE_TIME = 1<<3; static final int SCAN_BOOTING = 1<<4; - static final int SCAN_TRUSTED_OVERLAY = 1<<5; static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<6; static final int SCAN_REQUIRE_KNOWN = 1<<7; static final int SCAN_MOVE = 1<<8; @@ -473,7 +471,6 @@ public class PackageManagerService extends IPackageManager.Stub SCAN_NEW_INSTALL, SCAN_UPDATE_TIME, SCAN_BOOTING, - SCAN_TRUSTED_OVERLAY, SCAN_DELETE_DATA_ON_FAILURES, SCAN_REQUIRE_KNOWN, SCAN_MOVE, @@ -776,7 +773,7 @@ public class PackageManagerService extends IPackageManager.Stub Collection<PackageParser.Package> allPackages, String targetPackageName) { List<PackageParser.Package> overlayPackages = null; for (PackageParser.Package p : allPackages) { - if (targetPackageName.equals(p.mOverlayTarget) && p.mIsStaticOverlay) { + if (targetPackageName.equals(p.mOverlayTarget) && p.mOverlayIsStatic) { if (overlayPackages == null) { overlayPackages = new ArrayList<PackageParser.Package>(); } @@ -860,7 +857,7 @@ public class PackageManagerService extends IPackageManager.Stub void findStaticOverlayPackages() { synchronized (mPackages) { for (PackageParser.Package p : mPackages.values()) { - if (p.mIsStaticOverlay) { + if (p.mOverlayIsStatic) { if (mOverlayPackages == null) { mOverlayPackages = new ArrayList<PackageParser.Package>(); } @@ -2561,7 +2558,7 @@ public class PackageManagerService extends IPackageManager.Stub | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_AS_SYSTEM - | SCAN_TRUSTED_OVERLAY, + | SCAN_AS_VENDOR, 0); mParallelPackageParserCallback.findStaticOverlayPackages(); @@ -10616,7 +10613,6 @@ Slog.e("TODD", } } } - pkg.mTrustedOverlay = (scanFlags & SCAN_TRUSTED_OVERLAY) != 0; if ((scanFlags & SCAN_AS_PRIVILEGED) != 0) { pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; @@ -10638,6 +10634,14 @@ Slog.e("TODD", } } + private static @NonNull <T> T assertNotNull(@Nullable T object, String message) + throws PackageManagerException { + if (object == null) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, message); + } + return object; + } + /** * Asserts the parsed package is valid according to the given policy. If the * package is invalid, for whatever reason, throws {@link PackageManagerException}. @@ -10911,6 +10915,50 @@ Slog.e("TODD", } } } + + // Apply policies specific for runtime resource overlays (RROs). + if (pkg.mOverlayTarget != null) { + // System overlays have some restrictions on their use of the 'static' state. + if ((scanFlags & SCAN_AS_SYSTEM) != 0) { + // We are scanning a system overlay. This can be the first scan of the + // system/vendor/oem partition, or an update to the system overlay. + if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + // This must be an update to a system overlay. + final PackageSetting previousPkg = assertNotNull( + mSettings.getPackageLPr(pkg.packageName), + "previous package state not present"); + + // Static overlays cannot be updated. + if (previousPkg.pkg.mOverlayIsStatic) { + throw new PackageManagerException("Overlay " + pkg.packageName + + " is static and cannot be upgraded."); + // Non-static overlays cannot be converted to static overlays. + } else if (pkg.mOverlayIsStatic) { + throw new PackageManagerException("Overlay " + pkg.packageName + + " cannot be upgraded into a static overlay."); + } + } + } else { + // The overlay is a non-system overlay. Non-system overlays cannot be static. + if (pkg.mOverlayIsStatic) { + throw new PackageManagerException("Overlay " + pkg.packageName + + " is static but not pre-installed."); + } + + // The only case where we allow installation of a non-system overlay is when + // its signature is signed with the platform certificate. + PackageSetting platformPkgSetting = mSettings.getPackageLPr("android"); + if ((platformPkgSetting.signatures.mSigningDetails + != PackageParser.SigningDetails.UNKNOWN) + && (compareSignatures( + platformPkgSetting.signatures.mSigningDetails.signatures, + pkg.mSigningDetails.signatures) + != PackageManager.SIGNATURE_MATCH)) { + throw new PackageManagerException("Overlay " + pkg.packageName + + " must be signed with the platform certificate."); + } + } + } } } @@ -16987,6 +17035,43 @@ Slog.e("TODD", return; } + if (PackageManagerServiceUtils.isApkVerityEnabled()) { + String apkPath = null; + synchronized (mPackages) { + // Note that if the attacker managed to skip verify setup, for example by tampering + // with the package settings, upon reboot we will do full apk verification when + // verity is not detected. + final PackageSetting ps = mSettings.mPackages.get(pkgName); + if (ps != null && ps.isPrivileged()) { + apkPath = pkg.baseCodePath; + } + } + + if (apkPath != null) { + final VerityUtils.SetupResult result = + VerityUtils.generateApkVeritySetupData(apkPath); + if (result.isOk()) { + if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath); + FileDescriptor fd = result.getUnownedFileDescriptor(); + try { + mInstaller.installApkVerity(apkPath, fd); + } catch (InstallerException e) { + res.setError(INSTALL_FAILED_INTERNAL_ERROR, + "Failed to set up verity: " + e); + return; + } finally { + IoUtils.closeQuietly(fd); + } + } else if (result.isFailed()) { + res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Failed to generate verity"); + return; + } else { + // Do nothing if verity is skipped. Will fall back to full apk verification on + // reboot. + } + } + } + if (!instantApp) { startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg); } else { @@ -17022,7 +17107,7 @@ Slog.e("TODD", // Prepare the application profiles for the new code paths. // This needs to be done before invoking dexopt so that any install-time profile // can be used for optimizations. - mArtManagerService.prepareAppProfiles(pkg, args.user.getIdentifier()); + mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(args.user.getIdentifier())); // Check whether we need to dexopt the app. // diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 021c4b8a24c1..bf3eb8eaae1f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -579,7 +579,6 @@ public class PackageManagerServiceUtils { return true; } - /** * Checks the signing certificates to see if the provided certificate is a member. Invalid for * {@code SigningDetails} with multiple signing certificates. @@ -642,10 +641,14 @@ public class PackageManagerServiceUtils { return false; } + /** Returns true if APK Verity is enabled. */ + static boolean isApkVerityEnabled() { + return SystemProperties.getInt("ro.apk_verity.mode", 0) != 0; + } + /** Returns true to force apk verification if the updated package (in /data) is a priv app. */ static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) { - return disabledPs != null && disabledPs.isPrivileged() && - SystemProperties.getInt("ro.apk_verity.mode", 0) != 0; + return disabledPs != null && disabledPs.isPrivileged() && isApkVerityEnabled(); } /** diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 92d159b8ec04..81786890a436 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -245,6 +245,14 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { */ public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) { final int appId = UserHandle.getAppId(pkg.applicationInfo.uid); + if (user < 0) { + Slog.wtf(TAG, "Invalid user id: " + user); + return; + } + if (appId < 0) { + Slog.wtf(TAG, "Invalid app id: " + appId); + return; + } try { ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg); for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) { @@ -267,6 +275,15 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { } /** + * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}. + */ + public void prepareAppProfiles(PackageParser.Package pkg, int[] user) { + for (int i = 0; i < user.length; i++) { + prepareAppProfiles(pkg, user[i]); + } + } + + /** * Build the profiles names for all the package code paths (excluding resource only paths). * Return the map [code path -> profile name]. */ diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index fbdedceabb3b..cf36166757e2 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -70,6 +70,7 @@ import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; import android.util.EventLog; import android.util.KeyValueListParser; +import android.util.MathUtils; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; @@ -458,19 +459,11 @@ public final class PowerManagerService extends SystemService private int mScreenBrightnessSettingMinimum; private int mScreenBrightnessSettingMaximum; private int mScreenBrightnessSettingDefault; - private int mScreenBrightnessForVrSettingDefault; // The screen brightness setting, from 0 to 255. // Use -1 if no value has been set. private int mScreenBrightnessSetting; - // The screen brightness setting, from 0 to 255, to be used while in VR Mode. - private int mScreenBrightnessForVrSetting; - - // The screen auto-brightness adjustment setting, from -1 to 1. - // Use 0 if there is no adjustment. - private float mScreenAutoBrightnessAdjustmentSetting; - // The screen brightness mode. // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants. private int mScreenBrightnessModeSetting; @@ -493,17 +486,6 @@ public final class PowerManagerService extends SystemService // Use -1 to disable. private long mUserActivityTimeoutOverrideFromWindowManager = -1; - // The screen brightness setting override from the settings application - // to temporarily adjust the brightness until next updated, - // Use -1 to disable. - private int mTemporaryScreenBrightnessSettingOverride = -1; - - // The screen brightness adjustment setting override from the settings - // application to temporarily adjust the auto-brightness adjustment factor - // until next updated, in the range -1..1. - // Use NaN to disable. - private float mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN; - // The screen state to use while dozing. private int mDozeScreenStateOverrideFromDreamManager = Display.STATE_UNKNOWN; @@ -774,7 +756,6 @@ public final class PowerManagerService extends SystemService mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting(); mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting(); - mScreenBrightnessForVrSettingDefault = pm.getDefaultScreenBrightnessForVrSetting(); SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper()); @@ -837,12 +818,6 @@ public final class PowerManagerService extends SystemService Settings.Global.STAY_ON_WHILE_PLUGGED_IN), false, mSettingsObserver, UserHandle.USER_ALL); resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS), - false, mSettingsObserver, UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.SCREEN_BRIGHTNESS_FOR_VR), - false, mSettingsObserver, UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.SCREEN_BRIGHTNESS_MODE), false, mSettingsObserver, UserHandle.USER_ALL); resolver.registerContentObserver(Settings.System.getUriFor( @@ -978,29 +953,6 @@ public final class PowerManagerService extends SystemService SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue); } - final int oldScreenBrightnessSetting = getCurrentBrightnessSettingLocked(); - - mScreenBrightnessForVrSetting = Settings.System.getIntForUser(resolver, - Settings.System.SCREEN_BRIGHTNESS_FOR_VR, mScreenBrightnessForVrSettingDefault, - UserHandle.USER_CURRENT); - - mScreenBrightnessSetting = Settings.System.getIntForUser(resolver, - Settings.System.SCREEN_BRIGHTNESS, mScreenBrightnessSettingDefault, - UserHandle.USER_CURRENT); - - if (oldScreenBrightnessSetting != getCurrentBrightnessSettingLocked()) { - mTemporaryScreenBrightnessSettingOverride = -1; - } - - final float oldScreenAutoBrightnessAdjustmentSetting = - mScreenAutoBrightnessAdjustmentSetting; - mScreenAutoBrightnessAdjustmentSetting = Settings.System.getFloatForUser(resolver, - Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, - UserHandle.USER_CURRENT); - if (oldScreenAutoBrightnessAdjustmentSetting != mScreenAutoBrightnessAdjustmentSetting) { - mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN; - } - mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT); @@ -1019,10 +971,6 @@ public final class PowerManagerService extends SystemService mDirty |= DIRTY_SETTINGS; } - private int getCurrentBrightnessSettingLocked() { - return mIsVrModeEnabled ? mScreenBrightnessForVrSetting : mScreenBrightnessSetting; - } - private void postAfterBootCompleted(Runnable r) { if (mBootCompleted) { BackgroundThread.getHandler().post(r); @@ -2450,53 +2398,24 @@ public final class PowerManagerService extends SystemService mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(); // Determine appropriate screen brightness and auto-brightness adjustments. - boolean brightnessSetByUser = true; - int screenBrightness = mScreenBrightnessSettingDefault; - float screenAutoBrightnessAdjustment = 0.0f; - boolean autoBrightness = (mScreenBrightnessModeSetting == - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); - boolean brightnessIsTemporary = false; + final boolean autoBrightness; + final int screenBrightnessOverride; if (!mBootCompleted) { // Keep the brightness steady during boot. This requires the // bootloader brightness and the default brightness to be identical. autoBrightness = false; - brightnessSetByUser = false; - } else if (mIsVrModeEnabled) { - screenBrightness = mScreenBrightnessForVrSetting; - autoBrightness = false; + screenBrightnessOverride = mScreenBrightnessSettingDefault; } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) { - screenBrightness = mScreenBrightnessOverrideFromWindowManager; autoBrightness = false; - brightnessSetByUser = false; - } else if (isValidBrightness(mTemporaryScreenBrightnessSettingOverride)) { - screenBrightness = mTemporaryScreenBrightnessSettingOverride; - brightnessIsTemporary = true; - } else if (isValidBrightness(mScreenBrightnessSetting)) { - screenBrightness = mScreenBrightnessSetting; - } - if (autoBrightness) { - screenBrightness = mScreenBrightnessSettingDefault; - if (isValidAutoBrightnessAdjustment( - mTemporaryScreenAutoBrightnessAdjustmentSettingOverride)) { - screenAutoBrightnessAdjustment = - mTemporaryScreenAutoBrightnessAdjustmentSettingOverride; - brightnessIsTemporary = true; - } else if (isValidAutoBrightnessAdjustment( - mScreenAutoBrightnessAdjustmentSetting)) { - screenAutoBrightnessAdjustment = mScreenAutoBrightnessAdjustmentSetting; - } + screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager; + } else { + autoBrightness = (mScreenBrightnessModeSetting == + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + screenBrightnessOverride = -1; } - screenBrightness = Math.max(Math.min(screenBrightness, - mScreenBrightnessSettingMaximum), mScreenBrightnessSettingMinimum); - screenAutoBrightnessAdjustment = Math.max(Math.min( - screenAutoBrightnessAdjustment, 1.0f), -1.0f); // Update display power request. - mDisplayPowerRequest.screenBrightness = screenBrightness; - mDisplayPowerRequest.screenAutoBrightnessAdjustment = - screenAutoBrightnessAdjustment; - mDisplayPowerRequest.brightnessSetByUser = brightnessSetByUser; - mDisplayPowerRequest.brightnessIsTemporary = brightnessIsTemporary; + mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride; mDisplayPowerRequest.useAutoBrightness = autoBrightness; mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked(); mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness(); @@ -2534,6 +2453,8 @@ public final class PowerManagerService extends SystemService + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + ", mBootCompleted=" + mBootCompleted + + ", screenBrightnessOverride=" + screenBrightnessOverride + + ", useAutoBrightness=" + autoBrightness + ", mScreenBrightnessBoostInProgress=" + mScreenBrightnessBoostInProgress + ", mIsVrModeEnabled= " + mIsVrModeEnabled + ", sQuiescent=" + sQuiescent); @@ -2573,11 +2494,6 @@ public final class PowerManagerService extends SystemService return value >= 0 && value <= 255; } - private static boolean isValidAutoBrightnessAdjustment(float value) { - // Handles NaN by always returning false. - return value >= -1.0f && value <= 1.0f; - } - @VisibleForTesting int getDesiredScreenPolicyLocked() { if (mWakefulness == WAKEFULNESS_ASLEEP || sQuiescent) { @@ -3247,28 +3163,6 @@ public final class PowerManagerService extends SystemService } } - private void setTemporaryScreenBrightnessSettingOverrideInternal(int brightness) { - synchronized (mLock) { - if (mTemporaryScreenBrightnessSettingOverride != brightness) { - mTemporaryScreenBrightnessSettingOverride = brightness; - mDirty |= DIRTY_SETTINGS; - updatePowerStateLocked(); - } - } - } - - private void setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(float adj) { - synchronized (mLock) { - // Note: This condition handles NaN because NaN is not equal to any other - // value, including itself. - if (mTemporaryScreenAutoBrightnessAdjustmentSettingOverride != adj) { - mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = adj; - mDirty |= DIRTY_SETTINGS; - updatePowerStateLocked(); - } - } - } - private void setDozeOverrideFromDreamManagerInternal( int screenState, int screenBrightness) { synchronized (mLock) { @@ -3478,8 +3372,6 @@ public final class PowerManagerService extends SystemService + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")"); pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting); pw.println(" mScreenBrightnessSetting=" + mScreenBrightnessSetting); - pw.println(" mScreenAutoBrightnessAdjustmentSetting=" - + mScreenAutoBrightnessAdjustmentSetting); pw.println(" mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting); pw.println(" mScreenBrightnessOverrideFromWindowManager=" + mScreenBrightnessOverrideFromWindowManager); @@ -3487,10 +3379,6 @@ public final class PowerManagerService extends SystemService + mUserActivityTimeoutOverrideFromWindowManager); pw.println(" mUserInactiveOverrideFromWindowManager=" + mUserInactiveOverrideFromWindowManager); - pw.println(" mTemporaryScreenBrightnessSettingOverride=" - + mTemporaryScreenBrightnessSettingOverride); - pw.println(" mTemporaryScreenAutoBrightnessAdjustmentSettingOverride=" - + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride); pw.println(" mDozeScreenStateOverrideFromDreamManager=" + mDozeScreenStateOverrideFromDreamManager); pw.println(" mDozeScreenBrightnessOverrideFromDreamManager=" @@ -3498,9 +3386,6 @@ public final class PowerManagerService extends SystemService pw.println(" mScreenBrightnessSettingMinimum=" + mScreenBrightnessSettingMinimum); pw.println(" mScreenBrightnessSettingMaximum=" + mScreenBrightnessSettingMaximum); pw.println(" mScreenBrightnessSettingDefault=" + mScreenBrightnessSettingDefault); - pw.println(" mScreenBrightnessForVrSettingDefault=" - + mScreenBrightnessForVrSettingDefault); - pw.println(" mScreenBrightnessForVrSetting=" + mScreenBrightnessForVrSetting); pw.println(" mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled); pw.println(" mIsVrModeEnabled=" + mIsVrModeEnabled); pw.println(" mForegroundProfile=" + mForegroundProfile); @@ -3812,13 +3697,6 @@ public final class PowerManagerService extends SystemService proto.end(stayOnWhilePluggedInToken); proto.write( - PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_SETTING, - mScreenBrightnessSetting); - proto.write( - PowerServiceSettingsAndConfigurationDumpProto - .SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_SETTING, - mScreenAutoBrightnessAdjustmentSetting); - proto.write( PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_MODE_SETTING, mScreenBrightnessModeSetting); proto.write( @@ -3835,14 +3713,6 @@ public final class PowerManagerService extends SystemService mUserInactiveOverrideFromWindowManager); proto.write( PowerServiceSettingsAndConfigurationDumpProto - .TEMPORARY_SCREEN_BRIGHTNESS_SETTING_OVERRIDE, - mTemporaryScreenBrightnessSettingOverride); - proto.write( - PowerServiceSettingsAndConfigurationDumpProto - .TEMPORARY_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_SETTING_OVERRIDE, - mTemporaryScreenAutoBrightnessAdjustmentSettingOverride); - proto.write( - PowerServiceSettingsAndConfigurationDumpProto .DOZE_SCREEN_STATE_OVERRIDE_FROM_DREAM_MANAGER, mDozeScreenStateOverrideFromDreamManager); proto.write( @@ -3866,16 +3736,9 @@ public final class PowerManagerService extends SystemService PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto .SETTING_DEFAULT, mScreenBrightnessSettingDefault); - proto.write( - PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto - .SETTING_FOR_VR_DEFAULT, - mScreenBrightnessForVrSettingDefault); proto.end(screenBrightnessSettingLimitsToken); proto.write( - PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_FOR_VR_SETTING, - mScreenBrightnessForVrSetting); - proto.write( PowerServiceSettingsAndConfigurationDumpProto.IS_DOUBLE_TAP_WAKE_ENABLED, mDoubleTapWakeEnabled); proto.write( @@ -4709,56 +4572,6 @@ public final class PowerManagerService extends SystemService } /** - * Used by the settings application and brightness control widgets to - * temporarily override the current screen brightness setting so that the - * user can observe the effect of an intended settings change without applying - * it immediately. - * - * The override will be canceled when the setting value is next updated. - * - * @param brightness The overridden brightness. - * - * @see android.provider.Settings.System#SCREEN_BRIGHTNESS - */ - @Override // Binder call - public void setTemporaryScreenBrightnessSettingOverride(int brightness) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.DEVICE_POWER, null); - - final long ident = Binder.clearCallingIdentity(); - try { - setTemporaryScreenBrightnessSettingOverrideInternal(brightness); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - /** - * Used by the settings application and brightness control widgets to - * temporarily override the current screen auto-brightness adjustment setting so that the - * user can observe the effect of an intended settings change without applying - * it immediately. - * - * The override will be canceled when the setting value is next updated. - * - * @param adj The overridden brightness, or Float.NaN to disable the override. - * - * @see android.provider.Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ - */ - @Override // Binder call - public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.DEVICE_POWER, null); - - final long ident = Binder.clearCallingIdentity(); - try { - setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(adj); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - /** * Used by the phone application to make the attention LED flash when ringing. */ @Override // Binder call diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java new file mode 100644 index 000000000000..3908df46d3a4 --- /dev/null +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.security; + +import static android.system.OsConstants.PROT_READ; +import static android.system.OsConstants.PROT_WRITE; + +import android.annotation.NonNull; +import android.os.SharedMemory; +import android.system.ErrnoException; +import android.system.Os; +import android.util.apk.ApkSignatureVerifier; +import android.util.apk.ByteBufferFactory; +import android.util.apk.SignatureNotFoundException; +import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.DigestException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** Provides fsverity related operations. */ +abstract public class VerityUtils { + private static final String TAG = "VerityUtils"; + + private static final boolean DEBUG = false; + + /** + * Generates Merkle tree and fsverity metadata. + * + * @return {@code SetupResult} that contains the {@code EsetupResultCode}, and when success, the + * {@code FileDescriptor} to read all the data from. + */ + public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { + if (DEBUG) Slog.d(TAG, "Trying to install apk verity to " + apkPath); + SharedMemory shm = null; + try { + byte[] signedRootHash = ApkSignatureVerifier.getVerityRootHash(apkPath); + if (signedRootHash == null) { + if (DEBUG) { + Slog.d(TAG, "Skip verity tree generation since there is no root hash"); + } + return SetupResult.skipped(); + } + + shm = generateApkVerityIntoSharedMemory(apkPath, signedRootHash); + FileDescriptor rfd = shm.getFileDescriptor(); + if (rfd == null || !rfd.valid()) { + return SetupResult.failed(); + } + return SetupResult.ok(Os.dup(rfd)); + } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException | + SignatureNotFoundException | ErrnoException e) { + Slog.e(TAG, "Failed to set up apk verity: ", e); + return SetupResult.failed(); + } finally { + if (shm != null) { + shm.close(); + } + } + } + + /** + * Returns a {@code SharedMemory} that contains Merkle tree and fsverity headers for the given + * apk, in the form that can immediately be used for fsverity setup. + */ + private static SharedMemory generateApkVerityIntoSharedMemory( + String apkPath, byte[] expectedRootHash) + throws IOException, SecurityException, DigestException, NoSuchAlgorithmException, + SignatureNotFoundException { + TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory(); + byte[] generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath, + shmBufferFactory); + // We only generate Merkle tree once here, so it's important to make sure the root hash + // matches the signed one in the apk. + if (!Arrays.equals(expectedRootHash, generatedRootHash)) { + throw new SecurityException("Locally generated verity root hash does not match"); + } + + SharedMemory shm = shmBufferFactory.releaseSharedMemory(); + if (shm == null) { + throw new IllegalStateException("Failed to generate verity tree into shared memory"); + } + if (!shm.setProtect(PROT_READ)) { + throw new SecurityException("Failed to set up shared memory correctly"); + } + return shm; + } + + public static class SetupResult { + /** Result code if verity is set up correctly. */ + private static final int RESULT_OK = 1; + + /** Result code if the apk does not contain a verity root hash. */ + private static final int RESULT_SKIPPED = 2; + + /** Result code if the setup failed. */ + private static final int RESULT_FAILED = 3; + + private final int mCode; + private final FileDescriptor mFileDescriptor; + + public static SetupResult ok(@NonNull FileDescriptor fileDescriptor) { + return new SetupResult(RESULT_OK, fileDescriptor); + } + + public static SetupResult skipped() { + return new SetupResult(RESULT_SKIPPED, null); + } + + public static SetupResult failed() { + return new SetupResult(RESULT_FAILED, null); + } + + private SetupResult(int code, FileDescriptor fileDescriptor) { + this.mCode = code; + this.mFileDescriptor = fileDescriptor; + } + + public boolean isFailed() { + return mCode == RESULT_FAILED; + } + + public boolean isOk() { + return mCode == RESULT_OK; + } + + public @NonNull FileDescriptor getUnownedFileDescriptor() { + return mFileDescriptor; + } + } + + /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */ + private static class TrackedShmBufferFactory implements ByteBufferFactory { + private SharedMemory mShm; + private ByteBuffer mBuffer; + + @Override + public ByteBuffer create(int capacity) throws SecurityException { + try { + if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity"); + // NB: This method is supposed to be called once according to the contract with + // ApkSignatureSchemeV2Verifier. + if (mBuffer != null) { + throw new IllegalStateException("Multiple instantiation from this factory"); + } + mShm = SharedMemory.create("apkverity", capacity); + if (!mShm.setProtect(PROT_READ | PROT_WRITE)) { + throw new SecurityException("Failed to set protection"); + } + mBuffer = mShm.mapReadWrite(); + return mBuffer; + } catch (ErrnoException e) { + throw new SecurityException("Failed to set protection", e); + } + } + + public SharedMemory releaseSharedMemory() { + if (mBuffer != null) { + SharedMemory.unmap(mBuffer); + mBuffer = null; + } + SharedMemory tmp = mShm; + mShm = null; + return tmp; + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6dc384a8831e..3f49f0cd5c15 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1508,6 +1508,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mTaskStackContainers.getTopStack(); } + ArrayList<Task> getVisibleTasks() { + return mTaskStackContainers.getVisibleTasks(); + } + void onStackWindowingModeChanged(TaskStack stack) { mTaskStackContainers.onStackWindowingModeChanged(stack); } @@ -1802,6 +1806,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo getParent().positionChildAt(position, this, includingParents); } + void positionStackAt(int position, TaskStack child) { + mTaskStackContainers.positionChildAt(position, child, false /* includingParents */); + layoutAndAssignWindowLayersIfNeeded(); + } + int taskIdFromPoint(int x, int y) { for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); @@ -3255,6 +3264,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mSplitScreenPrimaryStack; } + ArrayList<Task> getVisibleTasks() { + final ArrayList<Task> visibleTasks = new ArrayList<>(); + forAllTasks(task -> { + if (task.isVisible()) { + visibleTasks.add(task); + } + }); + return visibleTasks; + } + /** * Adds the stack to this container. * @see DisplayContent#createStack(int, boolean, StackWindowController) diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java new file mode 100644 index 000000000000..ad4957e4fc6f --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayWindowController.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.content.res.Configuration; +import android.util.Slog; + +/** + * Controller for the display container. This is created by activity manager to link activity + * displays to the display content they use in window manager. + */ +public class DisplayWindowController + extends WindowContainerController<DisplayContent, WindowContainerListener> { + + private final int mDisplayId; + + public DisplayWindowController(int displayId, WindowContainerListener listener) { + super(listener, WindowManagerService.getInstance()); + mDisplayId = displayId; + + synchronized (mWindowMap) { + // TODO: Convert to setContainer() from DisplayContent once everything is hooked up. + // Currently we are not setup to register for config changes. + mContainer = mRoot.getDisplayContentOrCreate(displayId); + if (mContainer == null) { + throw new IllegalArgumentException("Trying to add displayId=" + displayId); + } + } + } + + @Override + public void removeContainer() { + // TODO: Pipe through from ActivityDisplay to remove the display + throw new UnsupportedOperationException("To be implemented"); + } + + @Override + public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + // TODO: Pipe through from ActivityDisplay to update the configuration for the display + throw new UnsupportedOperationException("To be implemented"); + } + + /** + * Positions the task stack at the given position in the task stack container. + */ + public void positionChildAt(StackWindowController child, int position) { + synchronized (mWindowMap) { + if (DEBUG_STACK) Slog.i(TAG_WM, "positionTaskStackAt: positioning stack=" + child + + " at " + position); + if (mContainer == null) { + if (DEBUG_STACK) Slog.i(TAG_WM, + "positionTaskStackAt: could not find display=" + mContainer); + return; + } + if (child.mContainer == null) { + if (DEBUG_STACK) Slog.i(TAG_WM, + "positionTaskStackAt: could not find stack=" + this); + return; + } + mContainer.positionStackAt(position, child.mContainer); + } + } + + @Override + public String toString() { + return "{DisplayWindowController displayId=" + mDisplayId + "}"; + } +} diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 88b7a11f02fd..281e0a8441e2 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.INPUT_CONSUMER_PIP; +import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS; @@ -86,6 +87,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { private boolean mAddInputConsumerHandle; private boolean mAddPipInputConsumerHandle; private boolean mAddWallpaperInputConsumerHandle; + private boolean mAddRecentsAnimationInputConsumerHandle; private boolean mDisableWallpaperTouchEvents; private final Rect mTmpRect = new Rect(); private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer = @@ -612,7 +614,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { InputConsumerImpl navInputConsumer; InputConsumerImpl pipInputConsumer; InputConsumerImpl wallpaperInputConsumer; - Rect pipTouchableBounds; + InputConsumerImpl recentsAnimationInputConsumer; boolean inDrag; WallpaperController wallpaperController; @@ -622,11 +624,13 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { navInputConsumer = getInputConsumer(INPUT_CONSUMER_NAVIGATION, DEFAULT_DISPLAY); pipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP, DEFAULT_DISPLAY); wallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER, DEFAULT_DISPLAY); + recentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION, + DEFAULT_DISPLAY); mAddInputConsumerHandle = navInputConsumer != null; mAddPipInputConsumerHandle = pipInputConsumer != null; mAddWallpaperInputConsumerHandle = wallpaperInputConsumer != null; + mAddRecentsAnimationInputConsumerHandle = recentsAnimationInputConsumer != null; mTmpRect.setEmpty(); - pipTouchableBounds = mAddPipInputConsumerHandle ? mTmpRect : null; mDisableWallpaperTouchEvents = false; this.inDrag = inDrag; wallpaperController = mService.mRoot.mWallpaperController; @@ -659,12 +663,28 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { final boolean hasFocus = w == mInputFocus; final boolean isVisible = w.isVisibleLw(); + if (mAddRecentsAnimationInputConsumerHandle) { + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); + if (recentsAnimationController != null + && recentsAnimationController.hasInputConsumerForApp(w.mAppToken)) { + if (recentsAnimationController.updateInputConsumerForApp( + recentsAnimationInputConsumer, hasFocus)) { + addInputWindowHandle(recentsAnimationInputConsumer.mWindowHandle); + mAddRecentsAnimationInputConsumerHandle = false; + } + // Skip adding the window below regardless of whether there is an input consumer + // to handle it + return; + } + } + if (w.inPinnedWindowingMode()) { if (mAddPipInputConsumerHandle && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) { // Update the bounds of the Pip input consumer to match the window bounds. - w.getBounds(pipTouchableBounds); - pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds); + w.getBounds(mTmpRect); + pipInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect); addInputWindowHandle(pipInputConsumer.mWindowHandle); mAddPipInputConsumerHandle = false; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java new file mode 100644 index 000000000000..c7d4b8ed0f16 --- /dev/null +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; +import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.app.ActivityManager; +import android.app.ActivityManager.TaskSnapshot; +import android.app.WindowConfiguration; +import android.graphics.GraphicBuffer; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Binder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; +import android.util.Slog; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Controls a single instance of the remote driven recents animation. In particular, this allows + * the calling SystemUI to animate the visible task windows as a part of the transition. The remote + * runner is provided an animation controller which allows it to take screenshots and to notify + * window manager when the animation is completed. In addition, window manager may also notify the + * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.) + */ +public class RecentsAnimationController { + private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentsAnimationController" : TAG_WM; + private static final boolean DEBUG = false; + + private final WindowManagerService mService; + private final IRecentsAnimationRunner mRunner; + private final RecentsAnimationCallbacks mCallbacks; + private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); + + // The recents component app token that is shown behind the visibile tasks + private AppWindowToken mHomeAppToken; + + // We start the RecentsAnimationController in a pending-start state since we need to wait for + // the wallpaper/activity to draw before we can give control to the handler to start animating + // the visible task surfaces + private boolean mPendingStart = true; + + // Set when the animation has been canceled + private boolean mCanceled = false; + + // Whether or not the input consumer is enabled. The input consumer must be both registered and + // enabled for it to start intercepting touch events. + private boolean mInputConsumerEnabled; + + private Rect mTmpRect = new Rect(); + + public interface RecentsAnimationCallbacks { + void onAnimationFinished(boolean moveHomeToTop); + } + + private final IRecentsAnimationController mController = + new IRecentsAnimationController.Stub() { + + @Override + public TaskSnapshot screenshotTask(int taskId) { + if (DEBUG) Log.d(TAG, "screenshotTask(" + taskId + "): mCanceled=" + mCanceled); + long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + if (mCanceled) { + return null; + } + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final TaskAnimationAdapter adapter = mPendingAnimations.get(i); + final Task task = adapter.mTask; + if (task.mTaskId == taskId) { + // TODO: Save this screenshot as the task snapshot? + final Rect taskFrame = new Rect(); + task.getBounds(taskFrame); + final GraphicBuffer buffer = SurfaceControl.captureLayers( + task.getSurfaceControl().getHandle(), taskFrame, 1f); + final AppWindowToken topChild = task.getTopChild(); + final WindowState mainWindow = topChild.findMainWindow(); + return new TaskSnapshot(buffer, topChild.getConfiguration().orientation, + mainWindow.mStableInsets, + ActivityManager.isLowRamDeviceStatic() /* reduced */, + 1.0f /* scale */); + } + } + return null; + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void finish(boolean moveHomeToTop) { + if (DEBUG) Log.d(TAG, "finish(" + moveHomeToTop + "): mCanceled=" + mCanceled); + long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + if (mCanceled) { + return; + } + } + + // Note, the callback will handle its own synchronization, do not lock on WM lock + // prior to calling the callback + mCallbacks.onAnimationFinished(moveHomeToTop); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setInputConsumerEnabled(boolean enabled) { + if (DEBUG) Log.d(TAG, "setInputConsumerEnabled(" + enabled + "): mCanceled=" + + mCanceled); + long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + if (mCanceled) { + return; + } + + mInputConsumerEnabled = enabled; + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + mService.scheduleAnimationLocked(); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + /** + * Initializes a new RecentsAnimationController. + * + * @param remoteAnimationRunner The remote runner which should be notified when the animation is + * ready to start or has been canceled + * @param callbacks Callbacks to be made when the animation finishes + * @param restoreHomeBehindStackId The stack id to restore the home stack behind once the + * animation is complete. Will be passed to the callback. + */ + RecentsAnimationController(WindowManagerService service, + IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks, + int displayId) { + mService = service; + mRunner = remoteAnimationRunner; + mCallbacks = callbacks; + + final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); + final ArrayList<Task> visibleTasks = dc.getVisibleTasks(); + if (visibleTasks.isEmpty()) { + cancelAnimation(); + return; + } + + // Make leashes for each of the visible tasks and add it to the recents animation to be + // started + final int taskCount = visibleTasks.size(); + for (int i = 0; i < taskCount; i++) { + final Task task = visibleTasks.get(i); + final WindowConfiguration config = task.getWindowConfiguration(); + if (config.tasksAreFloating() + || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || config.getActivityType() == ACTIVITY_TYPE_HOME) { + continue; + } + addAnimation(task); + } + + // Adjust the wallpaper visibility for the showing home activity + final AppWindowToken recentsComponentAppToken = + dc.getHomeStack().getTopChild().getTopFullscreenAppToken(); + if (recentsComponentAppToken != null) { + if (DEBUG) Log.d(TAG, "setHomeApp(" + recentsComponentAppToken.getName() + ")"); + mHomeAppToken = recentsComponentAppToken; + final WallpaperController wc = dc.mWallpaperController; + if (recentsComponentAppToken.windowsCanBeWallpaperTarget()) { + dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + dc.setLayoutNeeded(); + } + } + + mService.mWindowPlacerLocked.performSurfacePlacement(); + } + + private void addAnimation(Task task) { + if (DEBUG) Log.d(TAG, "addAnimation(" + task.getName() + ")"); + final SurfaceAnimator anim = new SurfaceAnimator(task, null /* animationFinishedCallback */, + mService.mAnimator::addAfterPrepareSurfacesRunnable, mService); + final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task); + anim.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */); + task.commitPendingTransaction(); + mPendingAnimations.add(taskAdapter); + } + + void startAnimation() { + if (DEBUG) Log.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart); + if (!mPendingStart) { + return; + } + try { + final RemoteAnimationTarget[] appAnimations = + new RemoteAnimationTarget[mPendingAnimations.size()]; + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + appAnimations[i] = mPendingAnimations.get(i).createRemoteAnimationApp(); + } + mPendingStart = false; + mRunner.onAnimationStart(mController, appAnimations); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to start recents animation", e); + } + } + + void cancelAnimation() { + if (DEBUG) Log.d(TAG, "cancelAnimation()"); + if (mCanceled) { + // We've already canceled the animation + return; + } + mCanceled = true; + try { + mRunner.onAnimationCanceled(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel recents animation", e); + } + + // Clean up and return to the previous app + mCallbacks.onAnimationFinished(false /* moveHomeToTop */); + } + + void cleanupAnimation() { + if (DEBUG) Log.d(TAG, "cleanupAnimation(): mPendingAnimations=" + + mPendingAnimations.size()); + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final TaskAnimationAdapter adapter = mPendingAnimations.get(i); + adapter.mCapturedFinishCallback.onAnimationFinished(adapter); + } + mPendingAnimations.clear(); + + mService.mInputMonitor.updateInputWindowsLw(true /*force*/); + mService.scheduleAnimationLocked(); + mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); + } + + void checkAnimationReady(WallpaperController wallpaperController) { + if (mPendingStart) { + final boolean wallpaperReady = !isHomeAppOverWallpaper() + || (wallpaperController.getWallpaperTarget() != null + && wallpaperController.wallpaperTransitionReady()); + if (wallpaperReady) { + mService.getRecentsAnimationController().startAnimation(); + } + } + } + + boolean isWallpaperVisible(WindowState w) { + return w != null && w.mAppToken != null && mHomeAppToken == w.mAppToken + && isHomeAppOverWallpaper(); + } + + boolean hasInputConsumerForApp(AppWindowToken appToken) { + return mInputConsumerEnabled && isAnimatingApp(appToken); + } + + boolean updateInputConsumerForApp(InputConsumerImpl recentsAnimationInputConsumer, + boolean hasFocus) { + // Update the input consumer touchable region to match the home app main window + final WindowState homeAppMainWindow = mHomeAppToken != null + ? mHomeAppToken.findMainWindow() + : null; + if (homeAppMainWindow != null) { + homeAppMainWindow.getBounds(mTmpRect); + recentsAnimationInputConsumer.mWindowHandle.hasFocus = hasFocus; + recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect); + return true; + } + return false; + } + + private boolean isHomeAppOverWallpaper() { + if (mHomeAppToken == null) { + return false; + } + return mHomeAppToken.windowsCanBeWallpaperTarget(); + } + + private boolean isAnimatingApp(AppWindowToken appToken) { + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final Task task = mPendingAnimations.get(i).mTask; + for (int j = task.getChildCount() - 1; j >= 0; j--) { + final AppWindowToken app = task.getChildAt(j); + if (app == appToken) { + return true; + } + } + } + return false; + } + + private class TaskAnimationAdapter implements AnimationAdapter { + + private Task mTask; + private SurfaceControl mCapturedLeash; + private OnAnimationFinishedCallback mCapturedFinishCallback; + + TaskAnimationAdapter(Task task) { + mTask = task; + } + + RemoteAnimationTarget createRemoteAnimationApp() { + // TODO: Do we need position and stack bounds? + return new RemoteAnimationTarget(mTask.mTaskId, MODE_CLOSING, mCapturedLeash, + !mTask.fillsParent(), + mTask.getTopVisibleAppMainWindow().mWinAnimator.mLastClipRect, + mTask.getPrefixOrderIndex(), new Point(), new Rect(), + mTask.getWindowConfiguration()); + } + + @Override + public boolean getDetachWallpaper() { + return false; + } + + @Override + public int getBackgroundColor() { + return 0; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, Transaction t, + OnAnimationFinishedCallback finishCallback) { + mCapturedLeash = animationLeash; + mCapturedFinishCallback = finishCallback; + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) { + cancelAnimation(); + } + + @Override + public long getDurationHint() { + return 0; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return SystemClock.uptimeMillis(); + } + } + + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":"); + pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart); + pw.print(innerPrefix); pw.println("mHomeAppToken=" + mHomeAppToken); + } +} diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 8515dcb69970..7d4eafb07fe9 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -160,7 +160,8 @@ class RemoteAnimationController { return new RemoteAnimationTarget(task.mTaskId, getMode(), mCapturedLeash, !mAppWindowToken.fillsParent(), mainWindow.mWinAnimator.mLastClipRect, - mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds); + mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds, + task.getWindowConfiguration()); } private int getMode() { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2cc96c9ee7b6..deed7f17e4e6 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -623,6 +623,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { defaultDisplay.pendingLayoutChanges); } + // Defer starting the recents animation until the wallpaper has drawn + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); + if (recentsAnimationController != null) { + recentsAnimationController.checkAnimationReady(mWallpaperController); + } + if (mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0 && !mService.mAppTransition.isReady()) { // At this point, there was a window with a wallpaper that was force hiding other diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 10f1c3a37dcf..0512a08c59db 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -62,7 +62,7 @@ class SurfaceAnimator { * @param addAfterPrepareSurfaces Consumer that takes a runnable and executes it after preparing * surfaces in WM. Can be implemented differently during testing. */ - SurfaceAnimator(Animatable animatable, Runnable animationFinishedCallback, + SurfaceAnimator(Animatable animatable, @Nullable Runnable animationFinishedCallback, Consumer<Runnable> addAfterPrepareSurfaces, WindowManagerService service) { mAnimatable = animatable; mService = service; @@ -71,7 +71,8 @@ class SurfaceAnimator { addAfterPrepareSurfaces); } - private OnAnimationFinishedCallback getFinishedCallback(Runnable animationFinishedCallback, + private OnAnimationFinishedCallback getFinishedCallback( + @Nullable Runnable animationFinishedCallback, Consumer<Runnable> addAfterPrepareSurfaces) { return anim -> { synchronized (mService.mWindowMap) { @@ -97,7 +98,9 @@ class SurfaceAnimator { SurfaceControl.openTransaction(); try { reset(t, true /* destroyLeash */); - animationFinishedCallback.run(); + if (animationFinishedCallback != null) { + animationFinishedCallback.run(); + } } finally { SurfaceControl.mergeToGlobalTransaction(t); SurfaceControl.closeTransaction(); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 1218d3bc1b9b..f2ad6fb7a888 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -149,8 +149,17 @@ class WallpaperController { mFindResults.setUseTopWallpaperAsTarget(true); } + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0; - if (hasWallpaper && w.isOnScreen() && (mWallpaperTarget == w || w.isDrawFinishedLw())) { + final boolean isRecentsTransitionTarget = (recentsAnimationController != null + && recentsAnimationController.isWallpaperVisible(w)); + if (isRecentsTransitionTarget) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w); + mFindResults.setWallpaperTarget(w); + return true; + } else if (hasWallpaper && w.isOnScreen() + && (mWallpaperTarget == w || w.isDrawFinishedLw())) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w); mFindResults.setWallpaperTarget(w); if (w == mWallpaperTarget && w.mWinAnimator.isAnimationSet()) { @@ -199,15 +208,22 @@ class WallpaperController { } } - private boolean isWallpaperVisible(WindowState wallpaperTarget) { + private final boolean isWallpaperVisible(WindowState wallpaperTarget) { + final RecentsAnimationController recentsAnimationController = + mService.getRecentsAnimationController(); + boolean isAnimatingWithRecentsComponent = recentsAnimationController != null + && recentsAnimationController.isWallpaperVisible(wallpaperTarget); if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured=" + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??") + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null) ? wallpaperTarget.mAppToken.isSelfAnimating() : null) - + " prev=" + mPrevWallpaperTarget); + + " prev=" + mPrevWallpaperTarget + + " recentsAnimationWallpaperVisible=" + isAnimatingWithRecentsComponent); return (wallpaperTarget != null - && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null - && wallpaperTarget.mAppToken.isSelfAnimating()))) + && (!wallpaperTarget.mObscured + || isAnimatingWithRecentsComponent + || (wallpaperTarget.mAppToken != null + && wallpaperTarget.mAppToken.isSelfAnimating()))) || mPrevWallpaperTarget != null; } @@ -587,6 +603,11 @@ class WallpaperController { mWallpaperDrawState = WALLPAPER_DRAW_TIMEOUT; if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG, "*** WALLPAPER DRAW TIMEOUT"); + + // If there was a recents animation in progress, cancel that animation + if (mService.getRecentsAnimationController() != null) { + mService.getRecentsAnimationController().cancelAnimation(); + } return true; } return false; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 42c6ec25a7a8..a0b59efe6ced 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -337,9 +337,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } /** Returns true if this window container has the input child. */ - boolean hasChild(WindowContainer child) { + boolean hasChild(E child) { for (int i = mChildren.size() - 1; i >= 0; --i) { - final WindowContainer current = mChildren.get(i); + final E current = mChildren.get(i); if (current == child || current.hasChild(child)) { return true; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index de1e7ecb2bb9..4fb239085e5c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -24,6 +24,8 @@ import static android.Manifest.permission.RESTRICTED_VR_ACCESS; import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_USER_HANDLE; @@ -123,6 +125,7 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IAssistDataReceiver; +import android.app.WindowConfiguration; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -196,6 +199,7 @@ import android.view.IDockedStackListener; import android.view.IInputFilter; import android.view.IOnKeyguardExitResult; import android.view.IPinnedStackListener; +import android.view.IRecentsAnimationRunner; import android.view.IRotationWatcher; import android.view.IWallpaperVisibilityListener; import android.view.IWindow; @@ -528,6 +532,7 @@ public class WindowManagerService extends IWindowManager.Stub IInputMethodManager mInputMethodManager; AccessibilityController mAccessibilityController; + private RecentsAnimationController mRecentsAnimationController; Watermark mWatermark; StrictModeFlash mStrictModeFlash; @@ -2670,6 +2675,39 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void initializeRecentsAnimation( + IRecentsAnimationRunner recentsAnimationRunner, + RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId) { + synchronized (mWindowMap) { + cancelRecentsAnimation(); + mRecentsAnimationController = new RecentsAnimationController(this, + recentsAnimationRunner, callbacks, displayId); + } + } + + public RecentsAnimationController getRecentsAnimationController() { + return mRecentsAnimationController; + } + + public void cancelRecentsAnimation() { + synchronized (mWindowMap) { + if (mRecentsAnimationController != null) { + // This call will call through to cleanupAnimation() below after the animation is + // canceled + mRecentsAnimationController.cancelAnimation(); + } + } + } + + public void cleanupRecentsAnimation() { + synchronized (mWindowMap) { + if (mRecentsAnimationController != null) { + mRecentsAnimationController.cleanupAnimation(); + mRecentsAnimationController = null; + } + } + } + public void setAppFullscreen(IBinder token, boolean toOpaque) { synchronized (mWindowMap) { final AppWindowToken atoken = mRoot.getAppWindowToken(token); @@ -6327,6 +6365,10 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mSkipAppTransitionAnimation=");pw.println(mSkipAppTransitionAnimation); pw.println(" mLayoutToAnim:"); mAppTransition.dump(pw, " "); + if (mRecentsAnimationController != null) { + pw.print(" mRecentsAnimationController="); pw.println(mRecentsAnimationController); + mRecentsAnimationController.dump(pw, " "); + } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 886747ccb435..a8e8237854cd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -70,6 +70,10 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void transferOwnership(ComponentName admin, ComponentName target, PersistableBundle bundle) {} + public PersistableBundle getTransferOwnershipBundle() { + return null; + } + public boolean generateKeyPair(ComponentName who, String callerPackage, String algorithm, ParcelableKeyGenParameterSpec keySpec, int idAttestationFlags, KeymasterCertificateChain attestationChain) { @@ -135,11 +139,6 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } @Override - public CharSequence getPrintingDisabledReason() { - return null; - } - - @Override public List<String> setMeteredDataDisabled(ComponentName admin, List<String> packageNames) { return packageNames; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 38e2168073ea..99712a5173db 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -20,7 +20,7 @@ import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.USER_OP_SUCCESS; -import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE; +import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED; @@ -58,6 +58,7 @@ import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; + import static android.provider.Telephony.Carriers.DPC_URI; import static android.provider.Telephony.Carriers.ENFORCE_KEY; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; @@ -67,6 +68,12 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; + +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.TEXT; @@ -181,6 +188,7 @@ import android.telephony.data.ApnSetting; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.AtomicFile; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -240,6 +248,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -256,6 +265,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String DEVICE_POLICIES_XML = "device_policies.xml"; + private static final String TRANSFER_OWNERSHIP_PARAMETERS_XML = + "transfer-ownership-parameters.xml"; + private static final String TAG_ACCEPTED_CA_CERTIFICATES = "accepted-ca-certificate"; private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component"; @@ -460,6 +472,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private SetupContentObserver mSetupContentObserver; + @VisibleForTesting + final TransferOwnershipMetadataManager mTransferOwnershipMetadataManager; + private final Runnable mRemoteBugreportTimeoutRunnable = new Runnable() { @Override public void run() { @@ -2023,6 +2038,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { void postOnSystemServerInitThreadPool(Runnable runnable) { SystemServerInitThreadPool.get().submit(runnable, LOG_TAG); } + + public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() { + return new TransferOwnershipMetadataManager(); + } } /** @@ -2067,6 +2086,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mOverlayPackagesProvider = new OverlayPackagesProvider(mContext); + mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager(); + if (!mHasFeature) { // Skip the rest of the initialization return; @@ -3308,6 +3329,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { activityManagerInternal.setSwitchingToSystemUserMessage( deviceOwner.endUserSessionMessage); } + + revertTransferOwnershipIfNecessaryLocked(); + } + } + + private void revertTransferOwnershipIfNecessaryLocked() { + if (!mTransferOwnershipMetadataManager.metadataFileExists()) { + return; + } + Slog.e(LOG_TAG, "Owner transfer metadata file exists! Reverting transfer."); + final TransferOwnershipMetadataManager.Metadata metadata = + mTransferOwnershipMetadataManager.loadMetadataFile(); + // Revert transfer + if (metadata.adminType.equals(ADMIN_TYPE_PROFILE_OWNER)) { + transferProfileOwnershipLocked(metadata.targetComponent, metadata.sourceComponent, + metadata.userId); + deleteTransferOwnershipMetadataFileLocked(); + deleteTransferOwnershipBundleLocked(metadata.userId); + } else if (metadata.adminType.equals(ADMIN_TYPE_DEVICE_OWNER)) { + transferDeviceOwnershipLocked(metadata.targetComponent, metadata.sourceComponent, + metadata.userId); + deleteTransferOwnershipMetadataFileLocked(); + deleteTransferOwnershipBundleLocked(metadata.userId); } } @@ -3568,6 +3612,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private void transferActiveAdminUncheckedLocked(ComponentName incomingReceiver, ComponentName outgoingReceiver, int userHandle) { final DevicePolicyData policy = getUserData(userHandle); + if (!policy.mAdminMap.containsKey(outgoingReceiver) + && policy.mAdminMap.containsKey(incomingReceiver)) { + // Nothing to transfer - the incoming receiver is already the active admin. + return; + } final DeviceAdminInfo incomingDeviceInfo = findAdmin(incomingReceiver, userHandle, /* throwForMissingPermission= */ true); final ActiveAdmin adminToTransfer = policy.mAdminMap.get(outgoingReceiver); @@ -3581,7 +3630,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } saveSettingsLocked(userHandle); - //TODO: Make sure we revert back when we detect a failure. sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED, null, null); } @@ -7331,6 +7379,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.securityLogSetLoggingEnabledProperty(false); mSecurityLogMonitor.stop(); setNetworkLoggingActiveInternal(false); + deleteTransferOwnershipBundleLocked(userId); try { if (mInjector.getIBackupManager() != null) { @@ -7433,6 +7482,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { clearUserPoliciesLocked(userId); mOwners.removeProfileOwner(userId); mOwners.writeProfileOwner(userId); + deleteTransferOwnershipBundleLocked(userId); } @Override @@ -10264,6 +10314,45 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId) { return DevicePolicyManagerService.this.canUserHaveUntrustedCredentialReset(userId); } + + @Override + public CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId) { + synchronized (DevicePolicyManagerService.this) { + DevicePolicyData policy = getUserData(userId); + if (policy.mPrintingEnabled) { + Log.e(LOG_TAG, "printing is enabled"); + return null; + } + String ownerPackage = mOwners.getProfileOwnerPackage(userId); + if (ownerPackage == null) { + ownerPackage = mOwners.getDeviceOwnerPackageName(); + } + PackageManager pm = mInjector.getPackageManager(); + PackageInfo packageInfo; + try { + packageInfo = pm.getPackageInfo(ownerPackage, 0); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "getPackageInfo error", e); + return null; + } + if (packageInfo == null) { + Log.e(LOG_TAG, "packageInfo is inexplicably null"); + return null; + } + ApplicationInfo appInfo = packageInfo.applicationInfo; + if (appInfo == null) { + Log.e(LOG_TAG, "appInfo is inexplicably null"); + return null; + } + CharSequence appLabel = pm.getApplicationLabel(appInfo); + if (appLabel == null) { + Log.e(LOG_TAG, "appLabel is inexplicably null"); + return null; + } + return ((Context) ActivityThread.currentActivityThread().getSystemUiContext()) + .getResources().getString(R.string.printing_disabled_by, appLabel); + } + } } private Intent createShowAdminSupportIntent(ComponentName admin, int userId) { @@ -12286,10 +12375,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction)); } - //TODO: Add callback information to the javadoc once it is completed. - //TODO: Make transferOwnership atomic. @Override - public void transferOwnership(ComponentName admin, ComponentName target, PersistableBundle bundle) { + public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target, + @Nullable PersistableBundle bundle) { if (!mHasFeature) { return; } @@ -12322,12 +12410,41 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long id = mInjector.binderClearCallingIdentity(); try { - //STOPSHIP add support for COMP, edge cases when device is rebooted/work mode off synchronized (this) { + /* + * We must ensure the whole process is atomic to prevent the device from ending up + * in an invalid state (e.g. no active admin). This could happen if the device + * is rebooted or work mode is turned off mid-transfer. + * In order to guarantee atomicity, we: + * + * 1. Save an atomic journal file describing the transfer process + * 2. Perform the transfer itself + * 3. Delete the journal file + * + * That way if the journal file exists on device boot, we know that the transfer + * must be reverted back to the original administrator. This logic is implemented in + * revertTransferOwnershipIfNecessaryLocked. + * */ + if (bundle == null) { + bundle = new PersistableBundle(); + } if (isProfileOwner(admin, callingUserId)) { - transferProfileOwnerLocked(admin, target, callingUserId, bundle); + prepareTransfer(admin, target, bundle, callingUserId, + ADMIN_TYPE_PROFILE_OWNER); + transferProfileOwnershipLocked(admin, target, callingUserId); + sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE, + getTransferOwnershipAdminExtras(bundle), callingUserId); + postTransfer(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED, callingUserId); + if (isUserAffiliatedWithDeviceLocked(callingUserId)) { + notifyAffiliatedProfileTransferOwnershipComplete(callingUserId); + } } else if (isDeviceOwner(admin, callingUserId)) { - transferDeviceOwnerLocked(admin, target, callingUserId, bundle); + prepareTransfer(admin, target, bundle, callingUserId, + ADMIN_TYPE_DEVICE_OWNER); + transferDeviceOwnershipLocked(admin, target, callingUserId); + sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE, + getTransferOwnershipAdminExtras(bundle)); + postTransfer(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, callingUserId); } } } finally { @@ -12335,43 +12452,55 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private void prepareTransfer(ComponentName admin, ComponentName target, + PersistableBundle bundle, int callingUserId, String adminType) { + saveTransferOwnershipBundleLocked(bundle, callingUserId); + mTransferOwnershipMetadataManager.saveMetadataFile( + new TransferOwnershipMetadataManager.Metadata(admin, target, + callingUserId, adminType)); + } + + private void postTransfer(String broadcast, int callingUserId) { + deleteTransferOwnershipMetadataFileLocked(); + sendOwnerChangedBroadcast(broadcast, callingUserId); + } + + private void notifyAffiliatedProfileTransferOwnershipComplete(int callingUserId) { + final Bundle extras = new Bundle(); + extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(callingUserId)); + sendDeviceOwnerCommand( + DeviceAdminReceiver.ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE, extras); + } + /** * Transfers the profile owner for user with id profileOwnerUserId from admin to target. */ - private void transferProfileOwnerLocked(ComponentName admin, ComponentName target, - int profileOwnerUserId, PersistableBundle bundle) { + private void transferProfileOwnershipLocked(ComponentName admin, ComponentName target, + int profileOwnerUserId) { transferActiveAdminUncheckedLocked(target, admin, profileOwnerUserId); mOwners.transferProfileOwner(target, profileOwnerUserId); Slog.i(LOG_TAG, "Profile owner set: " + target + " on user " + profileOwnerUserId); mOwners.writeProfileOwner(profileOwnerUserId); mDeviceAdminServiceController.startServiceForOwner( target.getPackageName(), profileOwnerUserId, "transfer-profile-owner"); - sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE, - getTransferOwnerAdminExtras(bundle), profileOwnerUserId); - sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED, - profileOwnerUserId); } /** * Transfers the device owner for user with id userId from admin to target. */ - private void transferDeviceOwnerLocked(ComponentName admin, ComponentName target, int userId, - PersistableBundle bundle) { + private void transferDeviceOwnershipLocked(ComponentName admin, ComponentName target, int userId) { transferActiveAdminUncheckedLocked(target, admin, userId); - mOwners.transferDeviceOwner(target); + mOwners.transferDeviceOwnership(target); Slog.i(LOG_TAG, "Device owner set: " + target + " on user " + userId); mOwners.writeDeviceOwner(); mDeviceAdminServiceController.startServiceForOwner( target.getPackageName(), userId, "transfer-device-owner"); - sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_TRANSFER_OWNERSHIP_COMPLETE, - getTransferOwnerAdminExtras(bundle)); - sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); } - private Bundle getTransferOwnerAdminExtras(PersistableBundle bundle) { + private Bundle getTransferOwnershipAdminExtras(PersistableBundle bundle) { Bundle extras = new Bundle(); if (bundle != null) { - extras.putParcelable(EXTRA_TRANSFER_OWNER_ADMIN_EXTRAS_BUNDLE, bundle); + extras.putParcelable(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE, bundle); } return extras; } @@ -12476,6 +12605,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private void deleteTransferOwnershipMetadataFileLocked() { + mTransferOwnershipMetadataManager.deleteMetadataFile(); + } + + @Override + @Nullable + public PersistableBundle getTransferOwnershipBundle() { + synchronized (this) { + final int callingUserId = mInjector.userHandleGetCallingUserId(); + getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + final File bundleFile = new File( + mInjector.environmentGetUserSystemDirectory(callingUserId), + TRANSFER_OWNERSHIP_PARAMETERS_XML); + if (!bundleFile.exists()) { + return null; + } + try (FileInputStream stream = new FileInputStream(bundleFile)) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + return PersistableBundle.restoreFromXml(parser); + } catch (IOException | XmlPullParserException | IllegalArgumentException e) { + Slog.e(LOG_TAG, "Caught exception while trying to load the " + + "owner transfer parameters from file " + bundleFile, e); + return null; + } + } + } + /** * Returns whether printing is enabled for current user. * @hide @@ -12495,55 +12652,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - /** - * Returns text of error message if printing is disabled. - * Only to be called by Print Service. - * @hide - */ - @Override - public CharSequence getPrintingDisabledReason() { - if (!hasPrinting() || !mHasFeature) { - Log.e(LOG_TAG, "no printing or no management"); - return null; - } - synchronized (this) { - final int userHandle = mInjector.userHandleGetCallingUserId(); - DevicePolicyData policy = getUserData(userHandle); - if (policy.mPrintingEnabled) { - Log.e(LOG_TAG, "printing is enabled"); - return null; - } - String ownerPackage = mOwners.getProfileOwnerPackage(userHandle); - if (ownerPackage == null) { - ownerPackage = mOwners.getDeviceOwnerPackageName(); - } - PackageManager pm = mInjector.getPackageManager(); - PackageInfo packageInfo; - try { - packageInfo = pm.getPackageInfo(ownerPackage, 0); - } catch (NameNotFoundException e) { - Log.e(LOG_TAG, "getPackageInfo error", e); - return null; - } - if (packageInfo == null) { - Log.e(LOG_TAG, "packageInfo is inexplicably null"); - return null; - } - ApplicationInfo appInfo = packageInfo.applicationInfo; - if (appInfo == null) { - Log.e(LOG_TAG, "appInfo is inexplicably null"); - return null; - } - CharSequence appLabel = pm.getApplicationLabel(appInfo); - if (appLabel == null) { - Log.e(LOG_TAG, "appLabel is inexplicably null"); - return null; - } - return ((Context) ActivityThread.currentActivityThread().getSystemUiContext()) - .getResources().getString(R.string.printing_disabled_by, appLabel); - } - } - @Override public int addOverrideApn(@NonNull ComponentName who, @NonNull ApnSetting apnSetting) { if (!mHasFeature) { @@ -12724,4 +12832,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } return false; } + + @VisibleForTesting + void saveTransferOwnershipBundleLocked(PersistableBundle bundle, int userId) { + final File parametersFile = new File( + mInjector.environmentGetUserSystemDirectory(userId), + TRANSFER_OWNERSHIP_PARAMETERS_XML); + final AtomicFile atomicFile = new AtomicFile(parametersFile); + FileOutputStream stream = null; + try { + stream = atomicFile.startWrite(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(stream, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + bundle.saveToXml(serializer); + atomicFile.finishWrite(stream); + } catch (IOException | XmlPullParserException e) { + Slog.e(LOG_TAG, "Caught exception while trying to save the " + + "owner transfer parameters to file " + parametersFile, e); + parametersFile.delete(); + atomicFile.failWrite(stream); + } + } + + void deleteTransferOwnershipBundleLocked(int userId) { + final File parametersFile = new File(mInjector.environmentGetUserSystemDirectory(userId), + TRANSFER_OWNERSHIP_PARAMETERS_XML); + parametersFile.delete(); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 2a23888b875c..d2151ed8ae5e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -34,6 +34,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.Xml; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import org.xmlpull.v1.XmlPullParser; @@ -110,13 +111,23 @@ class Owners { private SystemUpdateInfo mSystemUpdateInfo; private final Object mLock = new Object(); + private final Injector mInjector; public Owners(UserManager userManager, UserManagerInternal userManagerInternal, PackageManagerInternal packageManagerInternal) { + this(userManager, userManagerInternal, packageManagerInternal, new Injector()); + } + + @VisibleForTesting + Owners(UserManager userManager, + UserManagerInternal userManagerInternal, + PackageManagerInternal packageManagerInternal, + Injector injector) { mUserManager = userManager; mUserManagerInternal = userManagerInternal; mPackageManagerInternal = packageManagerInternal; + mInjector = injector; } /** @@ -125,7 +136,7 @@ class Owners { void load() { synchronized (mLock) { // First, try to read from the legacy file. - final File legacy = getLegacyConfigFileWithTestOverride(); + final File legacy = getLegacyConfigFile(); final List<UserInfo> users = mUserManager.getUsers(true); @@ -288,7 +299,7 @@ class Owners { } } - void transferDeviceOwner(ComponentName target) { + void transferDeviceOwnership(ComponentName target) { synchronized (mLock) { // We don't set a name because it's not used anyway. // See DevicePolicyManagerService#getDeviceOwnerName @@ -642,7 +653,7 @@ class Owners { private class DeviceOwnerReadWriter extends FileReadWriter { protected DeviceOwnerReadWriter() { - super(getDeviceOwnerFileWithTestOverride()); + super(getDeviceOwnerFile()); } @Override @@ -713,7 +724,7 @@ class Owners { private final int mUserId; ProfileOwnerReadWriter(int userId) { - super(getProfileOwnerFileWithTestOverride(userId)); + super(getProfileOwnerFile(userId)); mUserId = userId; } @@ -870,15 +881,29 @@ class Owners { } } - File getLegacyConfigFileWithTestOverride() { - return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY); + @VisibleForTesting + File getLegacyConfigFile() { + return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY); + } + + @VisibleForTesting + File getDeviceOwnerFile() { + return new File(mInjector.environmentGetDataSystemDirectory(), DEVICE_OWNER_XML); } - File getDeviceOwnerFileWithTestOverride() { - return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML); + @VisibleForTesting + File getProfileOwnerFile(int userId) { + return new File(mInjector.environmentGetUserSystemDirectory(userId), PROFILE_OWNER_XML); } - File getProfileOwnerFileWithTestOverride(int userId) { - return new File(Environment.getUserSystemDirectory(userId), PROFILE_OWNER_XML); + @VisibleForTesting + public static class Injector { + File environmentGetDataSystemDirectory() { + return Environment.getDataSystemDirectory(); + } + + File environmentGetUserSystemDirectory(int userId) { + return Environment.getUserSystemDirectory(userId); + } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java new file mode 100644 index 000000000000..1addeb6bee15 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/TransferOwnershipMetadataManager.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.devicepolicy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.os.Environment; +import android.text.TextUtils; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.Preconditions; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Handles reading and writing of the owner transfer metadata file. + * + * Before we perform a device or profile owner transfer, we save this xml file with information + * about the current admin, target admin, user id and admin type (device owner or profile owner). + * After {@link DevicePolicyManager#transferOwnership} completes, we delete the file. If after + * device boot the file is still there, this indicates that the transfer was interrupted by a + * reboot. + * + * Note that this class is not thread safe. + */ +class TransferOwnershipMetadataManager { + final static String ADMIN_TYPE_DEVICE_OWNER = "device-owner"; + final static String ADMIN_TYPE_PROFILE_OWNER = "profile-owner"; + private final static String TAG_USER_ID = "user-id"; + private final static String TAG_SOURCE_COMPONENT = "source-component"; + private final static String TAG_TARGET_COMPONENT = "target-component"; + private final static String TAG_ADMIN_TYPE = "admin-type"; + private final static String TAG = TransferOwnershipMetadataManager.class.getName(); + public static final String OWNER_TRANSFER_METADATA_XML = "owner-transfer-metadata.xml"; + + private final Injector mInjector; + + TransferOwnershipMetadataManager() { + this(new Injector()); + } + + @VisibleForTesting + TransferOwnershipMetadataManager(Injector injector) { + mInjector = injector; + } + + boolean saveMetadataFile(Metadata params) { + final File transferOwnershipMetadataFile = new File(mInjector.getOwnerTransferMetadataDir(), + OWNER_TRANSFER_METADATA_XML); + final AtomicFile atomicFile = new AtomicFile(transferOwnershipMetadataFile); + FileOutputStream stream = null; + try { + stream = atomicFile.startWrite(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(stream, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + insertSimpleTag(serializer, TAG_USER_ID, Integer.toString(params.userId)); + insertSimpleTag(serializer, + TAG_SOURCE_COMPONENT, params.sourceComponent.flattenToString()); + insertSimpleTag(serializer, + TAG_TARGET_COMPONENT, params.targetComponent.flattenToString()); + insertSimpleTag(serializer, TAG_ADMIN_TYPE, params.adminType); + serializer.endDocument(); + atomicFile.finishWrite(stream); + return true; + } catch (IOException e) { + Slog.e(TAG, "Caught exception while trying to save Owner Transfer " + + "Params to file " + transferOwnershipMetadataFile, e); + transferOwnershipMetadataFile.delete(); + atomicFile.failWrite(stream); + } + return false; + } + + private void insertSimpleTag(XmlSerializer serializer, String tagName, String value) + throws IOException { + serializer.startTag(null, tagName); + serializer.text(value); + serializer.endTag(null, tagName); + } + + @Nullable + Metadata loadMetadataFile() { + final File transferOwnershipMetadataFile = + new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML); + if (!transferOwnershipMetadataFile.exists()) { + return null; + } + Slog.d(TAG, "Loading TransferOwnershipMetadataManager from " + + transferOwnershipMetadataFile); + try (FileInputStream stream = new FileInputStream(transferOwnershipMetadataFile)) { + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + return parseMetadataFile(parser); + } catch (IOException | XmlPullParserException | IllegalArgumentException e) { + Slog.e(TAG, "Caught exception while trying to load the " + + "owner transfer params from file " + transferOwnershipMetadataFile, e); + } + return null; + } + + private Metadata parseMetadataFile(XmlPullParser parser) + throws XmlPullParserException, IOException { + int type; + final int outerDepth = parser.getDepth(); + int userId = 0; + String adminComponent = null; + String targetComponent = null; + String adminType = null; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + switch (parser.getName()) { + case TAG_USER_ID: + parser.next(); + userId = Integer.parseInt(parser.getText()); + break; + case TAG_TARGET_COMPONENT: + parser.next(); + targetComponent = parser.getText(); + break; + case TAG_SOURCE_COMPONENT: + parser.next(); + adminComponent = parser.getText(); + break; + case TAG_ADMIN_TYPE: + parser.next(); + adminType = parser.getText(); + break; + } + } + return new Metadata(adminComponent, targetComponent, userId, adminType); + } + + void deleteMetadataFile() { + new File(mInjector.getOwnerTransferMetadataDir(), OWNER_TRANSFER_METADATA_XML).delete(); + } + + boolean metadataFileExists() { + return new File(mInjector.getOwnerTransferMetadataDir(), + OWNER_TRANSFER_METADATA_XML).exists(); + } + + static class Metadata { + final int userId; + final ComponentName sourceComponent; + final ComponentName targetComponent; + final String adminType; + + Metadata(@NonNull String sourceComponent, @NonNull String targetComponent, + @NonNull int userId, @NonNull String adminType) { + this.sourceComponent = ComponentName.unflattenFromString(sourceComponent); + this.targetComponent = ComponentName.unflattenFromString(targetComponent); + Preconditions.checkNotNull(sourceComponent); + Preconditions.checkNotNull(targetComponent); + Preconditions.checkStringNotEmpty(adminType); + this.userId = userId; + this.adminType = adminType; + } + + Metadata(@NonNull ComponentName sourceComponent, @NonNull ComponentName targetComponent, + @NonNull int userId, @NonNull String adminType) { + this(sourceComponent.flattenToString(), targetComponent.flattenToString(), + userId, adminType); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Metadata)) { + return false; + } + Metadata params = (Metadata) obj; + + return userId == params.userId + && sourceComponent.equals(params.sourceComponent) + && targetComponent.equals(params.targetComponent) + && TextUtils.equals(adminType, params.adminType); + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + userId; + hashCode = 31 * hashCode + sourceComponent.hashCode(); + hashCode = 31 * hashCode + targetComponent.hashCode(); + hashCode = 31 * hashCode + adminType.hashCode(); + return hashCode; + } + } + + @VisibleForTesting + static class Injector { + public File getOwnerTransferMetadataDir() { + return Environment.getDataSystemDirectory(); + } + } +} diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/services/net/java/android/net/util/MultinetworkPolicyTracker.java index 424e40d2096f..30c5cd98b719 100644 --- a/services/net/java/android/net/util/MultinetworkPolicyTracker.java +++ b/services/net/java/android/net/util/MultinetworkPolicyTracker.java @@ -122,6 +122,7 @@ public class MultinetworkPolicyTracker { return mAvoidBadWifi; } + // TODO: move this to MultipathPolicyTracker. public int getMeteredMultipathPreference() { return mMeteredMultipathPreference; } diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index 89a5fe1b82c7..d6cc80516485 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -22,6 +22,7 @@ import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -64,6 +65,7 @@ import com.android.internal.print.DualDumpOutputStream; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; +import com.android.server.LocalServices; import com.android.server.SystemService; import java.io.FileDescriptor; @@ -113,12 +115,12 @@ public final class PrintManagerService extends SystemService { private final SparseArray<UserState> mUserStates = new SparseArray<>(); - private final DevicePolicyManager mDpc; + private final DevicePolicyManager mDpm; PrintManagerImpl(Context context) { mContext = context; mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - mDpc = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); + mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); registerContentObservers(); registerBroadcastReceivers(); } @@ -128,7 +130,16 @@ public final class PrintManagerService extends SystemService { PrintAttributes attributes, String packageName, int appId, int userId) { adapter = Preconditions.checkNotNull(adapter); if (!isPrintingEnabled()) { - final CharSequence disabledMessage = mDpc.getPrintingDisabledReason(); + CharSequence disabledMessage = null; + DevicePolicyManagerInternal dpmi = + LocalServices.getService(DevicePolicyManagerInternal.class); + final int callingUserId = UserHandle.getCallingUserId(); + final long identity = Binder.clearCallingIdentity(); + try { + disabledMessage = dpmi.getPrintingDisabledReasonForUser(callingUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } if (disabledMessage != null) { Toast.makeText(mContext, Looper.getMainLooper(), disabledMessage, Toast.LENGTH_LONG).show(); @@ -711,7 +722,7 @@ public final class PrintManagerService extends SystemService { } private boolean isPrintingEnabled() { - return mDpc == null || mDpc.isPrintingEnabled(); + return mDpm == null || mDpm.isPrintingEnabled(); } private void dump(@NonNull DualDumpOutputStream dumpStream, diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 94e4e306be15..372b5be98a3b 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -60,6 +60,7 @@ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/res/raw/active_admin_migrated.xml b/services/tests/servicestests/res/raw/active_admin_migrated.xml new file mode 100644 index 000000000000..47af30ff9a0f --- /dev/null +++ b/services/tests/servicestests/res/raw/active_admin_migrated.xml @@ -0,0 +1,11 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true" provisioning-state="3"> + <admin name="com.another.package.name/whatever.random.class"> + <policies flags="991"/> + <strong-auth-unlock-timeout value="0"/> + <user-restrictions no_add_managed_profile="true"/> + <default-enabled-user-restrictions> + <restriction value="no_add_managed_profile"/> + </default-enabled-user-restrictions> + </admin> +</policies>
\ No newline at end of file diff --git a/services/tests/servicestests/res/raw/active_admin_not_migrated.xml b/services/tests/servicestests/res/raw/active_admin_not_migrated.xml new file mode 100644 index 000000000000..54eba4c6bafb --- /dev/null +++ b/services/tests/servicestests/res/raw/active_admin_not_migrated.xml @@ -0,0 +1,11 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true" provisioning-state="3"> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> + <policies flags="991"/> + <strong-auth-unlock-timeout value="0"/> + <user-restrictions no_add_managed_profile="true"/> + <default-enabled-user-restrictions> + <restriction value="no_add_managed_profile"/> + </default-enabled-user-restrictions> + </admin> +</policies>
\ No newline at end of file diff --git a/services/tests/servicestests/res/raw/device_owner_migrated.xml b/services/tests/servicestests/res/raw/device_owner_migrated.xml new file mode 100644 index 000000000000..4ee05bf35dd0 --- /dev/null +++ b/services/tests/servicestests/res/raw/device_owner_migrated.xml @@ -0,0 +1,9 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<root> +<device-owner + package="com.another.package.name" + name="" + component="com.another.package.name/whatever.random.class" + userRestrictionsMigrated="true" /> +<device-owner-context userId="0" /> +</root>
\ No newline at end of file diff --git a/services/tests/servicestests/res/raw/device_owner_not_migrated.xml b/services/tests/servicestests/res/raw/device_owner_not_migrated.xml new file mode 100644 index 000000000000..3a532af38a05 --- /dev/null +++ b/services/tests/servicestests/res/raw/device_owner_not_migrated.xml @@ -0,0 +1,9 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<root> +<device-owner + package="com.android.frameworks.servicestests" + name="" + component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1" + userRestrictionsMigrated="true" /> +<device-owner-context userId="0" /> +</root>
\ No newline at end of file diff --git a/services/tests/servicestests/res/raw/profile_owner_migrated.xml b/services/tests/servicestests/res/raw/profile_owner_migrated.xml new file mode 100644 index 000000000000..f73d2cdc379d --- /dev/null +++ b/services/tests/servicestests/res/raw/profile_owner_migrated.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<root> + <profile-owner package="com.another.package.name" + name="com.another.package.name" + component="com.another.package.name/whatever.random.class" + userRestrictionsMigrated="true"/> +</root>
\ No newline at end of file diff --git a/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml b/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml new file mode 100644 index 000000000000..1ce3a47e9c75 --- /dev/null +++ b/services/tests/servicestests/res/raw/profile_owner_not_migrated.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<root> + <profile-owner package="com.android.frameworks.servicestests" + name="com.android.frameworks.servicestests" + component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1" + userRestrictionsMigrated="true"/> +</root>
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 96bf49b288c9..10253c570f3f 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; +import com.android.server.wm.DisplayWindowController; import org.mockito.invocation.InvocationOnMock; import android.app.IApplicationThread; @@ -345,7 +346,7 @@ public class ActivityTestsBase { } } - private static class TestActivityDisplay extends ActivityDisplay { + protected static class TestActivityDisplay extends ActivityDisplay { private final ActivityStackSupervisor mSupervisor; TestActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { @@ -374,6 +375,11 @@ public class ActivityTestsBase { this, stackId, mSupervisor, windowingMode, activityType, onTop); } } + + @Override + protected DisplayWindowController createWindowContainerController() { + return mock(DisplayWindowController.class); + } } private static WindowManagerService prepareMockWindowManager() { diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java index 5a2110258828..24566fcf8f0d 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -573,7 +573,8 @@ public class RecentTasksTest extends ActivityTestsBase { assertSecurityException(expectCallable, () -> mService.getTaskDescription(0)); assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0)); assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null, - null, 0)); + null)); + assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation()); } private void testGetTasksApis(boolean expectCallable) { @@ -676,8 +677,8 @@ public class RecentTasksTest extends ActivityTestsBase { @Override public void initialize() { super.initialize(); - mDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY); - mOtherDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY); + mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); + mOtherDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); attachDisplay(mOtherDisplay); attachDisplay(mDisplay); } diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java index fc7562869490..c6ce7e1188e8 100644 --- a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java @@ -68,7 +68,7 @@ public class RunningTasksTest extends ActivityTestsBase { // Create a number of stacks with tasks (of incrementing active time) final ActivityStackSupervisor supervisor = mService.mStackSupervisor; final SparseArray<ActivityDisplay> displays = new SparseArray<>(); - final ActivityDisplay display = new ActivityDisplay(supervisor, DEFAULT_DISPLAY); + final ActivityDisplay display = new TestActivityDisplay(supervisor, DEFAULT_DISPLAY); displays.put(DEFAULT_DISPLAY, display); final int numStacks = 2; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 06f138ba898b..00e27c9a54cb 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -60,40 +60,33 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi */ public static class OwnersTestable extends Owners { public static final String LEGACY_FILE = "legacy.xml"; - public static final String DEVICE_OWNER_FILE = "device_owner2.xml"; - public static final String PROFILE_OWNER_FILE = "profile_owner.xml"; - - private final File mLegacyFile; - private final File mDeviceOwnerFile; - private final File mUsersDataDir; public OwnersTestable(MockSystemServices services) { super(services.userManager, services.userManagerInternal, - services.packageManagerInternal); - mLegacyFile = new File(services.dataDir, LEGACY_FILE); - mDeviceOwnerFile = new File(services.dataDir, DEVICE_OWNER_FILE); - mUsersDataDir = new File(services.dataDir, "users"); + services.packageManagerInternal, new MockInjector(services)); } - @Override - File getLegacyConfigFileWithTestOverride() { - return mLegacyFile; - } + static class MockInjector extends Injector { + private final MockSystemServices mServices; - @Override - File getDeviceOwnerFileWithTestOverride() { - return mDeviceOwnerFile; - } + private MockInjector(MockSystemServices services) { + mServices = services; + } - @Override - File getProfileOwnerFileWithTestOverride(int userId) { - final File userDir = new File(mUsersDataDir, String.valueOf(userId)); - return new File(userDir, PROFILE_OWNER_FILE); + @Override + File environmentGetDataSystemDirectory() { + return mServices.dataDir; + } + + @Override + File environmentGetUserSystemDirectory(int userId) { + return mServices.environment.getUserSystemDirectory(userId); + } } } public final DpmMockContext context; - private final MockInjector mMockInjector; + protected final MockInjector mMockInjector; public DevicePolicyManagerServiceTestable(MockSystemServices services, DpmMockContext context) { this(new MockInjector(services, context)); @@ -124,8 +117,7 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } } - - private static class MockInjector extends Injector { + static class MockInjector extends Injector { public final DpmMockContext context; private final MockSystemServices services; @@ -133,7 +125,7 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi // Key is a pair of uri and userId private final Map<Pair<Uri, Integer>, ContentObserver> mContentObservers = new ArrayMap<>(); - private MockInjector(MockSystemServices services, DpmMockContext context) { + public MockInjector(MockSystemServices services, DpmMockContext context) { super(context); this.services = services; this.context = context; @@ -449,5 +441,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi void postOnSystemServerInitThreadPool(Runnable runnable) { runnable.run(); } + + @Override + public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() { + return new TransferOwnershipMetadataManager( + new TransferOwnershipMetadataManagerTest.MockInjector()); + } } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index bc65df83fe1b..6b87ea965686 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -50,6 +50,7 @@ import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; import android.Manifest.permission; +import android.annotation.RawRes; import android.app.Activity; import android.app.Notification; import android.app.admin.DeviceAdminReceiver; @@ -93,10 +94,13 @@ import com.android.server.pm.UserRestrictionsUtils; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.io.File; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -202,9 +206,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { setUpUserManager(); } + private TransferOwnershipMetadataManager getMockTransferMetadataManager() { + return dpms.mTransferOwnershipMetadataManager; + } + @Override protected void tearDown() throws Exception { flushTasks(); + getMockTransferMetadataManager().deleteMetadataFile(); super.tearDown(); } @@ -4835,6 +4844,176 @@ public class DevicePolicyManagerTest extends DpmTestBase { AttestationUtils.ID_TYPE_MEID}); } + public void testRevertDeviceOwnership_noMetadataFile() throws Exception { + setDeviceOwner(); + initializeDpms(); + assertFalse(getMockTransferMetadataManager().metadataFileExists()); + assertTrue(dpms.isDeviceOwner(admin1, UserHandle.USER_SYSTEM)); + assertTrue(dpms.isAdminActive(admin1, UserHandle.USER_SYSTEM)); + } + + public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception { + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + getDeviceOwnerPoliciesFile()); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated), + getDeviceOwnerFile()); + assertDeviceOwnershipRevertedWithFakeTransferMetadata(); + } + + public void testRevertDeviceOwnership_deviceNotMigrated() + throws Exception { + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + getDeviceOwnerPoliciesFile()); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated), + getDeviceOwnerFile()); + assertDeviceOwnershipRevertedWithFakeTransferMetadata(); + } + + public void testRevertDeviceOwnership_adminAndDeviceNotMigrated() + throws Exception { + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_not_migrated), + getDeviceOwnerPoliciesFile()); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated), + getDeviceOwnerFile()); + assertDeviceOwnershipRevertedWithFakeTransferMetadata(); + } + + public void testRevertProfileOwnership_noMetadataFile() throws Exception { + setupProfileOwner(); + initializeDpms(); + assertFalse(getMockTransferMetadataManager().metadataFileExists()); + assertTrue(dpms.isProfileOwner(admin1, DpmMockContext.CALLER_USER_HANDLE)); + assertTrue(dpms.isAdminActive(admin1, DpmMockContext.CALLER_USER_HANDLE)); + UserHandle userHandle = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE); + } + + public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception { + getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE, + UserHandle.USER_SYSTEM); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + getProfileOwnerPoliciesFile()); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated), + getProfileOwnerFile()); + assertProfileOwnershipRevertedWithFakeTransferMetadata(); + } + + public void testRevertProfileOwnership_profileNotMigrated() throws Exception { + getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE, + UserHandle.USER_SYSTEM); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated), + getProfileOwnerPoliciesFile()); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated), + getProfileOwnerFile()); + assertProfileOwnershipRevertedWithFakeTransferMetadata(); + } + + public void testRevertProfileOwnership_adminAndProfileNotMigrated() throws Exception { + getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, UserInfo.FLAG_MANAGED_PROFILE, + UserHandle.USER_SYSTEM); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_not_migrated), + getProfileOwnerPoliciesFile()); + DpmTestUtils.writeInputStreamToFile( + getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated), + getProfileOwnerFile()); + assertProfileOwnershipRevertedWithFakeTransferMetadata(); + } + + // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one. + private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception { + writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM, + TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER); + + final long ident = mServiceContext.binder.clearCallingIdentity(); + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID); + setUpPackageManagerForFakeAdmin(adminAnotherPackage, + DpmMockContext.CALLER_SYSTEM_USER_UID, admin1); + // To simulate a reboot, we just reinitialize dpms and call systemReady + initializeDpms(); + + assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName())); + assertFalse(dpm.isDeviceOwnerApp(adminAnotherPackage.getPackageName())); + assertFalse(dpm.isAdminActive(adminAnotherPackage)); + assertTrue(dpm.isAdminActive(admin1)); + assertTrue(dpm.isDeviceOwnerAppOnCallingUser(admin1.getPackageName())); + assertEquals(admin1, dpm.getDeviceOwnerComponentOnCallingUser()); + + assertTrue(dpm.isDeviceOwnerAppOnAnyUser(admin1.getPackageName())); + assertEquals(admin1, dpm.getDeviceOwnerComponentOnAnyUser()); + assertEquals(UserHandle.USER_SYSTEM, dpm.getDeviceOwnerUserId()); + assertFalse(getMockTransferMetadataManager().metadataFileExists()); + + mServiceContext.binder.restoreCallingIdentity(ident); + } + + // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one. + private void assertProfileOwnershipRevertedWithFakeTransferMetadata() throws Exception { + writeFakeTransferMetadataFile(DpmMockContext.CALLER_USER_HANDLE, + TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER); + + int uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, + DpmMockContext.CALLER_SYSTEM_USER_UID); + setUpPackageManagerForAdmin(admin1, uid); + setUpPackageManagerForFakeAdmin(adminAnotherPackage, uid, admin1); + // To simulate a reboot, we just reinitialize dpms and call systemReady + initializeDpms(); + + assertTrue(dpm.isProfileOwnerApp(admin1.getPackageName())); + assertTrue(dpm.isAdminActive(admin1)); + assertFalse(dpm.isProfileOwnerApp(adminAnotherPackage.getPackageName())); + assertFalse(dpm.isAdminActive(adminAnotherPackage)); + assertEquals(dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE), admin1); + assertFalse(getMockTransferMetadataManager().metadataFileExists()); + } + + private void writeFakeTransferMetadataFile(int callerUserHandle, String adminType) { + TransferOwnershipMetadataManager metadataManager = getMockTransferMetadataManager(); + metadataManager.deleteMetadataFile(); + + final TransferOwnershipMetadataManager.Metadata metadata = + new TransferOwnershipMetadataManager.Metadata( + admin1.flattenToString(), adminAnotherPackage.flattenToString(), + callerUserHandle, + adminType); + metadataManager.saveMetadataFile(metadata); + } + + private File getDeviceOwnerFile() { + return dpms.mOwners.getDeviceOwnerFile(); + } + + private File getProfileOwnerFile() { + return dpms.mOwners.getProfileOwnerFile(DpmMockContext.CALLER_USER_HANDLE); + } + + private File getProfileOwnerPoliciesFile() { + File parentDir = dpms.mMockInjector.environmentGetUserSystemDirectory( + DpmMockContext.CALLER_USER_HANDLE); + return getPoliciesFile(parentDir); + } + + private File getDeviceOwnerPoliciesFile() { + return getPoliciesFile(getServices().systemUserDataDir); + } + + private File getPoliciesFile(File parentDir) { + return new File(parentDir, "device_policies.xml"); + } + + private InputStream getRawStream(@RawRes int id) { + return mRealTestContext.getResources().openRawResource(id); + } + private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) { when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, userhandle)).thenReturn(isUserSetupComplete ? 1 : 0); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java index cceb2d2761fe..2882b88460c9 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java @@ -16,9 +16,6 @@ package com.android.server.devicepolicy; -import com.google.android.collect.Lists; -import com.google.android.collect.Sets; - import android.content.Context; import android.os.Bundle; import android.os.FileUtils; @@ -28,21 +25,25 @@ import android.test.AndroidTestCase; import android.util.Log; import android.util.Printer; +import libcore.io.Streams; + +import com.google.android.collect.Lists; + +import junit.framework.AssertionFailedError; + import org.junit.Assert; import java.io.BufferedReader; import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.Set; - -import junit.framework.AssertionFailedError; public class DpmTestUtils extends AndroidTestCase { public static void clearDir(File dir) { @@ -136,6 +137,11 @@ public class DpmTestUtils extends AndroidTestCase { } } + public static void writeInputStreamToFile(InputStream stream, File file) + throws IOException { + Streams.copy(stream, new FileOutputStream(file)); + } + private static boolean checkAssertRestrictions(Bundle a, Bundle b) { try { assertRestrictions(a, b); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java index 85835f7034e8..cb6a7472d3bc 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java @@ -42,21 +42,21 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getServices()); - DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.writeToFile(owners.getLegacyConfigFile(), DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test01/input.xml")); owners.load(); // The legacy file should be removed. - assertFalse(owners.getLegacyConfigFileWithTestOverride().exists()); + assertFalse(owners.getLegacyConfigFile().exists()); // File was empty, so no new files should be created. - assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists()); + assertFalse(owners.getDeviceOwnerFile().exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + assertFalse(owners.getProfileOwnerFile(10).exists()); + assertFalse(owners.getProfileOwnerFile(11).exists()); + assertFalse(owners.getProfileOwnerFile(20).exists()); + assertFalse(owners.getProfileOwnerFile(21).exists()); assertFalse(owners.hasDeviceOwner()); assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); @@ -95,20 +95,20 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getServices()); - DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.writeToFile(owners.getLegacyConfigFile(), DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test02/input.xml")); owners.load(); // The legacy file should be removed. - assertFalse(owners.getLegacyConfigFileWithTestOverride().exists()); + assertFalse(owners.getLegacyConfigFile().exists()); - assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists()); // TODO Check content + assertTrue(owners.getDeviceOwnerFile().exists()); // TODO Check content - assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + assertFalse(owners.getProfileOwnerFile(10).exists()); + assertFalse(owners.getProfileOwnerFile(11).exists()); + assertFalse(owners.getProfileOwnerFile(20).exists()); + assertFalse(owners.getProfileOwnerFile(21).exists()); assertTrue(owners.hasDeviceOwner()); assertEquals(null, owners.getDeviceOwnerName()); @@ -153,20 +153,20 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getServices()); - DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.writeToFile(owners.getLegacyConfigFile(), DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test03/input.xml")); owners.load(); // The legacy file should be removed. - assertFalse(owners.getLegacyConfigFileWithTestOverride().exists()); + assertFalse(owners.getLegacyConfigFile().exists()); - assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists()); + assertFalse(owners.getDeviceOwnerFile().exists()); - assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists()); - assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + assertTrue(owners.getProfileOwnerFile(10).exists()); + assertTrue(owners.getProfileOwnerFile(11).exists()); + assertFalse(owners.getProfileOwnerFile(20).exists()); + assertFalse(owners.getProfileOwnerFile(21).exists()); assertFalse(owners.hasDeviceOwner()); assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); @@ -231,20 +231,20 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getServices()); - DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.writeToFile(owners.getLegacyConfigFile(), DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml")); owners.load(); // The legacy file should be removed. - assertFalse(owners.getLegacyConfigFileWithTestOverride().exists()); + assertFalse(owners.getLegacyConfigFile().exists()); - assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists()); + assertTrue(owners.getDeviceOwnerFile().exists()); - assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists()); - assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + assertTrue(owners.getProfileOwnerFile(10).exists()); + assertTrue(owners.getProfileOwnerFile(11).exists()); + assertFalse(owners.getProfileOwnerFile(20).exists()); + assertFalse(owners.getProfileOwnerFile(21).exists()); assertTrue(owners.hasDeviceOwner()); assertEquals(null, owners.getDeviceOwnerName()); @@ -341,20 +341,20 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getServices()); - DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.writeToFile(owners.getLegacyConfigFile(), DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test05/input.xml")); owners.load(); // The legacy file should be removed. - assertFalse(owners.getLegacyConfigFileWithTestOverride().exists()); + assertFalse(owners.getLegacyConfigFile().exists()); // Note device initializer is no longer supported. No need to write the DO file. - assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists()); + assertFalse(owners.getDeviceOwnerFile().exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); + assertFalse(owners.getProfileOwnerFile(10).exists()); + assertFalse(owners.getProfileOwnerFile(11).exists()); + assertFalse(owners.getProfileOwnerFile(20).exists()); assertFalse(owners.hasDeviceOwner()); assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); @@ -397,19 +397,19 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getServices()); - DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.writeToFile(owners.getLegacyConfigFile(), DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test06/input.xml")); owners.load(); // The legacy file should be removed. - assertFalse(owners.getLegacyConfigFileWithTestOverride().exists()); + assertFalse(owners.getLegacyConfigFile().exists()); - assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists()); + assertTrue(owners.getDeviceOwnerFile().exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); + assertFalse(owners.getProfileOwnerFile(10).exists()); + assertFalse(owners.getProfileOwnerFile(11).exists()); + assertFalse(owners.getProfileOwnerFile(20).exists()); assertFalse(owners.hasDeviceOwner()); assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); @@ -451,16 +451,16 @@ public class OwnersTest extends DpmTestBase { final OwnersTestable owners = new OwnersTestable(getServices()); // First, migrate to create new-style config files. - DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.writeToFile(owners.getLegacyConfigFile(), DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml")); owners.load(); - assertFalse(owners.getLegacyConfigFileWithTestOverride().exists()); + assertFalse(owners.getLegacyConfigFile().exists()); - assertTrue(owners.getDeviceOwnerFileWithTestOverride().exists()); - assertTrue(owners.getProfileOwnerFileWithTestOverride(10).exists()); - assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists()); + assertTrue(owners.getDeviceOwnerFile().exists()); + assertTrue(owners.getProfileOwnerFile(10).exists()); + assertTrue(owners.getProfileOwnerFile(11).exists()); // Then clear all information and save. owners.clearDeviceOwner(); @@ -475,8 +475,8 @@ public class OwnersTest extends DpmTestBase { owners.writeProfileOwner(21); // Now all files should be removed. - assertFalse(owners.getDeviceOwnerFileWithTestOverride().exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); - assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); + assertFalse(owners.getDeviceOwnerFile().exists()); + assertFalse(owners.getProfileOwnerFile(10).exists()); + assertFalse(owners.getProfileOwnerFile(11).exists()); } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java new file mode 100644 index 000000000000..03cabb2fb303 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.devicepolicy; + +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; + + +import static com.android.server.devicepolicy.TransferOwnershipMetadataManager + .OWNER_TRANSFER_METADATA_XML; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Environment; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.devicepolicy.TransferOwnershipMetadataManager.Injector; +import com.android.server.devicepolicy.TransferOwnershipMetadataManager.Metadata; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** +* Unit tests for {@link TransferOwnershipMetadataManager}. + * + * bit FrameworksServicesTests:com.android.server.devicepolicy.TransferOwnershipMetadataManagerTest + * runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/TransferOwnershipMetadataManagerTest.java +* */ + +@RunWith(AndroidJUnit4.class) +public class TransferOwnershipMetadataManagerTest { + private final static String ADMIN_PACKAGE = "com.dummy.admin.package"; + private final static String TARGET_PACKAGE = "com.dummy.target.package"; + private final static int USER_ID = 123; + private final static Metadata TEST_PARAMS = new Metadata(ADMIN_PACKAGE, + TARGET_PACKAGE, USER_ID, ADMIN_TYPE_DEVICE_OWNER); + + private MockInjector mMockInjector; + + @Before + public void setUp() { + mMockInjector = new MockInjector(); + getOwnerTransferParams().deleteMetadataFile(); + } + + @Test + public void testSave() { + TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams(); + assertTrue(paramsManager.saveMetadataFile(TEST_PARAMS)); + assertTrue(paramsManager.metadataFileExists()); + } + + @Test + public void testFileContentValid() { + TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams(); + assertTrue(paramsManager.saveMetadataFile(TEST_PARAMS)); + Path path = Paths.get(new File(mMockInjector.getOwnerTransferMetadataDir(), + OWNER_TRANSFER_METADATA_XML).getAbsolutePath()); + try { + String contents = new String(Files.readAllBytes(path), Charset.forName("UTF-8")); + assertEquals( + "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<user-id>" + USER_ID + "</user-id>\n" + + "<admin-component>" + ADMIN_PACKAGE + "</admin-component>\n" + + "<target-component>" + TARGET_PACKAGE + "</target-component>\n" + + "<admin-type>" + ADMIN_TYPE_DEVICE_OWNER + "</admin-type>\n", + contents); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testLoad() { + TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams(); + paramsManager.saveMetadataFile(TEST_PARAMS); + assertEquals(TEST_PARAMS, paramsManager.loadMetadataFile()); + } + + @Test + public void testDelete() { + TransferOwnershipMetadataManager paramsManager = getOwnerTransferParams(); + paramsManager.saveMetadataFile(TEST_PARAMS); + paramsManager.deleteMetadataFile(); + assertFalse(paramsManager.metadataFileExists()); + } + + @After + public void tearDown() { + getOwnerTransferParams().deleteMetadataFile(); + } + + private TransferOwnershipMetadataManager getOwnerTransferParams() { + return new TransferOwnershipMetadataManager(mMockInjector); + } + + static class MockInjector extends Injector { + @Override + public File getOwnerTransferMetadataDir() { + return Environment.getExternalStorageDirectory(); + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java index 9e7ef6539c2b..fb25cf3f01e0 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -281,6 +281,68 @@ public class BrightnessMappingStrategyTest { assertNull(physical); } + @Test + public void testStrategiesAdaptToUserDataPoint() { + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, + DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); + assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res)); + res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); + assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res)); + } + + private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) { + // Save out all of the initial brightness data for comparison after reset. + float[] initialBrightnessLevels = new float[LUX_LEVELS.length]; + for (int i = 0; i < LUX_LEVELS.length; i++) { + initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]); + } + + // Add a data point in the middle of the curve where the user has set the brightness max + final int idx = LUX_LEVELS.length / 2; + strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f); + + // Then make sure that all control points after the middle lux level are also set to max... + for (int i = idx; i < LUX_LEVELS.length; i++) { + assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.01 /*tolerance*/); + } + + // ...and that all control points before the middle lux level are strictly less than the + // previous one still. + float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]); + for (int i = idx - 1; i >= 0; i--) { + float brightness = strategy.getBrightness(LUX_LEVELS[i]); + assertTrue("Brightness levels must be monotonic after adapting to user data", + prevBrightness >= brightness); + prevBrightness = brightness; + } + + // Now reset the curve and make sure we go back to the initial brightness levels recorded + // before adding the user data point. + strategy.clearUserDataPoints(); + for (int i = 0; i < LUX_LEVELS.length; i++) { + assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]), + 0.01 /*tolerance*/); + } + + // Now set the middle of the lux range to something just above the minimum. + final float minBrightness = strategy.getBrightness(LUX_LEVELS[0]); + strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.01f); + + // Then make sure the curve is still monotonic. + prevBrightness = 0f; + for (float lux : LUX_LEVELS) { + float brightness = strategy.getBrightness(lux); + assertTrue("Brightness levels must be monotonic after adapting to user data", + prevBrightness <= brightness); + prevBrightness = brightness; + } + + // And that the lowest lux level still gives the absolute minimum brightness. This should + // be true assuming that there are more than two lux levels in the curve since we picked a + // brightness just barely above the minimum for the middle of the curve. + assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.001 /*tolerance*/); + } + private static float[] toFloatArray(int[] vals) { float[] newVals = new float[vals.length]; for (int i = 0; i < vals.length; i++) { diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index 5c7348c68240..c491601fee1f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -195,7 +195,6 @@ public class PackageParserTest { assertEquals(a.installLocation, b.installLocation); assertEquals(a.coreApp, b.coreApp); assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers); - assertEquals(a.mTrustedOverlay, b.mTrustedOverlay); assertEquals(a.mCompileSdkVersion, b.mCompileSdkVersion); assertEquals(a.mCompileSdkVersionCodename, b.mCompileSdkVersionCodename); assertEquals(a.use32bitAbi, b.use32bitAbi); @@ -429,7 +428,6 @@ public class PackageParserTest { pkg.installLocation = 100; pkg.coreApp = true; pkg.mRequiredForAllUsers = true; - pkg.mTrustedOverlay = true; pkg.use32bitAbi = true; pkg.packageName = "foo"; pkg.splitNames = new String[] { "foo2" }; diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index d17bdc85eb22..6799417939f2 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -1960,6 +1960,15 @@ public final class Call { } } + /** {@hide} */ + final void internalOnHandoverComplete() { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(() -> callback.onHandoverComplete(call)); + } + } + private void fireStateChanged(final int newState) { for (CallbackRecord<Callback> record : mCallbackRecords) { final Call call = this; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 75224434bc1c..63f970a43b0f 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -2801,6 +2801,15 @@ public abstract class Connection extends Conferenceable { public void onCallEvent(String event, Bundle extras) {} /** + * Notifies this {@link Connection} that a handover has completed. + * <p> + * A handover is initiated with {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, + * Bundle)} on the initiating side of the handover, and + * {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)}. + */ + public void onHandoverComplete() {} + + /** * Notifies this {@link Connection} of a change to the extras made outside the * {@link ConnectionService}. * <p> diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 6af01aee86b9..c1040adc5163 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -140,6 +140,7 @@ public abstract class ConnectionService extends Service { private static final String SESSION_POST_DIAL_CONT = "CS.oPDC"; private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC"; private static final String SESSION_SEND_CALL_EVENT = "CS.sCE"; + private static final String SESSION_HANDOVER_COMPLETE = "CS.hC"; private static final String SESSION_EXTRAS_CHANGED = "CS.oEC"; private static final String SESSION_START_RTT = "CS.+RTT"; private static final String SESSION_STOP_RTT = "CS.-RTT"; @@ -179,6 +180,7 @@ public abstract class ConnectionService extends Service { private static final int MSG_CONNECTION_SERVICE_FOCUS_LOST = 30; private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31; private static final int MSG_HANDOVER_FAILED = 32; + private static final int MSG_HANDOVER_COMPLETE = 33; private static Connection sNullConnection; @@ -298,6 +300,19 @@ public abstract class ConnectionService extends Service { } @Override + public void handoverComplete(String callId, Session.Info sessionInfo) { + Log.startSession(sessionInfo, SESSION_HANDOVER_COMPLETE); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = Log.createSubsession(); + mHandler.obtainMessage(MSG_HANDOVER_COMPLETE, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override public void abort(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_ABORT); try { @@ -1028,6 +1043,19 @@ public abstract class ConnectionService extends Service { } break; } + case MSG_HANDOVER_COMPLETE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + Log.continueSession((Session) args.arg2, + SESSION_HANDLER + SESSION_HANDOVER_COMPLETE); + String callId = (String) args.arg1; + notifyHandoverComplete(callId); + } finally { + args.recycle(); + Log.endSession(); + } + break; + } case MSG_ON_EXTRAS_CHANGED: { SomeArgs args = (SomeArgs) msg.obj; try { @@ -1445,19 +1473,24 @@ public abstract class ConnectionService extends Service { final ConnectionRequest request, boolean isIncoming, boolean isUnknown) { + boolean isLegacyHandover = request.getExtras() != null && + request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false); + boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean( + TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false); Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + - "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, - isIncoming, - isUnknown); + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b", + callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover, + isHandover); Connection connection = null; - if (getApplicationContext().getApplicationInfo().targetSdkVersion > - Build.VERSION_CODES.O_MR1 && request.getExtras() != null && - request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER,false)) { + if (isHandover) { + PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null + ? (PhoneAccountHandle) request.getExtras().getParcelable( + TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null; if (!isIncoming) { - connection = onCreateOutgoingHandoverConnection(callManagerAccount, request); + connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request); } else { - connection = onCreateIncomingHandoverConnection(callManagerAccount, request); + connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request); } } else { connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request) @@ -1754,6 +1787,19 @@ public abstract class ConnectionService extends Service { } /** + * Notifies a {@link Connection} that a handover has completed. + * + * @param callId The ID of the call which completed handover. + */ + private void notifyHandoverComplete(String callId) { + Log.d(this, "notifyHandoverComplete(%s)", callId); + Connection connection = findConnectionForAction(callId, "notifyHandoverComplete"); + if (connection != null) { + connection.onHandoverComplete(); + } + } + + /** * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom. * <p> * These extra changes can originate from Telecom itself, or from an {@link InCallService} via diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 74fa62d62ccf..fcf04c9a7eef 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -81,6 +81,7 @@ public abstract class InCallService extends Service { private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10; private static final int MSG_ON_RTT_INITIATION_FAILURE = 11; private static final int MSG_ON_HANDOVER_FAILED = 12; + private static final int MSG_ON_HANDOVER_COMPLETE = 13; /** Default Handler used to consolidate binder method calls onto a single thread. */ private final Handler mHandler = new Handler(Looper.getMainLooper()) { @@ -157,6 +158,11 @@ public abstract class InCallService extends Service { mPhone.internalOnHandoverFailed(callId, error); break; } + case MSG_ON_HANDOVER_COMPLETE: { + String callId = (String) msg.obj; + mPhone.internalOnHandoverComplete(callId); + break; + } default: break; } @@ -237,6 +243,11 @@ public abstract class InCallService extends Service { public void onHandoverFailed(String callId, int error) { mHandler.obtainMessage(MSG_ON_HANDOVER_FAILED, error, 0, callId).sendToTarget(); } + + @Override + public void onHandoverComplete(String callId) { + mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget(); + } } private Phone.Listener mPhoneListener = new Phone.Listener() { diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index b5394b9b0290..99f94f28b6d3 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -230,6 +230,13 @@ public final class Phone { } } + final void internalOnHandoverComplete(String callId) { + Call call = mCallByTelecomCallId.get(callId); + if (call != null) { + call.internalOnHandoverComplete(); + } + } + /** * Called to destroy the phone and cleanup any lingering calls. */ diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 91d5da30935b..1fe5db5d2cc7 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -111,6 +111,12 @@ public class TelecomManager { "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS"; /** + * The {@link android.content.Intent} action used to show the assisted dialing settings. + */ + public static final String ACTION_SHOW_ASSISTED_DIALING_SETTINGS = + "android.telecom.action.SHOW_ASSISTED_DIALING_SETTINGS"; + + /** * The {@link android.content.Intent} action used to show the settings page used to configure * {@link PhoneAccount} preferences. */ @@ -379,6 +385,17 @@ public class TelecomManager { public static final String EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER"; /** + * When {@code true} indicates that a request to create a new connection is for the purpose of + * a handover. Note: This is used with the + * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)} API as part of the + * internal communication mechanism with the {@link android.telecom.ConnectionService}. It is + * not the same as the legacy {@link #EXTRA_IS_HANDOVER} extra. + * @hide + */ + public static final String EXTRA_IS_HANDOVER_CONNECTION = + "android.telecom.extra.IS_HANDOVER_CONNECTION"; + + /** * Parcelable extra used with {@link #EXTRA_IS_HANDOVER} to indicate the source * {@link PhoneAccountHandle} when initiating a handover which {@link ConnectionService} * the handover is from. diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index 3d04bfc1bae5..3a84f004462e 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -107,4 +107,6 @@ oneway interface IConnectionService { void handoverFailed(String callId, in ConnectionRequest request, int error, in Session.Info sessionInfo); + + void handoverComplete(String callId, in Session.Info sessionInfo); } diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl index 110109e6e276..b9563fa7bb18 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl @@ -56,4 +56,6 @@ oneway interface IInCallService { void onRttInitiationFailure(String callId, int reason); void onHandoverFailed(String callId, int error); + + void onHandoverComplete(String callId); } diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java index 703f96d0fe74..7cd16128b895 100644 --- a/telephony/java/android/telephony/AccessNetworkConstants.java +++ b/telephony/java/android/telephony/AccessNetworkConstants.java @@ -24,6 +24,7 @@ import android.annotation.SystemApi; public final class AccessNetworkConstants { public static final class AccessNetworkType { + public static final int UNKNOWN = 0; public static final int GERAN = 1; public static final int UTRAN = 2; public static final int EUTRAN = 3; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index cbc9428bcf56..91d86c698f84 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -39,13 +39,29 @@ public class CarrierConfigManager { private final static String TAG = "CarrierConfigManager"; /** + * Extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the slot index that the + * broadcast is for. + */ + public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; + + /** + * Optional extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the + * subscription index that the broadcast is for, if a valid one is available. + */ + public static final String EXTRA_SUBSCRIPTION_INDEX = + SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX; + + /** * @hide */ public CarrierConfigManager() { } /** - * This intent is broadcast by the system when carrier config changes. + * This intent is broadcast by the system when carrier config changes. An int is specified in + * {@link #EXTRA_SLOT_INDEX} to indicate the slot index that this is for. An optional int extra + * {@link #EXTRA_SUBSCRIPTION_INDEX} is included to indicate the subscription index if a valid + * one is available for the slot index. */ public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index 56e1e640373d..4fa304ae3b97 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -310,6 +310,13 @@ public class DisconnectCause { * {@hide} */ public static final int DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 70; + + /** + * The network has reported that an alternative emergency number has been dialed, but the user + * must exit airplane mode to place the call. + */ + public static final int IMS_SIP_ALTERNATE_EMERGENCY_CALL = 71; + //********************************************************************************************* // When adding a disconnect type: // 1) Update toString() with the newly added disconnect type. @@ -462,6 +469,8 @@ public class DisconnectCause { return "EMERGENCY_PERM_FAILURE"; case NORMAL_UNSPECIFIED: return "NORMAL_UNSPECIFIED"; + case IMS_SIP_ALTERNATE_EMERGENCY_CALL: + return "IMS_SIP_ALTERNATE_EMERGENCY_CALL"; default: return "INVALID: " + cause; } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/INetworkService.aidl index bf8d90b3241e..d810d58a59da 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl +++ b/telephony/java/android/telephony/INetworkService.aidl @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 The Android Open Source Project + * Copyright 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,16 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony; + +import android.telephony.INetworkServiceCallback; /** - * See MMTelFeature for more information. * {@hide} */ -interface IImsSmsListener { - void onSendSmsResult(in int token, in int messageRef, in int status, in int reason); - void onSmsStatusReportReceived(in int token, in int messageRef, in String format, - in byte[] pdu); - void onSmsReceived(in int token, in String format, in byte[] pdu); -}
\ No newline at end of file +oneway interface INetworkService +{ + void getNetworkRegistrationState(int domain, INetworkServiceCallback callback); + void registerForNetworkRegistrationStateChanged(INetworkServiceCallback callback); + void unregisterForNetworkRegistrationStateChanged(INetworkServiceCallback callback); +} diff --git a/telephony/java/android/telephony/INetworkServiceCallback.aidl b/telephony/java/android/telephony/INetworkServiceCallback.aidl new file mode 100644 index 000000000000..520598ff9144 --- /dev/null +++ b/telephony/java/android/telephony/INetworkServiceCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.telephony.NetworkRegistrationState; + +/** + * Network service call back interface + * @hide + */ +oneway interface INetworkServiceCallback +{ + void onGetNetworkRegistrationStateComplete(int result, in NetworkRegistrationState state); + void onNetworkStateChanged(); +} diff --git a/telephony/java/android/telephony/NetworkRegistrationState.aidl b/telephony/java/android/telephony/NetworkRegistrationState.aidl new file mode 100644 index 000000000000..98cba7720603 --- /dev/null +++ b/telephony/java/android/telephony/NetworkRegistrationState.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable NetworkRegistrationState; diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java new file mode 100644 index 000000000000..e0510694d4ad --- /dev/null +++ b/telephony/java/android/telephony/NetworkRegistrationState.java @@ -0,0 +1,258 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; + +/** + * Description of a mobile network registration state + * @hide + */ +@SystemApi +public class NetworkRegistrationState implements Parcelable { + /** + * Network domain + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "DOMAIN_", value = {DOMAIN_CS, DOMAIN_PS}) + public @interface Domain {} + + /** Circuit switching domain */ + public static final int DOMAIN_CS = 1; + /** Packet switching domain */ + public static final int DOMAIN_PS = 2; + + /** + * Registration state + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "REG_STATE_", + value = {REG_STATE_NOT_REG_NOT_SEARCHING, REG_STATE_HOME, REG_STATE_NOT_REG_SEARCHING, + REG_STATE_DENIED, REG_STATE_UNKNOWN, REG_STATE_ROAMING}) + public @interface RegState {} + + /** Not registered. The device is not currently searching a new operator to register */ + public static final int REG_STATE_NOT_REG_NOT_SEARCHING = 0; + /** Registered on home network */ + public static final int REG_STATE_HOME = 1; + /** Not registered. The device is currently searching a new operator to register */ + public static final int REG_STATE_NOT_REG_SEARCHING = 2; + /** Registration denied */ + public static final int REG_STATE_DENIED = 3; + /** Registration state is unknown */ + public static final int REG_STATE_UNKNOWN = 4; + /** Registered on roaming network */ + public static final int REG_STATE_ROAMING = 5; + + /** + * Supported service type + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "SERVICE_TYPE_", + value = {SERVICE_TYPE_VOICE, SERVICE_TYPE_DATA, SERVICE_TYPE_SMS, SERVICE_TYPE_VIDEO, + SERVICE_TYPE_EMERGENCY}) + public @interface ServiceType {} + + public static final int SERVICE_TYPE_VOICE = 1; + public static final int SERVICE_TYPE_DATA = 2; + public static final int SERVICE_TYPE_SMS = 3; + public static final int SERVICE_TYPE_VIDEO = 4; + public static final int SERVICE_TYPE_EMERGENCY = 5; + + /** {@link AccessNetworkConstants.TransportType}*/ + private final int mTransportType; + + @Domain + private final int mDomain; + + @RegState + private final int mRegState; + + private final int mAccessNetworkTechnology; + + private final int mReasonForDenial; + + private final boolean mEmergencyOnly; + + private final int[] mAvailableServices; + + @Nullable + private final CellIdentity mCellIdentity; + + + /** + * @param transportType Transport type. Must be {@link AccessNetworkConstants.TransportType} + * @param domain Network domain. Must be DOMAIN_CS or DOMAIN_PS. + * @param regState Network registration state. + * @param accessNetworkTechnology See TelephonyManager NETWORK_TYPE_XXXX. + * @param reasonForDenial Reason for denial if the registration state is DENIED. + * @param availableServices The supported service. + * @param cellIdentity The identity representing a unique cell + */ + public NetworkRegistrationState(int transportType, int domain, int regState, + int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly, + int[] availableServices, @Nullable CellIdentity cellIdentity) { + mTransportType = transportType; + mDomain = domain; + mRegState = regState; + mAccessNetworkTechnology = accessNetworkTechnology; + mReasonForDenial = reasonForDenial; + mAvailableServices = availableServices; + mCellIdentity = cellIdentity; + mEmergencyOnly = emergencyOnly; + } + + protected NetworkRegistrationState(Parcel source) { + mTransportType = source.readInt(); + mDomain = source.readInt(); + mRegState = source.readInt(); + mAccessNetworkTechnology = source.readInt(); + mReasonForDenial = source.readInt(); + mEmergencyOnly = source.readBoolean(); + mAvailableServices = source.createIntArray(); + mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader()); + } + + /** + * @return The transport type. + */ + public int getTransportType() { return mTransportType; } + + /** + * @return The network domain. + */ + public @Domain int getDomain() { return mDomain; } + + /** + * @return The registration state. + */ + public @RegState int getRegState() { + return mRegState; + } + + /** + * @return Whether emergency is enabled. + */ + public boolean isEmergencyEnabled() { return mEmergencyOnly; } + + /** + * @return List of available service types. + */ + public int[] getAvailableServices() { return mAvailableServices; } + + /** + * @return The access network technology. Must be one of TelephonyManager.NETWORK_TYPE_XXXX. + */ + public int getAccessNetworkTechnology() { + return mAccessNetworkTechnology; + } + + @Override + public int describeContents() { + return 0; + } + + private static String regStateToString(int regState) { + switch (regState) { + case REG_STATE_NOT_REG_NOT_SEARCHING: return "NOT_REG_NOT_SEARCHING"; + case REG_STATE_HOME: return "HOME"; + case REG_STATE_NOT_REG_SEARCHING: return "NOT_REG_SEARCHING"; + case REG_STATE_DENIED: return "DENIED"; + case REG_STATE_UNKNOWN: return "UNKNOWN"; + case REG_STATE_ROAMING: return "ROAMING"; + } + return "Unknown reg state " + regState; + } + + @Override + public String toString() { + return new StringBuilder("NetworkRegistrationState{") + .append("transportType=").append(mTransportType) + .append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS") + .append(" regState=").append(regStateToString(mRegState)) + .append(" accessNetworkTechnology=") + .append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology)) + .append(" reasonForDenial=").append(mReasonForDenial) + .append(" emergencyEnabled=").append(mEmergencyOnly) + .append(" supportedServices=").append(mAvailableServices) + .append(" cellIdentity=").append(mCellIdentity) + .append("}").toString(); + } + + @Override + public int hashCode() { + return Objects.hash(mTransportType, mDomain, mRegState, mAccessNetworkTechnology, + mReasonForDenial, mEmergencyOnly, mAvailableServices, mCellIdentity); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || !(o instanceof NetworkRegistrationState)) { + return false; + } + + NetworkRegistrationState other = (NetworkRegistrationState) o; + return mTransportType == other.mTransportType + && mDomain == other.mDomain + && mRegState == other.mRegState + && mAccessNetworkTechnology == other.mAccessNetworkTechnology + && mReasonForDenial == other.mReasonForDenial + && mEmergencyOnly == other.mEmergencyOnly + && (mAvailableServices == other.mAvailableServices + || Arrays.equals(mAvailableServices, other.mAvailableServices)) + && mCellIdentity == other.mCellIdentity; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mTransportType); + dest.writeInt(mDomain); + dest.writeInt(mRegState); + dest.writeInt(mAccessNetworkTechnology); + dest.writeInt(mReasonForDenial); + dest.writeBoolean(mEmergencyOnly); + dest.writeIntArray(mAvailableServices); + dest.writeParcelable(mCellIdentity, 0); + } + + public static final Parcelable.Creator<NetworkRegistrationState> CREATOR = + new Parcelable.Creator<NetworkRegistrationState>() { + @Override + public NetworkRegistrationState createFromParcel(Parcel source) { + return new NetworkRegistrationState(source); + } + + @Override + public NetworkRegistrationState[] newArray(int size) { + return new NetworkRegistrationState[size]; + } + }; +} diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java new file mode 100644 index 000000000000..6b3584c1309f --- /dev/null +++ b/telephony/java/android/telephony/NetworkService.java @@ -0,0 +1,314 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.CallSuper; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base class of network service. Services that extend NetworkService must register the service in + * their AndroidManifest to be detected by the framework. They must be protected by the permission + * "android.permission.BIND_NETWORK_SERVICE". The network service definition in the manifest must + * follow the following format: + * ... + * <service android:name=".xxxNetworkService" + * android:permission="android.permission.BIND_NETWORK_SERVICE" > + * <intent-filter> + * <action android:name="android.telephony.NetworkService" /> + * </intent-filter> + * </service> + * @hide + */ +@SystemApi +public abstract class NetworkService extends Service { + + private final String TAG = NetworkService.class.getSimpleName(); + + public static final String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService"; + public static final String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID"; + + private static final int NETWORK_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE = 1; + private static final int NETWORK_SERVICE_GET_REGISTRATION_STATE = 2; + private static final int NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE = 3; + private static final int NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE = 4; + private static final int NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED = 5; + + + private final HandlerThread mHandlerThread; + + private final NetworkServiceHandler mHandler; + + private final SparseArray<NetworkServiceProvider> mServiceMap = new SparseArray<>(); + + private final SparseArray<INetworkServiceWrapper> mBinderMap = new SparseArray<>(); + + /** + * The abstract class of the actual network service implementation. The network service provider + * must extend this class to support network connection. Note that each instance of network + * service is associated with one physical SIM slot. + */ + public class NetworkServiceProvider { + private final int mSlotId; + + private final List<INetworkServiceCallback> + mNetworkRegistrationStateChangedCallbacks = new ArrayList<>(); + + public NetworkServiceProvider(int slotId) { + mSlotId = slotId; + } + + /** + * @return SIM slot id the network service associated with. + */ + public final int getSlotId() { + return mSlotId; + } + + /** + * API to get network registration state. The result will be passed to the callback. + * @param domain + * @param callback + * @return SIM slot id the network service associated with. + */ + public void getNetworkRegistrationState(int domain, NetworkServiceCallback callback) { + callback.onGetNetworkRegistrationStateComplete( + NetworkServiceCallback.RESULT_ERROR_UNSUPPORTED, null); + } + + public final void notifyNetworkRegistrationStateChanged() { + mHandler.obtainMessage(NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED, + mSlotId, 0, null).sendToTarget(); + } + + private void registerForStateChanged(INetworkServiceCallback callback) { + synchronized (mNetworkRegistrationStateChangedCallbacks) { + mNetworkRegistrationStateChangedCallbacks.add(callback); + } + } + + private void unregisterForStateChanged(INetworkServiceCallback callback) { + synchronized (mNetworkRegistrationStateChangedCallbacks) { + mNetworkRegistrationStateChangedCallbacks.remove(callback); + } + } + + private void notifyStateChangedToCallbacks() { + for (INetworkServiceCallback callback : mNetworkRegistrationStateChangedCallbacks) { + try { + callback.onNetworkStateChanged(); + } catch (RemoteException exception) { + // Doing nothing. + } + } + } + + /** + * Called when the instance of network service is destroyed (e.g. got unbind or binder died). + */ + @CallSuper + protected void onDestroy() { + mNetworkRegistrationStateChangedCallbacks.clear(); + } + } + + private class NetworkServiceHandler extends Handler { + + NetworkServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + final int slotId = message.arg1; + final INetworkServiceCallback callback = (INetworkServiceCallback) message.obj; + NetworkServiceProvider service; + + synchronized (mServiceMap) { + service = mServiceMap.get(slotId); + } + + switch (message.what) { + case NETWORK_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE: + service = createNetworkServiceProvider(message.arg1); + if (service != null) { + mServiceMap.put(slotId, service); + } + break; + case NETWORK_SERVICE_GET_REGISTRATION_STATE: + if (service == null) break; + int domainId = message.arg2; + service.getNetworkRegistrationState(domainId, + new NetworkServiceCallback(callback)); + + break; + case NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE: + if (service == null) break; + service.registerForStateChanged(callback); + break; + case NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE: + if (service == null) break; + service.unregisterForStateChanged(callback); + break; + case NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED: + if (service == null) break; + service.notifyStateChangedToCallbacks(); + break; + default: + break; + } + } + } + + /** @hide */ + protected NetworkService() { + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + + mHandler = new NetworkServiceHandler(mHandlerThread.getLooper()); + log("network service created"); + } + + /** + * Create the instance of {@link NetworkServiceProvider}. Network service provider must override + * this method to facilitate the creation of {@link NetworkServiceProvider} instances. The system + * will call this method after binding the network service for each active SIM slot id. + * + * @param slotId SIM slot id the network service associated with. + * @return Network service object + */ + protected abstract NetworkServiceProvider createNetworkServiceProvider(int slotId); + + /** @hide */ + @Override + public IBinder onBind(Intent intent) { + if (intent == null || !NETWORK_SERVICE_INTERFACE.equals(intent.getAction())) { + loge("Unexpected intent " + intent); + return null; + } + + int slotId = intent.getIntExtra( + NETWORK_SERVICE_EXTRA_SLOT_ID, SubscriptionManager.INVALID_SIM_SLOT_INDEX); + + if (!SubscriptionManager.isValidSlotIndex(slotId)) { + loge("Invalid slot id " + slotId); + return null; + } + + log("onBind: slot id=" + slotId); + + INetworkServiceWrapper binder = mBinderMap.get(slotId); + if (binder == null) { + Message msg = mHandler.obtainMessage( + NETWORK_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE); + msg.arg1 = slotId; + msg.sendToTarget(); + + binder = new INetworkServiceWrapper(slotId); + mBinderMap.put(slotId, binder); + } + + return binder; + } + + /** @hide */ + @Override + public boolean onUnbind(Intent intent) { + int slotId = intent.getIntExtra(NETWORK_SERVICE_EXTRA_SLOT_ID, + SubscriptionManager.INVALID_SIM_SLOT_INDEX); + if (mBinderMap.get(slotId) != null) { + NetworkServiceProvider serviceImpl; + synchronized (mServiceMap) { + serviceImpl = mServiceMap.get(slotId); + } + // We assume only one component might bind to the service. So if onUnbind is ever + // called, we destroy the serviceImpl. + if (serviceImpl != null) { + serviceImpl.onDestroy(); + } + mBinderMap.remove(slotId); + } + + return false; + } + + /** @hide */ + @Override + public void onDestroy() { + synchronized (mServiceMap) { + for (int i = 0; i < mServiceMap.size(); i++) { + NetworkServiceProvider serviceImpl = mServiceMap.get(i); + if (serviceImpl != null) { + serviceImpl.onDestroy(); + } + } + mServiceMap.clear(); + } + + mHandlerThread.quit(); + } + + /** + * A wrapper around INetworkService that forwards calls to implementations of + * {@link NetworkService}. + */ + private class INetworkServiceWrapper extends INetworkService.Stub { + + private final int mSlotId; + + INetworkServiceWrapper(int slotId) { + mSlotId = slotId; + } + + @Override + public void getNetworkRegistrationState(int domain, INetworkServiceCallback callback) { + mHandler.obtainMessage(NETWORK_SERVICE_GET_REGISTRATION_STATE, mSlotId, + domain, callback).sendToTarget(); + } + + @Override + public void registerForNetworkRegistrationStateChanged(INetworkServiceCallback callback) { + mHandler.obtainMessage(NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE, mSlotId, + 0, callback).sendToTarget(); + } + + @Override + public void unregisterForNetworkRegistrationStateChanged(INetworkServiceCallback callback) { + mHandler.obtainMessage(NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE, mSlotId, + 0, callback).sendToTarget(); + } + } + + private final void log(String s) { + Rlog.d(TAG, s); + } + + private final void loge(String s) { + Rlog.e(TAG, s); + } +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java new file mode 100644 index 000000000000..92ebf367025c --- /dev/null +++ b/telephony/java/android/telephony/NetworkServiceCallback.java @@ -0,0 +1,88 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.os.RemoteException; +import android.telephony.NetworkService.NetworkServiceProvider; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; + +/** + * Network service callback. Object of this class is passed to NetworkServiceProvider upon + * calling getNetworkRegistrationState, to receive asynchronous feedback from NetworkServiceProvider + * upon onGetNetworkRegistrationStateComplete. It's like a wrapper of INetworkServiceCallback + * because INetworkServiceCallback can't be a parameter type in public APIs. + * + * @hide + */ +@SystemApi +public class NetworkServiceCallback { + + private static final String mTag = NetworkServiceCallback.class.getSimpleName(); + + /** + * Result of network requests + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESULT_SUCCESS, RESULT_ERROR_UNSUPPORTED, RESULT_ERROR_INVALID_ARG, RESULT_ERROR_BUSY, + RESULT_ERROR_ILLEGAL_STATE, RESULT_ERROR_FAILED}) + public @interface Result {} + + /** Request is completed successfully */ + public static final int RESULT_SUCCESS = 0; + /** Request is not support */ + public static final int RESULT_ERROR_UNSUPPORTED = 1; + /** Request contains invalid arguments */ + public static final int RESULT_ERROR_INVALID_ARG = 2; + /** Service is busy */ + public static final int RESULT_ERROR_BUSY = 3; + /** Request sent in illegal state */ + public static final int RESULT_ERROR_ILLEGAL_STATE = 4; + /** Request failed */ + public static final int RESULT_ERROR_FAILED = 5; + + private final WeakReference<INetworkServiceCallback> mCallback; + + /** @hide */ + public NetworkServiceCallback(INetworkServiceCallback callback) { + mCallback = new WeakReference<>(callback); + } + + /** + * Called to indicate result of + * {@link NetworkServiceProvider#getNetworkRegistrationState(int, NetworkServiceCallback)} + * + * @param result Result status like {@link NetworkServiceCallback#RESULT_SUCCESS} or + * {@link NetworkServiceCallback#RESULT_ERROR_UNSUPPORTED} + * @param state The state information to be returned to callback. + */ + public void onGetNetworkRegistrationStateComplete(int result, NetworkRegistrationState state) { + INetworkServiceCallback callback = mCallback.get(); + if (callback != null) { + try { + callback.onGetNetworkRegistrationStateComplete(result, state); + } catch (RemoteException e) { + Rlog.e(mTag, "Failed to onGetNetworkRegistrationStateComplete on the remote"); + } + } + } +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index d4b4b88081d6..77706e8fc54f 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -17,6 +17,7 @@ package android.telephony; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -25,6 +26,9 @@ import android.text.TextUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + /** * Contains phone state and service related information. * @@ -286,6 +290,8 @@ public class ServiceState implements Parcelable { * Reference: 3GPP TS 36.104 5.4.3 */ private int mLteEarfcnRsrpBoost = 0; + private List<NetworkRegistrationState> mNetworkRegistrationStates = new ArrayList<>(); + /** * get String description of roaming type * @hide @@ -366,6 +372,7 @@ public class ServiceState implements Parcelable { mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration; mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation; mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost; + mNetworkRegistrationStates = new ArrayList<>(s.mNetworkRegistrationStates); } /** @@ -396,6 +403,8 @@ public class ServiceState implements Parcelable { mIsDataRoamingFromRegistration = in.readInt() != 0; mIsUsingCarrierAggregation = in.readInt() != 0; mLteEarfcnRsrpBoost = in.readInt(); + mNetworkRegistrationStates = new ArrayList<>(); + in.readList(mNetworkRegistrationStates, NetworkRegistrationState.class.getClassLoader()); } public void writeToParcel(Parcel out, int flags) { @@ -423,6 +432,7 @@ public class ServiceState implements Parcelable { out.writeInt(mIsDataRoamingFromRegistration ? 1 : 0); out.writeInt(mIsUsingCarrierAggregation ? 1 : 0); out.writeInt(mLteEarfcnRsrpBoost); + out.writeList(mNetworkRegistrationStates); } public int describeContents() { @@ -751,13 +761,14 @@ public class ServiceState implements Parcelable { s.mCdmaDefaultRoamingIndicator) && mIsEmergencyOnly == s.mIsEmergencyOnly && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration - && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation); + && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation) + && mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates); } /** * Convert radio technology to String * - * @param radioTechnology + * @param rt radioTechnology * @return String representation of the RAT * * @hide @@ -884,6 +895,7 @@ public class ServiceState implements Parcelable { .append(", mIsDataRoamingFromRegistration=").append(mIsDataRoamingFromRegistration) .append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation) .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost) + .append(", mNetworkRegistrationStates=").append(mNetworkRegistrationStates) .append("}").toString(); } @@ -913,6 +925,7 @@ public class ServiceState implements Parcelable { mIsDataRoamingFromRegistration = false; mIsUsingCarrierAggregation = false; mLteEarfcnRsrpBoost = 0; + mNetworkRegistrationStates = new ArrayList<>(); } public void setStateOutOfService() { @@ -1394,4 +1407,52 @@ public class ServiceState implements Parcelable { return newSs; } + + /** + * Get all of the available network registration states. + * + * @return List of registration states + * @hide + */ + @SystemApi + public List<NetworkRegistrationState> getNetworkRegistrationStates() { + return mNetworkRegistrationStates; + } + + /** + * Get the network registration states with given transport type. + * + * @param transportType The transport type. See {@link AccessNetworkConstants.TransportType} + * @return List of registration states. + * @hide + */ + @SystemApi + public List<NetworkRegistrationState> getNetworkRegistrationStates(int transportType) { + List<NetworkRegistrationState> list = new ArrayList<>(); + for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) { + if (networkRegistrationState.getTransportType() == transportType) { + list.add(networkRegistrationState); + } + } + return list; + } + + /** + * Get the network registration states with given transport type and domain. + * + * @param transportType The transport type. See {@link AccessNetworkConstants.TransportType} + * @param domain The network domain. Must be DOMAIN_CS or DOMAIN_PS. + * @return The matching NetworkRegistrationState. + * @hide + */ + @SystemApi + public NetworkRegistrationState getNetworkRegistrationStates(int transportType, int domain) { + for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) { + if (networkRegistrationState.getTransportType() == transportType + && networkRegistrationState.getDomain() == domain) { + return networkRegistrationState; + } + } + return null; + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 2bdbfdd1de04..0a6d960421b2 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -22,14 +22,13 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; -import android.annotation.SuppressLint; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.WorkerThread; import android.app.ActivityThread; import android.app.PendingIntent; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; @@ -43,7 +42,6 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.SystemProperties; -import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.carrier.CarrierIdentifier; import android.telecom.PhoneAccount; @@ -63,7 +61,6 @@ import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.IPhoneSubInfo; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.ITelephonyRegistry; -import com.android.internal.telephony.OperatorInfo; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.TelephonyProperties; @@ -2601,6 +2598,53 @@ public class TelephonyManager { } } + /** + * Gets all the UICC slots. + * + * @return UiccSlotInfo array. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public UiccSlotInfo[] getUiccSlotsInfo() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + return null; + } + return telephony.getUiccSlotsInfo(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Map logicalSlot to physicalSlot, and activate the physicalSlot if it is inactive. For + * example, passing the physicalSlots array [1, 0] means mapping the first item 1, which is + * physical slot index 1, to the logical slot 0; and mapping the second item 0, which is + * physical slot index 0, to the logical slot 1. The index of the array means the index of the + * logical slots. + * + * @param physicalSlots Index i in the array representing physical slot for phone i. The array + * size should be same as {@link #getPhoneCount()}. + * @return boolean Return true if the switch succeeds, false if the switch fails. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean switchSlots(int[] physicalSlots) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + return false; + } + return telephony.switchSlots(physicalSlots); + } catch (RemoteException e) { + return false; + } + } + // // // Subscriber Info diff --git a/telephony/java/android/telephony/UiccSlotInfo.aidl b/telephony/java/android/telephony/UiccSlotInfo.aidl new file mode 100644 index 000000000000..5571a6c35225 --- /dev/null +++ b/telephony/java/android/telephony/UiccSlotInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable UiccSlotInfo; diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java new file mode 100644 index 000000000000..0b3cbad0d2e2 --- /dev/null +++ b/telephony/java/android/telephony/UiccSlotInfo.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +import android.annotation.IntDef; + +/** + * Class for the information of a UICC slot. + * @hide + */ +@SystemApi +public class UiccSlotInfo implements Parcelable { + /** + * Card state. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "CARD_STATE_INFO_" }, value = { + CARD_STATE_INFO_ABSENT, + CARD_STATE_INFO_PRESENT, + CARD_STATE_INFO_ERROR, + CARD_STATE_INFO_RESTRICTED + }) + public @interface CardStateInfo {} + + /** Card state absent. */ + public static final int CARD_STATE_INFO_ABSENT = 1; + + /** Card state present. */ + public static final int CARD_STATE_INFO_PRESENT = 2; + + /** Card state error. */ + public static final int CARD_STATE_INFO_ERROR = 3; + + /** Card state restricted. */ + public static final int CARD_STATE_INFO_RESTRICTED = 4; + + public final boolean isActive; + public final boolean isEuicc; + public final String cardId; + public final @CardStateInfo int cardStateInfo; + + public static final Creator<UiccSlotInfo> CREATOR = new Creator<UiccSlotInfo>() { + @Override + public UiccSlotInfo createFromParcel(Parcel in) { + return new UiccSlotInfo(in); + } + + @Override + public UiccSlotInfo[] newArray(int size) { + return new UiccSlotInfo[size]; + } + }; + + private UiccSlotInfo(Parcel in) { + isActive = in.readByte() != 0; + isEuicc = in.readByte() != 0; + cardId = in.readString(); + cardStateInfo = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (isActive ? 1 : 0)); + dest.writeByte((byte) (isEuicc ? 1 : 0)); + dest.writeString(cardId); + dest.writeInt(cardStateInfo); + } + + @Override + public int describeContents() { + return 0; + } + + public UiccSlotInfo(boolean isActive, boolean isEuicc, String cardId, + @CardStateInfo int cardStateInfo) { + this.isActive = isActive; + this.isEuicc = isEuicc; + this.cardId = cardId; + this.cardStateInfo = cardStateInfo; + } + + public boolean getIsActive() { + return isActive; + } + + public boolean getIsEuicc() { + return isEuicc; + } + + public String getCardId() { + return cardId; + } + + @CardStateInfo + public int getCardStateInfo() { + return cardStateInfo; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + UiccSlotInfo that = (UiccSlotInfo) obj; + return (isActive == that.isActive) + && (isEuicc == that.isEuicc) + && (cardId == that.cardId) + && (cardStateInfo == that.cardStateInfo); + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + (isActive ? 1 : 0); + result = 31 * result + (isEuicc ? 1 : 0); + result = 31 * result + Objects.hashCode(cardId); + result = 31 * result + cardStateInfo; + return result; + } + + @Override + public String toString() { + return "UiccSlotInfo (isActive=" + + isActive + + ", isEuicc=" + + isEuicc + + ", cardId=" + + cardId + + ", cardState=" + + cardStateInfo + + ")"; + } +} diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java deleted file mode 100644 index 33b23d94ad34..000000000000 --- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package android.telephony.ims.internal; - -import android.annotation.IntDef; -import android.os.RemoteException; -import android.telephony.SmsManager; -import android.telephony.SmsMessage; -import android.telephony.ims.internal.aidl.IImsSmsListener; -import android.telephony.ims.internal.feature.MmTelFeature; -import android.util.Log; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Base implementation for SMS over IMS. - * - * Any service wishing to provide SMS over IMS should extend this class and implement all methods - * that the service supports. - * @hide - */ -public class SmsImplBase { - private static final String LOG_TAG = "SmsImplBase"; - - /** @hide */ - @IntDef({ - SEND_STATUS_OK, - SEND_STATUS_ERROR, - SEND_STATUS_ERROR_RETRY, - SEND_STATUS_ERROR_FALLBACK - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SendStatusResult {} - /** - * Message was sent successfully. - */ - public static final int SEND_STATUS_OK = 1; - - /** - * IMS provider failed to send the message and platform should not retry falling back to sending - * the message using the radio. - */ - public static final int SEND_STATUS_ERROR = 2; - - /** - * IMS provider failed to send the message and platform should retry again after setting TP-RD bit - * to high. - */ - public static final int SEND_STATUS_ERROR_RETRY = 3; - - /** - * IMS provider failed to send the message and platform should retry falling back to sending - * the message using the radio. - */ - public static final int SEND_STATUS_ERROR_FALLBACK = 4; - - /** @hide */ - @IntDef({ - DELIVER_STATUS_OK, - DELIVER_STATUS_ERROR - }) - @Retention(RetentionPolicy.SOURCE) - public @interface DeliverStatusResult {} - /** - * Message was delivered successfully. - */ - public static final int DELIVER_STATUS_OK = 1; - - /** - * Message was not delivered. - */ - public static final int DELIVER_STATUS_ERROR = 2; - - /** @hide */ - @IntDef({ - STATUS_REPORT_STATUS_OK, - STATUS_REPORT_STATUS_ERROR - }) - @Retention(RetentionPolicy.SOURCE) - public @interface StatusReportResult {} - - /** - * Status Report was set successfully. - */ - public static final int STATUS_REPORT_STATUS_OK = 1; - - /** - * Error while setting status report. - */ - public static final int STATUS_REPORT_STATUS_ERROR = 2; - - - // Lock for feature synchronization - private final Object mLock = new Object(); - private IImsSmsListener mListener; - - /** - * Registers a listener responsible for handling tasks like delivering messages. - * - * @param listener listener to register. - * - * @hide - */ - public final void registerSmsListener(IImsSmsListener listener) { - synchronized (mLock) { - mListener = listener; - } - } - - /** - * This method will be triggered by the platform when the user attempts to send an SMS. This - * method should be implemented by the IMS providers to provide implementation of sending an SMS - * over IMS. - * - * @param token unique token generated by the platform that should be used when triggering - * callbacks for this specific message. - * @param messageRef the message reference. - * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and - * {@link SmsMessage#FORMAT_3GPP2}. - * @param smsc the Short Message Service Center address. - * @param isRetry whether it is a retry of an already attempted message or not. - * @param pdu PDUs representing the contents of the message. - */ - public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, - byte[] pdu) { - // Base implementation returns error. Should be overridden. - try { - onSendSmsResult(token, messageRef, SEND_STATUS_ERROR, - SmsManager.RESULT_ERROR_GENERIC_FAILURE); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Can not send sms: " + e.getMessage()); - } - } - - /** - * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])} - * has been called to deliver the result to the IMS provider. - * - * @param token token provided in {@link #onSmsReceived(int, String, byte[])} - * @param result result of delivering the message. Valid values are defined in - * {@link DeliverStatusResult} - * @param messageRef the message reference - */ - public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { - Log.e(LOG_TAG, "acknowledgeSms() not implemented."); - } - - /** - * This method will be triggered by the platform after - * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the - * result to the IMS provider. - * - * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} - * @param result result of delivering the message. Valid values are defined in - * {@link StatusReportResult} - * @param messageRef the message reference - */ - public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { - Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented."); - } - - /** - * This method should be triggered by the IMS providers when there is an incoming message. The - * platform will deliver the message to the messages database and notify the IMS provider of the - * result by calling {@link #acknowledgeSms(int, int, int)}. - * - * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. - * - * @param token unique token generated by IMS providers that the platform will use to trigger - * callbacks for this message. - * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and - * {@link SmsMessage#FORMAT_3GPP2}. - * @param pdu PDUs representing the contents of the message. - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} - */ - public final void onSmsReceived(int token, String format, byte[] pdu) - throws IllegalStateException { - synchronized (mLock) { - if (mListener == null) { - throw new IllegalStateException("Feature not ready."); - } - try { - mListener.onSmsReceived(token, format, pdu); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage()); - acknowledgeSms(token, 0, DELIVER_STATUS_ERROR); - } - } - } - - /** - * This method should be triggered by the IMS providers to pass the result of the sent message - * to the platform. - * - * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. - * - * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} - * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 - * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult} - * @param reason reason in case status is failure. Valid values are: - * {@link SmsManager#RESULT_ERROR_NONE}, - * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE}, - * {@link SmsManager#RESULT_ERROR_RADIO_OFF}, - * {@link SmsManager#RESULT_ERROR_NULL_PDU}, - * {@link SmsManager#RESULT_ERROR_NO_SERVICE}, - * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED}, - * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED}, - * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED} - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} - * @throws RemoteException if the connection to the framework is not available. If this happens - * attempting to send the SMS should be aborted. - */ - public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, - int reason) throws IllegalStateException, RemoteException { - synchronized (mLock) { - if (mListener == null) { - throw new IllegalStateException("Feature not ready."); - } - mListener.onSendSmsResult(token, messageRef, status, reason); - } - } - - /** - * Sets the status report of the sent message. - * - * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} - * @param messageRef the message reference. - * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and - * {@link SmsMessage#FORMAT_3GPP2}. - * @param pdu PDUs representing the content of the status report. - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} - */ - public final void onSmsStatusReportReceived(int token, int messageRef, String format, - byte[] pdu) { - synchronized (mLock) { - if (mListener == null) { - throw new IllegalStateException("Feature not ready."); - } - try { - mListener.onSmsStatusReportReceived(token, messageRef, format, pdu); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage()); - acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR); - } - } - } - - /** - * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS - * Provider. - * - * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and - * {@link SmsMessage#FORMAT_3GPP2}. - */ - public String getSmsFormat() { - return SmsMessage.FORMAT_3GPP; - } - -} diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl index 785113f0ff62..e226adaac07f 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl @@ -18,7 +18,6 @@ package android.telephony.ims.internal.aidl; import android.os.Message; import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.aidl.IImsSmsListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.feature.CapabilityChangeRequest; @@ -50,11 +49,4 @@ interface IImsMmTelFeature { IImsCapabilityCallback c); oneway void queryCapabilityConfiguration(int capability, int radioTech, IImsCapabilityCallback c); - // SMS APIs - void setSmsListener(IImsSmsListener l); - oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, - in byte[] pdu); - oneway void acknowledgeSms(int token, int messageRef, int result); - oneway void acknowledgeSmsReport(int token, int messageRef, int result); - String getSmsFormat(); } diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java index 8d888c2bcb28..9b576c72fa96 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java @@ -21,14 +21,10 @@ import android.os.Message; import android.os.RemoteException; import android.telecom.TelecomManager; import android.telephony.ims.internal.ImsCallSessionListener; -import android.telephony.ims.internal.SmsImplBase; -import android.telephony.ims.internal.SmsImplBase.DeliverStatusResult; -import android.telephony.ims.internal.SmsImplBase.StatusReportResult; import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.aidl.IImsSmsListener; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; @@ -68,11 +64,6 @@ public class MmTelFeature extends ImsFeature { } @Override - public void setSmsListener(IImsSmsListener l) throws RemoteException { - MmTelFeature.this.setSmsListener(l); - } - - @Override public int getFeatureState() throws RemoteException { synchronized (mLock) { return MmTelFeature.this.getFeatureState(); @@ -152,35 +143,6 @@ public class MmTelFeature extends ImsFeature { IImsCapabilityCallback c) { queryCapabilityConfigurationInternal(capability, radioTech, c); } - - @Override - public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, - byte[] pdu) { - synchronized (mLock) { - MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu); - } - } - - @Override - public void acknowledgeSms(int token, int messageRef, int result) { - synchronized (mLock) { - MmTelFeature.this.acknowledgeSms(token, messageRef, result); - } - } - - @Override - public void acknowledgeSmsReport(int token, int messageRef, int result) { - synchronized (mLock) { - MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result); - } - } - - @Override - public String getSmsFormat() { - synchronized (mLock) { - return MmTelFeature.this.getSmsFormat(); - } - } }; /** @@ -292,10 +254,6 @@ public class MmTelFeature extends ImsFeature { } } - private void setSmsListener(IImsSmsListener listener) { - getSmsImplementation().registerSmsListener(listener); - } - private void queryCapabilityConfigurationInternal(int capability, int radioTech, IImsCapabilityCallback c) { boolean enabled = queryCapabilityConfiguration(capability, radioTech); @@ -457,33 +415,6 @@ public class MmTelFeature extends ImsFeature { // Base Implementation - Should be overridden } - private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, - byte[] pdu) { - getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); - } - - private void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { - getSmsImplementation().acknowledgeSms(token, messageRef, result); - } - - private void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { - getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); - } - - private String getSmsFormat() { - return getSmsImplementation().getSmsFormat(); - } - - /** - * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default - * non-functional implementation is returned. - * - * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider. - */ - protected SmsImplBase getSmsImplementation() { - return new SmsImplBase(); - } - /**{@inheritDoc}*/ @Override public void onFeatureRemoved() { diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java index 4f6f68c34e3a..83d9bd94013e 100644 --- a/telephony/java/com/android/ims/ImsReasonInfo.java +++ b/telephony/java/com/android/ims/ImsReasonInfo.java @@ -384,6 +384,13 @@ public class ImsReasonInfo implements Parcelable { /** Call/IMS registration is failed/dropped because of a network detach */ public static final int CODE_NETWORK_DETACH = 1513; + /** + * Call failed due to SIP code 380 (Alternative Service response) while dialing an "undetected + * emergency number". This scenario is important in some regions where the carrier network will + * identify other non-emergency help numbers (e.g. mountain rescue) when attempting to dial. + */ + public static final int CODE_SIP_ALTERNATE_EMERGENCY_CALL = 1514; + /* OEM specific error codes. To be used by OEMs when they don't want to reveal error code which would be replaced by ERROR_UNSPECIFIED */ public static final int CODE_OEM_CAUSE_1 = 0xf001; diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index fba82ee17f77..dfb3c344cb93 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -47,6 +47,7 @@ import com.android.internal.telephony.OperatorInfo; import java.util.List; +import android.telephony.UiccSlotInfo; /** * Interface used to interact with the phone. Mostly this is used by the @@ -1444,4 +1445,19 @@ interface ITelephony { * @hide */ SignalStrength getSignalStrength(int subId); + + /** + * Get slot info for all the UICC slots. + * @return UiccSlotInfo array. + * @hide + */ + UiccSlotInfo[] getUiccSlotsInfo(); + + /** + * Map logicalSlot to physicalSlot, and activate the physicalSlot if it is inactive. + * @param physicalSlots Index i in the array representing physical slot for phone i. The array + * size should be same as getPhoneCount(). + * @return boolean Return true if the switch succeeds, false if the switch fails. + */ + boolean switchSlots(in int[] physicalSlots); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index f804cb068b31..cdee9e6f2d73 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -105,6 +105,8 @@ public interface RILConstants { int DEVICE_IN_USE = 64; /* Operation cannot be performed because the device is currently in use */ int ABORTED = 65; /* Operation aborted */ + int INVALID_RESPONSE = 66; /* Invalid response sent by vendor code */ + // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to // reveal particular replacement for Generic failure int OEM_ERROR_1 = 501; @@ -419,6 +421,8 @@ cat include/telephony/ril.h | \ int RIL_REQUEST_STOP_NETWORK_SCAN = 143; int RIL_REQUEST_GET_SLOT_STATUS = 144; int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145; + int RIL_REQUEST_START_KEEPALIVE = 146; + int RIL_REQUEST_STOP_KEEPALIVE = 147; int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -474,4 +478,5 @@ cat include/telephony/ril.h | \ int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048; int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049; int RIL_UNSOL_ICC_SLOT_STATUS = 1050; + int RIL_UNSOL_KEEPALIVE_STATUS = 1051; } diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index 9f8b3a822c81..c0954385a0a3 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -837,6 +837,13 @@ public class IccUtils { } /** + * Strip all the trailing 'F' characters of a string, e.g., an ICCID. + */ + public static String stripTrailingFs(String s) { + return s == null ? null : s.replaceAll("(?i)f*$", ""); + } + + /** * Converts a character of [0-9a-aA-F] to its hex value in a byte. If the character is not a * hex number, 0 will be returned. */ diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index 801a396ecb3d..66e0955b04c3 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -19,7 +19,6 @@ package com.android.server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -229,7 +228,7 @@ public class IpSecServiceParameterizedTest { anyInt(), anyString(), anyString(), - anyLong(), + anyInt(), eq(TEST_SPI), anyInt(), anyInt(), @@ -264,7 +263,7 @@ public class IpSecServiceParameterizedTest { anyInt(), anyString(), anyString(), - anyLong(), + anyInt(), eq(TEST_SPI), anyInt(), anyInt(), diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 2c6011823828..8d1a00b09d94 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -20,6 +20,7 @@ import android.annotation.SystemApi; import android.content.pm.PackageManager; import android.net.IpConfiguration; import android.net.IpConfiguration.ProxySettings; +import android.net.MacAddress; import android.net.ProxyInfo; import android.net.StaticIpConfiguration; import android.net.Uri; @@ -54,8 +55,10 @@ public class WifiConfiguration implements Parcelable { /** {@hide} */ public static final String pskVarName = "psk"; /** {@hide} */ + @Deprecated public static final String[] wepKeyVarNames = { "wep_key0", "wep_key1", "wep_key2", "wep_key3" }; /** {@hide} */ + @Deprecated public static final String wepTxKeyIdxVarName = "wep_tx_keyidx"; /** {@hide} */ public static final String priorityVarName = "priority"; @@ -82,6 +85,9 @@ public class WifiConfiguration implements Parcelable { /** WPA is not used; plaintext or static WEP could be used. */ public static final int NONE = 0; /** WPA pre-shared key (requires {@code preSharedKey} to be specified). */ + /** @deprecated Due to security and performance limitations, use of WPA-1 networks + * is discouraged. WPA-2 (RSN) should be used instead. */ + @Deprecated public static final int WPA_PSK = 1; /** WPA using EAP authentication. Generally used with an external authentication server. */ public static final int WPA_EAP = 2; @@ -115,8 +121,8 @@ public class WifiConfiguration implements Parcelable { public static final String varName = "key_mgmt"; - public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X", - "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP" }; + public static final String[] strings = { "NONE", /* deprecated */ "WPA_PSK", "WPA_EAP", + "IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP" }; } /** @@ -125,7 +131,10 @@ public class WifiConfiguration implements Parcelable { public static class Protocol { private Protocol() { } - /** WPA/IEEE 802.11i/D3.0 */ + /** WPA/IEEE 802.11i/D3.0 + * @deprecated Due to security and performance limitations, use of WPA-1 networks + * is discouraged. WPA-2 (RSN) should be used instead. */ + @Deprecated public static final int WPA = 0; /** WPA2/IEEE 802.11i */ public static final int RSN = 1; @@ -147,7 +156,10 @@ public class WifiConfiguration implements Parcelable { /** Open System authentication (required for WPA/WPA2) */ public static final int OPEN = 0; - /** Shared Key authentication (requires static WEP keys) */ + /** Shared Key authentication (requires static WEP keys) + * @deprecated Due to security and performance limitations, use of WEP networks + * is discouraged. */ + @Deprecated public static final int SHARED = 1; /** LEAP/Network EAP (only used with LEAP) */ public static final int LEAP = 2; @@ -165,7 +177,10 @@ public class WifiConfiguration implements Parcelable { /** Use only Group keys (deprecated) */ public static final int NONE = 0; - /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */ + /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] + * @deprecated Due to security and performance limitations, use of WPA-1 networks + * is discouraged. WPA-2 (RSN) should be used instead. */ + @Deprecated public static final int TKIP = 1; /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */ public static final int CCMP = 2; @@ -187,9 +202,15 @@ public class WifiConfiguration implements Parcelable { public static class GroupCipher { private GroupCipher() { } - /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11) */ + /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11) + * @deprecated Due to security and performance limitations, use of WEP networks + * is discouraged. */ + @Deprecated public static final int WEP40 = 0; - /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key */ + /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key + * @deprecated Due to security and performance limitations, use of WEP networks + * is discouraged. */ + @Deprecated public static final int WEP104 = 1; /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */ public static final int TKIP = 2; @@ -203,7 +224,8 @@ public class WifiConfiguration implements Parcelable { public static final String varName = "group"; public static final String[] strings = - { "WEP40", "WEP104", "TKIP", "CCMP", "GTK_NOT_USED" }; + { /* deprecated */ "WEP40", /* deprecated */ "WEP104", + "TKIP", "CCMP", "GTK_NOT_USED" }; } /** Possible status of a network configuration. */ @@ -309,10 +331,16 @@ public class WifiConfiguration implements Parcelable { * When the value of one of these keys is read, the actual key is * not returned, just a "*" if the key has a value, or the null * string otherwise. + * @deprecated Due to security and performance limitations, use of WEP networks + * is discouraged. */ + @Deprecated public String[] wepKeys; - /** Default WEP key index, ranging from 0 to 3. */ + /** Default WEP key index, ranging from 0 to 3. + * @deprecated Due to security and performance limitations, use of WEP networks + * is discouraged. */ + @Deprecated public int wepTxKeyIndex; /** @@ -852,6 +880,52 @@ public class WifiConfiguration implements Parcelable { @SystemApi public int numAssociation; + /** + * @hide + * Randomized MAC address to use with this particular network + */ + private MacAddress mRandomizedMacAddress; + + /** + * @hide + * Checks if the given MAC address can be used for Connected Mac Randomization + * by verifying that it is non-null, unicast, and locally assigned. + * @param mac MacAddress to check + * @return true if mac is good to use + */ + private boolean isValidMacAddressForRandomization(MacAddress mac) { + return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned(); + } + + /** + * @hide + * Returns Randomized MAC address to use with the network. + * If it is not set/valid, create a new randomized address. + */ + public MacAddress getOrCreateRandomizedMacAddress() { + if (!isValidMacAddressForRandomization(mRandomizedMacAddress)) { + mRandomizedMacAddress = MacAddress.createRandomUnicastAddress(); + } + return mRandomizedMacAddress; + } + + /** + * @hide + * Returns MAC address set to be the local randomized MAC address. + * Does not guarantee that the returned address is valid for use. + */ + public MacAddress getRandomizedMacAddress() { + return mRandomizedMacAddress; + } + + /** + * @hide + * @param mac MacAddress to change into + */ + public void setRandomizedMacAddress(MacAddress mac) { + mRandomizedMacAddress = mac; + } + /** @hide * Boost given to RSSI on a home network for the purpose of calculating the score * This adds stickiness to home networks, as defined by: @@ -2124,6 +2198,7 @@ public class WifiConfiguration implements Parcelable { updateTime = source.updateTime; shared = source.shared; recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus()); + mRandomizedMacAddress = source.mRandomizedMacAddress; } } @@ -2191,6 +2266,7 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(shared ? 1 : 0); dest.writeString(mPasspointManagementObjectTree); dest.writeInt(recentFailure.getAssociationStatus()); + dest.writeParcelable(mRandomizedMacAddress, flags); } /** Implement the Parcelable interface {@hide} */ @@ -2259,6 +2335,7 @@ public class WifiConfiguration implements Parcelable { config.shared = in.readInt() != 0; config.mPasspointManagementObjectTree = in.readString(); config.recentFailure.setAssociationStatus(in.readInt()); + config.mRandomizedMacAddress = in.readParcelable(null); return config; } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 50ae905990ed..05dcb335618d 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -3593,26 +3593,6 @@ public class WifiManager { } /** - * Deprecated - * Does nothing - * @hide - * @deprecated - */ - public void setAllowScansWithTraffic(int enabled) { - return; - } - - /** - * Deprecated - * returns value for 'disabled' - * @hide - * @deprecated - */ - public int getAllowScansWithTraffic() { - return 0; - } - - /** * Resets all wifi manager settings back to factory defaults. * * @hide diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 622dce6edbd9..e7377c169ec4 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import android.os.Parcel; +import android.net.MacAddress; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; import org.junit.Before; @@ -49,6 +50,7 @@ public class WifiConfigurationTest { String cookie = "C O.o |<IE"; WifiConfiguration config = new WifiConfiguration(); config.setPasspointManagementObjectTree(cookie); + MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress(); Parcel parcelW = Parcel.obtain(); config.writeToParcel(parcelW, 0); byte[] bytes = parcelW.marshall(); @@ -59,8 +61,9 @@ public class WifiConfigurationTest { parcelR.setDataPosition(0); WifiConfiguration reconfig = WifiConfiguration.CREATOR.createFromParcel(parcelR); - // lacking a useful config.equals, check one field near the end. + // lacking a useful config.equals, check two fields near the end. assertEquals(cookie, reconfig.getMoTree()); + assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress()); Parcel parcelWW = Parcel.obtain(); reconfig.writeToParcel(parcelWW, 0); @@ -169,4 +172,48 @@ public class WifiConfigurationTest { assertFalse(config.isOpenNetwork()); } + + @Test + public void testGetOrCreateRandomizedMacAddress_SavesAndReturnsSameAddress() { + WifiConfiguration config = new WifiConfiguration(); + MacAddress firstMacAddress = config.getOrCreateRandomizedMacAddress(); + MacAddress secondMacAddress = config.getOrCreateRandomizedMacAddress(); + + assertEquals(firstMacAddress, secondMacAddress); + } + + @Test + public void testSetRandomizedMacAddress_ChangesSavedAddress() { + WifiConfiguration config = new WifiConfiguration(); + MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress(); + config.setRandomizedMacAddress(macToChangeInto); + MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress(); + + assertEquals(macToChangeInto, macAfterChange); + } + + @Test + public void testGetOrCreateRandomizedMacAddress_ReRandomizesInvalidAddress() { + WifiConfiguration config = new WifiConfiguration(); + + MacAddress macAddressZeroes = MacAddress.ALL_ZEROS_ADDRESS; + MacAddress macAddressMulticast = MacAddress.fromString("03:ff:ff:ff:ff:ff"); + MacAddress macAddressGlobal = MacAddress.fromString("fc:ff:ff:ff:ff:ff"); + + config.setRandomizedMacAddress(null); + MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress(); + assertFalse(macAfterChange.equals(null)); + + config.setRandomizedMacAddress(macAddressZeroes); + macAfterChange = config.getOrCreateRandomizedMacAddress(); + assertFalse(macAfterChange.equals(macAddressZeroes)); + + config.setRandomizedMacAddress(macAddressMulticast); + macAfterChange = config.getOrCreateRandomizedMacAddress(); + assertFalse(macAfterChange.equals(macAddressMulticast)); + + config.setRandomizedMacAddress(macAddressGlobal); + macAfterChange = config.getOrCreateRandomizedMacAddress(); + assertFalse(macAfterChange.equals(macAddressGlobal)); + } } |