diff options
360 files changed, 8304 insertions, 5245 deletions
@@ -325,8 +325,6 @@ LOCAL_SRC_FILES += \ media/java/android/media/IRemoteVolumeObserver.aidl \ media/java/android/media/IRingtonePlayer.aidl \ media/java/android/media/IVolumeController.aidl \ - media/java/android/media/browse/IMediaBrowserService.aidl \ - media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl \ media/java/android/media/projection/IMediaProjection.aidl \ media/java/android/media/projection/IMediaProjectionCallback.aidl \ media/java/android/media/projection/IMediaProjectionManager.aidl \ @@ -346,6 +344,8 @@ LOCAL_SRC_FILES += \ media/java/android/media/tv/ITvInputServiceCallback.aidl \ media/java/android/media/tv/ITvInputSession.aidl \ media/java/android/media/tv/ITvInputSessionCallback.aidl \ + media/java/android/service/media/IMediaBrowserService.aidl \ + media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl \ telecomm/java/com/android/internal/telecomm/IVideoCallback.aidl \ telecomm/java/com/android/internal/telecomm/IVideoProvider.aidl \ telecomm/java/com/android/internal/telecomm/IConnectionService.aidl \ diff --git a/api/current.txt b/api/current.txt index 6723108..9270f8e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -309,7 +309,6 @@ package android { field public static final int addStatesFromChildren = 16842992; // 0x10100f0 field public static final int adjustViewBounds = 16843038; // 0x101011e field public static final int advancedPrintOptionsActivity = 16843761; // 0x10103f1 - field public static final int ageHint = 16843962; // 0x10104ba field public static final int alertDialogIcon = 16843605; // 0x1010355 field public static final int alertDialogStyle = 16842845; // 0x101005d field public static final int alertDialogTheme = 16843529; // 0x1010309 @@ -459,6 +458,7 @@ package android { field public static final int configure = 16843357; // 0x101025d field public static final int constantSize = 16843158; // 0x1010196 field public static final int content = 16843355; // 0x101025b + field public static final int contentAgeHint = 16843962; // 0x10104ba field public static final int contentAuthority = 16843408; // 0x1010290 field public static final int contentDescription = 16843379; // 0x1010273 field public static final int contentInsetEnd = 16843860; // 0x1010454 @@ -905,6 +905,8 @@ package android { field public static final int multiprocess = 16842771; // 0x1010013 field public static final int name = 16842755; // 0x1010003 field public static final int navigationBarColor = 16843858; // 0x1010452 + field public static final int navigationContentDescription = 16843970; // 0x10104c2 + field public static final int navigationIcon = 16843969; // 0x10104c1 field public static final int navigationMode = 16843471; // 0x10102cf field public static final int negativeButtonText = 16843254; // 0x10101f6 field public static final int nestedScrollingEnabled = 16843830; // 0x1010436 @@ -1360,7 +1362,6 @@ package android { field public static final int trimPathEnd = 16843811; // 0x1010423 field public static final int trimPathOffset = 16843812; // 0x1010424 field public static final int trimPathStart = 16843810; // 0x1010422 - field public static final int tvContentRatingDescription = 16843955; // 0x10104b3 field public static final int type = 16843169; // 0x10101a1 field public static final int typeface = 16842902; // 0x1010096 field public static final int uiOptions = 16843672; // 0x1010398 @@ -1758,6 +1759,7 @@ package android { field public static final int list = 16908298; // 0x102000a field public static final int mask = 16908353; // 0x1020041 field public static final int message = 16908299; // 0x102000b + field public static final int navigationBarBackground = 16908355; // 0x1020043 field public static final int paste = 16908322; // 0x1020022 field public static final int primary = 16908300; // 0x102000c field public static final int progress = 16908301; // 0x102000d @@ -1766,6 +1768,7 @@ package android { field public static final int selectTextMode = 16908333; // 0x102002d field public static final int selectedIcon = 16908302; // 0x102000e field public static final int startSelectingText = 16908328; // 0x1020028 + field public static final int statusBarBackground = 16908354; // 0x1020042 field public static final int stopSelectingText = 16908329; // 0x1020029 field public static final int summary = 16908304; // 0x1020010 field public static final int switchInputMethod = 16908324; // 0x1020024 @@ -2026,6 +2029,12 @@ package android { field public static final int TextAppearance_Material_Medium = 16974355; // 0x1030213 field public static final int TextAppearance_Material_Medium_Inverse = 16974356; // 0x1030214 field public static final int TextAppearance_Material_Menu = 16974554; // 0x10302da + field public static final int TextAppearance_Material_Notification = 16974560; // 0x10302e0 + field public static final int TextAppearance_Material_Notification_Emphasis = 16974565; // 0x10302e5 + field public static final int TextAppearance_Material_Notification_Info = 16974563; // 0x10302e3 + field public static final int TextAppearance_Material_Notification_Line2 = 16974562; // 0x10302e2 + field public static final int TextAppearance_Material_Notification_Time = 16974564; // 0x10302e4 + field public static final int TextAppearance_Material_Notification_Title = 16974561; // 0x10302e1 field public static final int TextAppearance_Material_SearchResult_Subtitle = 16974357; // 0x1030215 field public static final int TextAppearance_Material_SearchResult_Title = 16974358; // 0x1030216 field public static final int TextAppearance_Material_Small = 16974359; // 0x1030217 @@ -2064,13 +2073,6 @@ package android { field public static final int TextAppearance_StatusBar_EventContent = 16973927; // 0x1030067 field public static final int TextAppearance_StatusBar_EventContent_Title = 16973928; // 0x1030068 field public static final int TextAppearance_StatusBar_Icon = 16973926; // 0x1030066 - field public static final int TextAppearance_StatusBar_Material = 16974559; // 0x10302df - field public static final int TextAppearance_StatusBar_Material_EventContent = 16974560; // 0x10302e0 - field public static final int TextAppearance_StatusBar_Material_EventContent_Emphasis = 16974565; // 0x10302e5 - field public static final int TextAppearance_StatusBar_Material_EventContent_Info = 16974563; // 0x10302e3 - field public static final int TextAppearance_StatusBar_Material_EventContent_Line2 = 16974562; // 0x10302e2 - field public static final int TextAppearance_StatusBar_Material_EventContent_Time = 16974564; // 0x10302e4 - field public static final int TextAppearance_StatusBar_Material_EventContent_Title = 16974561; // 0x10302e1 field public static final int TextAppearance_StatusBar_Title = 16973925; // 0x1030065 field public static final int TextAppearance_SuggestionHighlight = 16974104; // 0x1030118 field public static final int TextAppearance_Theme = 16973888; // 0x1030040 @@ -2166,18 +2168,22 @@ package android { field public static final int Theme_Material_Dialog = 16974386; // 0x1030232 field public static final int Theme_Material_DialogWhenLarge = 16974390; // 0x1030236 field public static final int Theme_Material_DialogWhenLarge_NoActionBar = 16974391; // 0x1030237 + field public static final int Theme_Material_Dialog_Alert = 16974570; // 0x10302ea field public static final int Theme_Material_Dialog_MinWidth = 16974387; // 0x1030233 field public static final int Theme_Material_Dialog_NoActionBar = 16974388; // 0x1030234 field public static final int Theme_Material_Dialog_NoActionBar_MinWidth = 16974389; // 0x1030235 + field public static final int Theme_Material_Dialog_Presentation = 16974571; // 0x10302eb field public static final int Theme_Material_InputMethod = 16974392; // 0x1030238 field public static final int Theme_Material_Light = 16974402; // 0x1030242 field public static final int Theme_Material_Light_DarkActionBar = 16974403; // 0x1030243 field public static final int Theme_Material_Light_Dialog = 16974404; // 0x1030244 field public static final int Theme_Material_Light_DialogWhenLarge = 16974408; // 0x1030248 field public static final int Theme_Material_Light_DialogWhenLarge_NoActionBar = 16974409; // 0x1030249 + field public static final int Theme_Material_Light_Dialog_Alert = 16974572; // 0x10302ec field public static final int Theme_Material_Light_Dialog_MinWidth = 16974405; // 0x1030245 field public static final int Theme_Material_Light_Dialog_NoActionBar = 16974406; // 0x1030246 field public static final int Theme_Material_Light_Dialog_NoActionBar_MinWidth = 16974407; // 0x1030247 + field public static final int Theme_Material_Light_Dialog_Presentation = 16974573; // 0x10302ed field public static final int Theme_Material_Light_NoActionBar = 16974410; // 0x103024a field public static final int Theme_Material_Light_NoActionBar_Fullscreen = 16974411; // 0x103024b field public static final int Theme_Material_Light_NoActionBar_Overscan = 16974412; // 0x103024c @@ -2566,7 +2572,7 @@ package android { field public static final int Widget_Material_Light_SeekBar = 16974534; // 0x10302c6 field public static final int Widget_Material_Light_SegmentedButton = 16974535; // 0x10302c7 field public static final int Widget_Material_Light_Spinner = 16974537; // 0x10302c9 - field public static final int Widget_Material_Light_Spinner_Form = 16974567; // 0x10302e7 + field public static final int Widget_Material_Light_Spinner_Underlined = 16974567; // 0x10302e7 field public static final int Widget_Material_Light_StackView = 16974536; // 0x10302c8 field public static final int Widget_Material_Light_Tab = 16974538; // 0x10302ca field public static final int Widget_Material_Light_TabWidget = 16974539; // 0x10302cb @@ -2593,7 +2599,7 @@ package android { field public static final int Widget_Material_SeekBar = 16974471; // 0x1030287 field public static final int Widget_Material_SegmentedButton = 16974472; // 0x1030288 field public static final int Widget_Material_Spinner = 16974474; // 0x103028a - field public static final int Widget_Material_Spinner_Form = 16974566; // 0x10302e6 + field public static final int Widget_Material_Spinner_Underlined = 16974566; // 0x10302e6 field public static final int Widget_Material_StackView = 16974473; // 0x1030289 field public static final int Widget_Material_Tab = 16974475; // 0x103028b field public static final int Widget_Material_TabWidget = 16974476; // 0x103028c @@ -2625,6 +2631,7 @@ package android { field public static final int Widget_Toolbar = 16974339; // 0x1030203 field public static final int Widget_Toolbar_Button_Navigation = 16974340; // 0x1030204 field public static final int Widget_WebView = 16973875; // 0x1030033 + field public static final int __removed = 16974559; // 0x10302df field public static final int l_resource_pad1 = 16974336; // 0x1030200 field public static final int l_resource_pad10 = 16974327; // 0x10301f7 field public static final int l_resource_pad11 = 16974326; // 0x10301f6 @@ -3509,7 +3516,7 @@ package android.app { method public void onTrimMemory(int); method public void onUserInteraction(); method protected void onUserLeaveHint(); - method public void onVisibleBehindCancelled(); + method public void onVisibleBehindCanceled(); method public void onWindowAttributesChanged(android.view.WindowManager.LayoutParams); method public void onWindowFocusChanged(boolean); method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback); @@ -3604,8 +3611,7 @@ package android.app { method public int addAppTask(android.app.Activity, android.content.Intent, android.app.ActivityManager.TaskDescription, android.graphics.Bitmap); method public boolean clearApplicationUserData(); method public void dumpPackageState(java.io.FileDescriptor, java.lang.String); - method public int getAppTaskThumbnailHeight(); - method public int getAppTaskThumbnailWidth(); + method public android.util.Size getAppTaskThumbnailSize(); method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks(); method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo(); method public int getLargeMemoryClass(); @@ -4506,8 +4512,8 @@ package android.app { } public class KeyguardManager { + method public android.content.Intent createConfirmDeviceCredentialIntent(java.lang.CharSequence, java.lang.CharSequence); method public deprecated void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); - method public android.content.Intent getConfirmDeviceCredentialIntent(java.lang.CharSequence, java.lang.CharSequence); method public boolean inKeyguardRestrictedInputMode(); method public boolean isKeyguardLocked(); method public boolean isKeyguardSecure(); @@ -5450,8 +5456,6 @@ package android.app.admin { method public boolean getScreenCaptureDisabled(android.content.ComponentName); method public boolean getStorageEncryption(android.content.ComponentName); method public int getStorageEncryptionStatus(); - method public java.util.List<java.lang.String> getTrustAgentFeaturesEnabled(android.content.ComponentName, android.content.ComponentName); - method public boolean getUninstallBlocked(android.content.ComponentName, java.lang.String); method public boolean hasCaCertInstalled(byte[]); method public boolean hasGrantedPolicy(android.content.ComponentName, int); method public boolean installCaCert(android.content.ComponentName, byte[]); @@ -5462,6 +5466,7 @@ package android.app.admin { method public boolean isLockTaskPermitted(java.lang.String); method public boolean isMasterVolumeMuted(android.content.ComponentName); method public boolean isProfileOwnerApp(java.lang.String); + method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String); method public void lockNow(); method public void removeActiveAdmin(android.content.ComponentName); method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); @@ -5498,7 +5503,6 @@ package android.app.admin { method public void setScreenCaptureDisabled(android.content.ComponentName, boolean); method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public int setStorageEncryption(android.content.ComponentName, boolean); - method public void setTrustAgentFeaturesEnabled(android.content.ComponentName, android.content.ComponentName, java.util.List<java.lang.String>); method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean); method public boolean switchUser(android.content.ComponentName, android.os.UserHandle); method public void uninstallAllUserCaCerts(android.content.ComponentName); @@ -5746,7 +5750,7 @@ package android.app.usage { } public final class UsageStatsManager { - method public android.util.ArrayMap<java.lang.String, android.app.usage.UsageStats> queryAndAggregateUsageStats(long, long); + method public java.util.Map<java.lang.String, android.app.usage.UsageStats> queryAndAggregateUsageStats(long, long); method public android.app.usage.UsageEvents queryEvents(long, long); method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long); field public static final int INTERVAL_BEST = 4; // 0x4 @@ -7147,6 +7151,7 @@ package android.content { field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*"; field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir"; field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item"; + field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE"; field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource"; field public static final java.lang.String SCHEME_CONTENT = "content"; field public static final java.lang.String SCHEME_FILE = "file"; @@ -8892,6 +8897,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen"; field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods"; field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback"; + field public static final java.lang.String FEATURE_LIVE_TV = "android.software.live_tv"; field public static final java.lang.String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper"; field public static final java.lang.String FEATURE_LOCATION = "android.hardware.location"; field public static final java.lang.String FEATURE_LOCATION_GPS = "android.hardware.location.gps"; @@ -14524,7 +14530,6 @@ package android.media { method public abstract long getTimestamp(); method public abstract int getWidth(); method public void setCropRect(android.graphics.Rect); - field protected android.graphics.Rect mCropRect; } public static abstract class Image.Plane { @@ -14656,7 +14661,6 @@ package android.media { } public static final class MediaCodec.CodecException extends java.lang.IllegalStateException { - ctor public MediaCodec.CodecException(int, int, java.lang.String); method public int getErrorCode(); method public boolean isRecoverable(); method public boolean isTransient(); @@ -14688,14 +14692,22 @@ package android.media { method public final boolean isEncoder(); } + public static final class MediaCodecInfo.AudioCapabilities { + method public android.util.Range<java.lang.Integer> getBitrateRange(); + method public int getMaxInputChannelCount(); + method public android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges(); + method public int[] getSupportedSampleRates(); + method public boolean isSampleRateSupported(int); + } + public static final class MediaCodecInfo.CodecCapabilities { ctor public MediaCodecInfo.CodecCapabilities(); - method public static final android.media.MediaCodecInfo.CodecCapabilities CreateFromProfileLevel(java.lang.String, int, int); - method public final android.media.MediaCodecInfo.CodecCapabilities.AudioCapabilities getAudioCapabilities(); - method public final android.media.MediaFormat getDefaultFormat(); - method public final android.media.MediaCodecInfo.CodecCapabilities.EncoderCapabilities getEncoderCapabilities(); - method public final java.lang.String getMime(); - method public final android.media.MediaCodecInfo.CodecCapabilities.VideoCapabilities getVideoCapabilities(); + method public static android.media.MediaCodecInfo.CodecCapabilities CreateFromProfileLevel(java.lang.String, int, int); + method public android.media.MediaCodecInfo.AudioCapabilities getAudioCapabilities(); + method public android.media.MediaFormat getDefaultFormat(); + method public android.media.MediaCodecInfo.EncoderCapabilities getEncoderCapabilities(); + method public java.lang.String getMimeType(); + method public android.media.MediaCodecInfo.VideoCapabilities getVideoCapabilities(); method public final boolean isFeatureRequired(java.lang.String); method public final boolean isFeatureSupported(java.lang.String); method public final boolean isFormatSupported(android.media.MediaFormat); @@ -14753,39 +14765,6 @@ package android.media { field public android.media.MediaCodecInfo.CodecProfileLevel[] profileLevels; } - public static final class MediaCodecInfo.CodecCapabilities.AudioCapabilities extends android.media.MediaCodecInfo.CodecCapabilities.BaseCapabilities { - method public final int getMaxInputChannelCount(); - method public final android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges(); - method public final int[] getSupportedSampleRates(); - method public final boolean isSampleRateSupported(int); - } - - public static class MediaCodecInfo.CodecCapabilities.BaseCapabilities { - method public final android.util.Range<java.lang.Integer> getBitrateRange(); - } - - public static final class MediaCodecInfo.CodecCapabilities.EncoderCapabilities { - method public final android.util.Range<java.lang.Integer> getComplexityRange(); - method public final android.util.Range<java.lang.Integer> getQualityRange(); - method public final boolean isBitrateModeSupported(int); - field public static final int BITRATE_MODE_CBR = 2; // 0x2 - field public static final int BITRATE_MODE_CQ = 0; // 0x0 - field public static final int BITRATE_MODE_VBR = 1; // 0x1 - } - - public static final class MediaCodecInfo.CodecCapabilities.VideoCapabilities extends android.media.MediaCodecInfo.CodecCapabilities.BaseCapabilities { - method public final boolean areSizeAndRateSupported(int, int, double); - method public final int getHeightAlignment(); - method public final android.util.Range<java.lang.Integer> getSupportedFrameRates(); - method public final android.util.Range<java.lang.Double> getSupportedFrameRatesFor(int, int); - method public final android.util.Range<java.lang.Integer> getSupportedHeights(); - method public final android.util.Range<java.lang.Integer> getSupportedHeightsFor(int); - method public final android.util.Range<java.lang.Integer> getSupportedWidths(); - method public final android.util.Range<java.lang.Integer> getSupportedWidthsFor(int); - method public final int getWidthAlignment(); - method public final boolean isSizeSupported(int, int); - } - public static final class MediaCodecInfo.CodecProfileLevel { ctor public MediaCodecInfo.CodecProfileLevel(); field public static final int AACObjectELD = 39; // 0x27 @@ -14900,12 +14879,34 @@ package android.media { field public int profile; } + public static final class MediaCodecInfo.EncoderCapabilities { + method public android.util.Range<java.lang.Integer> getComplexityRange(); + 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 + field public static final int BITRATE_MODE_VBR = 1; // 0x1 + } + + public static final class MediaCodecInfo.VideoCapabilities { + method public boolean areSizeAndRateSupported(int, int, double); + method public android.util.Range<java.lang.Integer> getBitrateRange(); + method public int getHeightAlignment(); + method public android.util.Range<java.lang.Integer> getSupportedFrameRates(); + method public android.util.Range<java.lang.Double> getSupportedFrameRatesFor(int, int); + method public android.util.Range<java.lang.Integer> getSupportedHeights(); + method public android.util.Range<java.lang.Integer> getSupportedHeightsFor(int); + method public android.util.Range<java.lang.Integer> getSupportedWidths(); + method public android.util.Range<java.lang.Integer> getSupportedWidthsFor(int); + method public int getWidthAlignment(); + method public boolean isSizeSupported(int, int); + } + public final class MediaCodecList { ctor public MediaCodecList(int); method public final java.lang.String findDecoderForFormat(android.media.MediaFormat); method public final java.lang.String findEncoderForFormat(android.media.MediaFormat); - method public static final int getCodecCount(); - method public static final android.media.MediaCodecInfo getCodecInfoAt(int); + method public static final deprecated int getCodecCount(); + method public static final deprecated android.media.MediaCodecInfo getCodecInfoAt(int); method public final android.media.MediaCodecInfo[] getCodecInfos(); field public static final int ALL_CODECS = 1; // 0x1 field public static final int REGULAR_CODECS = 0; // 0x0 @@ -14922,6 +14923,31 @@ package android.media { ctor public MediaCryptoException(java.lang.String); } + public class MediaDescription implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.CharSequence getDescription(); + method public android.os.Bundle getExtras(); + method public android.graphics.Bitmap getIconBitmap(); + method public android.net.Uri getIconUri(); + method public java.lang.String getMediaId(); + method public java.lang.CharSequence getSubtitle(); + method public java.lang.CharSequence getTitle(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static class MediaDescription.Builder { + ctor public MediaDescription.Builder(); + method public android.media.MediaDescription build(); + method public android.media.MediaDescription.Builder setDescription(java.lang.CharSequence); + method public android.media.MediaDescription.Builder setExtras(android.os.Bundle); + method public android.media.MediaDescription.Builder setIconBitmap(android.graphics.Bitmap); + method public android.media.MediaDescription.Builder setIconUri(android.net.Uri); + method public android.media.MediaDescription.Builder setMediaId(java.lang.String); + method public android.media.MediaDescription.Builder setSubtitle(java.lang.CharSequence); + method public android.media.MediaDescription.Builder setTitle(java.lang.CharSequence); + } + public final class MediaDrm { ctor public MediaDrm(java.util.UUID) throws android.media.UnsupportedSchemeException; method public void closeSession(byte[]); @@ -15024,11 +15050,13 @@ package android.media { method public static final android.media.MediaFormat createSubtitleFormat(java.lang.String, java.lang.String); method public static final android.media.MediaFormat createVideoFormat(java.lang.String, int, int); method public final java.nio.ByteBuffer getByteBuffer(java.lang.String); + method public boolean getFeatureEnabled(java.lang.String); method public final float getFloat(java.lang.String); method public final int getInteger(java.lang.String); method public final long getLong(java.lang.String); method public final java.lang.String getString(java.lang.String); method public final void setByteBuffer(java.lang.String, java.nio.ByteBuffer); + method public void setFeatureEnabled(java.lang.String, boolean); method public final void setFloat(java.lang.String, float); method public final void setInteger(java.lang.String, int); method public final void setLong(java.lang.String, long); @@ -15050,7 +15078,6 @@ package android.media { field public static final java.lang.String KEY_COLOR_FORMAT = "color-format"; field public static final java.lang.String KEY_COMPLEXITY = "complexity"; field public static final java.lang.String KEY_DURATION = "durationUs"; - field public static final java.lang.String KEY_FEATURE_ = "feature-"; field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level"; field public static final java.lang.String KEY_FRAME_RATE = "frame-rate"; field public static final java.lang.String KEY_HEIGHT = "height"; @@ -15066,7 +15093,6 @@ package android.media { field public static final java.lang.String KEY_MIME = "mime"; 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_SAMPLE_RATE = "sample-rate"; field public static final java.lang.String KEY_TEMPORAL_LAYERING = "ts-schema"; @@ -15100,7 +15126,7 @@ package android.media { method public boolean containsKey(java.lang.String); method public int describeContents(); method public android.graphics.Bitmap getBitmap(java.lang.String); - method public android.media.MediaMetadata.Description getDescription(); + method public android.media.MediaDescription getDescription(); method public long getLong(java.lang.String); method public android.media.Rating getRating(java.lang.String); method public java.lang.String getString(java.lang.String); @@ -15128,6 +15154,7 @@ package android.media { field public static final java.lang.String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE"; field public static final java.lang.String METADATA_KEY_DURATION = "android.media.metadata.DURATION"; field public static final java.lang.String METADATA_KEY_GENRE = "android.media.metadata.GENRE"; + field public static final java.lang.String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID"; field public static final java.lang.String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS"; field public static final java.lang.String METADATA_KEY_RATING = "android.media.metadata.RATING"; field public static final java.lang.String METADATA_KEY_TITLE = "android.media.metadata.TITLE"; @@ -15148,14 +15175,6 @@ package android.media { method public android.media.MediaMetadata.Builder putText(java.lang.String, java.lang.CharSequence); } - public final class MediaMetadata.Description { - method public java.lang.CharSequence getDescription(); - method public android.graphics.Bitmap getIcon(); - method public android.net.Uri getIconUri(); - method public java.lang.CharSequence getSubtitle(); - method public java.lang.CharSequence getTitle(); - } - public abstract deprecated class MediaMetadataEditor { method public synchronized void addEditableKey(int); method public abstract void apply(); @@ -15706,6 +15725,7 @@ package android.media { } public class Ringtone { + method public android.media.AudioAttributes getAudioAttributes(); method public deprecated int getStreamType(); method public java.lang.String getTitle(android.content.Context); method public boolean isPlaying(); @@ -16241,7 +16261,6 @@ package android.media.browse { method public android.content.ComponentName getServiceComponent(); method public android.media.session.MediaSession.Token getSessionToken(); method public boolean isConnected(); - method public void loadIcon(android.net.Uri, int, int, android.media.browse.MediaBrowser.IconCallback); method public void subscribe(android.net.Uri, android.media.browse.MediaBrowser.SubscriptionCallback); method public void unsubscribe(android.net.Uri); } @@ -16253,27 +16272,12 @@ package android.media.browse { method public void onConnectionSuspended(); } - public static abstract class MediaBrowser.IconCallback { - ctor public MediaBrowser.IconCallback(); - method public void onError(android.net.Uri); - method public void onIconLoaded(android.net.Uri, android.graphics.Bitmap); - } - - public static abstract class MediaBrowser.SubscriptionCallback { - ctor public MediaBrowser.SubscriptionCallback(); - method public void onChildrenLoaded(android.net.Uri, java.util.List<android.media.browse.MediaBrowserItem>); - method public void onError(android.net.Uri); - } - - public final class MediaBrowserItem implements android.os.Parcelable { + public static class MediaBrowser.MediaItem implements android.os.Parcelable { + ctor public MediaBrowser.MediaItem(int, android.media.MediaDescription); method public int describeContents(); - method public android.os.Bundle getExtras(); + method public android.media.MediaDescription getDescription(); method public int getFlags(); - method public int getIconResourceId(); - method public android.net.Uri getIconUri(); - method public java.lang.CharSequence getSummary(); - method public java.lang.CharSequence getTitle(); - method public android.net.Uri getUri(); + method public java.lang.String getMediaId(); method public boolean isBrowsable(); method public boolean isPlayable(); method public void writeToParcel(android.os.Parcel, int); @@ -16282,37 +16286,10 @@ package android.media.browse { field public static final int FLAG_PLAYABLE = 2; // 0x2 } - public static final class MediaBrowserItem.Builder { - ctor public MediaBrowserItem.Builder(android.net.Uri, int, java.lang.CharSequence); - method public android.media.browse.MediaBrowserItem build(); - method public android.media.browse.MediaBrowserItem.Builder setExtras(android.os.Bundle); - method public android.media.browse.MediaBrowserItem.Builder setIconResourceId(int); - method public android.media.browse.MediaBrowserItem.Builder setIconUri(android.net.Uri); - method public android.media.browse.MediaBrowserItem.Builder setSummary(java.lang.CharSequence); - } - - public abstract class MediaBrowserService extends android.app.Service { - ctor public MediaBrowserService(); - method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); - method public android.media.session.MediaSession.Token getSessionToken(); - method public void notifyChildrenChanged(android.net.Uri); - method public android.os.IBinder onBind(android.content.Intent); - method public abstract android.media.browse.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle); - method public abstract void onLoadChildren(android.net.Uri, android.media.browse.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowserItem>>); - method public abstract void onLoadIcon(android.net.Uri, int, int, android.media.browse.MediaBrowserService.Result<android.graphics.Bitmap>); - method public void setSessionToken(android.media.session.MediaSession.Token); - field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService"; - } - - public static final class MediaBrowserService.BrowserRoot { - ctor public MediaBrowserService.BrowserRoot(android.net.Uri, android.os.Bundle); - method public android.os.Bundle getExtras(); - method public android.net.Uri getRootUri(); - } - - public class MediaBrowserService.Result { - method public void detach(); - method public void sendResult(T); + public static abstract class MediaBrowser.SubscriptionCallback { + ctor public MediaBrowser.SubscriptionCallback(); + method public void onChildrenLoaded(android.net.Uri, java.util.List<android.media.browse.MediaBrowser.MediaItem>); + method public void onError(android.net.Uri); } } @@ -16407,7 +16384,7 @@ package android.media.session { method public java.lang.String getPackageName(); method public android.media.session.MediaController.PlaybackInfo getPlaybackInfo(); method public android.media.session.PlaybackState getPlaybackState(); - method public java.util.List<android.media.session.MediaSession.Item> getQueue(); + method public java.util.List<android.media.session.MediaSession.QueueItem> getQueue(); method public java.lang.CharSequence getQueueTitle(); method public int getRatingType(); method public android.app.PendingIntent getSessionActivity(); @@ -16424,7 +16401,7 @@ package android.media.session { method public void onExtrasChanged(android.os.Bundle); method public void onMetadataChanged(android.media.MediaMetadata); method public void onPlaybackStateChanged(android.media.session.PlaybackState); - method public void onQueueChanged(java.util.List<android.media.session.MediaSession.Item>); + method public void onQueueChanged(java.util.List<android.media.session.MediaSession.QueueItem>); method public void onQueueTitleChanged(java.lang.CharSequence); method public void onSessionDestroyed(); method public void onSessionEvent(java.lang.String, android.os.Bundle); @@ -16444,16 +16421,16 @@ package android.media.session { method public void fastForward(); method public void pause(); method public void play(); + method public void playFromMediaId(java.lang.String, android.os.Bundle); method public void playFromSearch(java.lang.String, android.os.Bundle); - method public void playUri(android.net.Uri, android.os.Bundle); method public void rewind(); method public void seekTo(long); method public void sendCustomAction(android.media.session.PlaybackState.CustomAction, android.os.Bundle); method public void sendCustomAction(java.lang.String, android.os.Bundle); method public void setRating(android.media.Rating); - method public void skipToItem(long); method public void skipToNext(); method public void skipToPrevious(); + method public void skipToQueueItem(long); method public void stop(); } @@ -16474,7 +16451,7 @@ package android.media.session { method public void setPlaybackState(android.media.session.PlaybackState); method public void setPlaybackToLocal(android.media.AudioAttributes); method public void setPlaybackToRemote(android.media.VolumeProvider); - method public void setQueue(java.util.List<android.media.session.MediaSession.Item>); + method public void setQueue(java.util.List<android.media.session.MediaSession.QueueItem>); method public void setQueueTitle(java.lang.CharSequence); method public void setSessionActivity(android.app.PendingIntent); field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1 @@ -16489,34 +16466,27 @@ package android.media.session { method public boolean onMediaButtonEvent(android.content.Intent); method public void onPause(); method public void onPlay(); + method public void onPlayFromMediaId(java.lang.String, android.os.Bundle); method public void onPlayFromSearch(java.lang.String, android.os.Bundle); - method public void onPlayUri(android.net.Uri, android.os.Bundle); method public void onRewind(); method public void onSeekTo(long); method public void onSetRating(android.media.Rating); - method public void onSkipToItem(long); method public void onSkipToNext(); method public void onSkipToPrevious(); + method public void onSkipToQueueItem(long); method public void onStop(); } - public static final class MediaSession.Item implements android.os.Parcelable { + public static final class MediaSession.QueueItem implements android.os.Parcelable { + ctor public MediaSession.QueueItem(android.media.MediaDescription, long); method public int describeContents(); - method public android.os.Bundle getExtras(); - method public long getId(); - method public android.media.MediaMetadata getMetadata(); - method public android.net.Uri getUri(); + method public android.media.MediaDescription getDescription(); + method public long getQueueId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int UNKNOWN_ID = -1; // 0xffffffff } - public static final class MediaSession.Item.Builder { - ctor public MediaSession.Item.Builder(android.media.MediaMetadata, long, android.net.Uri); - method public android.media.session.MediaSession.Item build(); - method public android.media.session.MediaSession.Item.Builder setExtras(android.os.Bundle); - } - public static final class MediaSession.Token implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -16537,6 +16507,7 @@ package android.media.session { public final class PlaybackState implements android.os.Parcelable { method public int describeContents(); method public long getActions(); + method public long getActiveQueueItemId(); method public long getBufferedPosition(); method public java.util.List<android.media.session.PlaybackState.CustomAction> getCustomActions(); method public java.lang.CharSequence getErrorMessage(); @@ -16548,15 +16519,15 @@ package android.media.session { field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L field public static final long ACTION_PAUSE = 2L; // 0x2L field public static final long ACTION_PLAY = 4L; // 0x4L + field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L - field public static final long ACTION_PLAY_URI = 1024L; // 0x400L field public static final long ACTION_REWIND = 8L; // 0x8L field public static final long ACTION_SEEK_TO = 256L; // 0x100L field public static final long ACTION_SET_RATING = 128L; // 0x80L - field public static final long ACTION_SKIP_TO_ITEM = 4096L; // 0x1000L field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L + field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L field public static final long ACTION_STOP = 1L; // 0x1L field public static final android.os.Parcelable.Creator CREATOR; field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL @@ -16570,6 +16541,7 @@ package android.media.session { field public static final int STATE_REWINDING = 5; // 0x5 field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9 + field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb field public static final int STATE_STOPPED = 1; // 0x1 } @@ -16580,7 +16552,7 @@ package android.media.session { method public android.media.session.PlaybackState.Builder addCustomAction(android.media.session.PlaybackState.CustomAction); method public android.media.session.PlaybackState build(); method public android.media.session.PlaybackState.Builder setActions(long); - method public android.media.session.PlaybackState.Builder setActiveItem(long); + method public android.media.session.PlaybackState.Builder setActiveQueueItemId(long); method public android.media.session.PlaybackState.Builder setBufferedPosition(long); method public android.media.session.PlaybackState.Builder setErrorMessage(java.lang.CharSequence); method public android.media.session.PlaybackState.Builder setState(int, long, float, long); @@ -16778,11 +16750,13 @@ package android.media.tv { method public boolean isRatingBlocked(android.media.tv.TvContentRating); method public void registerListener(android.media.tv.TvInputManager.TvInputListener, android.os.Handler); method public void unregisterListener(android.media.tv.TvInputManager.TvInputListener); - field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED"; - field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; + field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED"; + field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED"; + field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS"; field public static final int INPUT_STATE_CONNECTED = 0; // 0x0 field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 + field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS"; field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3 field public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; // 0x1 field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0 @@ -17227,10 +17201,6 @@ package android.net { field public static final android.os.Parcelable.Creator CREATOR; } - public abstract interface NetworkBoundURLFactory { - method public abstract java.net.URL getBoundURL(android.net.Network, java.net.URL) throws java.net.MalformedURLException; - } - public final class NetworkCapabilities implements android.os.Parcelable { ctor public NetworkCapabilities(android.net.NetworkCapabilities); method public int describeContents(); @@ -22301,7 +22271,7 @@ package android.os { field public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; // 0x20 field public static final deprecated int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa field public static final deprecated int SCREEN_DIM_WAKE_LOCK = 6; // 0x6 - field public static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1; // 0x1 + field public static final int WAIT_FOR_DISTANT_PROXIMITY = 1; // 0x1 } public final class PowerManager.WakeLock { @@ -23733,7 +23703,7 @@ package android.provider { field public static final java.lang.String DATE = "date"; field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; field public static final java.lang.String DURATION = "duration"; - field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter"; + field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.call_type_filter"; field public static final java.lang.String FEATURES = "features"; field public static final int FEATURES_NONE = 0; // 0x0 field public static final int FEATURES_VIDEO = 1; // 0x1 @@ -27094,6 +27064,33 @@ package android.service.dreams { } +package android.service.media { + + public abstract class MediaBrowserService extends android.app.Service { + ctor public MediaBrowserService(); + method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]); + method public android.media.session.MediaSession.Token getSessionToken(); + method public void notifyChildrenChanged(android.net.Uri); + method public android.os.IBinder onBind(android.content.Intent); + method public abstract android.service.media.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle); + method public abstract void onLoadChildren(android.net.Uri, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>); + method public void setSessionToken(android.media.session.MediaSession.Token); + field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService"; + } + + public static final class MediaBrowserService.BrowserRoot { + ctor public MediaBrowserService.BrowserRoot(android.net.Uri, android.os.Bundle); + method public android.os.Bundle getExtras(); + method public android.net.Uri getRootUri(); + } + + public class MediaBrowserService.Result { + method public void detach(); + method public void sendResult(T); + } + +} + package android.service.notification { public abstract class NotificationListenerService extends android.app.Service { @@ -27104,9 +27101,11 @@ package android.service.notification { method public final void cancelNotifications(java.lang.String[]); method public android.service.notification.StatusBarNotification[] getActiveNotifications(); method public android.service.notification.StatusBarNotification[] getActiveNotifications(java.lang.String[]); + method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); method public android.os.IBinder onBind(android.content.Intent); + method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); method public void onListenerHintsChanged(int); method public void onNotificationPosted(android.service.notification.StatusBarNotification); @@ -27114,13 +27113,12 @@ package android.service.notification { method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap); method public void onNotificationRemoved(android.service.notification.StatusBarNotification); method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap); + method public final void requestInterruptionFilter(int); method public final void requestListenerHints(int); - field public static final int HINTS_NONE = 0; // 0x0 - field public static final int HINT_HOST_DISABLE_EFFECTS = 4; // 0x4 - field public static final int HINT_HOST_INTERRUPTION_LEVEL_ALL = 1; // 0x1 - field public static final int HINT_HOST_INTERRUPTION_LEVEL_NONE = 3; // 0x3 - field public static final int HINT_HOST_INTERRUPTION_LEVEL_PRIORITY = 2; // 0x2 - field public static final int HOST_INTERRUPTION_LEVEL_MASK = 3; // 0x3 + field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 + field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1 + field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3 + field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2 field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService"; } @@ -27198,16 +27196,14 @@ package android.service.textservice { package android.service.voice { public class AlwaysOnHotwordDetector { - method public android.content.Intent getManageIntent(int); + method public android.content.Intent createIntentToEnroll(); + method public android.content.Intent createIntentToReEnroll(); + method public android.content.Intent createIntentToUnEnroll(); method public int getSupportedRecognitionModes(); method public boolean startRecognition(int); method public boolean stopRecognition(); - field public static final int MANAGE_ACTION_ENROLL = 0; // 0x0 - field public static final int MANAGE_ACTION_RE_ENROLL = 1; // 0x1 - field public static final int MANAGE_ACTION_UN_ENROLL = 2; // 0x2 field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 - field public static final int RECOGNITION_FLAG_NONE = 0; // 0x0 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe @@ -27216,7 +27212,8 @@ package android.service.voice { field public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff } - public static abstract interface AlwaysOnHotwordDetector.Callback { + public static abstract class AlwaysOnHotwordDetector.Callback { + ctor public AlwaysOnHotwordDetector.Callback(); method public abstract void onAvailabilityChanged(int); method public abstract void onDetected(android.service.voice.AlwaysOnHotwordDetector.EventPayload); method public abstract void onError(); @@ -28423,6 +28420,7 @@ package android.telecomm { } public static class PhoneAccount.Builder { + ctor public PhoneAccount.Builder(); method public android.telecomm.PhoneAccount build(); method public android.telecomm.PhoneAccount.Builder withAccountHandle(android.telecomm.PhoneAccountHandle); method public android.telecomm.PhoneAccount.Builder withCapabilities(int); @@ -32235,17 +32233,23 @@ package android.util { field public static final android.util.Rational ZERO; } - public final class Size { + public final class Size implements android.os.Parcelable { ctor public Size(int, int); + method public int describeContents(); method public int getHeight(); method public int getWidth(); method public static android.util.Size parseSize(java.lang.String) throws java.lang.NumberFormatException; + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } - public final class SizeF { + public final class SizeF implements android.os.Parcelable { ctor public SizeF(float, float); + method public int describeContents(); method public float getHeight(); method public float getWidth(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } public class SparseArray implements java.lang.Cloneable { @@ -36570,15 +36574,14 @@ package android.webkit { public static abstract class WebChromeClient.FileChooserParams { ctor public WebChromeClient.FileChooserParams(); method public abstract java.lang.String[] getAcceptTypes(); - method public abstract java.lang.String getDefaultFilename(); + method public abstract java.lang.String getFilenameHint(); method public abstract int getMode(); method public abstract java.lang.CharSequence getTitle(); method public abstract android.webkit.WebChromeClient.UploadHelper getUploadHelper(); method public abstract boolean isCaptureEnabled(); - field public static final int OPEN = 0; // 0x0 - field public static final int OPEN_FOLDER = 2; // 0x2 - field public static final int OPEN_MULTIPLE = 1; // 0x1 - field public static final int SAVE = 3; // 0x3 + field public static final int MODE_OPEN = 0; // 0x0 + field public static final int MODE_OPEN_MULTIPLE = 1; // 0x1 + field public static final int MODE_SAVE = 3; // 0x3 } public static abstract class WebChromeClient.UploadHelper { @@ -39587,22 +39590,6 @@ package android.widget { } -package com.android.internal.telecomm { - - public abstract interface RemoteServiceCallback implements android.os.IInterface { - method public abstract void onError() throws android.os.RemoteException; - method public abstract void onResult(java.util.List<android.content.ComponentName>, java.util.List<android.os.IBinder>) throws android.os.RemoteException; - } - - public static abstract class RemoteServiceCallback.Stub extends android.os.Binder implements com.android.internal.telecomm.RemoteServiceCallback { - ctor public RemoteServiceCallback.Stub(); - method public android.os.IBinder asBinder(); - method public static com.android.internal.telecomm.RemoteServiceCallback asInterface(android.os.IBinder); - method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException; - } - -} - package com.android.internal.util { public abstract interface Predicate { diff --git a/api/removed.txt b/api/removed.txt index 1b8aef4..8915fc3 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -1,3 +1,19 @@ +package android { + + public static final class R.attr { + field public static final int __removed1 = 16843955; // 0x10104b3 + } + +} + +package android.app { + + public class KeyguardManager { + method public android.content.Intent getConfirmDeviceCredentialIntent(java.lang.CharSequence, java.lang.CharSequence); + } + +} + package android.media { public class AudioFormat { @@ -32,3 +48,11 @@ package android.view { } +package com.android.internal { + + public static final class R.attr { + field public static final int __removed1 = 16843955; // 0x10104b3 + } + +} + diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 74ccbc2..6e77e13 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -161,17 +161,17 @@ static void maybeCreateDalvikCache() { LOG_ALWAYS_FATAL_IF((numChars >= PATH_MAX || numChars < 0), "Error constructing dalvik cache : %s", strerror(errno)); - int result = mkdir(dalvikCacheDir, 0771); + int result = mkdir(dalvikCacheDir, 0711); LOG_ALWAYS_FATAL_IF((result < 0 && errno != EEXIST), "Error creating cache dir %s : %s", dalvikCacheDir, strerror(errno)); // We always perform these steps because the directory might // already exist, with wider permissions and a different owner // than we'd like. - result = chown(dalvikCacheDir, AID_SYSTEM, AID_SYSTEM); + result = chown(dalvikCacheDir, AID_ROOT, AID_ROOT); LOG_ALWAYS_FATAL_IF((result < 0), "Error changing dalvik-cache ownership : %s", strerror(errno)); - result = chmod(dalvikCacheDir, 0771); + result = chmod(dalvikCacheDir, 0711); LOG_ALWAYS_FATAL_IF((result < 0), "Error changing dalvik-cache permissions : %s", strerror(errno)); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index c80eeb9..2503d17 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5459,7 +5459,7 @@ public class Activity extends ContextThemeWrapper * this method anytime before a return from {@link #onPause()}. If this call is successful * then the activity will remain visible when {@link #onPause()} is called, and can continue to * play media in the background, but it must stop playing and release resources prior to or - * within the call to {@link #onVisibleBehindCancelled()}. If this call returns false, the + * within the call to {@link #onVisibleBehindCanceled()}. If this call returns false, the * activity will not be visible in the background, and must release any media resources * immediately. * @@ -5475,10 +5475,10 @@ public class Activity extends ContextThemeWrapper * @return the resulting visibiity state. If true the activity may remain visible beyond * {@link #onPause()}. If false then the activity may not count on being visible behind * other translucent activities, and must stop any media playback and release resources. - * Returning false may occur in lieu of a call to onVisibleBehindCancelled() so the return + * Returning false may occur in lieu of a call to onVisibleBehindCanceled() so the return * value must be checked. * - * @see #onVisibleBehindCancelled() + * @see #onVisibleBehindCanceled() * @see #onBackgroundVisibleBehindChanged(boolean) */ public boolean requestVisibleBehind(boolean visible) { @@ -5498,7 +5498,7 @@ public class Activity extends ContextThemeWrapper /** * Called when a translucent activity over this activity is becoming opaque or another * activity is being launched. Activities that override this method must call - * <code>super.onVisibleBehindCancelled()</code> or a SuperNotCalledException will be thrown. + * <code>super.onVisibleBehindCanceled()</code> or a SuperNotCalledException will be thrown. * * <p>When this method is called the activity has 500 msec to release any resources it may be * using while visible in the background. @@ -5509,7 +5509,7 @@ public class Activity extends ContextThemeWrapper * @see #requestVisibleBehind(boolean) * @see #onBackgroundVisibleBehindChanged(boolean) */ - public void onVisibleBehindCancelled() { + public void onVisibleBehindCanceled() { mCalled = true; } @@ -5521,7 +5521,7 @@ public class Activity extends ContextThemeWrapper * {@link #requestVisibleBehind(boolean)}, false otherwise. * * @see #requestVisibleBehind(boolean) - * @see #onVisibleBehindCancelled() + * @see #onVisibleBehindCanceled() * @see #onBackgroundVisibleBehindChanged(boolean) * @hide */ @@ -5544,7 +5544,7 @@ public class Activity extends ContextThemeWrapper * @param visible true if a background activity is visible, false otherwise. * * @see #requestVisibleBehind(boolean) - * @see #onVisibleBehindCancelled() + * @see #onVisibleBehindCanceled() * @hide */ @SystemApi diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index ffb9c95..bc54055 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -52,6 +52,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.DisplayMetrics; +import android.util.Size; import android.util.Slog; import java.io.FileDescriptor; @@ -1026,24 +1027,13 @@ public class ActivityManager { } /** - * Return the current design width for {@link AppTask} thumbnails, for use + * Return the current design dimensions for {@link AppTask} thumbnails, for use * with {@link #addAppTask}. */ - public int getAppTaskThumbnailWidth() { + public Size getAppTaskThumbnailSize() { synchronized (this) { ensureAppTaskThumbnailSizeLocked(); - return mAppTaskThumbnailSize.x; - } - } - - /** - * Return the current design height for {@link AppTask} thumbnails, for use - * with {@link #addAppTask}. - */ - public int getAppTaskThumbnailHeight() { - synchronized (this) { - ensureAppTaskThumbnailSizeLocked(); - return mAppTaskThumbnailSize.y; + return new Size(mAppTaskThumbnailSize.x, mAppTaskThumbnailSize.y); } } @@ -1072,9 +1062,9 @@ public class ActivityManager { * set on it. * @param description Optional additional description information. * @param thumbnail Thumbnail to use for the recents entry. Should be the size given by - * {@link #getAppTaskThumbnailWidth()} and {@link #getAppTaskThumbnailHeight()}. If the - * bitmap is not that exact size, it will be recreated in your process, probably in a way - * you don't like, before the recents entry is added. + * {@link #getAppTaskThumbnailSize()}. If the bitmap is not that exact size, it will be + * recreated in your process, probably in a way you don't like, before the recents entry + * is added. * * @return Returns the task id of the newly added app task, or -1 if the add failed. The * most likely cause of failure is that there is no more room for more tasks for your app. diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d70e5df..38999a8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2485,11 +2485,11 @@ public final class ActivityThread { final Activity activity = r.activity; if (activity.mVisibleBehind) { activity.mCalled = false; - activity.onVisibleBehindCancelled(); + activity.onVisibleBehindCanceled(); // Tick, tick, tick. The activity has 500 msec to return or it will be destroyed. if (!activity.mCalled) { throw new SuperNotCalledException("Activity " + activity.getLocalClassName() + - " did not call through to super.onVisibleBehindCancelled()"); + " did not call through to super.onVisibleBehindCanceled()"); } activity.mVisibleBehind = false; } diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index e4f2b88..a09a2e7 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -217,7 +217,10 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { } protected void viewsReady(ArrayMap<String, View> sharedElements) { - setSharedElements(sharedElements); + sharedElements.retainAll(mAllSharedElementNames); + mListener.remapSharedElements(mAllSharedElementNames, sharedElements); + mSharedElementNames.addAll(sharedElements.keySet()); + mSharedElements.addAll(sharedElements.values()); if (getViewsTransition() != null) { getDecor().captureTransitioningViews(mTransitioningViews); mTransitioningViews.removeAll(mSharedElements); @@ -339,32 +342,16 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted, ArrayList<View> localViews) { ArrayMap<String, View> sharedElements = new ArrayMap<String, View>(); - if (!mAllSharedElementNames.isEmpty()) { - if (accepted != null) { - for (int i = 0; i < accepted.size(); i++) { - sharedElements.put(accepted.get(i), localViews.get(i)); - } - } else { - getDecor().findNamedViews(sharedElements); + if (accepted != null) { + for (int i = 0; i < accepted.size(); i++) { + sharedElements.put(accepted.get(i), localViews.get(i)); } + } else { + getDecor().findNamedViews(sharedElements); } return sharedElements; } - private void setSharedElements(ArrayMap<String, View> sharedElements) { - sharedElements.retainAll(mAllSharedElementNames); - mListener.remapSharedElements(mAllSharedElementNames, sharedElements); - sharedElements.retainAll(mAllSharedElementNames); - for (int i = 0; i < mAllSharedElementNames.size(); i++) { - String name = mAllSharedElementNames.get(i); - View sharedElement = sharedElements.get(name); - if (sharedElement != null) { - mSharedElementNames.add(name); - mSharedElements.add(sharedElement); - } - } - } - protected void setResultReceiver(ResultReceiver resultReceiver) { mResultReceiver = resultReceiver; } diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java index 1e556d6..f79d32b 100644 --- a/core/java/android/app/DatePickerDialog.java +++ b/core/java/android/app/DatePickerDialog.java @@ -135,6 +135,9 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); } break; + case BUTTON_NEGATIVE: + cancel(); + break; } } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 75ecbd9..5a6898d 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -327,7 +327,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { public void run() { if (mAnimations++ < MIN_ANIMATION_FRAMES) { getDecor().postOnAnimation(this); - } else { + } else if (mResultReceiver != null) { mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null); mResultReceiver = null; // all done sending messages. } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 43b9ea8..f31800d 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -174,6 +174,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { }); setGhostVisibility(View.INVISIBLE); scheduleGhostVisibilityChange(View.INVISIBLE); + mListener.setSharedElementEnd(mSharedElementNames, mSharedElements, sharedElementSnapshots); TransitionManager.beginDelayedTransition(getDecor(), transition); scheduleGhostVisibilityChange(View.VISIBLE); setGhostVisibility(View.VISIBLE); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 07e9a94..fb28c5d 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -58,9 +58,12 @@ interface INotificationManager void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id); void cancelNotificationsFromListener(in INotificationListener token, in String[] keys); - ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys); + ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim); void requestHintsFromListener(in INotificationListener token, int hints); int getHintsFromListener(in INotificationListener token); + void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter); + int getInterruptionFilterFromListener(in INotificationListener token); + void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim); ComponentName getEffectsSuppressor(); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 50e3a10..e055237 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -56,6 +56,13 @@ public class KeyguardManager { public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION"; /** + * @removed + */ + public Intent getConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { + return createConfirmDeviceCredentialIntent(title, description); + } + + /** * Get an intent to prompt the user to confirm credentials (pin, pattern or password) * for the current user of the device. The caller is expected to launch this activity using * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for @@ -63,7 +70,7 @@ public class KeyguardManager { * * @return the intent for launching the activity or null if no password is required. **/ - public Intent getConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { + public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { if (!isKeyguardSecure()) return null; Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL); intent.putExtra(EXTRA_TITLE, title); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e1dc8bf..966d2ce 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1424,6 +1424,8 @@ public class Notification implements Parcelable extras.remove(Notification.EXTRA_LARGE_ICON); extras.remove(Notification.EXTRA_LARGE_ICON_BIG); extras.remove(Notification.EXTRA_PICTURE); + // Prevent light notifications from being rebuilt. + extras.remove(Builder.EXTRA_NEEDS_REBUILD); } } @@ -1868,6 +1870,15 @@ public class Notification implements Parcelable private Notification mRebuildNotification = null; /** + * Whether the build notification has three lines. This is used to make the top padding for + * both the contracted and expanded layout consistent. + * + * <p> + * This field is only valid during the build phase. + */ + private boolean mHasThreeLines; + + /** * Constructs a new Builder with the defaults: * @@ -2564,19 +2575,23 @@ public class Notification implements Parcelable return this; } - private Bitmap getProfileBadge() { + private Drawable getProfileBadgeDrawable() { // Note: This assumes that the current user can read the profile badge of the // originating user. UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - Drawable badge = userManager.getBadgeForUser(new UserHandle(mOriginatingUserId), 0); + return userManager.getBadgeForUser(new UserHandle(mOriginatingUserId), 0); + } + + private Bitmap getProfileBadge() { + Drawable badge = getProfileBadgeDrawable(); if (badge == null) { return null; } - final int width = badge.getIntrinsicWidth(); - final int height = badge.getIntrinsicHeight(); - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final int size = mContext.getResources().getDimensionPixelSize( + R.dimen.notification_badge_size); + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); - badge.setBounds(0, 0, width, height); + badge.setBounds(0, 0, size, size); badge.draw(canvas); return bitmap; } @@ -2602,6 +2617,12 @@ public class Notification implements Parcelable return false; } + private void shrinkLine3Text(RemoteViews contentView) { + float subTextSize = mContext.getResources().getDimensionPixelSize( + R.dimen.notification_subtext_size); + contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize); + } + private RemoteViews applyStandardTemplate(int resId) { RemoteViews contentView = new BuilderRemoteViews(mContext.getPackageName(), mOriginatingUserId, resId); @@ -2674,10 +2695,7 @@ public class Notification implements Parcelable if (showLine2) { // need to shrink all the type to make sure everything fits - final Resources res = mContext.getResources(); - final float subTextSize = res.getDimensionPixelSize( - R.dimen.notification_subtext_size); - contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize); + shrinkLine3Text(contentView); } if (mWhen != 0 && mShowWhen) { @@ -2696,7 +2714,7 @@ public class Notification implements Parcelable // Adjust padding depending on line count and font size. contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext, - hasThreeLines(), mContext.getResources().getConfiguration().fontScale), + mHasThreeLines, mContext.getResources().getConfiguration().fontScale), 0, 0); // We want to add badge to first line of text. @@ -2721,7 +2739,12 @@ public class Notification implements Parcelable * is going to have one or two lines */ private boolean hasThreeLines() { - boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0; + boolean contentTextInLine2 = mSubText != null && mContentText != null; + + // If we have content text in line 2, badge goes into line 2, or line 3 otherwise + boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2; + boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0 + || badgeInLine3; boolean hasLine2 = (mSubText != null && mContentText != null) || (mSubText == null && (mProgressMax != 0 || mProgressIndeterminate)); return hasLine2 && hasLine3; @@ -3092,6 +3115,7 @@ public class Notification implements Parcelable if (mRebuildNotification == null) { throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode."); } + mHasThreeLines = hasThreeLines(); Bundle extras = mRebuildNotification.extras; @@ -3124,6 +3148,7 @@ public class Notification implements Parcelable } extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW); + mHasThreeLines = false; return mRebuildNotification; } @@ -3238,6 +3263,7 @@ public class Notification implements Parcelable */ public Notification build() { mOriginatingUserId = mContext.getUserId(); + mHasThreeLines = hasThreeLines(); Notification n = buildUnstyled(); @@ -3259,6 +3285,7 @@ public class Notification implements Parcelable mStyle.addExtras(n.extras); } + mHasThreeLines = false; return n; } @@ -3388,7 +3415,7 @@ public class Notification implements Parcelable */ protected void applyTopPadding(RemoteViews contentView) { int topPadding = Builder.calculateTopPadding(mBuilder.mContext, - mBuilder.hasThreeLines(), + mBuilder.mHasThreeLines, mBuilder.mContext.getResources().getConfiguration().fontScale); contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0); } @@ -3661,6 +3688,8 @@ public class Notification implements Parcelable applyTopPadding(contentView); + mBuilder.shrinkLine3Text(contentView); + mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template); return contentView; @@ -3800,6 +3829,8 @@ public class Notification implements Parcelable applyTopPadding(contentView); + mBuilder.shrinkLine3Text(contentView); + mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template); return contentView; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 26c72a5..69b1139 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2456,10 +2456,10 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if the userId is invalid. */ @SystemApi - public String getProfileOwnerNameAsUser(UserHandle user) throws IllegalArgumentException { + public String getProfileOwnerNameAsUser(int userId) throws IllegalArgumentException { if (mService != null) { try { - return mService.getProfileOwnerName(user.getIdentifier()); + return mService.getProfileOwnerName(userId); } catch (RemoteException re) { Log.w(TAG, "Failed to get profile owner"); throw new IllegalArgumentException( @@ -2562,6 +2562,7 @@ public class DevicePolicyManager { * @param agent Which component to enable features for. * @param features List of features to enable. Consult specific TrustAgent documentation for * the feature list. + * @hide */ public void setTrustAgentFeaturesEnabled(ComponentName admin, ComponentName agent, List<String> features) { @@ -2582,6 +2583,7 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param agent Which component to get enabled features for. * @return List of enabled features. + * @hide */ public List<String> getTrustAgentFeaturesEnabled(ComponentName admin, ComponentName agent) { if (mService != null) { @@ -2689,10 +2691,10 @@ public class DevicePolicyManager { * Called by a profile or device owner to set the permitted accessibility services. When * set by a device owner or profile owner the restriction applies to all profiles of the * user the device owner or profile owner is an admin for. - * + * * By default the user can use any accessiblity service. When zero or more packages have * been added, accessiblity services that are not in the list and not part of the system - * can not be enabled by the user. + * can not be enabled by the user. * * <p> Calling with a null value for the list disables the restriction so that all services * can be used, calling with an empty list only allows the builtin system's services. @@ -3325,10 +3327,10 @@ public class DevicePolicyManager { * @param packageName package to check. * @return true if the user shouldn't be able to uninstall the package. */ - public boolean getUninstallBlocked(ComponentName admin, String packageName) { + public boolean isUninstallBlocked(ComponentName admin, String packageName) { if (mService != null) { try { - return mService.getUninstallBlocked(admin, packageName); + return mService.isUninstallBlocked(admin, packageName); } catch (RemoteException re) { Log.w(TAG, "Failed to call block uninstall on device policy service"); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 23f36fb..c984cf9 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -174,7 +174,7 @@ interface IDevicePolicyManager { void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userId); void setUninstallBlocked(in ComponentName admin, in String packageName, boolean uninstallBlocked); - boolean getUninstallBlocked(in ComponentName admin, in String packageName); + boolean isUninstallBlocked(in ComponentName admin, in String packageName); void setCrossProfileCallerIdDisabled(in ComponentName who, boolean disabled); boolean getCrossProfileCallerIdDisabled(in ComponentName who); diff --git a/core/java/android/app/backup/BackupHelper.java b/core/java/android/app/backup/BackupHelper.java index e3f0d54..7cbbbc3 100644 --- a/core/java/android/app/backup/BackupHelper.java +++ b/core/java/android/app/backup/BackupHelper.java @@ -37,10 +37,9 @@ import android.os.ParcelFileDescriptor; */ public interface BackupHelper { /** - * Based on <code>oldState</code>, determine which of the files from the - * application's data directory need to be backed up, write them to - * <code>data</code>, and fill in <code>newState</code> with the state as it - * exists now. + * Based on <code>oldState</code>, determine what application content + * needs to be backed up, write it to <code>data</code>, and fill in + * <code>newState</code> with the complete state as it exists now. * <p> * Implementing this method is much like implementing * {@link BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 2431ad0..fb80de2 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -106,7 +106,9 @@ public final class UsageEvents implements Parcelable { } /** - * The time at which this event occurred. + * The time at which this event occurred, measured in milliseconds since the epoch. + * <p/> + * See {@link System#currentTimeMillis()}. */ public long getTimeStamp() { return mTimeStamp; diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java index e47a802..abfc435 100644 --- a/core/java/android/app/usage/UsageStats.java +++ b/core/java/android/app/usage/UsageStats.java @@ -81,28 +81,36 @@ public final class UsageStats implements Parcelable { } /** - * Get the beginning of the time range this {@link android.app.usage.UsageStats} represents. + * Get the beginning of the time range this {@link android.app.usage.UsageStats} represents, + * measured in milliseconds since the epoch. + * <p/> + * See {@link System#currentTimeMillis()}. */ public long getFirstTimeStamp() { return mBeginTimeStamp; } /** - * Get the end of the time range this {@link android.app.usage.UsageStats} represents. + * Get the end of the time range this {@link android.app.usage.UsageStats} represents, + * measured in milliseconds since the epoch. + * <p/> + * See {@link System#currentTimeMillis()}. */ public long getLastTimeStamp() { return mEndTimeStamp; } /** - * Get the last time this package was used. + * Get the last time this package was used, measured in milliseconds since the epoch. + * <p/> + * See {@link System#currentTimeMillis()}. */ public long getLastTimeUsed() { return mLastTimeUsed; } /** - * Get the total time this package spent in the foreground. + * Get the total time this package spent in the foreground, measured in milliseconds. */ public long getTotalTimeInForeground() { return mTotalTimeInForeground; diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index f9b8928..5830fcf 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -23,6 +23,7 @@ import android.util.ArrayMap; import java.util.Collections; import java.util.List; +import java.util.Map; /** * Provides access to device usage history and statistics. Usage data is aggregated into @@ -149,7 +150,6 @@ public final class UsageStatsManager { * @param endTime The exclusive end of the range of events to include in the results. * @return A {@link UsageEvents}. */ - @SuppressWarnings("unchecked") public UsageEvents queryEvents(long beginTime, long endTime) { try { UsageEvents iter = mService.queryEvents(beginTime, endTime, @@ -170,15 +170,13 @@ public final class UsageStatsManager { * * @param beginTime The inclusive beginning of the range of stats to include in the results. * @param endTime The exclusive end of the range of stats to include in the results. - * @return An {@link android.util.ArrayMap} keyed by package name or null if no stats are + * @return A {@link java.util.Map} keyed by package name, or null if no stats are * available. */ - public ArrayMap<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) { + public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) { List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime); if (stats.isEmpty()) { - @SuppressWarnings("unchecked") - ArrayMap<String, UsageStats> emptyStats = ArrayMap.EMPTY; - return emptyStats; + return Collections.emptyMap(); } ArrayMap<String, UsageStats> aggregatedStats = new ArrayMap<>(); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index b13792b..b2b48e8 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -27,6 +27,7 @@ import android.database.ContentObserver; import android.database.CrossProcessCursorWrapper; import android.database.Cursor; import android.database.IContentObserver; +import android.graphics.Point; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; @@ -161,6 +162,17 @@ public abstract class ContentResolver { public static final String SCHEME_FILE = "file"; /** + * An extra {@link Point} describing the optimal size for a requested image + * resource, in pixels. If a provider has multiple sizes of the image, it + * should return the image closest to this size. + * + * @see #openTypedAssetFileDescriptor(Uri, String, Bundle) + * @see #openTypedAssetFileDescriptor(Uri, String, Bundle, + * CancellationSignal) + */ + public static final String EXTRA_SIZE = "android.content.extra.SIZE"; + + /** * This is the Android platform's base MIME type for a content: URI * containing a Cursor of a single item. Applications should use this * as the base type along with their own sub-type of their content: URIs diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index c928a18..44e24b1 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -623,7 +623,7 @@ public class PackageInstaller { try { final ParcelFileDescriptor clientSocket = mSession.openWrite(name, offsetBytes, lengthBytes); - return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor()); + return new FileBridge.FileBridgeOutputStream(clientSocket); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 5492775..fa2bb4d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1456,6 +1456,15 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports live TV and can display + * contents from TV inputs implemented with the + * {@link android.media.tv.TvInputService} API. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_LIVE_TV = "android.software.live_tv"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports WiFi (802.11) networking. */ @SdkConstant(SdkConstantType.FEATURE) diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl index caabed3..ca0935c 100644 --- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl +++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl @@ -26,8 +26,8 @@ interface ICameraDeviceCallbacks * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h */ - oneway void onCameraError(int errorCode, in CaptureResultExtras resultExtras); - oneway void onCameraIdle(); + oneway void onDeviceError(int errorCode, in CaptureResultExtras resultExtras); + oneway void onDeviceIdle(); oneway void onCaptureStarted(in CaptureResultExtras resultExtras, long timestamp); oneway void onResultReceived(in CameraMetadataNative result, in CaptureResultExtras resultExtras); diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java index ec4bc7d..0895fe3 100644 --- a/core/java/android/hardware/camera2/TotalCaptureResult.java +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -19,6 +19,7 @@ package android.hardware.camera2; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -48,13 +49,23 @@ import java.util.List; */ public final class TotalCaptureResult extends CaptureResult { + private final List<CaptureResult> mPartialResults; + /** - * Takes ownership of the passed-in properties object + * Takes ownership of the passed-in camera metadata and the partial results + * + * @param partials a list of partial results; {@code null} will be substituted for an empty list * @hide */ public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, - CaptureResultExtras extras) { + CaptureResultExtras extras, List<CaptureResult> partials) { super(results, parent, extras); + + if (partials == null) { + mPartialResults = new ArrayList<>(); + } else { + mPartialResults = partials; + } } /** @@ -65,6 +76,8 @@ public final class TotalCaptureResult extends CaptureResult { */ public TotalCaptureResult(CameraMetadataNative results, int sequenceId) { super(results, sequenceId); + + mPartialResults = new ArrayList<>(); } /** @@ -73,14 +86,13 @@ public final class TotalCaptureResult extends CaptureResult { * <p>The list is returned is unmodifiable; attempting to modify it will result in a * {@code UnsupportedOperationException} being thrown.</p> * - * <p>The list size will be inclusive between {@code 1} and - * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT}, in ascending order + * <p>The list size will be inclusive between {@code 0} and + * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT}, with elements in ascending order * of when {@link CameraCaptureSession.CaptureListener#onCaptureProgressed} was invoked.</p> * * @return unmodifiable list of partial results */ public List<CaptureResult> getPartialResults() { - // TODO - return Collections.unmodifiableList(null); + return Collections.unmodifiableList(mPartialResults); } } diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index 621968b..9ca1fba 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -74,7 +74,7 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { private boolean mSkipUnconfigure = false; /** Is the session in the process of aborting? Pay attention to BUSY->IDLE transitions. */ - private boolean mAborting; + private volatile boolean mAborting; /** * Create a new CameraCaptureSession. @@ -346,6 +346,20 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { } /** + * Whether currently in mid-abort. + * + * <p>This is used by the implementation to set the capture failure + * reason, in lieu of more accurate error codes from the camera service. + * Unsynchronized to avoid deadlocks between simultaneous session->device, + * device->session calls.</p> + * + * <p>Package-private.</p> + */ + boolean isAborting() { + return mAborting; + } + + /** * Post calls into a CameraCaptureSession.StateListener to the user-specified {@code handler}. */ private StateListener createUserStateListenerProxy(Handler handler, StateListener listener) { @@ -502,8 +516,8 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { // TODO: Queue captures during abort instead of failing them // since the app won't be able to distinguish the two actives + // Don't signal the application since there's no clean mapping here Log.w(TAG, "Device is now busy; do not submit new captures (TODO: allow this)"); - mStateListener.onActive(session); } @Override diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 79ce9df..f5666bf 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -41,6 +41,7 @@ import android.view.Surface; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -384,7 +385,7 @@ public class CameraDeviceImpl extends CameraDevice { catch (IllegalArgumentException e) { // OK. camera service can reject stream config if it's not supported by HAL // This is only the result of a programmer misusing the camera2 api. - Log.e(TAG, "Stream configuration failed", e); + Log.w(TAG, "Stream configuration failed"); return false; } @@ -721,6 +722,13 @@ public class CameraDeviceImpl extends CameraDevice { checkIfCameraClosedOrInError(); mDeviceHandler.post(mCallOnBusy); + + // If already idle, just do a busy->idle transition immediately, don't actually + // flush. + if (mIdle) { + mDeviceHandler.post(mCallOnIdle); + return; + } try { LongParcelable lastFrameNumberRef = new LongParcelable(); mRemoteDevice.flush(/*out*/lastFrameNumberRef); @@ -960,6 +968,8 @@ public class CameraDeviceImpl extends CameraDevice { private long mCompletedFrameNumber = -1; private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>(); + /** Map frame numbers to list of partial results */ + private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>(); private void update() { Iterator<Long> iter = mFutureErrorSet.iterator(); @@ -976,8 +986,8 @@ public class CameraDeviceImpl extends CameraDevice { /** * This function is called every time when a result or an error is received. - * @param frameNumber: the frame number corresponding to the result or error - * @param isError: true if it is an error, false if it is not an error + * @param frameNumber the frame number corresponding to the result or error + * @param isError true if it is an error, false if it is not an error */ public void updateTracker(long frameNumber, boolean isError) { if (isError) { @@ -999,6 +1009,55 @@ public class CameraDeviceImpl extends CameraDevice { update(); } + /** + * This function is called every time a result has been completed. + * + * <p>It keeps a track of all the partial results already created for a particular + * frame number.</p> + * + * @param frameNumber the frame number corresponding to the result + * @param result the total or partial result + * @param partial {@true} if the result is partial, {@code false} if total + */ + public void updateTracker(long frameNumber, CaptureResult result, boolean partial) { + + if (!partial) { + // Update the total result's frame status as being successful + updateTracker(frameNumber, /*isError*/false); + // Don't keep a list of total results, we don't need to track them + return; + } + + if (result == null) { + // Do not record blank results; this also means there will be no total result + // so it doesn't matter that the partials were not recorded + return; + } + + // Partial results must be aggregated in-order for that frame number + List<CaptureResult> partials = mPartialResults.get(frameNumber); + if (partials == null) { + partials = new ArrayList<>(); + mPartialResults.put(frameNumber, partials); + } + + partials.add(result); + } + + /** + * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}. + * + * <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker} + * is called again with new partials for that frame number).</p> + * + * @param frameNumber the frame number corresponding to the result + * @return a list of partial results for that frame with at least 1 element, + * or {@code null} if there were no partials recorded for that frame + */ + public List<CaptureResult> popPartialResults(long frameNumber) { + return mPartialResults.remove(frameNumber); + } + public long getCompletedFrameNumber() { return mCompletedFrameNumber; } @@ -1097,31 +1156,51 @@ public class CameraDeviceImpl extends CameraDevice { */ static final int ERROR_CAMERA_SERVICE = 2; + /** + * Camera has encountered an error processing a single request. + */ + static final int ERROR_CAMERA_REQUEST = 3; + + /** + * Camera has encountered an error producing metadata for a single capture + */ + static final int ERROR_CAMERA_RESULT = 4; + + /** + * Camera has encountered an error producing an image buffer for a single capture + */ + static final int ERROR_CAMERA_BUFFER = 5; + @Override public IBinder asBinder() { return this; } @Override - public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) { - Runnable r = null; + public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) { + if (DEBUG) { + Log.d(TAG, String.format( + "Device error received, code %d, frame number %d, request ID %d, subseq ID %d", + errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(), + resultExtras.getSubsequenceId())); + } synchronized(mInterfaceLock) { if (mRemoteDevice == null) { return; // Camera already closed } - mInError = true; switch (errorCode) { case ERROR_CAMERA_DISCONNECTED: - r = mCallOnDisconnected; + CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected); break; default: Log.e(TAG, "Unknown error from camera device: " + errorCode); // no break case ERROR_CAMERA_DEVICE: case ERROR_CAMERA_SERVICE: - r = new Runnable() { + mInError = true; + Runnable r = new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()) { @@ -1129,21 +1208,19 @@ public class CameraDeviceImpl extends CameraDevice { } } }; + CameraDeviceImpl.this.mDeviceHandler.post(r); + break; + case ERROR_CAMERA_REQUEST: + case ERROR_CAMERA_RESULT: + case ERROR_CAMERA_BUFFER: + onCaptureErrorLocked(errorCode, resultExtras); break; } - CameraDeviceImpl.this.mDeviceHandler.post(r); - - // Fire onCaptureSequenceCompleted - if (DEBUG) { - Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber())); - } - mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true); - checkAndFireSequenceComplete(); } } @Override - public void onCameraIdle() { + public void onDeviceIdle() { if (DEBUG) { Log.d(TAG, "Camera now idle"); } @@ -1219,12 +1296,6 @@ public class CameraDeviceImpl extends CameraDevice { boolean isPartialResult = (resultExtras.getPartialResultCount() < mTotalPartialCount); - // Update tracker (increment counter) when it's not a partial result. - if (!isPartialResult) { - mFrameNumberTracker.updateTracker(frameNumber, - /*error*/false); - } - // Check if we have a listener for this if (holder == null) { if (DEBUG) { @@ -1232,6 +1303,9 @@ public class CameraDeviceImpl extends CameraDevice { "holder is null, early return at frame " + frameNumber); } + + mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult); + return; } @@ -1241,14 +1315,17 @@ public class CameraDeviceImpl extends CameraDevice { "camera is closed, early return at frame " + frameNumber); } + + mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult); return; } final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); - Runnable resultDispatch = null; + CaptureResult finalResult; + // Either send a partial result or the final capture completed result if (isPartialResult) { final CaptureResult resultAsCapture = @@ -1266,9 +1343,14 @@ public class CameraDeviceImpl extends CameraDevice { } } }; + + finalResult = resultAsCapture; } else { + List<CaptureResult> partialResults = + mFrameNumberTracker.popPartialResults(frameNumber); + final TotalCaptureResult resultAsCapture = - new TotalCaptureResult(result, request, resultExtras); + new TotalCaptureResult(result, request, resultExtras, partialResults); // Final capture result resultDispatch = new Runnable() { @@ -1282,19 +1364,80 @@ public class CameraDeviceImpl extends CameraDevice { } } }; + + finalResult = resultAsCapture; } holder.getHandler().post(resultDispatch); + // Collect the partials for a total result; or mark the frame as totally completed + mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult); + // Fire onCaptureSequenceCompleted if (!isPartialResult) { checkAndFireSequenceComplete(); } + } + } + + /** + * Called by onDeviceError for handling single-capture failures. + */ + private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) { + + final int requestId = resultExtras.getRequestId(); + final int subsequenceId = resultExtras.getSubsequenceId(); + final long frameNumber = resultExtras.getFrameNumber(); + final CaptureListenerHolder holder = + CameraDeviceImpl.this.mCaptureListenerMap.get(requestId); + + final CaptureRequest request = holder.getRequest(subsequenceId); + + // No way to report buffer errors right now + if (errorCode == ERROR_CAMERA_BUFFER) { + Log.e(TAG, String.format("Lost output buffer reported for frame %d", frameNumber)); + return; + } + + boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT); + + // This is only approximate - exact handling needs the camera service and HAL to + // disambiguate between request failures to due abort and due to real errors. + // For now, assume that if the session believes we're mid-abort, then the error + // is due to abort. + int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ? + CaptureFailure.REASON_FLUSHED : + CaptureFailure.REASON_ERROR; + + final CaptureFailure failure = new CaptureFailure( + request, + reason, + /*dropped*/ mayHaveBuffers, + requestId, + frameNumber); + + Runnable failureDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()){ + holder.getListener().onCaptureFailed( + CameraDeviceImpl.this, + request, + failure); + } + } + }; + holder.getHandler().post(failureDispatch); + // Fire onCaptureSequenceCompleted if appropriate + if (DEBUG) { + Log.v(TAG, String.format("got error frame %d", frameNumber)); } + mFrameNumberTracker.updateTracker(frameNumber, /*error*/true); + checkAndFireSequenceComplete(); } - } + } // public class CameraDeviceCallbacks /** * Default handler management. diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index febb015..f47ce79 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -67,7 +67,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; /** * Implementation of camera metadata marshal/unmarshal across Binder to @@ -655,6 +654,15 @@ public class CameraMetadataNative implements Parcelable { private Face[] getFaces() { Integer faceDetectMode = get(CaptureResult.STATISTICS_FACE_DETECT_MODE); + byte[] faceScores = get(CaptureResult.STATISTICS_FACE_SCORES); + Rect[] faceRectangles = get(CaptureResult.STATISTICS_FACE_RECTANGLES); + int[] faceIds = get(CaptureResult.STATISTICS_FACE_IDS); + int[] faceLandmarks = get(CaptureResult.STATISTICS_FACE_LANDMARKS); + + if (areValuesAllNull(faceDetectMode, faceScores, faceRectangles, faceIds, faceLandmarks)) { + return null; + } + if (faceDetectMode == null) { Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE"); faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE; @@ -670,8 +678,6 @@ public class CameraMetadataNative implements Parcelable { } // Face scores and rectangles are required by SIMPLE and FULL mode. - byte[] faceScores = get(CaptureResult.STATISTICS_FACE_SCORES); - Rect[] faceRectangles = get(CaptureResult.STATISTICS_FACE_RECTANGLES); if (faceScores == null || faceRectangles == null) { Log.w(TAG, "Expect face scores and rectangles to be non-null"); return new Face[0]; @@ -683,8 +689,6 @@ public class CameraMetadataNative implements Parcelable { // To be safe, make number of faces is the minimal of all face info metadata length. int numFaces = Math.min(faceScores.length, faceRectangles.length); // Face id and landmarks are only required by FULL mode. - int[] faceIds = get(CaptureResult.STATISTICS_FACE_IDS); - int[] faceLandmarks = get(CaptureResult.STATISTICS_FACE_LANDMARKS); if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) { if (faceIds == null || faceLandmarks == null) { Log.w(TAG, "Expect face ids and landmarks to be non-null for FULL mode," + @@ -755,22 +759,32 @@ public class CameraMetadataNative implements Parcelable { private LensShadingMap getLensShadingMap() { float[] lsmArray = getBase(CaptureResult.STATISTICS_LENS_SHADING_MAP); + Size s = get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE); + + // Do not warn if lsmArray is null while s is not. This is valid. if (lsmArray == null) { - Log.w(TAG, "getLensShadingMap - Lens shading map was null."); return null; } - Size s = get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE); + + if (s == null) { + Log.w(TAG, "getLensShadingMap - Lens shading map size was null."); + return null; + } + LensShadingMap map = new LensShadingMap(lsmArray, s.getHeight(), s.getWidth()); return map; } private Location getGpsLocation() { String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD); - Location l = new Location(translateProcessToLocationProvider(processingMethod)); - double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES); Long timeStamp = get(CaptureResult.JPEG_GPS_TIMESTAMP); + if (areValuesAllNull(processingMethod, coords, timeStamp)) { + return null; + } + + Location l = new Location(translateProcessToLocationProvider(processingMethod)); if (timeStamp != null) { l.setTime(timeStamp); } else { @@ -873,7 +887,13 @@ public class CameraMetadataNative implements Parcelable { float[] red = getBase(CaptureRequest.TONEMAP_CURVE_RED); float[] green = getBase(CaptureRequest.TONEMAP_CURVE_GREEN); float[] blue = getBase(CaptureRequest.TONEMAP_CURVE_BLUE); + + if (areValuesAllNull(red, green, blue)) { + return null; + } + if (red == null || green == null || blue == null) { + Log.w(TAG, "getTonemapCurve - missing tone curve components"); return null; } TonemapCurve tc = new TonemapCurve(red, green, blue); @@ -1208,6 +1228,18 @@ public class CameraMetadataNative implements Parcelable { } } + /** Check if input arguments are all {@code null}. + * + * @param objs Input arguments for null check + * @return {@code true} if input arguments are all {@code null}, otherwise {@code false} + */ + private static boolean areValuesAllNull(Object... objs) { + for (Object o : objs) { + if (o != null) return false; + } + return true; + } + static { /* * We use a class initializer to allow the native code to cache some field offsets diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index 5cbf109..410934e 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -211,7 +211,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override - public void onCameraError(final int errorCode, final CaptureResultExtras resultExtras) { + public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) { Message msg = getHandler().obtainMessage(CAMERA_ERROR, /*arg1*/ errorCode, /*arg2*/ 0, /*obj*/ resultExtras); @@ -219,7 +219,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { } @Override - public void onCameraIdle() { + public void onDeviceIdle() { Message msg = getHandler().obtainMessage(CAMERA_IDLE); getHandler().sendMessage(msg); } @@ -267,11 +267,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { case CAMERA_ERROR: { int errorCode = msg.arg1; CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj; - mCallbacks.onCameraError(errorCode, resultExtras); + mCallbacks.onDeviceError(errorCode, resultExtras); break; } case CAMERA_IDLE: - mCallbacks.onCameraIdle(); + mCallbacks.onDeviceIdle(); break; case CAPTURE_STARTED: { long timestamp = msg.arg2 & 0xFFFFFFFFL; diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index 1cf7797..ffc55f1 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -97,7 +97,7 @@ public class LegacyCameraDevice implements AutoCloseable { Log.d(TAG, "doing onError callback."); } try { - mDeviceCallbacks.onCameraError(errorCode, extras); + mDeviceCallbacks.onDeviceError(errorCode, extras); } catch (RemoteException e) { throw new IllegalStateException( "Received remote exception during onCameraError callback: ", e); @@ -125,7 +125,7 @@ public class LegacyCameraDevice implements AutoCloseable { Log.d(TAG, "doing onIdle callback."); } try { - mDeviceCallbacks.onCameraIdle(); + mDeviceCallbacks.onDeviceIdle(); } catch (RemoteException e) { throw new IllegalStateException( "Received remote exception during onCameraIdle callback: ", e); diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java index 85e7531..2ec6126 100644 --- a/core/java/android/hardware/hdmi/HdmiPortInfo.java +++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java @@ -166,7 +166,7 @@ public final class HdmiPortInfo implements Parcelable { public String toString() { StringBuffer s = new StringBuffer(); s.append("port_id: ").append(mId).append(", "); - s.append("address: ").append(mAddress).append(", "); + s.append("address: ").append(String.format("0x%04x", mAddress)).append(", "); s.append("cec: ").append(mCecSupported).append(", "); s.append("arc: ").append(mArcSupported).append(", "); s.append("mhl: ").append(mMhlSupported); diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 22da90e..8df9916 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -45,7 +45,7 @@ public abstract class NetworkAgent extends Handler { private volatile AsyncChannel mAsyncChannel; private final String LOG_TAG; private static final boolean DBG = true; - private static final boolean VDBG = true; + private static final boolean VDBG = false; private final Context mContext; private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>(); @@ -134,7 +134,7 @@ public abstract class NetworkAgent extends Handler { throw new IllegalArgumentException(); } - if (DBG) log("Registering NetworkAgent"); + if (VDBG) log("Registering NetworkAgent"); ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( Context.CONNECTIVITY_SERVICE); cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni), @@ -148,7 +148,7 @@ public abstract class NetworkAgent extends Handler { if (mAsyncChannel != null) { log("Received new connection while already connected!"); } else { - if (DBG) log("NetworkAgent fully connected"); + if (VDBG) log("NetworkAgent fully connected"); AsyncChannel ac = new AsyncChannel(); ac.connected(null, this, msg.replyTo); ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, @@ -164,7 +164,7 @@ public abstract class NetworkAgent extends Handler { break; } case AsyncChannel.CMD_CHANNEL_DISCONNECT: { - if (DBG) log("CMD_CHANNEL_DISCONNECT"); + if (VDBG) log("CMD_CHANNEL_DISCONNECT"); if (mAsyncChannel != null) mAsyncChannel.disconnect(); break; } diff --git a/core/java/android/net/NetworkBoundURLFactory.java b/core/java/android/net/NetworkBoundURLFactory.java deleted file mode 100644 index 356100e..0000000 --- a/core/java/android/net/NetworkBoundURLFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import java.net.MalformedURLException; -import java.net.URL; - -/** - * An interface that describes a factory for network-specific {@link URL} objects. - */ -public interface NetworkBoundURLFactory { - /** - * Returns a {@link URL} based on the given URL but bound to the specified {@code Network}, - * such that opening the URL will send all network traffic on the specified Network. - * - * @return a {@link URL} bound to this {@code Network}. - * @throws MalformedURLException if the URL was not valid, or this factory cannot handle the - * specified URL (e.g., if it does not support the protocol of the URL). - */ - public URL getBoundURL(Network network, URL url) throws MalformedURLException; -} diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java index a20e8e7..6ddd8b3 100644 --- a/core/java/android/net/NetworkFactory.java +++ b/core/java/android/net/NetworkFactory.java @@ -46,6 +46,7 @@ import com.android.internal.util.Protocol; **/ public class NetworkFactory extends Handler { private static final boolean DBG = true; + private static final boolean VDBG = false; private static final int BASE = Protocol.BASE_NETWORK_FACTORY; /** @@ -164,13 +165,14 @@ public class NetworkFactory extends Handler { private void handleAddRequest(NetworkRequest request, int score) { NetworkRequestInfo n = mNetworkRequests.get(request.requestId); if (n == null) { + if (DBG) log("got request " + request + " with score " + score); n = new NetworkRequestInfo(request, score); mNetworkRequests.put(n.request.requestId, n); } else { + if (VDBG) log("new score " + score + " for exisiting request " + request); n.score = score; } - if (DBG) log("got request " + request + " with score " + score); - if (DBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); + if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); evalRequest(n); } diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java index bf8d15c..022a106 100644 --- a/core/java/android/os/FileBridge.java +++ b/core/java/android/os/FileBridge.java @@ -131,10 +131,17 @@ public class FileBridge extends Thread { } public static class FileBridgeOutputStream extends OutputStream { + private final ParcelFileDescriptor mClientPfd; private final FileDescriptor mClient; private final byte[] mTemp = new byte[MSG_LENGTH]; + public FileBridgeOutputStream(ParcelFileDescriptor clientPfd) { + mClientPfd = clientPfd; + mClient = clientPfd.getFileDescriptor(); + } + public FileBridgeOutputStream(FileDescriptor client) { + mClientPfd = null; mClient = client; } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index f7b0ead..00e2e22 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -248,7 +248,7 @@ public final class PowerManager { * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor * indicates that an object is not in close proximity. */ - public static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1; + public static final int WAIT_FOR_DISTANT_PROXIMITY = 1; /** * Brightness value for fully on. @@ -961,7 +961,8 @@ public final class PowerManager { * </p> * * @param flags Combination of flag values to modify the release behavior. - * Currently only {@link #WAIT_FOR_PROXIMITY_NEGATIVE} is supported. + * Currently only {@link #WAIT_FOR_DISTANT_PROXIMITY} is supported. Passing 0 is + * equivalent to calling {@link #release()}. */ public void release(int flags) { synchronized (mToken) { diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 942da5a..0202f91 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -116,7 +116,8 @@ public class CallLog { * </pre> * </p> */ - public static final String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter"; + public static final String EXTRA_CALL_TYPE_FILTER + = "android.provider.extra.call_type_filter"; /** * Content uri used to access call log entries, including voicemail records. You must have @@ -349,6 +350,15 @@ public class CallLog { public static final String PHONE_ACCOUNT_ID = "subscription_id"; /** + * The identifier of a account that is unique to a specified component. Equivalent value + * to {@link #PHONE_ACCOUNT_ID}. For ContactsProvider internal use only. + * <P>Type: INTEGER</P> + * + * @hide + */ + public static final String SUB_ID = "sub_id"; + + /** * If a successful call is made that is longer than this duration, update the phone number * in the ContactsProvider with the normalized version of the number, based on the user's * current country code. diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 327fe4a..9a0858a 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -511,8 +511,6 @@ public final class DocumentsContract { public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; /** {@hide} */ - public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; - /** {@hide} */ public static final String EXTRA_URI = "uri"; private static final String PATH_ROOT = "root"; @@ -819,7 +817,7 @@ public final class DocumentsContract { ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal) throws RemoteException, IOException { final Bundle openOpts = new Bundle(); - openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); + openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size); AssetFileDescriptor afd = null; Bitmap bitmap = null; diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 021fff4..270d786 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -16,7 +16,6 @@ package android.provider; -import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT; @@ -763,8 +762,8 @@ public abstract class DocumentsProvider extends ContentProvider { public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) throws FileNotFoundException { enforceTree(uri); - if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { - final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); + if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) { + final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE); return openDocumentThumbnail(getDocumentId(uri), sizeHint, null); } else { return super.openTypedAssetFile(uri, mimeTypeFilter, opts); @@ -781,8 +780,8 @@ public abstract class DocumentsProvider extends ContentProvider { Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) throws FileNotFoundException { enforceTree(uri); - if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { - final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); + if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) { + final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE); return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal); } else { return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 95d1351..ae11f47 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2113,6 +2113,13 @@ public final class Settings { public static final String VOLUME_MASTER_MUTE = "volume_master_mute"; /** + * Microphone mute (int 1 = mute, 0 = not muted). + * + * @hide + */ + public static final String MICROPHONE_MUTE = "microphone_mute"; + + /** * Whether the notifications should use the ring volume (value of 1) or * a separate notification volume (value of 0). In most cases, users * will have this enabled so the notification and ringer volumes will be diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index 93b2d3b..8ca9b6c 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -29,4 +29,5 @@ oneway interface INotificationListener in NotificationRankingUpdate update); void onNotificationRankingUpdate(in NotificationRankingUpdate update); void onListenerHintsChanged(int hints); -}
\ No newline at end of file + void onInterruptionFilterChanged(int interruptionFilter); +} diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 450b9a7..cb0bcf2 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -58,26 +58,55 @@ public abstract class NotificationListenerService extends Service { private final String TAG = NotificationListenerService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; - /** {@link #getCurrentListenerHints() Listener hints} constant - default state. */ - public static final int HINTS_NONE = 0; - - /** Bitmask range for {@link #getCurrentListenerHints() Listener hints} host interruption level - * constants. */ - public static final int HOST_INTERRUPTION_LEVEL_MASK = 0x3; - - /** {@link #getCurrentListenerHints() Listener hints} constant - Normal interruption level. */ - public static final int HINT_HOST_INTERRUPTION_LEVEL_ALL = 1; + /** + * {@link #getCurrentInterruptionFilter() Interruption filter} constant - + * Normal interruption filter. + */ + public static final int INTERRUPTION_FILTER_ALL = 1; - /** {@link #getCurrentListenerHints() Listener hints} constant - Priority interruption level. */ - public static final int HINT_HOST_INTERRUPTION_LEVEL_PRIORITY = 2; + /** + * {@link #getCurrentInterruptionFilter() Interruption filter} constant - + * Priority interruption filter. + */ + public static final int INTERRUPTION_FILTER_PRIORITY = 2; - /** {@link #getCurrentListenerHints() Listener hints} constant - No interruptions level. */ - public static final int HINT_HOST_INTERRUPTION_LEVEL_NONE = 3; + /** + * {@link #getCurrentInterruptionFilter() Interruption filter} constant - + * No interruptions filter. + */ + public static final int INTERRUPTION_FILTER_NONE = 3; /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI * should disable notification sound, vibrating and other visual or aural effects. - * This does not change the interruption level, only the effects. **/ - public static final int HINT_HOST_DISABLE_EFFECTS = 1 << 2; + * This does not change the interruption filter, only the effects. **/ + public static final int HINT_HOST_DISABLE_EFFECTS = 1; + + /** + * The full trim of the StatusBarNotification including all its features. + * + * @hide + */ + @SystemApi + public static final int TRIM_FULL = 0; + + /** + * A light trim of the StatusBarNotification excluding the following features: + * + * <ol> + * <li>{@link Notification#tickerView tickerView}</li> + * <li>{@link Notification#contentView contentView}</li> + * <li>{@link Notification#largeIcon largeIcon}</li> + * <li>{@link Notification#bigContentView bigContentView}</li> + * <li>{@link Notification#headsUpContentView headsUpContentView}</li> + * <li>{@link Notification#EXTRA_LARGE_ICON extras[EXTRA_LARGE_ICON]}</li> + * <li>{@link Notification#EXTRA_LARGE_ICON_BIG extras[EXTRA_LARGE_ICON_BIG]}</li> + * <li>{@link Notification#EXTRA_PICTURE extras[EXTRA_PICTURE]}</li> + * </ol> + * + * @hide + */ + @SystemApi + public static final int TRIM_LIGHT = 1; private INotificationListenerWrapper mWrapper = null; private RankingMap mRankingMap; @@ -197,6 +226,17 @@ public abstract class NotificationListenerService extends Service { // optional } + /** + * Implement this method to be notified when the + * {@link #getCurrentInterruptionFilter() interruption filter} changed. + * + * @param interruptionFilter The current + * {@link #getCurrentInterruptionFilter() interruption filter}. + */ + public void onInterruptionFilterChanged(int interruptionFilter) { + // optional + } + private final INotificationManager getNotificationInterface() { if (mNoMan == null) { mNoMan = INotificationManager.Stub.asInterface( @@ -301,13 +341,53 @@ public abstract class NotificationListenerService extends Service { } /** + * Sets the notification trim that will be received via {@link #onNotificationPosted}. + * + * <p> + * Setting a trim other than {@link #TRIM_FULL} enables listeners that don't need access to the + * full notification features right away to reduce their memory footprint. Full notifications + * can be requested on-demand via {@link #getActiveNotifications(int)}. + * + * <p> + * Set to {@link #TRIM_FULL} initially. + * + * @hide + * + * @param trim trim of the notifications to be passed via {@link #onNotificationPosted}. + * See <code>TRIM_*</code> constants. + */ + @SystemApi + public final void setOnNotificationPostedTrim(int trim) { + if (!isBound()) return; + try { + getNotificationInterface().setOnNotificationPostedTrimFromListener(mWrapper, trim); + } catch (RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** * Request the list of outstanding notifications (that is, those that are visible to the * current user). Useful when you don't know what's already been posted. * * @return An array of active notifications, sorted in natural order. */ public StatusBarNotification[] getActiveNotifications() { - return getActiveNotifications(null); + return getActiveNotifications(null, TRIM_FULL); + } + + /** + * Request the list of outstanding notifications (that is, those that are visible to the + * current user). Useful when you don't know what's already been posted. + * + * @hide + * + * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. + * @return An array of active notifications, sorted in natural order. + */ + @SystemApi + public StatusBarNotification[] getActiveNotifications(int trim) { + return getActiveNotifications(null, trim); } /** @@ -315,14 +395,33 @@ public abstract class NotificationListenerService extends Service { * notifications but didn't want to retain the bits, and now need to go back and extract * more data out of those notifications. * + * @param keys the keys of the notifications to request * @return An array of notifications corresponding to the requested keys, in the * same order as the key list. */ public StatusBarNotification[] getActiveNotifications(String[] keys) { - if (!isBound()) return null; + return getActiveNotifications(keys, TRIM_FULL); + } + + /** + * Request one or more notifications by key. Useful if you have been keeping track of + * notifications but didn't want to retain the bits, and now need to go back and extract + * more data out of those notifications. + * + * @hide + * + * @param keys the keys of the notifications to request + * @param trim trim of the notifications to be returned. See <code>TRIM_*</code> constants. + * @return An array of notifications corresponding to the requested keys, in the + * same order as the key list. + */ + @SystemApi + public StatusBarNotification[] getActiveNotifications(String[] keys, int trim) { + if (!isBound()) + return null; try { - ParceledListSlice<StatusBarNotification> parceledList = - getNotificationInterface().getActiveNotificationsFromListener(mWrapper, keys); + ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface() + .getActiveNotificationsFromListener(mWrapper, keys, trim); List<StatusBarNotification> list = parceledList.getList(); int N = list.size(); @@ -345,15 +444,42 @@ public abstract class NotificationListenerService extends Service { * shared across all listeners or a feature the notification host does not support or refuses * to grant. * - * @return One or more of the HINT_ constants. + * @return Zero or more of the HINT_ constants. */ public final int getCurrentListenerHints() { - if (!isBound()) return HINTS_NONE; + if (!isBound()) return 0; try { return getNotificationInterface().getHintsFromListener(mWrapper); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); - return HINTS_NONE; + return 0; + } + } + + /** + * Gets the current notification interruption filter active on the host. + * + * <p> + * The interruption filter defines which notifications are allowed to interrupt the user + * (e.g. via sound & vibration) and is applied globally. Listeners can find out whether + * a specific notification matched the interruption filter via + * {@link Ranking#matchesInterruptionFilter()}. + * <p> + * The current filter may differ from the previously requested filter if the notification host + * does not support or refuses to apply the requested filter, or if another component changed + * the filter in the meantime. + * <p> + * Listen for updates using {@link #onInterruptionFilterChanged(int)}. + * + * @return One of the INTERRUPTION_FILTER_ constants, or 0 on errors. + */ + public final int getCurrentInterruptionFilter() { + if (!isBound()) return 0; + try { + return getNotificationInterface().getHintsFromListener(mWrapper); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + return 0; } } @@ -361,7 +487,7 @@ public abstract class NotificationListenerService extends Service { * Sets the desired {@link #getCurrentListenerHints() listener hints}. * * <p> - * This is merely a request, the host may or not choose to take action depending + * This is merely a request, the host may or may not choose to take action depending * on other listener requests or other global state. * <p> * Listen for updates using {@link #onListenerHintsChanged(int)}. @@ -378,6 +504,27 @@ public abstract class NotificationListenerService extends Service { } /** + * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}. + * + * <p> + * This is merely a request, the host may or may not choose to apply the requested + * interruption filter depending on other listener requests or other global state. + * <p> + * Listen for updates using {@link #onInterruptionFilterChanged(int)}. + * + * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants. + */ + public final void requestInterruptionFilter(int interruptionFilter) { + if (!isBound()) return; + try { + getNotificationInterface() + .requestInterruptionFilterFromListener(mWrapper, interruptionFilter); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** * Returns current ranking information. * * <p> @@ -514,6 +661,15 @@ public abstract class NotificationListenerService extends Service { Log.w(TAG, "Error running onListenerHintsChanged", t); } } + + @Override + public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException { + try { + NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter); + } catch (Throwable t) { + Log.w(TAG, "Error running onInterruptionFilterChanged", t); + } + } } private void applyUpdate(NotificationRankingUpdate update) { diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index 337ae60..3ef5b37 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -89,6 +89,7 @@ public class TrustAgentService extends Service { /** * A white list of features that the given trust agent should support when otherwise disabled * by device policy. + * @hide */ public static final String KEY_FEATURES = "trust_agent_features"; @@ -184,6 +185,7 @@ public class TrustAgentService extends Service { * * @param options Option feature bundle. * @return true if the {@link TrustAgentService} supports this feature. + * @hide */ public boolean onSetTrustAgentFeaturesEnabled(Bundle options) { return false; diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 2095773..519bc28 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -19,6 +19,7 @@ package android.service.voice; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Activity; import android.content.Intent; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; @@ -84,20 +85,31 @@ public class AlwaysOnHotwordDetector { private static final int STATE_NOT_READY = 0; // Keyphrase management actions. Used in getManageIntent() ----// - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { MANAGE_ACTION_ENROLL, MANAGE_ACTION_RE_ENROLL, MANAGE_ACTION_UN_ENROLL }) - public @interface ManageActions {} + private @interface ManageActions {} - /** Indicates that we need to enroll. */ + /** + * Indicates that we need to enroll. + * + * @hide + */ public static final int MANAGE_ACTION_ENROLL = 0; - /** Indicates that we need to re-enroll. */ + /** + * Indicates that we need to re-enroll. + * + * @hide + */ public static final int MANAGE_ACTION_RE_ENROLL = 1; - /** Indicates that we need to un-enroll. */ + /** + * Indicates that we need to un-enroll. + * + * @hide + */ public static final int MANAGE_ACTION_UN_ENROLL = 2; //-- Flags for startRecognition ----// @@ -111,7 +123,11 @@ public class AlwaysOnHotwordDetector { }) public @interface RecognitionFlags {} - /** Empty flag for {@link #startRecognition(int)}. */ + /** + * Empty flag for {@link #startRecognition(int)}. + * + * @hide + */ public static final int RECOGNITION_FLAG_NONE = 0; /** * Recognition flag for {@link #startRecognition(int)} that indicates @@ -264,7 +280,7 @@ public class AlwaysOnHotwordDetector { /** * Callbacks for always-on hotword detection. */ - public interface Callback { + public static abstract class Callback { /** * Called when the hotword availability changes. * This indicates a change in the availability of recognition for the given keyphrase. @@ -278,7 +294,7 @@ public class AlwaysOnHotwordDetector { * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_UNENROLLED * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_ENROLLED */ - void onAvailabilityChanged(int status); + public abstract void onAvailabilityChanged(int status); /** * Called when the keyphrase is spoken. * This implicitly stops listening for the keyphrase once it's detected. @@ -289,23 +305,23 @@ public class AlwaysOnHotwordDetector { * This may contain the trigger audio, if requested when calling * {@link AlwaysOnHotwordDetector#startRecognition(int)}. */ - void onDetected(@NonNull EventPayload eventPayload); + public abstract void onDetected(@NonNull EventPayload eventPayload); /** * Called when the detection fails due to an error. */ - void onError(); + public abstract void onError(); /** * Called when the recognition is paused temporarily for some reason. * This is an informational callback, and the clients shouldn't be doing anything here * except showing an indication on their UI if they have to. */ - void onRecognitionPaused(); + public abstract void onRecognitionPaused(); /** * Called when the recognition is resumed after it was temporarily paused. * This is an informational callback, and the clients shouldn't be doing anything here * except showing an indication on their UI if they have to. */ - void onRecognitionResumed(); + public abstract void onRecognitionResumed(); } /** @@ -372,10 +388,10 @@ public class AlwaysOnHotwordDetector { /** * Starts recognition for the associated keyphrase. * + * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO + * @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS + * * @param recognitionFlags The flags to control the recognition properties. - * The allowed flags are {@link #RECOGNITION_FLAG_NONE}, - * {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO} and - * {@link #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS}. * @return Indicates whether the call succeeded or not. * @throws UnsupportedOperationException if the recognition isn't supported. * Callers should only call this method after a supported state callback on @@ -430,12 +446,34 @@ public class AlwaysOnHotwordDetector { } /** - * Gets an intent to manage the associated keyphrase. + * Creates an intent to start the enrollment for the associated keyphrase. + * This intent must be invoked using {@link Activity#startActivityForResult(Intent, int)}. + * Starting re-enrollment is only valid if the keyphrase is un-enrolled, + * i.e. {@link #STATE_KEYPHRASE_UNENROLLED}, + * otherwise {@link #createIntentToReEnroll()} should be preferred. + * + * @return An {@link Intent} to start enrollment for the given keyphrase. + * @throws UnsupportedOperationException if managing they keyphrase isn't supported. + * Callers should only call this method after a supported state callback on + * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. + * @throws IllegalStateException if the detector is in an invalid state. + * This may happen if another detector has been instantiated or the + * {@link VoiceInteractionService} hosting this detector has been shut down. + */ + public Intent createIntentToEnroll() { + if (DBG) Slog.d(TAG, "createIntentToEnroll"); + synchronized (mLock) { + return getManageIntentLocked(MANAGE_ACTION_ENROLL); + } + } + + /** + * Creates an intent to start the un-enrollment for the associated keyphrase. + * This intent must be invoked using {@link Activity#startActivityForResult(Intent, int)}. + * Starting re-enrollment is only valid if the keyphrase is already enrolled, + * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error. * - * @param action The manage action that needs to be performed. - * One of {@link #MANAGE_ACTION_ENROLL}, {@link #MANAGE_ACTION_RE_ENROLL} or - * {@link #MANAGE_ACTION_UN_ENROLL}. - * @return An {@link Intent} to manage the given keyphrase. + * @return An {@link Intent} to start un-enrollment for the given keyphrase. * @throws UnsupportedOperationException if managing they keyphrase isn't supported. * Callers should only call this method after a supported state callback on * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. @@ -443,10 +481,31 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ - public Intent getManageIntent(@ManageActions int action) { - if (DBG) Slog.d(TAG, "getManageIntent(" + action + ")"); + public Intent createIntentToUnEnroll() { + if (DBG) Slog.d(TAG, "createIntentToUnEnroll"); synchronized (mLock) { - return getManageIntentLocked(action); + return getManageIntentLocked(MANAGE_ACTION_UN_ENROLL); + } + } + + /** + * Creates an intent to start the re-enrollment for the associated keyphrase. + * This intent must be invoked using {@link Activity#startActivityForResult(Intent, int)}. + * Starting re-enrollment is only valid if the keyphrase is already enrolled, + * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error. + * + * @return An {@link Intent} to start re-enrollment for the given keyphrase. + * @throws UnsupportedOperationException if managing they keyphrase isn't supported. + * Callers should only call this method after a supported state callback on + * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. + * @throws IllegalStateException if the detector is in an invalid state. + * This may happen if another detector has been instantiated or the + * {@link VoiceInteractionService} hosting this detector has been shut down. + */ + public Intent createIntentToReEnroll() { + if (DBG) Slog.d(TAG, "createIntentToReEnroll"); + synchronized (mLock) { + return getManageIntentLocked(MANAGE_ACTION_RE_ENROLL); } } @@ -462,12 +521,6 @@ public class AlwaysOnHotwordDetector { "Managing the given keyphrase is not supported"); } - if (action != MANAGE_ACTION_ENROLL - && action != MANAGE_ACTION_RE_ENROLL - && action != MANAGE_ACTION_UN_ENROLL) { - throw new IllegalArgumentException("Invalid action specified " + action); - } - return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale); } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index aecf488..e82057c 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -262,6 +262,8 @@ public class StaticLayout extends Layout { int fit = paraStart; float fitWidth = w; int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0; + // same as fitWidth but not including any trailing whitespace + float fitWidthGraphing = w; boolean hasTabOrEmoji = false; boolean hasTab = false; @@ -346,6 +348,9 @@ public class StaticLayout extends Layout { if (w <= width || isSpaceOrTab) { fitWidth = w; + if (!isSpaceOrTab) { + fitWidthGraphing = w; + } fit = j + 1; if (fmTop < fitTop) @@ -365,7 +370,7 @@ public class StaticLayout extends Layout { breakOpp[breakOppIndex] == j - paraStart + 1; if (isLineBreak) { - okWidth = w; + okWidth = fitWidthGraphing; ok = j + 1; if (fitTop < okTop) @@ -426,6 +431,7 @@ public class StaticLayout extends Layout { j = here - 1; // restart j-span loop from here, compensating for the j++ ok = fit = here; w = 0; + fitWidthGraphing = w; fitAscent = fitDescent = fitTop = fitBottom = 0; okAscent = okDescent = okTop = okBottom = 0; @@ -842,7 +848,7 @@ public class StaticLayout extends Layout { void prepare() { mMeasured = MeasuredText.obtain(); } - + void finish() { mMeasured = MeasuredText.recycle(mMeasured); } diff --git a/core/java/android/text/format/TimeFormatter.java b/core/java/android/text/format/TimeFormatter.java index ec79b36..3a63805 100644 --- a/core/java/android/text/format/TimeFormatter.java +++ b/core/java/android/text/format/TimeFormatter.java @@ -22,8 +22,7 @@ package android.text.format; import android.content.res.Resources; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; +import java.nio.CharBuffer; import java.util.Formatter; import java.util.Locale; import java.util.TimeZone; @@ -31,15 +30,13 @@ import libcore.icu.LocaleData; import libcore.util.ZoneInfo; /** - * Formatting logic for {@link Time}. Contains a port of Bionic's broken strftime_tz to Java. The - * main issue with this implementation is the treatment of characters as ASCII, despite returning - * localized (UTF-16) strings from the LocaleData. + * Formatting logic for {@link Time}. Contains a port of Bionic's broken strftime_tz to Java. * * <p>This class is not thread safe. */ class TimeFormatter { - // An arbitrary value outside the range representable by a byte / ASCII character code. - private static final int FORCE_LOWER_CASE = 0x100; + // An arbitrary value outside the range representable by a char. + private static final int FORCE_LOWER_CASE = -1; private static final int SECSPERMIN = 60; private static final int MINSPERHOUR = 60; @@ -62,10 +59,9 @@ class TimeFormatter { private final String dateTimeFormat; private final String timeOnlyFormat; private final String dateOnlyFormat; - private final Locale locale; private StringBuilder outputBuilder; - private Formatter outputFormatter; + private Formatter numberFormatter; public TimeFormatter() { synchronized (TimeFormatter.class) { @@ -84,7 +80,6 @@ class TimeFormatter { this.dateTimeFormat = sDateTimeFormat; this.timeOnlyFormat = sTimeOnlyFormat; this.dateOnlyFormat = sDateOnlyFormat; - this.locale = locale; localeData = sLocaleData; } } @@ -97,19 +92,21 @@ class TimeFormatter { StringBuilder stringBuilder = new StringBuilder(); outputBuilder = stringBuilder; - outputFormatter = new Formatter(stringBuilder, locale); + // This uses the US locale because number localization is handled separately (see below) + // and locale sensitive strings are output directly using outputBuilder. + numberFormatter = new Formatter(stringBuilder, Locale.US); formatInternal(pattern, wallTime, zoneInfo); String result = stringBuilder.toString(); // This behavior is the source of a bug since some formats are defined as being - // in ASCII. Generally localization is very broken. + // in ASCII and not localized. if (localeData.zeroDigit != '0') { result = localizeDigits(result); } return result; } finally { outputBuilder = null; - outputFormatter = null; + numberFormatter = null; } } @@ -132,38 +129,30 @@ class TimeFormatter { * {@link #outputBuilder}. */ private void formatInternal(String pattern, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) { - // Convert to ASCII bytes to be compatible with old implementation behavior. - byte[] bytes = pattern.getBytes(StandardCharsets.US_ASCII); - if (bytes.length == 0) { - return; - } - - ByteBuffer formatBuffer = ByteBuffer.wrap(bytes); + CharBuffer formatBuffer = CharBuffer.wrap(pattern); while (formatBuffer.remaining() > 0) { - boolean outputCurrentByte = true; - char currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); - if (currentByteAsChar == '%') { - outputCurrentByte = handleToken(formatBuffer, wallTime, zoneInfo); + boolean outputCurrentChar = true; + char currentChar = formatBuffer.get(formatBuffer.position()); + if (currentChar == '%') { + outputCurrentChar = handleToken(formatBuffer, wallTime, zoneInfo); } - if (outputCurrentByte) { - currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); - outputBuilder.append(currentByteAsChar); + if (outputCurrentChar) { + outputBuilder.append(formatBuffer.get(formatBuffer.position())); } - formatBuffer.position(formatBuffer.position() + 1); } } - private boolean handleToken(ByteBuffer formatBuffer, ZoneInfo.WallTime wallTime, + private boolean handleToken(CharBuffer formatBuffer, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) { - // The byte at formatBuffer.position() is expected to be '%' at this point. + // The char at formatBuffer.position() is expected to be '%' at this point. int modifier = 0; while (formatBuffer.remaining() > 1) { - // Increment the position then get the new current byte. + // Increment the position then get the new current char. formatBuffer.position(formatBuffer.position() + 1); - char currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); - switch (currentByteAsChar) { + char currentChar = formatBuffer.get(formatBuffer.position()); + switch (currentChar) { case 'A': modifyAndAppend((wallTime.getWeekDay() < 0 || wallTime.getWeekDay() >= DAYSPERWEEK) @@ -206,7 +195,7 @@ class TimeFormatter { formatInternal("%m/%d/%y", wallTime, zoneInfo); return false; case 'd': - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), wallTime.getMonthDay()); return false; case 'E': @@ -218,46 +207,46 @@ class TimeFormatter { case '0': case '^': case '#': - modifier = currentByteAsChar; + modifier = currentChar; continue; case 'e': - outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), + numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), wallTime.getMonthDay()); return false; case 'F': formatInternal("%Y-%m-%d", wallTime, zoneInfo); return false; case 'H': - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), wallTime.getHour()); return false; case 'I': int hour = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12; - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), hour); + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), hour); return false; case 'j': int yearDay = wallTime.getYearDay() + 1; - outputFormatter.format(getFormat(modifier, "%03d", "%3d", "%d", "%03d"), + numberFormatter.format(getFormat(modifier, "%03d", "%3d", "%d", "%03d"), yearDay); return false; case 'k': - outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), + numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), wallTime.getHour()); return false; case 'l': int n2 = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12; - outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), n2); + numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), n2); return false; case 'M': - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), wallTime.getMinute()); return false; case 'm': - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), wallTime.getMonth() + 1); return false; case 'n': - modifyAndAppend("\n", modifier); + outputBuilder.append('\n'); return false; case 'p': modifyAndAppend((wallTime.getHour() >= (HOURSPERDAY / 2)) ? localeData.amPm[1] @@ -274,27 +263,27 @@ class TimeFormatter { formatInternal("%I:%M:%S %p", wallTime, zoneInfo); return false; case 'S': - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), wallTime.getSecond()); return false; case 's': int timeInSeconds = wallTime.mktime(zoneInfo); - modifyAndAppend(Integer.toString(timeInSeconds), modifier); + outputBuilder.append(Integer.toString(timeInSeconds)); return false; case 'T': formatInternal("%H:%M:%S", wallTime, zoneInfo); return false; case 't': - modifyAndAppend("\t", modifier); + outputBuilder.append('\t'); return false; case 'U': - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), (wallTime.getYearDay() + DAYSPERWEEK - wallTime.getWeekDay()) / DAYSPERWEEK); return false; case 'u': int day = (wallTime.getWeekDay() == 0) ? DAYSPERWEEK : wallTime.getWeekDay(); - outputFormatter.format("%d", day); + numberFormatter.format("%d", day); return false; case 'V': /* ISO 8601 week number */ case 'G': /* ISO 8601 year (four digits) */ @@ -326,9 +315,9 @@ class TimeFormatter { --year; yday += isLeap(year) ? DAYSPERLYEAR : DAYSPERNYEAR; } - if (currentByteAsChar == 'V') { - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), w); - } else if (currentByteAsChar == 'g') { + if (currentChar == 'V') { + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), w); + } else if (currentChar == 'g') { outputYear(year, false, true, modifier); } else { outputYear(year, true, true, modifier); @@ -342,10 +331,10 @@ class TimeFormatter { int n = (wallTime.getYearDay() + DAYSPERWEEK - ( wallTime.getWeekDay() != 0 ? (wallTime.getWeekDay() - 1) : (DAYSPERWEEK - 1))) / DAYSPERWEEK; - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n); + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n); return false; case 'w': - outputFormatter.format("%d", wallTime.getWeekDay()); + numberFormatter.format("%d", wallTime.getWeekDay()); return false; case 'X': formatInternal(timeOnlyFormat, wallTime, zoneInfo); @@ -371,17 +360,17 @@ class TimeFormatter { return false; } int diff = wallTime.getGmtOffset(); - String sign; + char sign; if (diff < 0) { - sign = "-"; + sign = '-'; diff = -diff; } else { - sign = "+"; + sign = '+'; } - modifyAndAppend(sign, modifier); + outputBuilder.append(sign); diff /= SECSPERMIN; diff = (diff / MINSPERHOUR) * 100 + (diff % MINSPERHOUR); - outputFormatter.format(getFormat(modifier, "%04d", "%4d", "%d", "%04d"), diff); + numberFormatter.format(getFormat(modifier, "%04d", "%4d", "%d", "%04d"), diff); return false; } case '+': @@ -422,7 +411,6 @@ class TimeFormatter { break; default: outputBuilder.append(str); - } } @@ -443,14 +431,14 @@ class TimeFormatter { } if (outputTop) { if (lead == 0 && trail < 0) { - modifyAndAppend("-0", modifier); + outputBuilder.append("-0"); } else { - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), lead); + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), lead); } } if (outputBottom) { int n = ((trail < 0) ? -trail : trail); - outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n); + numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n); } } @@ -472,24 +460,24 @@ class TimeFormatter { } /** - * A broken implementation of {@link Character#isUpperCase(char)} that assumes ASCII in order to - * be compatible with the old native implementation. + * A broken implementation of {@link Character#isUpperCase(char)} that assumes ASCII codes in + * order to be compatible with the old native implementation. */ private static boolean brokenIsUpper(char toCheck) { return toCheck >= 'A' && toCheck <= 'Z'; } /** - * A broken implementation of {@link Character#isLowerCase(char)} that assumes ASCII in order to - * be compatible with the old native implementation. + * A broken implementation of {@link Character#isLowerCase(char)} that assumes ASCII codes in + * order to be compatible with the old native implementation. */ private static boolean brokenIsLower(char toCheck) { return toCheck >= 'a' && toCheck <= 'z'; } /** - * A broken implementation of {@link Character#toLowerCase(char)} that assumes ASCII in order to - * be compatible with the old native implementation. + * A broken implementation of {@link Character#toLowerCase(char)} that assumes ASCII codes in + * order to be compatible with the old native implementation. */ private static char brokenToLower(char input) { if (input >= 'A' && input <= 'Z') { @@ -499,8 +487,8 @@ class TimeFormatter { } /** - * A broken implementation of {@link Character#toUpperCase(char)} that assumes ASCII in order to - * be compatible with the old native implementation. + * A broken implementation of {@link Character#toUpperCase(char)} that assumes ASCII codes in + * order to be compatible with the old native implementation. */ private static char brokenToUpper(char input) { if (input >= 'a' && input <= 'z') { @@ -509,11 +497,4 @@ class TimeFormatter { return input; } - /** - * Safely convert a byte containing an ASCII character to a char, even for character codes - * > 127. - */ - private static char convertToChar(byte b) { - return (char) (b & 0xFF); - } } diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index 59ba71f..bd52e71 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1656,7 +1656,7 @@ public abstract class Transition implements Cloneable { WindowId windowId = sceneRoot.getWindowId(); for (int i = numOldAnims - 1; i >= 0; i--) { AnimationInfo info = runningAnimators.valueAt(i); - if (info.view != null && windowId.equals(info.windowId)) { + if (info.view != null && windowId != null && windowId.equals(info.windowId)) { Animator anim = runningAnimators.keyAt(i); anim.pause(); } @@ -1689,7 +1689,7 @@ public abstract class Transition implements Cloneable { WindowId windowId = sceneRoot.getWindowId(); for (int i = numOldAnims - 1; i >= 0; i--) { AnimationInfo info = runningAnimators.valueAt(i); - if (info.view != null && windowId.equals(info.windowId)) { + if (info.view != null && windowId != null && windowId.equals(info.windowId)) { Animator anim = runningAnimators.keyAt(i); anim.resume(); } diff --git a/core/java/android/util/Size.java b/core/java/android/util/Size.java index d58f778..6424344 100644 --- a/core/java/android/util/Size.java +++ b/core/java/android/util/Size.java @@ -16,12 +16,15 @@ package android.util; -import static com.android.internal.util.Preconditions.*; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.os.Parcel; +import android.os.Parcelable; /** * Immutable class for describing width and height dimensions in pixels. */ -public final class Size { +public final class Size implements Parcelable { /** * Create a new immutable Size instance. * @@ -33,6 +36,11 @@ public final class Size { mHeight = height; } + private Size(Parcel in) { + mWidth = in.readInt(); + mHeight = in.readInt(); + } + /** * Get the width of the size (in pixels). * @return width @@ -147,6 +155,29 @@ public final class Size { return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mWidth); + out.writeInt(mHeight); + } + + public static final Parcelable.Creator<Size> CREATOR = new Parcelable.Creator<Size>() { + @Override + public Size createFromParcel(Parcel in) { + return new Size(in); + } + + @Override + public Size[] newArray(int size) { + return new Size[size]; + } + }; + private final int mWidth; private final int mHeight; -}; +} diff --git a/core/java/android/util/SizeF.java b/core/java/android/util/SizeF.java index 0a8b4ed..88bb439 100644 --- a/core/java/android/util/SizeF.java +++ b/core/java/android/util/SizeF.java @@ -16,7 +16,10 @@ package android.util; -import static com.android.internal.util.Preconditions.*; +import static com.android.internal.util.Preconditions.checkArgumentFinite; + +import android.os.Parcel; +import android.os.Parcelable; /** * Immutable class for describing width and height dimensions in some arbitrary @@ -25,7 +28,7 @@ import static com.android.internal.util.Preconditions.*; * Width and height are finite values stored as a floating point representation. * </p> */ -public final class SizeF { +public final class SizeF implements Parcelable { /** * Create a new immutable SizeF instance. * @@ -43,6 +46,11 @@ public final class SizeF { mHeight = checkArgumentFinite(height, "height"); } + private SizeF(Parcel in) { + mWidth = in.readFloat(); + mHeight = in.readFloat(); + } + /** * Get the width of the size (as an arbitrary unit). * @return width @@ -103,6 +111,29 @@ public final class SizeF { return Float.floatToIntBits(mWidth) ^ Float.floatToIntBits(mHeight); } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeFloat(mWidth); + out.writeFloat(mHeight); + } + + public static final Parcelable.Creator<SizeF> CREATOR = new Parcelable.Creator<SizeF>() { + @Override + public SizeF createFromParcel(Parcel in) { + return new SizeF(in); + } + + @Override + public SizeF[] newArray(int size) { + return new SizeF[size]; + } + }; + private final float mWidth; private final float mHeight; -}; +} diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index de90899..edb3798 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -485,4 +485,6 @@ public abstract class HardwareRenderer { * Called by {@link ViewRootImpl} when a new performTraverals is scheduled. */ abstract void notifyFramePending(); + + abstract void registerAnimatingRenderNode(RenderNode animator); } diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 099f153..9dc9766 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -167,10 +167,13 @@ public class RenderNode { public static final int STATUS_DREW = 0x4; private boolean mValid; - private final long mNativeRenderNode; + // Do not access directly unless you are ThreadedRenderer + final long mNativeRenderNode; + private final View mOwningView; - private RenderNode(String name) { + private RenderNode(String name, View owningView) { mNativeRenderNode = nCreate(name); + mOwningView = owningView; } /** @@ -178,6 +181,7 @@ public class RenderNode { */ private RenderNode(long nativePtr) { mNativeRenderNode = nativePtr; + mOwningView = null; } /** @@ -188,8 +192,8 @@ public class RenderNode { * * @return A new RenderNode. */ - public static RenderNode create(String name) { - return new RenderNode(name); + public static RenderNode create(String name, @Nullable View owningView) { + return new RenderNode(name, owningView); } /** @@ -805,7 +809,15 @@ public class RenderNode { /////////////////////////////////////////////////////////////////////////// public void addAnimator(RenderNodeAnimator animator) { + if (mOwningView == null || mOwningView.mAttachInfo == null) { + throw new IllegalStateException("Cannot start this animator on a detached view!"); + } nAddAnimator(mNativeRenderNode, animator.getNativeAnimator()); + mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this); + } + + public void endAllAnimators() { + nEndAllAnimators(mNativeRenderNode); } /////////////////////////////////////////////////////////////////////////// @@ -891,6 +903,7 @@ public class RenderNode { /////////////////////////////////////////////////////////////////////////// private static native void nAddAnimator(long renderNode, long animatorPtr); + private static native void nEndAllAnimators(long renderNode); /////////////////////////////////////////////////////////////////////////// // Finalization diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index 9433237..fa4a13a 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -19,7 +19,6 @@ package android.view; import android.animation.Animator; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; -import android.animation.Animator.AnimatorListener; import android.graphics.Canvas; import android.graphics.CanvasProperty; import android.graphics.Paint; @@ -30,7 +29,6 @@ import com.android.internal.view.animation.FallbackLUTInterpolator; import com.android.internal.view.animation.HasNativeInterpolator; import com.android.internal.view.animation.NativeInterpolatorFactory; -import java.lang.ref.WeakReference; import java.util.ArrayList; /** @@ -89,8 +87,11 @@ public class RenderNodeAnimator extends Animator { private float mFinalValue; private TimeInterpolator mInterpolator; - private boolean mStarted = false; - private boolean mFinished = false; + private static final int STATE_PREPARE = 0; + private static final int STATE_DELAYED = 1; + private static final int STATE_RUNNING = 2; + private static final int STATE_FINISHED = 3; + private int mState = STATE_PREPARE; private long mUnscaledDuration = 300; private long mUnscaledStartDelay = 0; @@ -111,13 +112,11 @@ public class RenderNodeAnimator extends Animator { mRenderProperty = property; mFinalValue = finalValue; mUiThreadHandlesDelay = true; - init(nCreateAnimator(new WeakReference<RenderNodeAnimator>(this), - property, finalValue)); + init(nCreateAnimator(property, finalValue)); } public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) { init(nCreateCanvasPropertyFloatAnimator( - new WeakReference<RenderNodeAnimator>(this), property.getNativeContainer(), finalValue)); mUiThreadHandlesDelay = false; } @@ -132,14 +131,12 @@ public class RenderNodeAnimator extends Animator { */ public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) { init(nCreateCanvasPropertyPaintAnimator( - new WeakReference<RenderNodeAnimator>(this), property.getNativeContainer(), paintField, finalValue)); mUiThreadHandlesDelay = false; } public RenderNodeAnimator(int x, int y, float startRadius, float endRadius) { - init(nCreateRevealAnimator(new WeakReference<RenderNodeAnimator>(this), - x, y, startRadius, endRadius)); + init(nCreateRevealAnimator(x, y, startRadius, endRadius)); mUiThreadHandlesDelay = true; } @@ -148,7 +145,7 @@ public class RenderNodeAnimator extends Animator { } private void checkMutable() { - if (mStarted) { + if (mState != STATE_PREPARE) { throw new IllegalStateException("Animator has already started, cannot change it now!"); } } @@ -176,11 +173,11 @@ public class RenderNodeAnimator extends Animator { throw new IllegalStateException("Missing target!"); } - if (mStarted) { + if (mState != STATE_PREPARE) { throw new IllegalStateException("Already started!"); } - mStarted = true; + mState = STATE_DELAYED; applyInterpolator(); if (mStartDelay <= 0 || !mUiThreadHandlesDelay) { @@ -192,7 +189,8 @@ public class RenderNodeAnimator extends Animator { } private void doStart() { - nStart(mNativePtr.get()); + mState = STATE_RUNNING; + nStart(mNativePtr.get(), this); // Alpha is a special snowflake that has the canonical value stored // in mTransformationInfo instead of in RenderNode, so we need to update @@ -203,11 +201,7 @@ public class RenderNodeAnimator extends Animator { mViewTarget.mTransformationInfo.mAlpha = mFinalValue; } - final ArrayList<AnimatorListener> listeners = cloneListeners(); - final int numListeners = listeners == null ? 0 : listeners.size(); - for (int i = 0; i < numListeners; i++) { - listeners.get(i).onAnimationStart(this); - } + notifyStartListeners(); if (mViewTarget != null) { // Kick off a frame to start the process @@ -215,10 +209,21 @@ public class RenderNodeAnimator extends Animator { } } + private void notifyStartListeners() { + final ArrayList<AnimatorListener> listeners = cloneListeners(); + final int numListeners = listeners == null ? 0 : listeners.size(); + for (int i = 0; i < numListeners; i++) { + listeners.get(i).onAnimationStart(this); + } + } + @Override public void cancel() { - if (!mFinished) { - getHelper().removeDelayedAnimation(this); + if (mState != STATE_FINISHED) { + if (mState == STATE_DELAYED) { + getHelper().removeDelayedAnimation(this); + notifyStartListeners(); + } nEnd(mNativePtr.get()); final ArrayList<AnimatorListener> listeners = cloneListeners(); @@ -226,12 +231,17 @@ public class RenderNodeAnimator extends Animator { for (int i = 0; i < numListeners; i++) { listeners.get(i).onAnimationCancel(this); } + + if (mViewTarget != null) { + // Kick off a frame to flush the state change + mViewTarget.invalidateViewProperty(true, false); + } } } @Override public void end() { - if (!mFinished) { + if (mState != STATE_FINISHED) { nEnd(mNativePtr.get()); } } @@ -248,24 +258,21 @@ public class RenderNodeAnimator extends Animator { public void setTarget(View view) { mViewTarget = view; - mTarget = view.mRenderNode; - mTarget.addAnimator(this); + setTarget(mViewTarget.mRenderNode); } public void setTarget(Canvas canvas) { if (!(canvas instanceof GLES20RecordingCanvas)) { throw new IllegalArgumentException("Not a GLES20RecordingCanvas"); } - final GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas; setTarget(recordingCanvas.mNode); } - public void setTarget(RenderNode node) { + private void setTarget(RenderNode node) { if (mTarget != null) { throw new IllegalStateException("Target already set!"); } - mViewTarget = null; mTarget = node; mTarget.addAnimator(this); } @@ -308,12 +315,12 @@ public class RenderNodeAnimator extends Animator { @Override public boolean isRunning() { - return mStarted && !mFinished; + return mState == STATE_DELAYED || mState == STATE_RUNNING; } @Override public boolean isStarted() { - return mStarted; + return mState != STATE_PREPARE; } @Override @@ -328,13 +335,23 @@ public class RenderNodeAnimator extends Animator { } protected void onFinished() { - mFinished = true; + if (mState == STATE_DELAYED) { + getHelper().removeDelayedAnimation(this); + notifyStartListeners(); + } + mState = STATE_FINISHED; final ArrayList<AnimatorListener> listeners = cloneListeners(); final int numListeners = listeners == null ? 0 : listeners.size(); for (int i = 0; i < numListeners; i++) { listeners.get(i).onAnimationEnd(this); } + + // Release the native object, as it has a global reference to us. This + // breaks the cyclic reference chain, and allows this object to be + // GC'd + mNativePtr.release(); + mNativePtr = null; } @SuppressWarnings("unchecked") @@ -427,11 +444,8 @@ public class RenderNodeAnimator extends Animator { } // Called by native - private static void callOnFinished(WeakReference<RenderNodeAnimator> weakThis) { - RenderNodeAnimator animator = weakThis.get(); - if (animator != null) { - animator.onFinished(); - } + private static void callOnFinished(RenderNodeAnimator animator) { + animator.onFinished(); } @Override @@ -439,22 +453,20 @@ public class RenderNodeAnimator extends Animator { throw new IllegalStateException("Cannot clone this animator"); } - private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis, - int property, float finalValue); - private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis, + private static native long nCreateAnimator(int property, float finalValue); + private static native long nCreateCanvasPropertyFloatAnimator( long canvasProperty, float finalValue); - private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis, + private static native long nCreateCanvasPropertyPaintAnimator( long canvasProperty, int paintField, float finalValue); - private static native long nCreateRevealAnimator(WeakReference<RenderNodeAnimator> weakThis, + private static native long nCreateRevealAnimator( int x, int y, float startRadius, float endRadius); private static native void nSetStartValue(long nativePtr, float startValue); private static native void nSetDuration(long nativePtr, long duration); private static native long nGetDuration(long nativePtr); private static native void nSetStartDelay(long nativePtr, long startDelay); - private static native long nGetStartDelay(long nativePtr); private static native void nSetInterpolator(long animPtr, long interpolatorPtr); - private static native void nStart(long animPtr); + private static native void nStart(long animPtr, RenderNodeAnimator finishListener); private static native void nEnd(long animPtr); } diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 3af214d..ca08ecc 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -277,7 +277,11 @@ public class ThreadedRenderer extends HardwareRenderer { final int saveCount = canvas.save(); canvas.translate(mInsetLeft, mInsetTop); callbacks.onHardwarePreDraw(canvas); + + canvas.insertReorderBarrier(); canvas.drawRenderNode(view.getDisplayList()); + canvas.insertInorderBarrier(); + callbacks.onHardwarePostDraw(canvas); canvas.restoreToCount(saveCount); mRootNodeNeedsUpdate = false; @@ -312,6 +316,20 @@ public class ThreadedRenderer extends HardwareRenderer { attachInfo.mIgnoreDirtyState = false; + // register animating rendernodes which started animating prior to renderer + // creation, which is typical for animators started prior to first draw + if (attachInfo.mPendingAnimatingRenderNodes != null) { + final int count = attachInfo.mPendingAnimatingRenderNodes.size(); + for (int i = 0; i < count; i++) { + registerAnimatingRenderNode( + attachInfo.mPendingAnimatingRenderNodes.get(i)); + } + attachInfo.mPendingAnimatingRenderNodes.clear(); + // We don't need this anymore as subsequent calls to + // ViewRootImpl#attachRenderNodeAnimator will go directly to us. + attachInfo.mPendingAnimatingRenderNodes = null; + } + int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos, recordDuration, view.getResources().getDisplayMetrics().density); if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) { @@ -370,6 +388,11 @@ public class ThreadedRenderer extends HardwareRenderer { } @Override + void registerAnimatingRenderNode(RenderNode animator) { + nRegisterAnimatingRenderNode(mRootNode.mNativeRenderNode, animator.mNativeRenderNode); + } + + @Override protected void finalize() throws Throwable { try { nDeleteProxy(mNativeProxy); @@ -466,6 +489,7 @@ public class ThreadedRenderer extends HardwareRenderer { private static native int nSyncAndDrawFrame(long nativeProxy, long frameTimeNanos, long recordDuration, float density); private static native void nDestroyCanvasAndSurface(long nativeProxy); + private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode); private static native void nInvokeFunctor(long functor, boolean waitForCompletion); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2d58ecf..fce6f0b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3565,7 +3565,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; - mRenderNode = RenderNode.create(getClass().getName()); + mRenderNode = RenderNode.create(getClass().getName(), this); if (!sCompatibilityDone && context != null) { final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; @@ -4161,7 +4161,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ View() { mResources = null; - mRenderNode = RenderNode.create(getClass().getName()); + mRenderNode = RenderNode.create(getClass().getName(), this); } private static SparseArray<String> getAttributeMap() { @@ -15183,9 +15183,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param renderNode Existing RenderNode, or {@code null} * @return A valid display list for the specified drawable */ - private static RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) { + private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) { if (renderNode == null) { - renderNode = RenderNode.create(drawable.getClass().getName()); + renderNode = RenderNode.create(drawable.getClass().getName(), this); } final Rect bounds = drawable.getBounds(); @@ -19911,6 +19911,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean mHardwareAccelerated; boolean mHardwareAccelerationRequested; HardwareRenderer mHardwareRenderer; + List<RenderNode> mPendingAnimatingRenderNodes; /** * The state of the display to which the window is attached, as reported @@ -20691,6 +20692,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mPosted) { return; } + final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; final long minEventIntevalMillis = ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); @@ -20699,7 +20701,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, run(); } else { postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); - mPosted = true; mPostedWithDelay = true; } } diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index bae0cfb..b73b9fa 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -430,6 +430,10 @@ public class ViewPropertyAnimator { } } mPendingAnimations.clear(); + mPendingSetupAction = null; + mPendingCleanupAction = null; + mPendingOnStartAction = null; + mPendingOnEndAction = null; mView.removeCallbacks(mAnimationStarter); if (mRTBackend != null) { mRTBackend.cancelAll(); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index bb469a3..49d925f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -333,6 +333,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mRemoved; private boolean mIsEmulator; + private boolean mIsCircularEmulator; + private final boolean mWindowIsRound; /** * Consistency verifier for debugging purposes. @@ -388,6 +390,8 @@ public final class ViewRootImpl implements ViewParent, mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); loadSystemProperties(); + mWindowIsRound = context.getResources().getBoolean( + com.android.internal.R.bool.config_windowIsRound); } public static void addFirstDrawHandler(Runnable callback) { @@ -671,6 +675,17 @@ public final class ViewRootImpl implements ViewParent, ThreadedRenderer.invokeFunctor(functor, waitForCompletion); } + public void registerAnimatingRenderNode(RenderNode animator) { + if (mAttachInfo.mHardwareRenderer != null) { + mAttachInfo.mHardwareRenderer.registerAnimatingRenderNode(animator); + } else { + if (mAttachInfo.mPendingAnimatingRenderNodes == null) { + mAttachInfo.mPendingAnimatingRenderNodes = new ArrayList<RenderNode>(); + } + mAttachInfo.mPendingAnimatingRenderNodes.add(animator); + } + } + private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) { mAttachInfo.mHardwareAccelerated = false; mAttachInfo.mHardwareAccelerationRequested = false; @@ -1183,14 +1198,7 @@ public final class ViewRootImpl implements ViewParent, void dispatchApplyInsets(View host) { mDispatchContentInsets.set(mAttachInfo.mContentInsets); mDispatchStableInsets.set(mAttachInfo.mStableInsets); - boolean isRound = false; - if ((mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0 - && mDisplay.getDisplayId() == 0) { - // we're fullscreen and not hosted in an ActivityView - isRound = (mIsEmulator && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false)) - || mContext.getResources().getBoolean( - com.android.internal.R.bool.config_windowIsRound); - } + final boolean isRound = (mIsEmulator && mIsCircularEmulator) || mWindowIsRound; host.dispatchApplyWindowInsets(new WindowInsets( mDispatchContentInsets, null /* windowDecorInsets */, mDispatchStableInsets, isRound)); @@ -2329,6 +2337,16 @@ public final class ViewRootImpl implements ViewParent, Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + // For whatever reason we didn't create a HardwareRenderer, end any + // hardware animations that are now dangling + if (mAttachInfo.mPendingAnimatingRenderNodes != null) { + final int count = mAttachInfo.mPendingAnimatingRenderNodes.size(); + for (int i = 0; i < count; i++) { + mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators(); + } + mAttachInfo.mPendingAnimatingRenderNodes.clear(); + } + if (mReportNextDraw) { mReportNextDraw = false; if (mAttachInfo.mHardwareRenderer != null) { @@ -5431,6 +5449,8 @@ public final class ViewRootImpl implements ViewParent, // detect emulator mIsEmulator = Build.HARDWARE.contains("goldfish"); + mIsCircularEmulator = + SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false); } }); } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 2c7ea3e..9b6f200 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1304,12 +1304,22 @@ public abstract class Window { public abstract int getVolumeControlStream(); /** + * Sets a {@link MediaController} to send media keys and volume changes to. + * If set, this should be preferred for all media keys and volume requests + * sent to this window. + * + * @param controller The controller for the session which should receive + * media keys and volume changes. * @see android.app.Activity#setMediaController(android.media.session.MediaController) */ public void setMediaController(MediaController controller) { } /** + * Gets the {@link MediaController} that was previously set. + * + * @return The controller which should receive events. + * @see #setMediaController(android.media.session.MediaController) * @see android.app.Activity#getMediaController() */ public MediaController getMediaController() { diff --git a/core/java/android/webkit/PermissionRequest.java b/core/java/android/webkit/PermissionRequest.java index 862e8c2..6ad639c 100644 --- a/core/java/android/webkit/PermissionRequest.java +++ b/core/java/android/webkit/PermissionRequest.java @@ -19,8 +19,10 @@ package android.webkit; import android.net.Uri; /** - * This interface defines a permission request and is used when web content - * requests access to protected resources. + * This class defines a permission request and is used when web content + * requests access to protected resources. The permission request related events + * are delivered via {@link WebChromeClient#onPermissionRequest} and + * {@link WebChromeClient#onPermissionRequestCanceled}. * * Either {@link #grant(String[]) grant()} or {@link #deny()} must be called in UI * thread to respond to the request. diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 35c9598..547acfa 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -452,14 +452,16 @@ public class WebChromeClient { */ public static abstract class FileChooserParams { /** Open single file. Requires that the file exists before allowing the user to pick it. */ - public static final int OPEN = 0; + public static final int MODE_OPEN = 0; /** Like Open but allows multiple files to be selected. */ - public static final int OPEN_MULTIPLE = 1; + public static final int MODE_OPEN_MULTIPLE = 1; /** Like Open but allows a folder to be selected. The implementation should enumerate - all files selected by this operation. */ - public static final int OPEN_FOLDER = 2; + all files selected by this operation. + This feature is not supported at the moment. + @hide */ + public static final int MODE_OPEN_FOLDER = 2; /** Allows picking a nonexistent file and saving it. */ - public static final int SAVE = 3; + public static final int MODE_SAVE = 3; /** * Returns a helper to simplify choosing and uploading files. The helper builds a default @@ -474,7 +476,8 @@ public class WebChromeClient { public abstract int getMode(); /** - * Returns an array of acceptable MIME types. The array will be empty if no + * Returns an array of acceptable MIME types. The returned MIME type + * could be partial such as audio/*. The array will be empty if no * acceptable types are specified. */ public abstract String[] getAcceptTypes(); @@ -494,9 +497,9 @@ public class WebChromeClient { public abstract CharSequence getTitle(); /** - * The file path of a default selection if specified, or null. + * The file name of a default selection if specified, or null. */ - public abstract String getDefaultFilename(); + public abstract String getFilenameHint(); }; /** diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 1b0cb3d..e1f19ee 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1624,10 +1624,18 @@ public class WebView extends AbsoluteLayout } /** - * Enable drawing the entire HTML document at a significant performance - * cost. Call this to enable drawing and capturing HTML content outside of - * the WebView's viewport. This should be called before any WebViews are - * created. + * For apps targeting the L release, WebView has a new default behavior that reduces + * memory footprint and increases performance by intelligently choosing + * the portion of the HTML document that needs to be drawn. These + * optimizations are transparent to the developers. However, under certain + * circumstances, an App developer may want to disable them: + * 1. When an app uses {@link #onDraw} to do own drawing and accesses portions + * of the page that is way outside the visible portion of the page. + * 2. When an app uses {@link #capturePicture} to capture a very large HTML document. + * Note that capturePicture is a deprecated API. + * + * Enabling drawing the entire HTML document has a significant performance + * cost. This method should be called before any WebViews are created. */ public static void enableSlowWholeDocumentDraw() { getFactory().getStatics().enableSlowWholeDocumentDraw(); diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index 7123b9a..ef8c006 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -19,6 +19,8 @@ package android.widget; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.util.SparseBooleanArray; @@ -645,6 +647,23 @@ public class ActionMenuPresenter extends BaseMenuPresenter super.onInitializeAccessibilityNodeInfo(info); info.setCanOpenPopup(true); } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + // Set up the hotspot bounds to be centered on the image. + final Drawable d = getDrawable(); + final Drawable bg = getBackground(); + if (d != null && bg != null) { + final Rect bounds = d.getBounds(); + final int height = bottom - top; + final int offset = (height - bounds.width()) / 2; + final int hotspotLeft = bounds.left - offset; + final int hotspotRight = bounds.right + offset; + bg.setHotspotBounds(hotspotLeft, 0, hotspotRight, height); + } + } } private class OverflowPopup extends MenuPopupHelper { diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index eb232fd..3b16aba 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -35,9 +35,8 @@ import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; - import com.android.internal.R; - +import java.lang.ref.WeakReference; /** * <p>An editable text view that shows completion suggestions automatically @@ -85,8 +84,8 @@ import com.android.internal.R; * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight - * @attr ref android.R.styleable#AutoCompleteTextView_dropDownVerticalOffset - * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHorizontalOffset + * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset + * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset */ public class AutoCompleteTextView extends EditText implements Filter.FilterListener { static final boolean DEBUG = false; @@ -130,7 +129,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } public AutoCompleteTextView(Context context, AttributeSet attrs) { - this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); + this(context, attrs, R.attr.autoCompleteTextViewStyle); } public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { @@ -141,23 +140,17 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mPopup = new ListPopupWindow(context, attrs, - com.android.internal.R.attr.autoCompleteTextViewStyle); + mPopup = new ListPopupWindow(context, attrs, defStyleAttr, defStyleRes); mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW); - final TypedArray a = context.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes); - mThreshold = a.getInt( - R.styleable.AutoCompleteTextView_completionThreshold, 2); + mThreshold = a.getInt(R.styleable.AutoCompleteTextView_completionThreshold, 2); mPopup.setListSelector(a.getDrawable(R.styleable.AutoCompleteTextView_dropDownSelector)); - mPopup.setVerticalOffset((int) - a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f)); - mPopup.setHorizontalOffset((int) - a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f)); - + // Get the anchor's id now, but the view won't be ready, so wait to actually get the // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later. // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return @@ -167,11 +160,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe // For dropdown width, the developer can specify a specific width, or MATCH_PARENT // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view). - mPopup.setWidth(a.getLayoutDimension( - R.styleable.AutoCompleteTextView_dropDownWidth, + mPopup.setWidth(a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth, ViewGroup.LayoutParams.WRAP_CONTENT)); - mPopup.setHeight(a.getLayoutDimension( - R.styleable.AutoCompleteTextView_dropDownHeight, + mPopup.setHeight(a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownHeight, ViewGroup.LayoutParams.WRAP_CONTENT)); mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView, @@ -373,6 +364,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * <p>Sets the vertical offset used for the auto-complete drop-down list.</p> * * @param offset the vertical offset + * + * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset */ public void setDropDownVerticalOffset(int offset) { mPopup.setVerticalOffset(offset); @@ -382,6 +375,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * <p>Gets the vertical offset used for the auto-complete drop-down list.</p> * * @return the vertical offset + * + * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset */ public int getDropDownVerticalOffset() { return mPopup.getVerticalOffset(); @@ -391,6 +386,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * <p>Sets the horizontal offset used for the auto-complete drop-down list.</p> * * @param offset the horizontal offset + * + * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset */ public void setDropDownHorizontalOffset(int offset) { mPopup.setHorizontalOffset(offset); @@ -400,6 +397,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * <p>Gets the horizontal offset used for the auto-complete drop-down list.</p> * * @return the horizontal offset + * + * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset */ public int getDropDownHorizontalOffset() { return mPopup.getHorizontalOffset(); @@ -629,7 +628,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe */ public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { if (mObserver == null) { - mObserver = new PopupDataSetObserver(); + mObserver = new PopupDataSetObserver(this); } else if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mObserver); } @@ -1255,25 +1254,44 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } } - private class PopupDataSetObserver extends DataSetObserver { + /** + * Static inner listener that keeps a WeakReference to the actual AutoCompleteTextView. + * <p> + * This way, if adapter has a longer life span than the View, we won't leak the View, instead + * we will just leak a small Observer with 1 field. + */ + private static class PopupDataSetObserver extends DataSetObserver { + private final WeakReference<AutoCompleteTextView> mViewReference; + + private PopupDataSetObserver(AutoCompleteTextView view) { + mViewReference = new WeakReference<AutoCompleteTextView>(view); + } + @Override public void onChanged() { - if (mAdapter != null) { + final AutoCompleteTextView textView = mViewReference.get(); + if (textView != null && textView.mAdapter != null) { // If the popup is not showing already, showing it will cause // the list of data set observers attached to the adapter to // change. We can't do it from here, because we are in the middle // of iterating through the list of observers. - post(new Runnable() { - public void run() { - final ListAdapter adapter = mAdapter; - if (adapter != null) { - // This will re-layout, thus resetting mDataChanged, so that the - // listView click listener stays responsive - updateDropDownForFilter(adapter.getCount()); - } - } - }); + textView.post(updateRunnable); } } + + private final Runnable updateRunnable = new Runnable() { + @Override + public void run() { + final AutoCompleteTextView textView = mViewReference.get(); + if (textView == null) { + return; + } + final ListAdapter adapter = textView.mAdapter; + if (adapter == null) { + return; + } + textView.updateDropDownForFilter(adapter.getCount()); + } + }; } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 29c8298..46b225d 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -147,7 +147,7 @@ public class Editor { boolean isDirty; public TextDisplayList(String name) { isDirty = true; - displayList = RenderNode.create(name); + displayList = RenderNode.create(name, null); } boolean needsRecord() { return isDirty || !displayList.isValid(); } } diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 6a514ba..3c186e3 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; +import android.content.res.TypedArray; import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -40,6 +41,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.AccelerateDecelerateInterpolator; +import com.android.internal.R; import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller; import java.util.Locale; @@ -208,6 +210,18 @@ public class ListPopupWindow { */ public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; + + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow, + defStyleAttr, defStyleRes); + mDropDownHorizontalOffset = a.getDimensionPixelOffset( + R.styleable.ListPopupWindow_dropDownHorizontalOffset, 0); + mDropDownVerticalOffset = a.getDimensionPixelOffset( + R.styleable.ListPopupWindow_dropDownVerticalOffset, 0); + if (mDropDownVerticalOffset != 0) { + mDropDownVerticalOffsetSet = true; + } + a.recycle(); + mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes); mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); // Set the default layout direction to match the default locale one diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 602f955..56bdb9b 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -176,7 +176,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback RemoteViewsAdapter adapter; final AppWidgetManager mgr = AppWidgetManager.getInstance(context); if ((adapter = mAdapter.get()) != null) { - mgr.unbindRemoteViewsService(context.getPackageName(), appWidgetId, intent); + mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent); } else { Slog.w(TAG, "unbind: adapter was null"); } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 9914800..98d52ff 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -49,14 +49,14 @@ import android.widget.PopupWindow.OnDismissListener; * * <p>See the <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide.</p> * - * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset * @attr ref android.R.styleable#Spinner_dropDownSelector - * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset * @attr ref android.R.styleable#Spinner_dropDownWidth * @attr ref android.R.styleable#Spinner_gravity * @attr ref android.R.styleable#Spinner_popupBackground * @attr ref android.R.styleable#Spinner_prompt * @attr ref android.R.styleable#Spinner_spinnerMode + * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset + * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset */ @Widget public class Spinner extends AbsSpinner implements OnClickListener { @@ -209,17 +209,6 @@ public class Spinner extends AbsSpinner implements OnClickListener { ViewGroup.LayoutParams.WRAP_CONTENT); popup.setBackgroundDrawable(a.getDrawable( com.android.internal.R.styleable.Spinner_popupBackground)); - final int verticalOffset = a.getDimensionPixelOffset( - com.android.internal.R.styleable.Spinner_dropDownVerticalOffset, 0); - if (verticalOffset != 0) { - popup.setVerticalOffset(verticalOffset); - } - - final int horizontalOffset = a.getDimensionPixelOffset( - com.android.internal.R.styleable.Spinner_dropDownHorizontalOffset, 0); - if (horizontalOffset != 0) { - popup.setHorizontalOffset(horizontalOffset); - } mPopup = popup; mForwardingListener = new ForwardingListener(this) { @@ -303,7 +292,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { * * @param pixels Vertical offset in pixels * - * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset + * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset */ public void setDropDownVerticalOffset(int pixels) { mPopup.setVerticalOffset(pixels); @@ -315,7 +304,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { * * @return Vertical offset in pixels * - * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset + * @attr ref android.R.styleable#ListPopupWindow_dropDownVerticalOffset */ public int getDropDownVerticalOffset() { return mPopup.getVerticalOffset(); @@ -327,7 +316,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { * * @param pixels Horizontal offset in pixels * - * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset + * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset */ public void setDropDownHorizontalOffset(int pixels) { mPopup.setHorizontalOffset(pixels); @@ -339,7 +328,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { * * @return Horizontal offset in pixels * - * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset + * @attr ref android.R.styleable#ListPopupWindow_dropDownHorizontalOffset */ public int getDropDownHorizontalOffset() { return mPopup.getHorizontalOffset(); diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index 818efaa..ece8aa4 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -244,6 +244,16 @@ public class Toolbar extends ViewGroup { // Set the default context, since setPopupTheme() may be a no-op. mPopupContext = mContext; setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0)); + + final Drawable navIcon = a.getDrawable(R.styleable.Toolbar_navigationIcon); + if (navIcon != null) { + setNavigationIcon(navIcon); + final CharSequence navDesc = a.getText( + R.styleable.Toolbar_navigationContentDescription); + if (!TextUtils.isEmpty(navDesc)) { + setNavigationContentDescription(navDesc); + } + } a.recycle(); } @@ -669,6 +679,8 @@ public class Toolbar extends ViewGroup { * as screen readers or tooltips. * * @return The navigation button's content description + * + * @attr ref android.R.styleable#Toolbar_navigationContentDescription */ @Nullable public CharSequence getNavigationContentDescription() { @@ -682,6 +694,8 @@ public class Toolbar extends ViewGroup { * * @param resId Resource ID of a content description string to set, or 0 to * clear the description + * + * @attr ref android.R.styleable#Toolbar_navigationContentDescription */ public void setNavigationContentDescription(int resId) { setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null); @@ -694,6 +708,8 @@ public class Toolbar extends ViewGroup { * * @param description Content description to set, or <code>null</code> to * clear the content description + * + * @attr ref android.R.styleable#Toolbar_navigationContentDescription */ public void setNavigationContentDescription(@Nullable CharSequence description) { if (!TextUtils.isEmpty(description)) { @@ -715,6 +731,8 @@ public class Toolbar extends ViewGroup { * tooltips.</p> * * @param resId Resource ID of a drawable to set + * + * @attr ref android.R.styleable#Toolbar_navigationIcon */ public void setNavigationIcon(int resId) { setNavigationIcon(getContext().getDrawable(resId)); @@ -731,6 +749,8 @@ public class Toolbar extends ViewGroup { * tooltips.</p> * * @param icon Drawable to set, may be null to clear the icon + * + * @attr ref android.R.styleable#Toolbar_navigationIcon */ public void setNavigationIcon(@Nullable Drawable icon) { if (icon != null) { @@ -751,6 +771,8 @@ public class Toolbar extends ViewGroup { * Return the current drawable used as the navigation icon. * * @return The navigation icon drawable + * + * @attr ref android.R.styleable#Toolbar_navigationIcon */ @Nullable public Drawable getNavigationIcon() { @@ -1316,6 +1338,8 @@ public class Toolbar extends ViewGroup { final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView; final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams(); final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams(); + final boolean titleHasWidth = layoutTitle && mTitleTextView.getMeasuredWidth() > 0 + || layoutSubtitle && mSubtitleTextView.getMeasuredWidth() > 0; switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: @@ -1343,7 +1367,7 @@ public class Toolbar extends ViewGroup { break; } if (isRtl) { - final int rd = mTitleMarginStart - collapsingMargins[1]; + final int rd = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[1]; right -= Math.max(0, rd); collapsingMargins[1] = Math.max(0, -rd); int titleRight = right; @@ -1366,9 +1390,11 @@ public class Toolbar extends ViewGroup { subtitleRight = subtitleRight - mTitleMarginEnd; titleTop = subtitleBottom + lp.bottomMargin; } - right = Math.min(titleRight, subtitleRight); + if (titleHasWidth) { + right = Math.min(titleRight, subtitleRight); + } } else { - final int ld = mTitleMarginStart - collapsingMargins[0]; + final int ld = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[0]; left += Math.max(0, ld); collapsingMargins[0] = Math.max(0, -ld); int titleLeft = left; @@ -1391,7 +1417,9 @@ public class Toolbar extends ViewGroup { subtitleLeft = subtitleRight + mTitleMarginEnd; titleTop = subtitleBottom + lp.bottomMargin; } - left = Math.max(titleLeft, subtitleLeft); + if (titleHasWidth) { + left = Math.max(titleLeft, subtitleLeft); + } } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 61b4567..b6e7353 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -23,7 +23,6 @@ import android.app.usage.UsageStatsManager; import android.os.AsyncTask; import android.provider.Settings; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.Slog; import android.widget.AbsListView; import android.widget.GridView; @@ -73,6 +72,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -100,7 +100,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private boolean mResolvingHome = false; private UsageStatsManager mUsm; - private ArrayMap<String, UsageStats> mStats; + private Map<String, UsageStats> mStats; private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14; private boolean mRegistered; diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index ee406bd..eae4427 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -993,6 +993,6 @@ public final class BatteryStatsHelper { } catch (RemoteException e) { Log.w(TAG, "RemoteException:", e); } - return null; + return new BatteryStatsImpl(); } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 299b0e6..69cdbff 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -2487,6 +2487,16 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryEventLocked(elapsedRealtime, uptime, code, name, uid); } + public void noteCurrentTimeChangedLocked() { + final long currentTime = System.currentTimeMillis(); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + recordCurrentTimeChangeLocked(currentTime, elapsedRealtime, uptime); + if (isStartClockTimeValid()) { + mStartClockTime = currentTime; + } + } + public void noteProcessStartLocked(String name, int uid) { uid = mapUid(uid); if (isOnBattery()) { @@ -4060,7 +4070,20 @@ public final class BatteryStatsImpl extends BatteryStats { } } + boolean isStartClockTimeValid() { + return mStartClockTime > 365*24*60*60*1000L; + } + @Override public long getStartClockTime() { + if (!isStartClockTimeValid()) { + // If the last clock time we got was very small, then we hadn't had a real + // time yet, so try to get it again. + mStartClockTime = System.currentTimeMillis(); + if (isStartClockTimeValid()) { + recordCurrentTimeChangeLocked(mStartClockTime, SystemClock.elapsedRealtime(), + SystemClock.uptimeMillis()); + } + } return mStartClockTime; } @@ -6799,6 +6822,16 @@ public final class BatteryStatsImpl extends BatteryStats { } } + private void recordCurrentTimeChangeLocked(final long currentTime, final long elapsedRealtimeMs, + final long uptimeMs) { + if (mRecordingHistory) { + mHistoryCur.currentTime = currentTime; + addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_CURRENT_TIME, + mHistoryCur); + mHistoryCur.currentTime = 0; + } + } + // This should probably be exposed in the API, though it's not critical private static final int BATTERY_PLUGGED_NONE = 0; @@ -8004,6 +8037,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void writeSummaryToParcel(Parcel out, boolean inclHistory) { pullPendingStateUpdatesLocked(); + // Pull the clock time. This may update the time and make a new history entry + // if we had originally pulled a time before the RTC was set. + long startClockTime = getStartClockTime(); + final long NOW_SYS = SystemClock.uptimeMillis() * 1000; final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000; @@ -8014,7 +8051,7 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mStartCount); out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED)); out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); - out.writeLong(mStartClockTime); + out.writeLong(startClockTime); out.writeString(mStartPlatformVersion); out.writeString(mEndPlatformVersion); mOnBatteryTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS); @@ -8453,6 +8490,10 @@ public final class BatteryStatsImpl extends BatteryStats { // Need to update with current kernel wake lock counts. pullPendingStateUpdatesLocked(); + // Pull the clock time. This may update the time and make a new history entry + // if we had originally pulled a time before the RTC was set. + long startClockTime = getStartClockTime(); + final long uSecUptime = SystemClock.uptimeMillis() * 1000; final long uSecRealtime = SystemClock.elapsedRealtime() * 1000; final long batteryRealtime = mOnBatteryTimeBase.getRealtime(uSecRealtime); @@ -8463,7 +8504,7 @@ public final class BatteryStatsImpl extends BatteryStats { writeHistory(out, true, false); out.writeInt(mStartCount); - out.writeLong(mStartClockTime); + out.writeLong(startClockTime); out.writeString(mStartPlatformVersion); out.writeString(mEndPlatformVersion); out.writeLong(mUptime); @@ -8588,6 +8629,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void prepareForDumpLocked() { // Need to retrieve current kernel wake lock stats before printing. pullPendingStateUpdatesLocked(); + + // Pull the clock time. This may update the time and make a new history entry + // if we had originally pulled a time before the RTC was set. + getStartClockTime(); } public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) { diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index 2967938..ba236f3 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -191,6 +191,20 @@ public class EditableInputConnection extends BaseInputConnection { public boolean requestUpdateCursorAnchorInfo(int cursorUpdateMode) { if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode); + // It is possible that any other bit is used as a valid flag in a future release. + // We should reject the entire request in such a case. + final int KNOWN_FLAGS_MASK = InputConnection.REQUEST_UPDATE_CURSOR_ANCHOR_INFO_IMMEDIATE | + InputConnection.REQUEST_UPDATE_CURSOR_ANCHOR_INFO_MONITOR; + final int unknownFlags = cursorUpdateMode & ~KNOWN_FLAGS_MASK; + if (unknownFlags != 0) { + if (DEBUG) { + Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags." + + " cursorUpdateMode=" + cursorUpdateMode + + " unknownFlags=" + unknownFlags); + } + return false; + } + if (mIMM == null) { // In this case, TYPE_CURSOR_ANCHOR_INFO is not handled. // TODO: Return some notification code rather than false to indicate method that diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index 1e9d722..1296831 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -455,6 +455,12 @@ static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz, renderNode->addAnimator(animator); } +static void android_view_RenderNode_endAllAnimators(JNIEnv* env, jobject clazz, + jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + renderNode->animators().endAllAnimators(); +} + #endif // USE_OPENGL_RENDERER // ---------------------------------------------------------------------------- @@ -534,6 +540,7 @@ static JNINativeMethod gMethods[] = { { "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY }, { "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator }, + { "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators }, #endif }; diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp index 767534f..85c2a09 100644 --- a/core/jni/android_view_RenderNodeAnimator.cpp +++ b/core/jni/android_view_RenderNodeAnimator.cpp @@ -51,28 +51,36 @@ public: // cyclic-references-of-doom. If you think "I know, just use NewWeakGlobalRef!" // then you end up with basically a PhantomReference, which is totally not // what we want. - AnimationListenerBridge(JNIEnv* env, jobject weakThis) { - mWeakThis = env->NewGlobalRef(weakThis); + AnimationListenerBridge(JNIEnv* env, jobject finishListener) { + mFinishListener = env->NewGlobalRef(finishListener); env->GetJavaVM(&mJvm); } virtual ~AnimationListenerBridge() { - JNIEnv* env = getEnv(mJvm); - env->DeleteGlobalRef(mWeakThis); - mWeakThis = NULL; + if (mFinishListener) { + onAnimationFinished(NULL); + } } virtual void onAnimationFinished(BaseRenderNodeAnimator*) { + LOG_ALWAYS_FATAL_IF(!mFinishListener, "Finished listener twice?"); JNIEnv* env = getEnv(mJvm); env->CallStaticVoidMethod( gRenderNodeAnimatorClassInfo.clazz, gRenderNodeAnimatorClassInfo.callOnFinished, - mWeakThis); + mFinishListener); + releaseJavaObject(); } private: + void releaseJavaObject() { + JNIEnv* env = getEnv(mJvm); + env->DeleteGlobalRef(mFinishListener); + mFinishListener = NULL; + } + JavaVM* mJvm; - jobject mWeakThis; + jobject mFinishListener; }; static inline RenderPropertyAnimator::RenderProperty toRenderProperty(jint property) { @@ -88,38 +96,33 @@ static inline CanvasPropertyPaintAnimator::PaintField toPaintField(jint field) { return static_cast<CanvasPropertyPaintAnimator::PaintField>(field); } -static jlong createAnimator(JNIEnv* env, jobject clazz, jobject weakThis, +static jlong createAnimator(JNIEnv* env, jobject clazz, jint propertyRaw, jfloat finalValue) { RenderPropertyAnimator::RenderProperty property = toRenderProperty(propertyRaw); - BaseRenderNodeAnimator* animator = new RenderPropertyAnimator(property, finalValue); - animator->setListener(new AnimationListenerBridge(env, weakThis)); return reinterpret_cast<jlong>( animator ); } static jlong createCanvasPropertyFloatAnimator(JNIEnv* env, jobject clazz, - jobject weakThis, jlong canvasPropertyPtr, jfloat finalValue) { + jlong canvasPropertyPtr, jfloat finalValue) { CanvasPropertyPrimitive* canvasProperty = reinterpret_cast<CanvasPropertyPrimitive*>(canvasPropertyPtr); BaseRenderNodeAnimator* animator = new CanvasPropertyPrimitiveAnimator(canvasProperty, finalValue); - animator->setListener(new AnimationListenerBridge(env, weakThis)); return reinterpret_cast<jlong>( animator ); } static jlong createCanvasPropertyPaintAnimator(JNIEnv* env, jobject clazz, - jobject weakThis, jlong canvasPropertyPtr, jint paintFieldRaw, + jlong canvasPropertyPtr, jint paintFieldRaw, jfloat finalValue) { CanvasPropertyPaint* canvasProperty = reinterpret_cast<CanvasPropertyPaint*>(canvasPropertyPtr); CanvasPropertyPaintAnimator::PaintField paintField = toPaintField(paintFieldRaw); BaseRenderNodeAnimator* animator = new CanvasPropertyPaintAnimator( canvasProperty, paintField, finalValue); - animator->setListener(new AnimationListenerBridge(env, weakThis)); return reinterpret_cast<jlong>( animator ); } -static jlong createRevealAnimator(JNIEnv* env, jobject clazz, jobject weakThis, +static jlong createRevealAnimator(JNIEnv* env, jobject clazz, jint centerX, jint centerY, jfloat startRadius, jfloat endRadius) { BaseRenderNodeAnimator* animator = new RevealAnimator(centerX, centerY, startRadius, endRadius); - animator->setListener(new AnimationListenerBridge(env, weakThis)); return reinterpret_cast<jlong>( animator ); } @@ -156,8 +159,11 @@ static void setInterpolator(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong animator->setInterpolator(interpolator); } -static void start(JNIEnv* env, jobject clazz, jlong animatorPtr) { +static void start(JNIEnv* env, jobject clazz, jlong animatorPtr, jobject finishListener) { BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + if (finishListener) { + animator->setListener(new AnimationListenerBridge(env, finishListener)); + } animator->start(); } @@ -176,17 +182,16 @@ const char* const kClassPathName = "android/view/RenderNodeAnimator"; static JNINativeMethod gMethods[] = { #ifdef USE_OPENGL_RENDERER - { "nCreateAnimator", "(Ljava/lang/ref/WeakReference;IF)J", (void*) createAnimator }, - { "nCreateCanvasPropertyFloatAnimator", "(Ljava/lang/ref/WeakReference;JF)J", (void*) createCanvasPropertyFloatAnimator }, - { "nCreateCanvasPropertyPaintAnimator", "(Ljava/lang/ref/WeakReference;JIF)J", (void*) createCanvasPropertyPaintAnimator }, - { "nCreateRevealAnimator", "(Ljava/lang/ref/WeakReference;IIFF)J", (void*) createRevealAnimator }, + { "nCreateAnimator", "(IF)J", (void*) createAnimator }, + { "nCreateCanvasPropertyFloatAnimator", "(JF)J", (void*) createCanvasPropertyFloatAnimator }, + { "nCreateCanvasPropertyPaintAnimator", "(JIF)J", (void*) createCanvasPropertyPaintAnimator }, + { "nCreateRevealAnimator", "(IIFF)J", (void*) createRevealAnimator }, { "nSetStartValue", "(JF)V", (void*) setStartValue }, { "nSetDuration", "(JJ)V", (void*) setDuration }, { "nGetDuration", "(J)J", (void*) getDuration }, { "nSetStartDelay", "(JJ)V", (void*) setStartDelay }, - { "nGetStartDelay", "(J)J", (void*) getStartDelay }, { "nSetInterpolator", "(JJ)V", (void*) setInterpolator }, - { "nStart", "(J)V", (void*) start }, + { "nStart", "(JLandroid/view/RenderNodeAnimator;)V", (void*) start }, { "nEnd", "(J)V", (void*) end }, #endif }; @@ -204,7 +209,7 @@ int register_android_view_RenderNodeAnimator(JNIEnv* env) { gRenderNodeAnimatorClassInfo.clazz = jclass(env->NewGlobalRef(gRenderNodeAnimatorClassInfo.clazz)); GET_STATIC_METHOD_ID(gRenderNodeAnimatorClassInfo.callOnFinished, gRenderNodeAnimatorClassInfo.clazz, - "callOnFinished", "(Ljava/lang/ref/WeakReference;)V"); + "callOnFinished", "(Landroid/view/RenderNodeAnimator;)V"); return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 4e3419a..99babac 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -33,6 +33,8 @@ #include "android_view_GraphicBuffer.h" #include <Animator.h> +#include <AnimationContext.h> +#include <IContextFactory.h> #include <RenderNode.h> #include <renderthread/CanvasContext.h> #include <renderthread/RenderProxy.h> @@ -103,7 +105,7 @@ private: std::string mMessage; }; -class RootRenderNode : public RenderNode, AnimationHook, ErrorHandler { +class RootRenderNode : public RenderNode, ErrorHandler { public: RootRenderNode(JNIEnv* env) : RenderNode() { mLooper = Looper::getForThread(); @@ -114,34 +116,84 @@ public: virtual ~RootRenderNode() {} - virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) { - OnFinishedEvent event(animator, listener); - mOnFinishedEvents.push_back(event); - } - virtual void onError(const std::string& message) { mLooper->sendMessage(new RenderingException(mVm, message), 0); } virtual void prepareTree(TreeInfo& info) { - info.animationHook = this; info.errorHandler = this; RenderNode::prepareTree(info); - info.animationHook = NULL; info.errorHandler = NULL; + } + + void sendMessage(const sp<MessageHandler>& handler) { + mLooper->sendMessage(handler, 0); + } + + void attachAnimatingNode(RenderNode* animatingNode) { + mPendingAnimatingRenderNodes.push_back(animatingNode); + } + + void doAttachAnimatingNodes(AnimationContext* context) { + for (size_t i = 0; i < mPendingAnimatingRenderNodes.size(); i++) { + RenderNode* node = mPendingAnimatingRenderNodes[i].get(); + context->addAnimatingRenderNode(*node); + } + mPendingAnimatingRenderNodes.clear(); + } + +private: + sp<Looper> mLooper; + JavaVM* mVm; + std::vector< sp<RenderNode> > mPendingAnimatingRenderNodes; +}; + +class AnimationContextBridge : public AnimationContext { +public: + AnimationContextBridge(renderthread::TimeLord& clock, RootRenderNode* rootNode) + : AnimationContext(clock), mRootNode(rootNode) { + } + + virtual ~AnimationContextBridge() {} + // Marks the start of a frame, which will update the frame time and move all + // next frame animations into the current frame + virtual void startFrame() { + mRootNode->doAttachAnimatingNodes(this); + AnimationContext::startFrame(); + } + + // Runs any animations still left in mCurrentFrameAnimations + virtual void runRemainingAnimations(TreeInfo& info) { + AnimationContext::runRemainingAnimations(info); // post all the finished stuff if (mOnFinishedEvents.size()) { sp<InvokeAnimationListeners> message = new InvokeAnimationListeners(mOnFinishedEvents); - mLooper->sendMessage(message, 0); + mRootNode->sendMessage(message); } } + virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) { + OnFinishedEvent event(animator, listener); + mOnFinishedEvents.push_back(event); + } + private: - sp<Looper> mLooper; + sp<RootRenderNode> mRootNode; std::vector<OnFinishedEvent> mOnFinishedEvents; - JavaVM* mVm; +}; + +class ContextFactoryImpl : public IContextFactory { +public: + ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {} + + virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) { + return new AnimationContextBridge(clock, mRootNode); + } + +private: + RootRenderNode* mRootNode; }; static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, @@ -168,8 +220,9 @@ static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, job static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz, jboolean translucent, jlong rootRenderNodePtr) { - RenderNode* rootRenderNode = reinterpret_cast<RenderNode*>(rootRenderNodePtr); - return (jlong) new RenderProxy(translucent, rootRenderNode); + RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr); + ContextFactoryImpl factory(rootRenderNode); + return (jlong) new RenderProxy(translucent, rootRenderNode, &factory); } static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz, @@ -244,6 +297,13 @@ static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, j proxy->destroyCanvasAndSurface(); } +static void android_view_ThreadedRenderer_registerAnimatingRenderNode(JNIEnv* env, jobject clazz, + jlong rootNodePtr, jlong animatingNodePtr) { + RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr); + RenderNode* animatingNode = reinterpret_cast<RenderNode*>(animatingNodePtr); + rootRenderNode->attachAnimatingNode(animatingNode); +} + static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz, jlong functorPtr, jboolean waitForCompletion) { Functor* functor = reinterpret_cast<Functor*>(functorPtr); @@ -371,6 +431,7 @@ static JNINativeMethod gMethods[] = { { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque }, { "nSyncAndDrawFrame", "(JJJF)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, { "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface }, + { "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode }, { "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor }, { "nCreateDisplayListLayer", "(JII)J", (void*) android_view_ThreadedRenderer_createDisplayListLayer }, { "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer }, diff --git a/core/res/res/drawable/btn_borderless_material.xml b/core/res/res/drawable/btn_borderless_material.xml index 016f0ff..08e1060 100644 --- a/core/res/res/drawable/btn_borderless_material.xml +++ b/core/res/res/drawable/btn_borderless_material.xml @@ -14,10 +14,8 @@ limitations under the License. --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:inset="@dimen/control_inset_material"> - <ripple android:color="?attr/colorControlHighlight"> - <item android:id="@id/mask" - android:drawable="@drawable/btn_default_mtrl_shape" /> - </ripple> -</inset> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?attr/colorControlHighlight"> + <item android:id="@id/mask" + android:drawable="@drawable/btn_default_mtrl_shape" /> +</ripple> diff --git a/core/res/res/drawable/btn_default_material.xml b/core/res/res/drawable/btn_default_material.xml index d00a348..ed2b5aa 100644 --- a/core/res/res/drawable/btn_default_material.xml +++ b/core/res/res/drawable/btn_default_material.xml @@ -14,9 +14,7 @@ limitations under the License. --> -<inset xmlns:android="http://schemas.android.com/apk/res/android" - android:inset="@dimen/control_inset_material"> - <ripple android:color="?attr/colorControlHighlight"> - <item android:drawable="@drawable/btn_default_mtrl_shape" /> - </ripple> -</inset> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?attr/colorControlHighlight"> + <item android:drawable="@drawable/btn_default_mtrl_shape" /> +</ripple> diff --git a/core/res/res/drawable/btn_default_mtrl_shape.xml b/core/res/res/drawable/btn_default_mtrl_shape.xml index 9235c76..6d0f7f8 100644 --- a/core/res/res/drawable/btn_default_mtrl_shape.xml +++ b/core/res/res/drawable/btn_default_mtrl_shape.xml @@ -15,12 +15,18 @@ --> <!-- Used as the canonical button shape. --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <corners android:radius="@dimen/control_corner_material" /> - <solid android:color="?attr/colorButtonNormal" /> - <padding android:top="@dimen/control_padding_material" - android:bottom="@dimen/control_padding_material" - android:left="@dimen/control_padding_material" - android:right="@dimen/control_padding_material" /> -</shape> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetLeft="@dimen/button_inset_horizontal_material" + android:insetTop="@dimen/button_inset_vertical_material" + android:insetRight="@dimen/button_inset_horizontal_material" + android:insetBottom="@dimen/button_inset_vertical_material"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/control_corner_material" /> + <solid android:color="?attr/colorButtonNormal" /> + <padding android:left="@dimen/button_padding_horizontal_material" + android:top="@dimen/button_padding_vertical_material" + android:right="@dimen/button_padding_horizontal_material" + android:bottom="@dimen/button_padding_vertical_material" /> + </shape> +</inset> diff --git a/core/res/res/drawable/btn_toggle_material.xml b/core/res/res/drawable/btn_toggle_material.xml index 9726782..f91d4cc 100644 --- a/core/res/res/drawable/btn_toggle_material.xml +++ b/core/res/res/drawable/btn_toggle_material.xml @@ -15,7 +15,10 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" - android:inset="@dimen/control_inset_material"> + android:insetLeft="@dimen/button_inset_horizontal_material" + android:insetTop="@dimen/button_inset_vertical_material" + android:insetRight="@dimen/button_inset_horizontal_material" + android:insetBottom="@dimen/button_inset_vertical_material"> <layer-list android:paddingMode="stack"> <item> <ripple android:color="?attr/colorControlHighlight"> @@ -25,10 +28,10 @@ <corners android:topLeftRadius="@dimen/control_corner_material" android:topRightRadius="@dimen/control_corner_material"/> <solid android:color="?attr/colorButtonNormal" /> - <padding android:top="@dimen/control_padding_material" - android:bottom="@dimen/control_padding_material" - android:left="@dimen/control_padding_material" - android:right="@dimen/control_padding_material" /> + <padding android:left="@dimen/button_padding_horizontal_material" + android:top="@dimen/button_padding_vertical_material" + android:right="@dimen/button_padding_horizontal_material" + android:bottom="@dimen/button_padding_vertical_material" /> </shape> </item> </ripple> diff --git a/core/res/res/drawable/progress_horizontal_material.xml b/core/res/res/drawable/progress_horizontal_material.xml index d7440a9..7a1079c 100644 --- a/core/res/res/drawable/progress_horizontal_material.xml +++ b/core/res/res/drawable/progress_horizontal_material.xml @@ -17,7 +17,8 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@id/background"> <nine-patch android:src="@drawable/progress_mtrl_alpha" - android:tint="?attr/colorControlNormal" /> + android:tint="?attr/colorControlNormal" + android:alpha="0.5" /> </item> <item android:id="@id/secondaryProgress"> <scale android:scaleWidth="100%"> diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml index f264b7b..ef916ed 100644 --- a/core/res/res/layout/notification_template_material_big_base.xml +++ b/core/res/res/layout/notification_template_material_big_base.xml @@ -45,7 +45,7 @@ android:gravity="top" > <TextView android:id="@+id/big_text" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" + android:textAppearance="@style/TextAppearance.Material.Notification" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" @@ -53,8 +53,8 @@ android:visibility="gone" /> <ImageView android:id="@+id/profile_badge_large_template" - android:layout_width="20dp" - android:layout_height="20dp" + android:layout_width="@dimen/notification_badge_size" + android:layout_height="@dimen/notification_badge_size" android:layout_weight="0" android:layout_marginStart="4dp" android:scaleType="fitCenter" diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml index f8e1986..3c44141 100644 --- a/core/res/res/layout/notification_template_material_big_media.xml +++ b/core/res/res/layout/notification_template_material_big_media.xml @@ -49,7 +49,7 @@ android:orientation="horizontal" > <TextView android:id="@+id/title" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Title" + android:textAppearance="@style/TextAppearance.Material.Notification.Title" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" @@ -73,7 +73,7 @@ /> </LinearLayout> <TextView android:id="@+id/text2" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Line2" + android:textAppearance="@style/TextAppearance.Material.Notification.Line2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="-2dp" @@ -86,7 +86,7 @@ android:visibility="gone" /> <TextView android:id="@+id/big_text" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" + android:textAppearance="@style/TextAppearance.Material.Notification" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="8dp" @@ -104,7 +104,7 @@ android:gravity="center_vertical" > <TextView android:id="@+id/text" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" + android:textAppearance="@style/TextAppearance.Material.Notification" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" @@ -114,7 +114,7 @@ android:fadingEdge="horizontal" /> <TextView android:id="@+id/info" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Info" + android:textAppearance="@style/TextAppearance.Material.Notification.Info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" @@ -146,7 +146,7 @@ android:layout_height="6dp" android:layout_gravity="top" android:visibility="gone" - style="@style/Widget.StatusBar.Material.ProgressBar.Media" + style="@style/Widget.Material.Notification.ProgressBar.Media" /> </FrameLayout> </LinearLayout> diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml index d9120f6..3415814 100644 --- a/core/res/res/layout/notification_template_material_big_text.xml +++ b/core/res/res/layout/notification_template_material_big_text.xml @@ -39,14 +39,15 @@ <include layout="@layout/notification_template_part_line2" /> <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="0dp" android:layout_marginEnd="8dp" android:layout_marginBottom="10dp" android:orientation="horizontal" android:gravity="top" + android:layout_weight="1" > <TextView android:id="@+id/big_text" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" + android:textAppearance="@style/TextAppearance.Material.Notification" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" @@ -54,8 +55,8 @@ android:visibility="gone" /> <ImageView android:id="@+id/profile_badge_large_template" - android:layout_width="20dp" - android:layout_height="20dp" + android:layout_width="@dimen/notification_badge_size" + android:layout_height="@dimen/notification_badge_size" android:layout_weight="0" android:layout_marginStart="4dp" android:scaleType="fitCenter" diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml index 38b3ae2..2382d18 100644 --- a/core/res/res/layout/notification_template_material_inbox.xml +++ b/core/res/res/layout/notification_template_material_inbox.xml @@ -37,95 +37,27 @@ > <include layout="@layout/notification_template_part_line1" /> <include layout="@layout/notification_template_part_line2" /> + + <!-- We can't have another vertical linear layout here with weight != 0 so this forces us to + put the badge on the first line. --> <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_height="0dp" android:orientation="horizontal" - android:gravity="top" > - <LinearLayout + <TextView android:id="@+id/inbox_text0" + android:textAppearance="@style/TextAppearance.Material.Notification" android:layout_width="0dp" - android:layout_weight="1" android:layout_height="wrap_content" - android:orientation="vertical" - > - <TextView android:id="@+id/inbox_text0" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" - android:layout_width="match_parent" - android:layout_height="0dp" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_weight="1" - /> - <TextView android:id="@+id/inbox_text1" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" - android:layout_width="match_parent" - android:layout_height="0dp" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_weight="1" - /> - <TextView android:id="@+id/inbox_text2" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" - android:layout_width="match_parent" - android:layout_height="0dp" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_weight="1" - /> - <TextView android:id="@+id/inbox_text3" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" - android:layout_width="match_parent" - android:layout_height="0dp" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_weight="1" - /> - <TextView android:id="@+id/inbox_text4" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" - android:layout_width="match_parent" - android:layout_height="0dp" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_weight="1" - /> - <TextView android:id="@+id/inbox_text5" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" - android:layout_width="match_parent" - android:layout_height="0dp" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_weight="1" - /> - <TextView android:id="@+id/inbox_text6" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" - android:layout_width="match_parent" - android:layout_height="0dp" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_weight="1" - /> - <TextView android:id="@+id/inbox_more" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" - android:layout_width="match_parent" - android:layout_height="0dp" - android:singleLine="true" - android:ellipsize="end" - android:visibility="gone" - android:layout_weight="1" - android:text="@android:string/ellipsis" - /> - </LinearLayout> + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> <ImageView android:id="@+id/profile_badge_large_template" - android:layout_width="20dp" - android:layout_height="20dp" + android:layout_width="@dimen/notification_badge_size" + android:layout_height="@dimen/notification_badge_size" android:layout_weight="0" android:layout_marginStart="4dp" android:layout_marginEnd="8dp" @@ -133,6 +65,77 @@ android:visibility="gone" /> </LinearLayout> + <TextView android:id="@+id/inbox_text1" + android:textAppearance="@style/TextAppearance.Material.Notification" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginEnd="8dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text2" + android:textAppearance="@style/TextAppearance.Material.Notification" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginEnd="8dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text3" + android:textAppearance="@style/TextAppearance.Material.Notification" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginEnd="8dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text4" + android:textAppearance="@style/TextAppearance.Material.Notification" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginEnd="8dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text5" + android:textAppearance="@style/TextAppearance.Material.Notification" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginEnd="8dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_text6" + android:textAppearance="@style/TextAppearance.Material.Notification" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginEnd="8dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + /> + <TextView android:id="@+id/inbox_more" + android:textAppearance="@style/TextAppearance.Material.Notification" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginEnd="8dp" + android:singleLine="true" + android:ellipsize="end" + android:visibility="gone" + android:layout_weight="1" + android:text="@android:string/ellipsis" + /> <FrameLayout android:id="@+id/inbox_end_pad" android:layout_width="match_parent" diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml index c2fc006..db24c1f 100644 --- a/core/res/res/layout/notification_template_material_media.xml +++ b/core/res/res/layout/notification_template_material_media.xml @@ -50,7 +50,7 @@ android:orientation="horizontal" > <TextView android:id="@+id/title" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Title" + android:textAppearance="@style/TextAppearance.Material.Notification.Title" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" @@ -74,7 +74,7 @@ /> </LinearLayout> <TextView android:id="@+id/text2" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Line2" + android:textAppearance="@style/TextAppearance.Material.Notification.Line2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="-2dp" @@ -91,7 +91,7 @@ android:layout_height="12dp" android:layout_marginStart="8dp" android:visibility="gone" - style="@style/Widget.StatusBar.Material.ProgressBar" + style="@style/Widget.Material.Notification.ProgressBar" /> <LinearLayout android:id="@+id/line3" @@ -102,7 +102,7 @@ android:layout_marginStart="8dp" > <TextView android:id="@+id/text" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" + android:textAppearance="@style/TextAppearance.Material.Notification" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" @@ -112,7 +112,7 @@ android:fadingEdge="horizontal" /> <TextView android:id="@+id/info" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Info" + android:textAppearance="@style/TextAppearance.Material.Notification.Info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/core/res/res/layout/notification_template_part_chronometer.xml b/core/res/res/layout/notification_template_part_chronometer.xml index 87dfe1f..1f0430e 100644 --- a/core/res/res/layout/notification_template_part_chronometer.xml +++ b/core/res/res/layout/notification_template_part_chronometer.xml @@ -15,7 +15,7 @@ --> <Chronometer android:id="@+id/chronometer" xmlns:android="http://schemas.android.com/apk/res/android" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Time" + android:textAppearance="@style/TextAppearance.Material.Notification.Time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/core/res/res/layout/notification_template_part_line1.xml b/core/res/res/layout/notification_template_part_line1.xml index c6ea6bf..7de4089 100644 --- a/core/res/res/layout/notification_template_part_line1.xml +++ b/core/res/res/layout/notification_template_part_line1.xml @@ -23,7 +23,7 @@ android:orientation="horizontal" > <TextView android:id="@+id/title" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Title" + android:textAppearance="@style/TextAppearance.Material.Notification.Title" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" diff --git a/core/res/res/layout/notification_template_part_line2.xml b/core/res/res/layout/notification_template_part_line2.xml index 1f95150..7e99c5e 100644 --- a/core/res/res/layout/notification_template_part_line2.xml +++ b/core/res/res/layout/notification_template_part_line2.xml @@ -20,14 +20,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginEnd="8dp" - android:visibility="gone" - android:layout_weight="0" android:orientation="horizontal" android:gravity="center_vertical" > <TextView android:id="@+id/text2" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Line2" + android:textAppearance="@style/TextAppearance.Material.Notification.Line2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="-1dp" @@ -39,8 +37,8 @@ android:layout_weight="1" /> <ImageView android:id="@+id/profile_badge_line2" - android:layout_width="20dp" - android:layout_height="20dp" + android:layout_width="@dimen/notification_badge_size" + android:layout_height="@dimen/notification_badge_size" android:layout_weight="0" android:layout_marginStart="4dp" android:scaleType="fitCenter" diff --git a/core/res/res/layout/notification_template_part_line3.xml b/core/res/res/layout/notification_template_part_line3.xml index 06de2a5..6c043a0 100644 --- a/core/res/res/layout/notification_template_part_line3.xml +++ b/core/res/res/layout/notification_template_part_line3.xml @@ -24,7 +24,7 @@ android:gravity="center_vertical" > <TextView android:id="@+id/text" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent" + android:textAppearance="@style/TextAppearance.Material.Notification" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" @@ -34,7 +34,7 @@ android:fadingEdge="horizontal" /> <TextView android:id="@+id/info" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Info" + android:textAppearance="@style/TextAppearance.Material.Notification.Info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" @@ -44,8 +44,8 @@ android:paddingStart="8dp" /> <ImageView android:id="@+id/profile_badge_line3" - android:layout_width="20dp" - android:layout_height="20dp" + android:layout_width="@dimen/notification_badge_size" + android:layout_height="@dimen/notification_badge_size" android:layout_gravity="center" android:layout_weight="0" android:layout_marginStart="4dp" diff --git a/core/res/res/layout/notification_template_part_time.xml b/core/res/res/layout/notification_template_part_time.xml index 5982c48..37c7ebe 100644 --- a/core/res/res/layout/notification_template_part_time.xml +++ b/core/res/res/layout/notification_template_part_time.xml @@ -15,7 +15,7 @@ --> <DateTimeView android:id="@+id/time" xmlns:android="http://schemas.android.com/apk/res/android" - android:textAppearance="@style/TextAppearance.StatusBar.Material.EventContent.Time" + android:textAppearance="@style/TextAppearance.Material.Notification.Time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/core/res/res/values-television/dimens.xml b/core/res/res/values-television/dimens.xml index 8266642..69c3da5 100644 --- a/core/res/res/values-television/dimens.xml +++ b/core/res/res/values-television/dimens.xml @@ -16,8 +16,8 @@ <resources> <!-- Lighting and shadow properties --> - <dimen name="light_y">-300dp</dimen> - <item type="dimen" format="float" name="ambient_shadow_alpha">0.4</item> - <item type="dimen" format="float" name="spot_shadow_alpha">0.4</item> + <dimen name="light_y">-400dp</dimen> + <item type="dimen" format="float" name="ambient_shadow_alpha">0.06</item> + <item type="dimen" format="float" name="spot_shadow_alpha">0.3</item> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index d8b129c..a798d2e 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4172,10 +4172,6 @@ <attr name="completionThreshold" format="integer" min="1" /> <!-- Selector in a drop down list. --> <attr name="dropDownSelector" format="reference|color" /> - <!-- Amount of pixels by which the drop down should be offset vertically. --> - <attr name="dropDownVerticalOffset" format="dimension" /> - <!-- Amount of pixels by which the drop down should be offset horizontally. --> - <attr name="dropDownHorizontalOffset" format="dimension" /> <!-- View to anchor the auto-complete dropdown to. If not specified, the text view itself is used. --> <attr name="dropDownAnchor" format="reference" /> @@ -4223,6 +4219,12 @@ <!-- Whether the popup window should overlap its anchor view. --> <attr name="overlapAnchor" format="boolean" /> </declare-styleable> + <declare-styleable name="ListPopupWindow"> + <!-- Amount of pixels by which the drop down should be offset vertically. --> + <attr name="dropDownVerticalOffset" format="dimension" /> + <!-- Amount of pixels by which the drop down should be offset horizontally. --> + <attr name="dropDownHorizontalOffset" format="dimension" /> + </declare-styleable> <declare-styleable name="ViewAnimator"> <!-- Identifier for the animation to use when a view is shown. --> <attr name="inAnimation" format="reference" /> @@ -4281,12 +4283,6 @@ <attr name="popupBackground" /> <!-- Window elevation to use for the dropdown in spinnerMode="dropdown". --> <attr name="popupElevation" /> - <!-- Vertical offset from the spinner widget for positioning the dropdown in - spinnerMode="dropdown". --> - <attr name="dropDownVerticalOffset" /> - <!-- Horizontal offset from the spinner widget for positioning the dropdown - in spinnerMode="dropdown". --> - <attr name="dropDownHorizontalOffset" /> <!-- Width of the dropdown in spinnerMode="dropdown". --> <attr name="dropDownWidth" /> <!-- Reference to a layout to use for displaying a prompt in the dropdown for @@ -7322,6 +7318,12 @@ <!-- Reference to a theme that should be used to inflate popups shown by widgets in the toolbar. --> <attr name="popupTheme" format="reference" /> + <!-- Icon drawable to use for the navigation button located at + the start of the toolbar. --> + <attr name="navigationIcon" format="reference" /> + <!-- Text to set as the content description for the navigation button + located at the start of the toolbar. --> + <attr name="navigationContentDescription" format="string" /> </declare-styleable> <declare-styleable name="Toolbar_LayoutParams"> @@ -7348,14 +7350,15 @@ <!-- Component name of an activity that allows the user to modify the settings for this service. --> <attr name="settingsActivity" /> - <!-- Reference to an XML document that describes TV content rating. --> - <attr name="tvContentRatingDescription" format="reference" /> </declare-styleable> + <!-- @removed --> + <attr name="__removed1" format="reference" /> + <!-- Attributes that can be used with <code>rating-system-definition</code> tags inside of the - XML resource that describes TV content rating of a - {@link android.media.tv.TvInputService}, which is referenced from - {@link android.R.attr#tvContentRatingDescription}. --> + XML resource that describes TV content rating of a {@link android.media.tv.TvInputService}, + which is referenced from its + {@link android.media.tv.TvInputManager#META_DATA_CONTENT_RATING_SYSTEMS}. --> <declare-styleable name="RatingSystemDefinition"> <!-- The unique name of the content rating system. --> <attr name="name" /> @@ -7363,13 +7366,15 @@ <attr name="title" /> <!-- The short description of the content rating system. --> <attr name="description" /> - <!-- The country associated with the content rating system. --> + <!-- The country code associated with the content rating system, which consists of two + uppercase letters that conform to the ISO 3166 standard. --> <attr name="country" format="string" /> </declare-styleable> <!-- Attributes that can be used with <code>rating-definition</code> tags inside of the XML resource that describes TV content rating of a {@link android.media.tv.TvInputService}, - which is referenced from {@link android.R.attr#tvContentRatingDescription}. --> + which is referenced from its + {@link android.media.tv.TvInputManager#META_DATA_CONTENT_RATING_SYSTEMS}. --> <declare-styleable name="RatingDefinition"> <!-- The unique name of the content rating. --> <attr name="name" /> @@ -7379,7 +7384,7 @@ <attr name="description" /> <!-- The age associated with the content rating. The content of this rating is suitable for people of this age or above. --> - <attr name="ageHint" format="integer" /> + <attr name="contentAgeHint" format="integer" /> </declare-styleable> <declare-styleable name="ResolverDrawerLayout"> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 4e3abb9..77b451f 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -236,6 +236,9 @@ <!-- Padding for notification icon when drawn with circle around it --> <dimen name="notification_large_icon_circle_padding">11dp</dimen> + <!-- Size of the profile badge for notifications --> + <dimen name="notification_badge_size">16dp</dimen> + <!-- Keyguard dimensions --> <!-- TEMP --> <dimen name="kg_security_panel_height">600dp</dimen> diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml index 972ae5e..f5c9299 100644 --- a/core/res/res/values/dimens_material.xml +++ b/core/res/res/values/dimens_material.xml @@ -33,7 +33,6 @@ <dimen name="action_button_min_width_material">48dp</dimen> <dimen name="action_button_min_height_material">48dp</dimen> - <dimen name="action_overflow_min_width_material">36dp</dimen> <dimen name="text_size_display_4_material">112sp</dimen> <dimen name="text_size_display_3_material">56sp</dimen> @@ -64,6 +63,13 @@ <dimen name="button_elevation_material">1dp</dimen> <!-- Z translation to apply when button is pressed --> <dimen name="button_pressed_z_material">2dp</dimen> + <!-- Default insets (outer padding) around buttons --> + <dimen name="button_inset_vertical_material">6dp</dimen> + <dimen name="button_inset_horizontal_material">@dimen/control_inset_material</dimen> + <!-- Default inner padding within buttons --> + <dimen name="button_padding_vertical_material">@dimen/control_padding_material</dimen> + <dimen name="button_padding_horizontal_material">8dp</dimen> + <!-- Default insets (outer padding) around controls --> <dimen name="control_inset_material">4dp</dimen> <!-- Default inner padding within controls --> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index 4c59f73..bd24f3e 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -87,4 +87,6 @@ <item type="id" name="transitionPosition" /> <item type="id" name="transitionTransform" /> <item type="id" name="parentMatrix" /> + <item type="id" name="statusBarBackground" /> + <item type="id" name="navigationBarBackground" /> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 82125fe..5e76a87 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2266,26 +2266,30 @@ <public type="attr" name="windowReenterTransition" /> <public type="attr" name="windowSharedElementReturnTransition" /> <public type="attr" name="windowSharedElementReenterTransition" /> - <public type="attr" name="tvContentRatingDescription"/> + <public type="attr" name="__removed1" /> <public type="attr" name="datePickerMode"/> <public type="attr" name="timePickerMode"/> <public type="attr" name="inset" /> <public type="attr" name="letterSpacing" /> <public type="attr" name="fontFeatureSettings" /> <public type="attr" name="outlineProvider" /> - <public type="attr" name="ageHint" /> + <public type="attr" name="contentAgeHint" /> <public type="attr" name="country" /> <public type="attr" name="windowSharedElementsUseOverlay" /> <public type="attr" name="reparent" /> <public type="attr" name="reparentWithOverlay" /> <public type="attr" name="ambientShadowAlpha" /> <public type="attr" name="spotShadowAlpha" /> + <public type="attr" name="navigationIcon" /> + <public type="attr" name="navigationContentDescription" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> <public-padding type="id" name="l_resource_pad" end="0x01020040" /> <public type="id" name="mask" /> + <public type="id" name="statusBarBackground" /> + <public type="id" name="navigationBarBackground" /> <public-padding type="style" name="l_resource_pad" end="0x01030200" /> @@ -2527,20 +2531,25 @@ <public type="style" name="Theme.Leanback.FormWizard"/> - <public type="style" name="TextAppearance.StatusBar.Material" /> - <public type="style" name="TextAppearance.StatusBar.Material.EventContent" /> - <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Title" /> - <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Line2" /> - <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Info" /> - <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Time" /> - <public type="style" name="TextAppearance.StatusBar.Material.EventContent.Emphasis" /> + <public type="style" name="__removed" /> + <public type="style" name="TextAppearance.Material.Notification" /> + <public type="style" name="TextAppearance.Material.Notification.Title" /> + <public type="style" name="TextAppearance.Material.Notification.Line2" /> + <public type="style" name="TextAppearance.Material.Notification.Info" /> + <public type="style" name="TextAppearance.Material.Notification.Time" /> + <public type="style" name="TextAppearance.Material.Notification.Emphasis" /> - <public type="style" name="Widget.Material.Spinner.Form" /> - <public type="style" name="Widget.Material.Light.Spinner.Form" /> + <public type="style" name="Widget.Material.Spinner.Underlined" /> + <public type="style" name="Widget.Material.Light.Spinner.Underlined" /> <public type="style" name="TextAppearance.Material.Widget.Toolbar.Title" /> <public type="style" name="TextAppearance.Material.Widget.Toolbar.Subtitle" /> + <public type="style" name="Theme.Material.Dialog.Alert" /> + <public type="style" name="Theme.Material.Dialog.Presentation" /> + <public type="style" name="Theme.Material.Light.Dialog.Alert" /> + <public type="style" name="Theme.Material.Light.Dialog.Presentation" /> + <public-padding type="string" name="l_resource_pad" end="0x01040030" /> <public type="string" name="config_webSettingsDefaultTextEncoding" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d6224da..f1ec5d2 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3642,9 +3642,9 @@ <!-- The message text for the SMS short code confirmation dialog. [CHAR LIMIT=NONE] --> <string name="sms_short_code_confirm_message"><b><xliff:g id="app_name">%1$s</xliff:g></b> would like to send a message to <b><xliff:g id="dest_address">%2$s</xliff:g></b>.</string> <!-- Message details for the SMS short code confirmation dialog (possible premium short code). [CHAR LIMIT=NONE] --> - <string name="sms_short_code_details">This <font fgcolor="#ffffb060">may cause charges</font> on your mobile account.</string> + <string name="sms_short_code_details">This <b>may cause charges</b> on your mobile account.</string> <!-- Message details for the SMS short code confirmation dialog (premium short code). [CHAR LIMIT=NONE] --> - <string name="sms_premium_short_code_details"><font fgcolor="#ffffb060">This will cause charges on your mobile account.</font></string> + <string name="sms_premium_short_code_details"><b>This will cause charges on your mobile account.</b></string> <!-- Text of the approval button for the SMS short code confirmation dialog. [CHAR LIMIT=30] --> <string name="sms_short_code_confirm_allow">Send</string> <!-- Text of the cancel button for the SMS short code confirmation dialog. [CHAR LIMIT=30] --> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index fb70d6b..e783cd6 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -306,9 +306,9 @@ please see styles_device_defaults.xml. </style> <style name="TextAppearance.Material.Widget.TextView.PopupMenu" parent="TextAppearance.Material.Menu" /> - <style name="TextAppearance.Material.Widget.TextView.SpinnerItem" /> + <style name="TextAppearance.Material.Widget.TextView.SpinnerItem" parent="TextAppearance.Material.Menu" /> - <style name="TextAppearance.Material.Widget.DropDownItem"> + <style name="TextAppearance.Material.Widget.DropDownItem" parent="TextAppearance.Material.Menu"> <item name="textColor">?attr/textColorPrimaryDisableOnly</item> </style> @@ -410,37 +410,35 @@ please see styles_device_defaults.xml. <item name="textSize">@dimen/datepicker_year_label_text_size</item> </style> - <style name="TextAppearance.StatusBar.Material" /> - - <style name="TextAppearance.StatusBar.Material.EventContent"> + <style name="TextAppearance.Material.Notification"> <item name="textColor">@color/secondary_text_material_light</item> <item name="textSize">@dimen/notification_text_size</item> </style> - <style name="TextAppearance.StatusBar.Material.EventContent.Title"> + <style name="TextAppearance.Material.Notification.Title"> <item name="textColor">@color/primary_text_default_material_light</item> <item name="textSize">@dimen/notification_title_text_size</item> </style> - <style name="TextAppearance.StatusBar.Material.EventContent.Line2"> + <style name="TextAppearance.Material.Notification.Line2"> <item name="textSize">@dimen/notification_subtext_size</item> </style> - <style name="TextAppearance.StatusBar.Material.EventContent.Info"> + <style name="TextAppearance.Material.Notification.Info"> <item name="textSize">@dimen/notification_subtext_size</item> </style> - <style name="TextAppearance.StatusBar.Material.EventContent.Time"> + <style name="TextAppearance.Material.Notification.Time"> <item name="textSize">@dimen/notification_subtext_size</item> </style> - <style name="TextAppearance.StatusBar.Material.EventContent.Emphasis"> + <style name="TextAppearance.Material.Notification.Emphasis"> <item name="textColor">#66000000</item> </style> - <style name="Widget.StatusBar.Material.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" /> + <style name="Widget.Material.Notification.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" /> - <style name="Widget.StatusBar.Material.ProgressBar.Media"> + <style name="Widget.Material.Notification.ProgressBar.Media"> <item name="progressDrawable">@drawable/notification_material_media_progress</item> </style> @@ -746,6 +744,7 @@ please see styles_device_defaults.xml. <item name="popupElevation">@dimen/floating_window_z</item> <item name="dropDownVerticalOffset">0dip</item> <item name="dropDownHorizontalOffset">0dip</item> + <item name="overlapAnchor">true</item> <item name="dropDownWidth">wrap_content</item> <item name="popupPromptView">@layout/simple_dropdown_hint</item> <item name="gravity">start|center_vertical</item> @@ -759,7 +758,7 @@ please see styles_device_defaults.xml. <item name="overlapAnchor">true</item> </style> - <style name="Widget.Material.Spinner.Form"> + <style name="Widget.Material.Spinner.Underlined"> <item name="background">@drawable/spinner_textfield_background_material</item> </style> @@ -829,6 +828,7 @@ please see styles_device_defaults.xml. <style name="Widget.Material.PopupMenu.Overflow"> <item name="overlapAnchor">true</item> + <item name="dropDownHorizontalOffset">-4dip</item> </style> <style name="Widget.Material.ActionButton" parent="Widget.ActionButton"> @@ -837,6 +837,8 @@ please see styles_device_defaults.xml. <item name="gravity">center</item> <item name="scaleType">center</item> <item name="maxLines">2</item> + <item name="paddingStart">0dp</item> + <item name="paddingEnd">0dp</item> </style> <style name="Widget.Material.ActionButton.CloseMode"> @@ -847,8 +849,9 @@ please see styles_device_defaults.xml. <item name="src">@drawable/ic_menu_moreoverflow_material</item> <item name="background">?attr/actionBarItemBackground</item> <item name="contentDescription">@string/action_menu_overflow_description</item> - <item name="minWidth">@dimen/action_overflow_min_width_material</item> + <item name="minWidth">@dimen/action_button_min_width_material</item> <item name="minHeight">@dimen/action_button_min_height_material</item> + <item name="paddingEnd">12dp</item> <item name="scaleType">center</item> </style> @@ -1022,7 +1025,7 @@ please see styles_device_defaults.xml. <style name="Widget.Material.Light.Spinner" parent="Widget.Material.Spinner" /> <style name="Widget.Material.Light.Spinner.DropDown" parent="Widget.Material.Spinner.DropDown"/> <style name="Widget.Material.Light.Spinner.DropDown.ActionBar" parent="Widget.Material.Spinner.DropDown.ActionBar"/> - <style name="Widget.Material.Light.Spinner.Form" parent="Widget.Material.Spinner.Form" /> + <style name="Widget.Material.Light.Spinner.Underlined" parent="Widget.Material.Spinner.Underlined" /> <style name="Widget.Material.Light.TabWidget" parent="Widget.Material.TabWidget"/> <style name="Widget.Material.Light.WebTextView" parent="Widget.Material.WebTextView"/> <style name="Widget.Material.Light.WebView" parent="Widget.Material.WebView"/> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d8c29b0..622a01a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -357,6 +357,7 @@ <java-symbol type="dimen" name="notification_top_pad_large_text" /> <java-symbol type="dimen" name="notification_top_pad_large_text_narrow" /> <java-symbol type="dimen" name="notification_large_icon_circle_padding" /> + <java-symbol type="dimen" name="notification_badge_size" /> <java-symbol type="dimen" name="immersive_mode_cling_width" /> <java-symbol type="dimen" name="circular_display_mask_offset" /> diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index a79bd0a..89fac13 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -1257,4 +1257,6 @@ please see themes_device_defaults.xml. <item name="colorAccent">@color/material_deep_teal_500</item> </style> + <!-- TODO: Spacer to be removed from here and public.xml --> + <style name="__removed" /> </resources> diff --git a/data/sounds/AudioPackage13.mk b/data/sounds/AudioPackage13.mk index 57fe762..9bbfa7f 100644 --- a/data/sounds/AudioPackage13.mk +++ b/data/sounds/AudioPackage13.mk @@ -8,12 +8,10 @@ LOCAL_PATH := frameworks/base/data/sounds # Simple files that do not require renaming -ALARM_FILES := Alarm1 Alarm2 Alarm3 Alarm4 Alarm5 Alarm6 Alarm7 Alarm8 Timer -NOTIFICATION_FILES := Notification1 Notification2 Notification3 Notification4 \ - Notification5 Notification6 Notification7 Notification8 Notification9 \ - Notification10 Notification11 -RINGTONE_FILES := Ringtone1 Ringtone2 Ringtone3 Ringtone4 Ringtone5 Ringtone6 \ - Ringtone7 Ringtone8 Ringtone9 Ringtone10 Ringtone11 Ringtone12 +ALARM_FILES := Argon Carbon Helium Krypton Neon Oxygen Osmium Platinum Timer +NOTIFICATION_FILES := Ariel Ceres Carme Elara Europa Iapetus Io Rhea Salacia Titan Tethys +RINGTONE_FILES := Atria Callisto Dione Ganymede Luna Oberon Phobos Pyxis Sedna Titania Triton \ + Umbriel EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \ camera_click camera_focus Dock Undock Lock Unlock Trusted MATERIAL_EFFECT_FILES := VideoRecord WirelessChargingStarted LowBattery diff --git a/data/sounds/AudioPackage13_48.mk b/data/sounds/AudioPackage13_48.mk index 187bccb..b90cd00 100644 --- a/data/sounds/AudioPackage13_48.mk +++ b/data/sounds/AudioPackage13_48.mk @@ -8,12 +8,10 @@ LOCAL_PATH := frameworks/base/data/sounds # Simple files that do not require renaming -ALARM_FILES := Alarm1 Alarm2 Alarm3 Alarm4 Alarm5 Alarm6 Alarm7 Alarm8 Timer -NOTIFICATION_FILES := Notification1 Notification2 Notification3 Notification4 \ - Notification5 Notification6 Notification7 Notification8 Notification9 \ - Notification10 Notification11 -RINGTONE_FILES := Ringtone1 Ringtone2 Ringtone3 Ringtone4 Ringtone5 Ringtone6 \ - Ringtone7 Ringtone8 Ringtone9 Ringtone10 Ringtone11 Ringtone12 +ALARM_FILES := Argon Carbon Helium Krypton Neon Oxygen Osmium Platinum Timer +NOTIFICATION_FILES := Ariel Ceres Carme Elara Europa Iapetus Io Rhea Salacia Titan Tethys +RINGTONE_FILES := Atria Callisto Dione Ganymede Luna Oberon Phobos Pyxis Sedna Titania Triton \ + Umbriel EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \ camera_click Lock Unlock Trusted MATERIAL_EFFECT_FILES := VideoRecord WirelessChargingStarted LowBattery diff --git a/data/sounds/alarms/material/ogg/Alarm1.ogg b/data/sounds/alarms/material/ogg/Argon.ogg Binary files differindex 87ecfeb..87ecfeb 100644 --- a/data/sounds/alarms/material/ogg/Alarm1.ogg +++ b/data/sounds/alarms/material/ogg/Argon.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm1_48k.ogg b/data/sounds/alarms/material/ogg/Argon_48k.ogg Binary files differindex 7ca6faa..7ca6faa 100644 --- a/data/sounds/alarms/material/ogg/Alarm1_48k.ogg +++ b/data/sounds/alarms/material/ogg/Argon_48k.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm2.ogg b/data/sounds/alarms/material/ogg/Carbon.ogg Binary files differindex 0d96853..0d96853 100644 --- a/data/sounds/alarms/material/ogg/Alarm2.ogg +++ b/data/sounds/alarms/material/ogg/Carbon.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm2_48k.ogg b/data/sounds/alarms/material/ogg/Carbon_48k.ogg Binary files differindex 133fcb5..133fcb5 100644 --- a/data/sounds/alarms/material/ogg/Alarm2_48k.ogg +++ b/data/sounds/alarms/material/ogg/Carbon_48k.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm3.ogg b/data/sounds/alarms/material/ogg/Helium.ogg Binary files differindex d381448..d381448 100644 --- a/data/sounds/alarms/material/ogg/Alarm3.ogg +++ b/data/sounds/alarms/material/ogg/Helium.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm3_48k.ogg b/data/sounds/alarms/material/ogg/Helium_48k.ogg Binary files differindex a53fbb8..a53fbb8 100644 --- a/data/sounds/alarms/material/ogg/Alarm3_48k.ogg +++ b/data/sounds/alarms/material/ogg/Helium_48k.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm4.ogg b/data/sounds/alarms/material/ogg/Krypton.ogg Binary files differindex 4529aa8..4529aa8 100644 --- a/data/sounds/alarms/material/ogg/Alarm4.ogg +++ b/data/sounds/alarms/material/ogg/Krypton.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm4_48k.ogg b/data/sounds/alarms/material/ogg/Krypton_48k.ogg Binary files differindex ac8781d..ac8781d 100644 --- a/data/sounds/alarms/material/ogg/Alarm4_48k.ogg +++ b/data/sounds/alarms/material/ogg/Krypton_48k.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm5.ogg b/data/sounds/alarms/material/ogg/Neon.ogg Binary files differindex 61582d5..61582d5 100644 --- a/data/sounds/alarms/material/ogg/Alarm5.ogg +++ b/data/sounds/alarms/material/ogg/Neon.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm5_48k.ogg b/data/sounds/alarms/material/ogg/Neon_48k.ogg Binary files differindex 0f0ff03..0f0ff03 100644 --- a/data/sounds/alarms/material/ogg/Alarm5_48k.ogg +++ b/data/sounds/alarms/material/ogg/Neon_48k.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm6.ogg b/data/sounds/alarms/material/ogg/Osmium.ogg Binary files differindex 245c4ce..245c4ce 100644 --- a/data/sounds/alarms/material/ogg/Alarm6.ogg +++ b/data/sounds/alarms/material/ogg/Osmium.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm6_48k.ogg b/data/sounds/alarms/material/ogg/Osmium_48k.ogg Binary files differindex b8abc3a..b8abc3a 100644 --- a/data/sounds/alarms/material/ogg/Alarm6_48k.ogg +++ b/data/sounds/alarms/material/ogg/Osmium_48k.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm7.ogg b/data/sounds/alarms/material/ogg/Oxygen.ogg Binary files differindex 0b489bd..0b489bd 100644 --- a/data/sounds/alarms/material/ogg/Alarm7.ogg +++ b/data/sounds/alarms/material/ogg/Oxygen.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm7_48k.ogg b/data/sounds/alarms/material/ogg/Oxygen_48k.ogg Binary files differindex 0fdeab6..0fdeab6 100644 --- a/data/sounds/alarms/material/ogg/Alarm7_48k.ogg +++ b/data/sounds/alarms/material/ogg/Oxygen_48k.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm8.ogg b/data/sounds/alarms/material/ogg/Platinum.ogg Binary files differindex 053b008..053b008 100644 --- a/data/sounds/alarms/material/ogg/Alarm8.ogg +++ b/data/sounds/alarms/material/ogg/Platinum.ogg diff --git a/data/sounds/alarms/material/ogg/Alarm8_48k.ogg b/data/sounds/alarms/material/ogg/Platinum_48k.ogg Binary files differindex 2107172..2107172 100644 --- a/data/sounds/alarms/material/ogg/Alarm8_48k.ogg +++ b/data/sounds/alarms/material/ogg/Platinum_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification3.ogg b/data/sounds/notifications/material/ogg/Ariel.ogg Binary files differindex ff17500..ff17500 100644 --- a/data/sounds/notifications/material/ogg/Notification3.ogg +++ b/data/sounds/notifications/material/ogg/Ariel.ogg diff --git a/data/sounds/notifications/material/ogg/Notification3_48k.ogg b/data/sounds/notifications/material/ogg/Ariel_48k.ogg Binary files differindex b138284..b138284 100644 --- a/data/sounds/notifications/material/ogg/Notification3_48k.ogg +++ b/data/sounds/notifications/material/ogg/Ariel_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification4.ogg b/data/sounds/notifications/material/ogg/Carme.ogg Binary files differindex ec6e7cb..ec6e7cb 100644 --- a/data/sounds/notifications/material/ogg/Notification4.ogg +++ b/data/sounds/notifications/material/ogg/Carme.ogg diff --git a/data/sounds/notifications/material/ogg/Notification4_48k.ogg b/data/sounds/notifications/material/ogg/Carme_48k.ogg Binary files differindex 2be9332..2be9332 100644 --- a/data/sounds/notifications/material/ogg/Notification4_48k.ogg +++ b/data/sounds/notifications/material/ogg/Carme_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification5.ogg b/data/sounds/notifications/material/ogg/Ceres.ogg Binary files differindex f7386a7..f7386a7 100644 --- a/data/sounds/notifications/material/ogg/Notification5.ogg +++ b/data/sounds/notifications/material/ogg/Ceres.ogg diff --git a/data/sounds/notifications/material/ogg/Notification5_48k.ogg b/data/sounds/notifications/material/ogg/Ceres_48k.ogg Binary files differindex 0ffba07..0ffba07 100644 --- a/data/sounds/notifications/material/ogg/Notification5_48k.ogg +++ b/data/sounds/notifications/material/ogg/Ceres_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification6.ogg b/data/sounds/notifications/material/ogg/Elara.ogg Binary files differindex e4dcf28..e4dcf28 100644 --- a/data/sounds/notifications/material/ogg/Notification6.ogg +++ b/data/sounds/notifications/material/ogg/Elara.ogg diff --git a/data/sounds/notifications/material/ogg/Notification6_48k.ogg b/data/sounds/notifications/material/ogg/Elara_48k.ogg Binary files differindex 2891de6..2891de6 100644 --- a/data/sounds/notifications/material/ogg/Notification6_48k.ogg +++ b/data/sounds/notifications/material/ogg/Elara_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification7.ogg b/data/sounds/notifications/material/ogg/Europa.ogg Binary files differindex ce94e21..ce94e21 100644 --- a/data/sounds/notifications/material/ogg/Notification7.ogg +++ b/data/sounds/notifications/material/ogg/Europa.ogg diff --git a/data/sounds/notifications/material/ogg/Notification7_48k.ogg b/data/sounds/notifications/material/ogg/Europa_48k.ogg Binary files differindex 45c5f0a..45c5f0a 100644 --- a/data/sounds/notifications/material/ogg/Notification7_48k.ogg +++ b/data/sounds/notifications/material/ogg/Europa_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification8.ogg b/data/sounds/notifications/material/ogg/Iapetus.ogg Binary files differindex d7c810d..d7c810d 100644 --- a/data/sounds/notifications/material/ogg/Notification8.ogg +++ b/data/sounds/notifications/material/ogg/Iapetus.ogg diff --git a/data/sounds/notifications/material/ogg/Notification8_48k.ogg b/data/sounds/notifications/material/ogg/Iapetus_48k.ogg Binary files differindex 8b9703e..8b9703e 100644 --- a/data/sounds/notifications/material/ogg/Notification8_48k.ogg +++ b/data/sounds/notifications/material/ogg/Iapetus_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification9.ogg b/data/sounds/notifications/material/ogg/Io.ogg Binary files differindex e05a529..e05a529 100644 --- a/data/sounds/notifications/material/ogg/Notification9.ogg +++ b/data/sounds/notifications/material/ogg/Io.ogg diff --git a/data/sounds/notifications/material/ogg/Notification9_48k.ogg b/data/sounds/notifications/material/ogg/Io_48k.ogg Binary files differindex 599b3fc..599b3fc 100644 --- a/data/sounds/notifications/material/ogg/Notification9_48k.ogg +++ b/data/sounds/notifications/material/ogg/Io_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification10.ogg b/data/sounds/notifications/material/ogg/Rhea.ogg Binary files differindex 347c38d..347c38d 100644 --- a/data/sounds/notifications/material/ogg/Notification10.ogg +++ b/data/sounds/notifications/material/ogg/Rhea.ogg diff --git a/data/sounds/notifications/material/ogg/Notification10_48k.ogg b/data/sounds/notifications/material/ogg/Rhea_48k.ogg Binary files differindex 1de91de..1de91de 100644 --- a/data/sounds/notifications/material/ogg/Notification10_48k.ogg +++ b/data/sounds/notifications/material/ogg/Rhea_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification11.ogg b/data/sounds/notifications/material/ogg/Salacia.ogg Binary files differindex ea83b5b..ea83b5b 100644 --- a/data/sounds/notifications/material/ogg/Notification11.ogg +++ b/data/sounds/notifications/material/ogg/Salacia.ogg diff --git a/data/sounds/notifications/material/ogg/Notification11_48k.ogg b/data/sounds/notifications/material/ogg/Salacia_48k.ogg Binary files differindex dba0416..dba0416 100644 --- a/data/sounds/notifications/material/ogg/Notification11_48k.ogg +++ b/data/sounds/notifications/material/ogg/Salacia_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification1.ogg b/data/sounds/notifications/material/ogg/Tethys.ogg Binary files differindex fc61f70..fc61f70 100644 --- a/data/sounds/notifications/material/ogg/Notification1.ogg +++ b/data/sounds/notifications/material/ogg/Tethys.ogg diff --git a/data/sounds/notifications/material/ogg/Notification1_48k.ogg b/data/sounds/notifications/material/ogg/Tethys_48k.ogg Binary files differindex b2ab648..b2ab648 100644 --- a/data/sounds/notifications/material/ogg/Notification1_48k.ogg +++ b/data/sounds/notifications/material/ogg/Tethys_48k.ogg diff --git a/data/sounds/notifications/material/ogg/Notification2.ogg b/data/sounds/notifications/material/ogg/Titan.ogg Binary files differindex 0e54844..0e54844 100644 --- a/data/sounds/notifications/material/ogg/Notification2.ogg +++ b/data/sounds/notifications/material/ogg/Titan.ogg diff --git a/data/sounds/notifications/material/ogg/Notification2_48k.ogg b/data/sounds/notifications/material/ogg/Titan_48k.ogg Binary files differindex 4c4af31..4c4af31 100644 --- a/data/sounds/notifications/material/ogg/Notification2_48k.ogg +++ b/data/sounds/notifications/material/ogg/Titan_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone4.ogg b/data/sounds/ringtones/material/ogg/Atria.ogg Binary files differindex 173f73d..173f73d 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone4.ogg +++ b/data/sounds/ringtones/material/ogg/Atria.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone4_48k.ogg b/data/sounds/ringtones/material/ogg/Atria_48k.ogg Binary files differindex c98a437..c98a437 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone4_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Atria_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone5.ogg b/data/sounds/ringtones/material/ogg/Callisto.ogg Binary files differindex dd9bc5b..dd9bc5b 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone5.ogg +++ b/data/sounds/ringtones/material/ogg/Callisto.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone5_48k.ogg b/data/sounds/ringtones/material/ogg/Callisto_48k.ogg Binary files differindex 44855dc..44855dc 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone5_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Callisto_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone6.ogg b/data/sounds/ringtones/material/ogg/Dione.ogg Binary files differindex cf7d1cb..cf7d1cb 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone6.ogg +++ b/data/sounds/ringtones/material/ogg/Dione.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone6_48k.ogg b/data/sounds/ringtones/material/ogg/Dione_48k.ogg Binary files differindex 8abbf98..8abbf98 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone6_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Dione_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone7.ogg b/data/sounds/ringtones/material/ogg/Ganymede.ogg Binary files differindex e7ff9f4..e7ff9f4 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone7.ogg +++ b/data/sounds/ringtones/material/ogg/Ganymede.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone7_48k.ogg b/data/sounds/ringtones/material/ogg/Ganymede_48k.ogg Binary files differindex 1ce7d33..1ce7d33 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone7_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Ganymede_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone8.ogg b/data/sounds/ringtones/material/ogg/Luna.ogg Binary files differindex 8aeb3a0..8aeb3a0 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone8.ogg +++ b/data/sounds/ringtones/material/ogg/Luna.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone8_48k.ogg b/data/sounds/ringtones/material/ogg/Luna_48k.ogg Binary files differindex a2f7f49..a2f7f49 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone8_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Luna_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone9.ogg b/data/sounds/ringtones/material/ogg/Oberon.ogg Binary files differindex 145f474..145f474 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone9.ogg +++ b/data/sounds/ringtones/material/ogg/Oberon.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone9_48k.ogg b/data/sounds/ringtones/material/ogg/Oberon_48k.ogg Binary files differindex 7f2e274..7f2e274 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone9_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Oberon_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone10.ogg b/data/sounds/ringtones/material/ogg/Phobos.ogg Binary files differindex 5e312d2..5e312d2 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone10.ogg +++ b/data/sounds/ringtones/material/ogg/Phobos.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone10_48k.ogg b/data/sounds/ringtones/material/ogg/Phobos_48k.ogg Binary files differindex cd02cbc..cd02cbc 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone10_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Phobos_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone11.ogg b/data/sounds/ringtones/material/ogg/Pyxis.ogg Binary files differindex 4786b18..4786b18 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone11.ogg +++ b/data/sounds/ringtones/material/ogg/Pyxis.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone11_48k.ogg b/data/sounds/ringtones/material/ogg/Pyxis_48k.ogg Binary files differindex 6b39351..6b39351 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone11_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Pyxis_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone12.ogg b/data/sounds/ringtones/material/ogg/Sedna.ogg Binary files differindex ff889e6..ff889e6 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone12.ogg +++ b/data/sounds/ringtones/material/ogg/Sedna.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone12_48k.ogg b/data/sounds/ringtones/material/ogg/Sedna_48k.ogg Binary files differindex 68bc35e..68bc35e 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone12_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Sedna_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone1.ogg b/data/sounds/ringtones/material/ogg/Titania.ogg Binary files differindex 87a05ee..87a05ee 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone1.ogg +++ b/data/sounds/ringtones/material/ogg/Titania.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone1_48k.ogg b/data/sounds/ringtones/material/ogg/Titania_48k.ogg Binary files differindex ebf8f32..ebf8f32 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone1_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Titania_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone2.ogg b/data/sounds/ringtones/material/ogg/Triton.ogg Binary files differindex b5893bd..b5893bd 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone2.ogg +++ b/data/sounds/ringtones/material/ogg/Triton.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone2_48k.ogg b/data/sounds/ringtones/material/ogg/Triton_48k.ogg Binary files differindex e44d01e..e44d01e 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone2_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Triton_48k.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone3.ogg b/data/sounds/ringtones/material/ogg/Umbriel.ogg Binary files differindex 193e566..193e566 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone3.ogg +++ b/data/sounds/ringtones/material/ogg/Umbriel.ogg diff --git a/data/sounds/ringtones/material/ogg/Ringtone3_48k.ogg b/data/sounds/ringtones/material/ogg/Umbriel_48k.ogg Binary files differindex 583a1d7..583a1d7 100644 --- a/data/sounds/ringtones/material/ogg/Ringtone3_48k.ogg +++ b/data/sounds/ringtones/material/ogg/Umbriel_48k.ogg diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index 0bc4fdf..4c8b4f1 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -346,22 +346,24 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac private boolean mCanConstantState; private boolean mCheckedConstantState; - public AnimatedRotateState(AnimatedRotateState source, AnimatedRotateDrawable owner, + public AnimatedRotateState(AnimatedRotateState orig, AnimatedRotateDrawable owner, Resources res) { - if (source != null) { + if (orig != null) { if (res != null) { - mDrawable = source.mDrawable.getConstantState().newDrawable(res); + mDrawable = orig.mDrawable.getConstantState().newDrawable(res); } else { - mDrawable = source.mDrawable.getConstantState().newDrawable(); + mDrawable = orig.mDrawable.getConstantState().newDrawable(); } mDrawable.setCallback(owner); - mDrawable.setLayoutDirection(source.mDrawable.getLayoutDirection()); - mPivotXRel = source.mPivotXRel; - mPivotX = source.mPivotX; - mPivotYRel = source.mPivotYRel; - mPivotY = source.mPivotY; - mFramesCount = source.mFramesCount; - mFrameDuration = source.mFrameDuration; + mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); + mDrawable.setBounds(orig.mDrawable.getBounds()); + mDrawable.setLevel(orig.mDrawable.getLevel()); + mPivotXRel = orig.mPivotXRel; + mPivotX = orig.mPivotX; + mPivotYRel = orig.mPivotYRel; + mPivotY = orig.mPivotY; + mFramesCount = orig.mFramesCount; + mFrameDuration = orig.mFrameDuration; mCanConstantState = mCheckedConstantState = true; } } diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index f116376..61ef81b 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -285,6 +285,8 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } mDrawable.setCallback(owner); mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); + mDrawable.setBounds(orig.mDrawable.getBounds()); + mDrawable.setLevel(orig.mDrawable.getLevel()); mOrientation = orig.mOrientation; mGravity = orig.mGravity; mCheckedConstantState = mCanConstantState = true; diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index dd0f06f..a20b6d8 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -400,6 +400,8 @@ public class InsetDrawable extends Drawable implements Drawable.Callback { } mDrawable.setCallback(owner); mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); + mDrawable.setBounds(orig.mDrawable.getBounds()); + mDrawable.setLevel(orig.mDrawable.getLevel()); mInsetLeft = orig.mInsetLeft; mInsetTop = orig.mInsetTop; mInsetRight = orig.mInsetRight; diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 43bc89a..001ed88 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -961,20 +961,22 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { // Default empty constructor. } - ChildDrawable(ChildDrawable or, LayerDrawable owner, Resources res) { + ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) { if (res != null) { - mDrawable = or.mDrawable.getConstantState().newDrawable(res); + mDrawable = orig.mDrawable.getConstantState().newDrawable(res); } else { - mDrawable = or.mDrawable.getConstantState().newDrawable(); + mDrawable = orig.mDrawable.getConstantState().newDrawable(); } mDrawable.setCallback(owner); - mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection()); - mThemeAttrs = or.mThemeAttrs; - mInsetL = or.mInsetL; - mInsetT = or.mInsetT; - mInsetR = or.mInsetR; - mInsetB = or.mInsetB; - mId = or.mId; + mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); + mDrawable.setBounds(orig.mDrawable.getBounds()); + mDrawable.setLevel(orig.mDrawable.getLevel()); + mThemeAttrs = orig.mThemeAttrs; + mInsetL = orig.mInsetL; + mInsetT = orig.mInsetT; + mInsetR = orig.mInsetR; + mInsetB = orig.mInsetB; + mId = orig.mId; } } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index ca32751..7402bdb 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -33,7 +33,6 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.util.Log; import com.android.internal.R; @@ -41,6 +40,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.Arrays; /** * Drawable that shows a ripple effect in response to state changes. The @@ -88,7 +88,6 @@ import java.io.IOException; * @attr ref android.R.styleable#RippleDrawable_color */ public class RippleDrawable extends LayerDrawable { - private static final String LOG_TAG = RippleDrawable.class.getSimpleName(); private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN); private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP); private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER); @@ -215,10 +214,14 @@ public class RippleDrawable extends LayerDrawable { final Ripple[] ripples = mAnimatingRipples; for (int i = 0; i < count; i++) { ripples[i].jump(); - ripples[i] = null; + } + if (ripples != null) { + Arrays.fill(ripples, 0, count, null); } mAnimatingRipplesCount = 0; mClearingHotspots = false; + + invalidateSelf(); } @Override @@ -549,6 +552,15 @@ public class RippleDrawable extends LayerDrawable { mAnimatingRipples[mAnimatingRipplesCount++] = mRipple; } + @Override + public void invalidateSelf() { + // Don't invalidate when we're clearing hotspots. We'll handle that + // manually when we're done. + if (!mClearingHotspots) { + super.invalidateSelf(); + } + } + private void removeRipple() { if (mRipple != null) { mRipple.exit(); @@ -572,7 +584,9 @@ public class RippleDrawable extends LayerDrawable { final Ripple[] ripples = mAnimatingRipples; for (int i = 0; i < count; i++) { ripples[i].cancel(); - ripples[i] = null; + } + if (ripples != null) { + Arrays.fill(ripples, 0, count, null); } mAnimatingRipplesCount = 0; mClearingHotspots = false; @@ -680,7 +694,7 @@ public class RippleDrawable extends LayerDrawable { final int count = mAnimatingRipplesCount; final int index = getRippleIndex(ripple); if (index >= 0) { - System.arraycopy(ripples, index + 1, ripples, index + 1 - 1, count - (index + 1)); + System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1)); ripples[count - 1] = null; mAnimatingRipplesCount--; invalidateSelf(); diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 63b9e02..70482a6 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -507,21 +507,23 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { private boolean mCanConstantState; private boolean mCheckedConstantState; - public RotateState(RotateState source, RotateDrawable owner, Resources res) { - if (source != null) { + public RotateState(RotateState orig, RotateDrawable owner, Resources res) { + if (orig != null) { if (res != null) { - mDrawable = source.mDrawable.getConstantState().newDrawable(res); + mDrawable = orig.mDrawable.getConstantState().newDrawable(res); } else { - mDrawable = source.mDrawable.getConstantState().newDrawable(); + mDrawable = orig.mDrawable.getConstantState().newDrawable(); } mDrawable.setCallback(owner); - mDrawable.setLayoutDirection(source.mDrawable.getLayoutDirection()); - mPivotXRel = source.mPivotXRel; - mPivotX = source.mPivotX; - mPivotYRel = source.mPivotYRel; - mPivotY = source.mPivotY; - mFromDegrees = mCurrentDegrees = source.mFromDegrees; - mToDegrees = source.mToDegrees; + mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); + mDrawable.setBounds(orig.mDrawable.getBounds()); + mDrawable.setLevel(orig.mDrawable.getLevel()); + mPivotXRel = orig.mPivotXRel; + mPivotX = orig.mPivotX; + mPivotYRel = orig.mPivotYRel; + mPivotY = orig.mPivotY; + mFromDegrees = mCurrentDegrees = orig.mFromDegrees; + mToDegrees = orig.mToDegrees; mCanConstantState = mCheckedConstantState = true; } } diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index a954474..b40038a 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -295,6 +295,8 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { } mDrawable.setCallback(owner); mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); + mDrawable.setBounds(orig.mDrawable.getBounds()); + mDrawable.setLevel(orig.mDrawable.getLevel()); mScaleWidth = orig.mScaleWidth; mScaleHeight = orig.mScaleHeight; mGravity = orig.mGravity; diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index 11568d2..c65efe4 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -1557,7 +1557,7 @@ public: }; const char16_t* valueToString(const Res_value* value, size_t stringBlock, char16_t tmpBuffer[TMP_BUFFER_SIZE], - size_t* outLen); + size_t* outLen) const; struct bag_entry { ssize_t stringBlock; diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 3f014ef..690b1d6 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -2939,7 +2939,7 @@ struct ResTable::PackageGroup for (size_t i = 0; i < bags->size(); i++) { TABLE_NOISY(printf("type=%d\n", i)); const TypeList& typeList = types[i]; - if (typeList.isEmpty()) { + if (!typeList.isEmpty()) { bag_set** typeBags = bags->get(i); TABLE_NOISY(printf("typeBags=%p\n", typeBags)); if (typeBags) { @@ -3728,7 +3728,7 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex, const char16_t* ResTable::valueToString( const Res_value* value, size_t stringBlock, - char16_t /*tmpBuffer*/ [TMP_BUFFER_SIZE], size_t* outLen) + char16_t /*tmpBuffer*/ [TMP_BUFFER_SIZE], size_t* outLen) const { if (!value) { return NULL; diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp index 17f91ca..92af7fe 100644 --- a/libs/androidfw/tests/BackupData_test.cpp +++ b/libs/androidfw/tests/BackupData_test.cpp @@ -45,7 +45,7 @@ namespace android { class BackupDataTest : public testing::Test { protected: char* m_external_storage; - char* m_filename; + String8 mFilename; String8 mKey1; String8 mKey2; String8 mKey3; @@ -53,15 +53,13 @@ protected: virtual void SetUp() { m_external_storage = getenv("EXTERNAL_STORAGE"); + mFilename.append(m_external_storage); + mFilename.append(TEST_FILENAME); - const int totalLen = strlen(m_external_storage) + strlen(TEST_FILENAME) + 1; - m_filename = new char[totalLen]; - snprintf(m_filename, totalLen, "%s%s", m_external_storage, TEST_FILENAME); - - ::unlink(m_filename); - int fd = ::open(m_filename, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + ::unlink(mFilename.string()); + int fd = ::open(mFilename.string(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd < 0) { - FAIL() << "Couldn't create " << m_filename << " for writing"; + FAIL() << "Couldn't create " << mFilename.string() << " for writing"; } mKey1 = String8(KEY1); mKey2 = String8(KEY2); @@ -74,7 +72,7 @@ protected: }; TEST_F(BackupDataTest, WriteAndReadSingle) { - int fd = ::open(m_filename, O_WRONLY); + int fd = ::open(mFilename.string(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); EXPECT_EQ(NO_ERROR, writer->WriteEntityHeader(mKey1, sizeof(DATA1))) @@ -83,7 +81,7 @@ TEST_F(BackupDataTest, WriteAndReadSingle) { << "WriteEntityData returned an error"; ::close(fd); - fd = ::open(m_filename, O_RDONLY); + fd = ::open(mFilename.string(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); EXPECT_EQ(NO_ERROR, reader->Status()) << "Reader ctor failed"; @@ -116,7 +114,7 @@ TEST_F(BackupDataTest, WriteAndReadSingle) { } TEST_F(BackupDataTest, WriteAndReadMultiple) { - int fd = ::open(m_filename, O_WRONLY); + int fd = ::open(mFilename.string(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, sizeof(DATA1)); writer->WriteEntityData(DATA1, sizeof(DATA1)); @@ -124,7 +122,7 @@ TEST_F(BackupDataTest, WriteAndReadMultiple) { writer->WriteEntityData(DATA2, sizeof(DATA2)); ::close(fd); - fd = ::open(m_filename, O_RDONLY); + fd = ::open(mFilename.string(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -164,7 +162,7 @@ TEST_F(BackupDataTest, WriteAndReadMultiple) { } TEST_F(BackupDataTest, SkipEntity) { - int fd = ::open(m_filename, O_WRONLY); + int fd = ::open(mFilename.string(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, sizeof(DATA1)); writer->WriteEntityData(DATA1, sizeof(DATA1)); @@ -174,7 +172,7 @@ TEST_F(BackupDataTest, SkipEntity) { writer->WriteEntityData(DATA3, sizeof(DATA3)); ::close(fd); - fd = ::open(m_filename, O_RDONLY); + fd = ::open(mFilename.string(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -219,14 +217,14 @@ TEST_F(BackupDataTest, SkipEntity) { } TEST_F(BackupDataTest, DeleteEntity) { - int fd = ::open(m_filename, O_WRONLY); + int fd = ::open(mFilename.string(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, sizeof(DATA1)); writer->WriteEntityData(DATA1, sizeof(DATA1)); writer->WriteEntityHeader(mKey2, -1); ::close(fd); - fd = ::open(m_filename, O_RDONLY); + fd = ::open(mFilename.string(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -258,7 +256,7 @@ TEST_F(BackupDataTest, DeleteEntity) { } TEST_F(BackupDataTest, EneityAfterDelete) { - int fd = ::open(m_filename, O_WRONLY); + int fd = ::open(mFilename.string(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, sizeof(DATA1)); writer->WriteEntityData(DATA1, sizeof(DATA1)); @@ -267,7 +265,7 @@ TEST_F(BackupDataTest, EneityAfterDelete) { writer->WriteEntityData(DATA3, sizeof(DATA3)); ::close(fd); - fd = ::open(m_filename, O_RDONLY); + fd = ::open(mFilename.string(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -319,7 +317,7 @@ TEST_F(BackupDataTest, EneityAfterDelete) { } TEST_F(BackupDataTest, OnlyDeleteEntities) { - int fd = ::open(m_filename, O_WRONLY); + int fd = ::open(mFilename.string(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, -1); writer->WriteEntityHeader(mKey2, -1); @@ -327,7 +325,7 @@ TEST_F(BackupDataTest, OnlyDeleteEntities) { writer->WriteEntityHeader(mKey4, -1); ::close(fd); - fd = ::open(m_filename, O_RDONLY); + fd = ::open(mFilename.string(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -387,13 +385,13 @@ TEST_F(BackupDataTest, OnlyDeleteEntities) { } TEST_F(BackupDataTest, ReadDeletedEntityData) { - int fd = ::open(m_filename, O_WRONLY); + int fd = ::open(mFilename.string(), O_WRONLY); BackupDataWriter* writer = new BackupDataWriter(fd); writer->WriteEntityHeader(mKey1, -1); writer->WriteEntityHeader(mKey2, -1); ::close(fd); - fd = ::open(m_filename, O_RDONLY); + fd = ::open(mFilename.string(), O_RDONLY); BackupDataReader* reader = new BackupDataReader(fd); bool done; @@ -431,6 +429,7 @@ TEST_F(BackupDataTest, ReadDeletedEntityData) { EXPECT_EQ(-1, (int) dataSize) << "not recognizing deletion on second entity"; + delete[] dataBytes; delete writer; delete reader; } diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp index 7a4dd13..1151121 100644 --- a/libs/androidfw/tests/ObbFile_test.cpp +++ b/libs/androidfw/tests/ObbFile_test.cpp @@ -34,20 +34,18 @@ namespace android { class ObbFileTest : public testing::Test { protected: sp<ObbFile> mObbFile; - char* mExternalStorage; - char* mFileName; + String8 mFileName; virtual void SetUp() { mObbFile = new ObbFile(); - mExternalStorage = getenv("EXTERNAL_STORAGE"); + char* externalStorage = getenv("EXTERNAL_STORAGE"); - const int totalLen = strlen(mExternalStorage) + strlen(TEST_FILENAME) + 1; - mFileName = new char[totalLen]; - snprintf(mFileName, totalLen, "%s%s", mExternalStorage, TEST_FILENAME); + mFileName.append(externalStorage); + mFileName.append(TEST_FILENAME); - int fd = ::open(mFileName, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + int fd = ::open(mFileName.string(), O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd < 0) { - FAIL() << "Couldn't create " << mFileName << " for tests"; + FAIL() << "Couldn't create " << mFileName.string() << " for tests"; } } @@ -71,12 +69,12 @@ TEST_F(ObbFileTest, WriteThenRead) { EXPECT_TRUE(mObbFile->setSalt(salt, SALT_SIZE)) << "Salt should be successfully set"; - EXPECT_TRUE(mObbFile->writeTo(mFileName)) + EXPECT_TRUE(mObbFile->writeTo(mFileName.string())) << "couldn't write to fake .obb file"; mObbFile = new ObbFile(); - EXPECT_TRUE(mObbFile->readFrom(mFileName)) + EXPECT_TRUE(mObbFile->readFrom(mFileName.string())) << "couldn't read from fake .obb file"; EXPECT_EQ(versionNum, mObbFile->getVersion()) diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index d9f7941..49560ff 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -12,6 +12,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) font/CacheTexture.cpp \ font/Font.cpp \ AmbientShadow.cpp \ + AnimationContext.cpp \ Animator.cpp \ AnimatorManager.cpp \ AssetAtlas.cpp \ diff --git a/libs/hwui/AnimationContext.cpp b/libs/hwui/AnimationContext.cpp new file mode 100644 index 0000000..d7d9743 --- /dev/null +++ b/libs/hwui/AnimationContext.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "AnimationContext.h" + +#include "Animator.h" +#include "RenderNode.h" +#include "TreeInfo.h" +#include "renderthread/TimeLord.h" + +namespace android { +namespace uirenderer { + +AnimationContext::AnimationContext(renderthread::TimeLord& clock) + : mClock(clock) + , mCurrentFrameAnimations(*this) + , mNextFrameAnimations(*this) + , mFrameTimeMs(0) { +} + +AnimationContext::~AnimationContext() { + startFrame(); + while (mCurrentFrameAnimations.mNextHandle) { + AnimationHandle* current = mCurrentFrameAnimations.mNextHandle; + AnimatorManager& animators = current->mRenderNode->animators(); + animators.endAllAnimators(); + LOG_ALWAYS_FATAL_IF(mCurrentFrameAnimations.mNextHandle == current, + "endAllAnimators failed to remove from current frame list!"); + } +} + +void AnimationContext::addAnimatingRenderNode(RenderNode& node) { + if (!node.animators().hasAnimationHandle()) { + AnimationHandle* handle = new AnimationHandle(node, *this); + addAnimationHandle(handle); + } +} + +void AnimationContext::addAnimationHandle(AnimationHandle* handle) { + handle->insertAfter(&mNextFrameAnimations); +} + +void AnimationContext::startFrame() { + LOG_ALWAYS_FATAL_IF(mCurrentFrameAnimations.mNextHandle, + "Missed running animations last frame!"); + AnimationHandle* head = mNextFrameAnimations.mNextHandle; + if (head) { + mNextFrameAnimations.mNextHandle = NULL; + mCurrentFrameAnimations.mNextHandle = head; + head->mPreviousHandle = &mCurrentFrameAnimations; + } + mFrameTimeMs = mClock.computeFrameTimeMs(); +} + +void AnimationContext::runRemainingAnimations(TreeInfo& info) { + while (mCurrentFrameAnimations.mNextHandle) { + AnimationHandle* current = mCurrentFrameAnimations.mNextHandle; + AnimatorManager& animators = current->mRenderNode->animators(); + animators.pushStaging(); + animators.animateNoDamage(info); + LOG_ALWAYS_FATAL_IF(mCurrentFrameAnimations.mNextHandle == current, + "Animate failed to remove from current frame list!"); + } +} + +void AnimationContext::callOnFinished(BaseRenderNodeAnimator* animator, + AnimationListener* listener) { + listener->onAnimationFinished(animator); +} + +AnimationHandle::AnimationHandle(AnimationContext& context) + : mContext(context) + , mPreviousHandle(NULL) + , mNextHandle(NULL) { +} + +AnimationHandle::AnimationHandle(RenderNode& animatingNode, AnimationContext& context) + : mRenderNode(&animatingNode) + , mContext(context) + , mPreviousHandle(NULL) + , mNextHandle(NULL) { + mRenderNode->animators().setAnimationHandle(this); +} + +AnimationHandle::~AnimationHandle() { + LOG_ALWAYS_FATAL_IF(mPreviousHandle || mNextHandle, + "AnimationHandle destroyed while still animating!"); +} + +void AnimationHandle::notifyAnimationsRan() { + removeFromList(); + if (mRenderNode->animators().hasAnimators()) { + mContext.addAnimationHandle(this); + } else { + release(); + } +} + +void AnimationHandle::release() { + LOG_ALWAYS_FATAL_IF(mRenderNode->animators().hasAnimators(), + "Releasing the handle for an RenderNode with outstanding animators!"); + removeFromList(); + mRenderNode->animators().setAnimationHandle(NULL); + delete this; +} + +void AnimationHandle::insertAfter(AnimationHandle* prev) { + removeFromList(); + mNextHandle = prev->mNextHandle; + if (mNextHandle) { + mNextHandle->mPreviousHandle = this; + } + prev->mNextHandle = this; + mPreviousHandle = prev; +} + +void AnimationHandle::removeFromList() { + if (mPreviousHandle) { + mPreviousHandle->mNextHandle = mNextHandle; + } + if (mNextHandle) { + mNextHandle->mPreviousHandle = mPreviousHandle; + } + mPreviousHandle = NULL; + mNextHandle = NULL; +} + +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/AnimationContext.h b/libs/hwui/AnimationContext.h new file mode 100644 index 0000000..900d953 --- /dev/null +++ b/libs/hwui/AnimationContext.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TREEANIMATIONTRACKER_H_ +#define TREEANIMATIONTRACKER_H_ + +#include <cutils/compiler.h> +#include <utils/RefBase.h> +#include <utils/StrongPointer.h> + +#include "renderthread/TimeLord.h" +#include "utils/Macros.h" + +namespace android { +namespace uirenderer { + +class AnimationContext; +class AnimationListener; +class BaseRenderNodeAnimator; +class RenderNode; +class TreeInfo; + +/* + * AnimationHandle is several classes merged into one. + * 1: It maintains the reference to the AnimationContext required to run animators. + * 2: It keeps a strong reference to RenderNodes with animators so that + * we don't lose them if they are no longer in the display tree. This is + * required so that we can keep animating them, and properly notify listeners + * of onAnimationFinished. + * 3: It forms a doubly linked list so that we can cheaply move between states. + */ +class AnimationHandle { + PREVENT_COPY_AND_ASSIGN(AnimationHandle); +public: + AnimationContext& context() { return mContext; } + + // Called by the RenderNode when it has internally pulsed its own animations + // this frame and does not need to be run again this frame. + void notifyAnimationsRan(); + + // Stops tracking the RenderNode and destroys the handle. The node must be + // re-attached to the AnimationContext to receive managed animation + // pulses. + void release(); + +private: + friend class AnimationContext; + AnimationHandle(AnimationContext& context); + AnimationHandle(RenderNode& animatingNode, AnimationContext& context); + ~AnimationHandle(); + + void insertAfter(AnimationHandle* prev); + void removeFromList(); + + sp<RenderNode> mRenderNode; + + AnimationContext& mContext; + + AnimationHandle* mPreviousHandle; + AnimationHandle* mNextHandle; +}; + +class AnimationContext { + PREVENT_COPY_AND_ASSIGN(AnimationContext); +public: + ANDROID_API AnimationContext(renderthread::TimeLord& clock); + ANDROID_API virtual ~AnimationContext(); + + nsecs_t frameTimeMs() { return mFrameTimeMs; } + bool hasAnimations() { + return mCurrentFrameAnimations.mNextHandle + || mNextFrameAnimations.mNextHandle; + } + + // Will always add to the next frame list, which is swapped when + // startFrame() is called + ANDROID_API void addAnimatingRenderNode(RenderNode& node); + + // Marks the start of a frame, which will update the frame time and move all + // next frame animations into the current frame + ANDROID_API virtual void startFrame(); + + // Runs any animations still left in mCurrentFrameAnimations that were not run + // as part of the standard RenderNode:prepareTree pass. + ANDROID_API virtual void runRemainingAnimations(TreeInfo& info); + + ANDROID_API virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener); + +private: + friend class AnimationHandle; + void addAnimationHandle(AnimationHandle* handle); + + renderthread::TimeLord& mClock; + + // Animations left to run this frame, at the end of the frame this should + // be null + AnimationHandle mCurrentFrameAnimations; + // Animations queued for next frame + AnimationHandle mNextFrameAnimations; + + nsecs_t mFrameTimeMs; +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* TREEANIMATIONTRACKER_H_ */ diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index 78d569d..da65f38 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -19,6 +19,7 @@ #include <inttypes.h> #include <set> +#include "AnimationContext.h" #include "RenderNode.h" #include "RenderProperties.h" @@ -85,7 +86,7 @@ void BaseRenderNodeAnimator::attach(RenderNode* target) { onAttached(); } -void BaseRenderNodeAnimator::pushStaging(TreeInfo& info) { +void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { if (!mHasStartValue) { doSetStartValue(getValue(mTarget)); } @@ -93,21 +94,24 @@ void BaseRenderNodeAnimator::pushStaging(TreeInfo& info) { mPlayState = mStagingPlayState; // Oh boy, we're starting! Man the battle stations! if (mPlayState == RUNNING) { - transitionToRunning(info); + transitionToRunning(context); + } else if (mPlayState == FINISHED) { + callOnFinishedListener(context); } } } -void BaseRenderNodeAnimator::transitionToRunning(TreeInfo& info) { - LOG_ALWAYS_FATAL_IF(info.frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", info.frameTimeMs); +void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) { + nsecs_t frameTimeMs = context.frameTimeMs(); + LOG_ALWAYS_FATAL_IF(frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", frameTimeMs); if (mStartDelay < 0 || mStartDelay > 50000) { ALOGW("Your start delay is strange and confusing: %" PRId64, mStartDelay); } - mStartTime = info.frameTimeMs + mStartDelay; + mStartTime = frameTimeMs + mStartDelay; if (mStartTime < 0) { ALOGW("Ended up with a really weird start time of %" PRId64 " with frame time %" PRId64 " and start delay %" PRId64, - mStartTime, info.frameTimeMs, mStartDelay); + mStartTime, frameTimeMs, mStartDelay); // Set to 0 so that the animate() basically instantly finishes mStartTime = 0; } @@ -120,7 +124,7 @@ void BaseRenderNodeAnimator::transitionToRunning(TreeInfo& info) { } } -bool BaseRenderNodeAnimator::animate(TreeInfo& info) { +bool BaseRenderNodeAnimator::animate(AnimationContext& context) { if (mPlayState < RUNNING) { return false; } @@ -132,15 +136,14 @@ bool BaseRenderNodeAnimator::animate(TreeInfo& info) { // because the staging properties reflect the final value, we always need // to call setValue even if the animation isn't yet running or is still // being delayed as we need to override the staging value - if (mStartTime > info.frameTimeMs) { - info.out.hasAnimations |= true; + if (mStartTime > context.frameTimeMs()) { setValue(mTarget, mFromValue); return false; } float fraction = 1.0f; if (mPlayState == RUNNING && mDuration > 0) { - fraction = (float)(info.frameTimeMs - mStartTime) / mDuration; + fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration; } if (fraction >= 1.0f) { fraction = 1.0f; @@ -151,21 +154,16 @@ bool BaseRenderNodeAnimator::animate(TreeInfo& info) { setValue(mTarget, mFromValue + (mDeltaValue * fraction)); if (mPlayState == FINISHED) { - callOnFinishedListener(info); + callOnFinishedListener(context); return true; } - info.out.hasAnimations |= true; return false; } -void BaseRenderNodeAnimator::callOnFinishedListener(TreeInfo& info) { +void BaseRenderNodeAnimator::callOnFinishedListener(AnimationContext& context) { if (mListener.get()) { - if (!info.animationHook) { - mListener->onAnimationFinished(this); - } else { - info.animationHook->callOnFinished(this, mListener.get()); - } + context.callOnFinished(this, mListener.get()); } } diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h index 6dfe7b4..c52a93f 100644 --- a/libs/hwui/Animator.h +++ b/libs/hwui/Animator.h @@ -28,6 +28,8 @@ namespace android { namespace uirenderer { +class AnimationContext; +class BaseRenderNodeAnimator; class RenderNode; class RenderProperties; @@ -50,15 +52,17 @@ public: ANDROID_API void setListener(AnimationListener* listener) { mListener = listener; } + AnimationListener* listener() { return mListener.get(); } ANDROID_API void start() { mStagingPlayState = RUNNING; onStagingPlayStateChanged(); } ANDROID_API void end() { mStagingPlayState = FINISHED; onStagingPlayStateChanged(); } void attach(RenderNode* target); virtual void onAttached() {} void detach() { mTarget = 0; } - void pushStaging(TreeInfo& info); - bool animate(TreeInfo& info); + void pushStaging(AnimationContext& context); + bool animate(AnimationContext& context); + bool isRunning() { return mPlayState == RUNNING; } bool isFinished() { return mPlayState == FINISHED; } float finalValue() { return mFinalValue; } @@ -72,7 +76,7 @@ protected: virtual void setValue(RenderNode* target, float value) = 0; RenderNode* target() { return mTarget; } - void callOnFinishedListener(TreeInfo& info); + void callOnFinishedListener(AnimationContext& context); virtual void onStagingPlayStateChanged() {} @@ -100,7 +104,7 @@ protected: private: inline void checkMutable(); - virtual void transitionToRunning(TreeInfo& info); + virtual void transitionToRunning(AnimationContext& context); void doSetStartValue(float value); }; diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp index 7221295a4..678b1ee 100644 --- a/libs/hwui/AnimatorManager.cpp +++ b/libs/hwui/AnimatorManager.cpp @@ -17,6 +17,7 @@ #include <algorithm> +#include "AnimationContext.h" #include "RenderNode.h" namespace android { @@ -30,7 +31,8 @@ static void unref(BaseRenderNodeAnimator* animator) { } AnimatorManager::AnimatorManager(RenderNode& parent) - : mParent(parent) { + : mParent(parent) + , mAnimationHandle(NULL) { } AnimatorManager::~AnimatorManager() { @@ -44,6 +46,11 @@ void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) { mNewAnimators.push_back(animator.get()); } +void AnimatorManager::setAnimationHandle(AnimationHandle* handle) { + LOG_ALWAYS_FATAL_IF(mAnimationHandle && handle, "Already have an AnimationHandle!"); + mAnimationHandle = handle; +} + template<typename T> static void move_all(T& source, T& dest) { dest.reserve(source.size() + dest.size()); @@ -53,26 +60,30 @@ static void move_all(T& source, T& dest) { source.clear(); } -void AnimatorManager::pushStaging(TreeInfo& info) { +void AnimatorManager::pushStaging() { if (mNewAnimators.size()) { // Since this is a straight move, we don't need to inc/dec the ref count move_all(mNewAnimators, mAnimators); } for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) { - (*it)->pushStaging(info); + (*it)->pushStaging(mAnimationHandle->context()); } } class AnimateFunctor { public: - AnimateFunctor(RenderNode& target, TreeInfo& info) - : dirtyMask(0), mTarget(target), mInfo(info) {} + AnimateFunctor(TreeInfo& info, AnimationContext& context) + : dirtyMask(0), mInfo(info), mContext(context) {} bool operator() (BaseRenderNodeAnimator* animator) { dirtyMask |= animator->dirtyMask(); - bool remove = animator->animate(mInfo); + bool remove = animator->animate(mContext); if (remove) { animator->decStrong(0); + } else { + if (animator->isRunning()) { + mInfo.out.hasAnimations = true; + } } return remove; } @@ -80,8 +91,8 @@ public: uint32_t dirtyMask; private: - RenderNode& mTarget; TreeInfo& mInfo; + AnimationContext& mContext; }; uint32_t AnimatorManager::animate(TreeInfo& info) { @@ -93,17 +104,72 @@ uint32_t AnimatorManager::animate(TreeInfo& info) { mParent.damageSelf(info); info.damageAccumulator->popTransform(); - AnimateFunctor functor(mParent, info); - std::vector< BaseRenderNodeAnimator* >::iterator newEnd; - newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor); - mAnimators.erase(newEnd, mAnimators.end()); + uint32_t dirty = animateCommon(info); mParent.mProperties.updateMatrix(); info.damageAccumulator->pushTransform(&mParent); mParent.damageSelf(info); + return dirty; +} + +void AnimatorManager::animateNoDamage(TreeInfo& info) { + if (!mAnimators.size()) return; + + animateCommon(info); +} + +uint32_t AnimatorManager::animateCommon(TreeInfo& info) { + AnimateFunctor functor(info, mAnimationHandle->context()); + std::vector< BaseRenderNodeAnimator* >::iterator newEnd; + newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor); + mAnimators.erase(newEnd, mAnimators.end()); + mAnimationHandle->notifyAnimationsRan(); return functor.dirtyMask; } +class EndAnimatorsFunctor { +public: + EndAnimatorsFunctor(AnimationContext& context) : mContext(context) {} + + void operator() (BaseRenderNodeAnimator* animator) { + animator->end(); + animator->pushStaging(mContext); + animator->animate(mContext); + animator->decStrong(0); + } + +private: + AnimationContext& mContext; +}; + +static void endAnimatorsHard(BaseRenderNodeAnimator* animator) { + animator->end(); + if (animator->listener()) { + animator->listener()->onAnimationFinished(animator); + } + animator->decStrong(0); +} + +void AnimatorManager::endAllAnimators() { + if (mNewAnimators.size()) { + // Since this is a straight move, we don't need to inc/dec the ref count + move_all(mNewAnimators, mAnimators); + } + // First try gracefully ending them + if (mAnimationHandle) { + EndAnimatorsFunctor functor(mAnimationHandle->context()); + for_each(mAnimators.begin(), mAnimators.end(), functor); + mAnimators.clear(); + mAnimationHandle->release(); + } else { + // We have no context, so bust out the sledgehammer + // This works because this state can only happen on the UI thread, + // which means we're already on the right thread to invoke listeners + for_each(mAnimators.begin(), mAnimators.end(), endAnimatorsHard); + mAnimators.clear(); + } +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h index 0d177c5..d5f56ed 100644 --- a/libs/hwui/AnimatorManager.h +++ b/libs/hwui/AnimatorManager.h @@ -27,6 +27,7 @@ namespace android { namespace uirenderer { +class AnimationHandle; class BaseRenderNodeAnimator; class RenderNode; @@ -39,12 +40,26 @@ public: void addAnimator(const sp<BaseRenderNodeAnimator>& animator); - void pushStaging(TreeInfo& info); + void setAnimationHandle(AnimationHandle* handle); + bool hasAnimationHandle() { return mAnimationHandle; } + + void pushStaging(); + // Returns the combined dirty mask of all animators run uint32_t animate(TreeInfo& info); + void animateNoDamage(TreeInfo& info); + + // Hard-ends all animators. Used for cleanup if the root is being destroyed. + ANDROID_API void endAllAnimators(); + + bool hasAnimators() { return mAnimators.size(); } + private: + uint32_t animateCommon(TreeInfo& info); + RenderNode& mParent; + AnimationHandle* mAnimationHandle; // To improve the efficiency of resizing & removing from the vector // use manual ref counting instead of sp<>. diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index 8818510..5ff7b7f 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -1528,8 +1528,7 @@ public: virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { TessellationCache::vertexBuffer_pair_t buffers; - Matrix4 drawTransform; - renderer.getMatrix(&drawTransform); + Matrix4 drawTransform(*(renderer.currentTransform())); renderer.getCaches().tessellationCache.getShadowBuffers(&drawTransform, renderer.getLocalClipBounds(), isCasterOpaque(), mCasterOutline, &mTransformXY, &mTransformZ, renderer.getLightCenter(), renderer.getLightRadius(), diff --git a/libs/hwui/IContextFactory.h b/libs/hwui/IContextFactory.h new file mode 100644 index 0000000..463b55e --- /dev/null +++ b/libs/hwui/IContextFactory.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CONTEXTFACTORY_H_ +#define CONTEXTFACTORY_H_ + +namespace android { +namespace uirenderer { + +namespace renderthread { +class TimeLord; +} + +class AnimationContext; + +class IContextFactory { +public: + virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) = 0; + +protected: + virtual ~IContextFactory() {} +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* CONTEXTFACTORY_H_ */ diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 658265d..6a92a6e 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -34,6 +34,7 @@ #include "LayerRenderer.h" #include "OpenGLRenderer.h" #include "utils/MathUtils.h" +#include "renderthread/CanvasContext.h" namespace android { namespace uirenderer { @@ -208,6 +209,13 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { if (info.renderer && mLayer->deferredUpdateScheduled) { info.renderer->pushLayerUpdate(mLayer); } + + if (CC_UNLIKELY(info.canvasContext)) { + // If canvasContext is not null that means there are prefetched layers + // that need to be accounted for. That might be us, so tell CanvasContext + // that this layer is in the tree and should not be destroyed. + info.canvasContext->markLayerInUse(this); + } } void RenderNode::prepareTreeImpl(TreeInfo& info) { @@ -235,7 +243,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { // before properties() is trampled by stagingProperties(), as they are // required by some animators. if (CC_LIKELY(info.runAnimations)) { - mAnimatorManager.pushStaging(info); + mAnimatorManager.pushStaging(); } if (mDirtyPropertyFields) { mDirtyPropertyFields = 0; @@ -653,41 +661,12 @@ void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler(shadowOp, PROPERTY_SAVECOUNT, properties().getClipToBounds()); } -template <class T> -int RenderNode::issueOperationsOfNegZChildren( - const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, - OpenGLRenderer& renderer, T& handler) { - if (zTranslatedNodes.isEmpty()) return -1; - - // create a save around the body of the ViewGroup's draw method, so that - // matrix/clip methods don't affect composited children - int shadowSaveCount = renderer.getSaveCount(); - handler(new (handler.allocator()) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag), - PROPERTY_SAVECOUNT, properties().getClipToBounds()); - - issueOperationsOf3dChildren(zTranslatedNodes, kNegativeZChildren, renderer, handler); - return shadowSaveCount; -} - -template <class T> -void RenderNode::issueOperationsOfPosZChildren(int shadowRestoreTo, - const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, - OpenGLRenderer& renderer, T& handler) { - if (zTranslatedNodes.isEmpty()) return; - - LOG_ALWAYS_FATAL_IF(shadowRestoreTo < 0, "invalid save to restore to"); - handler(new (handler.allocator()) RestoreToCountOp(shadowRestoreTo), - PROPERTY_SAVECOUNT, properties().getClipToBounds()); - renderer.setOverrideLayerAlpha(1.0f); - - issueOperationsOf3dChildren(zTranslatedNodes, kPositiveZChildren, renderer, handler); -} - #define SHADOW_DELTA 0.1f template <class T> -void RenderNode::issueOperationsOf3dChildren(const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, - ChildrenSelectMode mode, OpenGLRenderer& renderer, T& handler) { +void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, + const Matrix4& initialTransform, const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, + OpenGLRenderer& renderer, T& handler) { const int size = zTranslatedNodes.size(); if (size == 0 || (mode == kNegativeZChildren && zTranslatedNodes[0].key > 0.0f) @@ -696,6 +675,11 @@ void RenderNode::issueOperationsOf3dChildren(const Vector<ZDrawRenderNodeOpPair> return; } + // Apply the base transform of the parent of the 3d children. This isolates + // 3d children of the current chunk from transformations made in previous chunks. + int rootRestoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag); + renderer.setMatrix(initialTransform); + /** * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters * with very similar Z heights to draw together. @@ -750,6 +734,7 @@ void RenderNode::issueOperationsOf3dChildren(const Vector<ZDrawRenderNodeOpPair> renderer.restoreToCount(restoreTo); drawIndex++; } + renderer.restoreToCount(rootRestoreTo); } template <class T> @@ -869,6 +854,8 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { bool quickRejected = properties().getClipToBounds() && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight()); if (!quickRejected) { + Matrix4 initialTransform(*(renderer.currentTransform())); + if (drawLayer) { handler(new (alloc) DrawLayerOp(mLayer, 0, 0), renderer.getSaveCount() - 1, properties().getClipToBounds()); @@ -880,9 +867,9 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { Vector<ZDrawRenderNodeOpPair> zTranslatedNodes; buildZSortedChildList(chunk, zTranslatedNodes); - // for 3d root, draw children with negative z values - int shadowRestoreTo = issueOperationsOfNegZChildren(zTranslatedNodes, - renderer, handler); + issueOperationsOf3dChildren(kNegativeZChildren, + initialTransform, zTranslatedNodes, renderer, handler); + const int saveCountOffset = renderer.getSaveCount() - 1; const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex; @@ -899,8 +886,8 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { } } - // for 3d root, draw children with positive z values - issueOperationsOfPosZChildren(shadowRestoreTo, zTranslatedNodes, renderer, handler); + issueOperationsOf3dChildren(kPositiveZChildren, + initialTransform, zTranslatedNodes, renderer, handler); } } } diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 18402b2..d897997 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -174,6 +174,8 @@ public: // UI thread only! ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator); + AnimatorManager& animators() { return mAnimatorManager; } + void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const; private: @@ -206,16 +208,9 @@ private: inline void issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler); template <class T> - inline int issueOperationsOfNegZChildren( - const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, - OpenGLRenderer& renderer, T& handler); - template <class T> - inline void issueOperationsOfPosZChildren(int shadowRestoreTo, - const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, + inline void issueOperationsOf3dChildren(ChildrenSelectMode mode, + const Matrix4& initialTransform, const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, OpenGLRenderer& renderer, T& handler); - template <class T> - inline void issueOperationsOf3dChildren(const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, - ChildrenSelectMode mode, OpenGLRenderer& renderer, T& handler); template <class T> inline void issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& handler); diff --git a/libs/hwui/StatefulBaseRenderer.cpp b/libs/hwui/StatefulBaseRenderer.cpp index bdac47b..3e1aed3 100644 --- a/libs/hwui/StatefulBaseRenderer.cpp +++ b/libs/hwui/StatefulBaseRenderer.cpp @@ -102,10 +102,6 @@ void StatefulBaseRenderer::restoreToCount(int saveCount) { // Matrix /////////////////////////////////////////////////////////////////////////////// -void StatefulBaseRenderer::getMatrix(Matrix4* matrix) const { - matrix->load(*(mSnapshot->transform)); -} - void StatefulBaseRenderer::getMatrix(SkMatrix* matrix) const { mSnapshot->transform->copyTo(*matrix); } diff --git a/libs/hwui/StatefulBaseRenderer.h b/libs/hwui/StatefulBaseRenderer.h index 3957d36..c6974b4 100644 --- a/libs/hwui/StatefulBaseRenderer.h +++ b/libs/hwui/StatefulBaseRenderer.h @@ -69,7 +69,6 @@ public: // int alpha, SkXfermode::Mode mode, int flags); // Matrix - void getMatrix(Matrix4* outMatrix) const; virtual void getMatrix(SkMatrix* outMatrix) const; virtual void translate(float dx, float dy, float dz = 0.0f); virtual void rotate(float degrees); @@ -100,6 +99,10 @@ public: void setClippingRoundRect(LinearAllocator& allocator, const Rect& rect, float radius); + inline const mat4* currentTransform() const { + return mSnapshot->transform; + } + protected: const Rect& getRenderTargetClipBounds() const { return mSnapshot->getRenderTargetClip(); } @@ -134,10 +137,6 @@ protected: return mSnapshot->clipRect; } - inline const mat4* currentTransform() const { - return mSnapshot->transform; - } - inline const Snapshot* currentSnapshot() const { return mSnapshot != NULL ? mSnapshot.get() : mFirstSnapshot.get(); } diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index 74d52a3..ae6ea94 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -26,18 +26,13 @@ namespace android { namespace uirenderer { -class BaseRenderNodeAnimator; -class AnimationListener; +namespace renderthread { +class CanvasContext; +} + class OpenGLRenderer; class RenderState; -class AnimationHook { -public: - virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) = 0; -protected: - ~AnimationHook() {} -}; - class ErrorHandler { public: virtual void onError(const std::string& message) = 0; @@ -62,31 +57,27 @@ public: explicit TreeInfo(TraversalMode mode, RenderState& renderState) : mode(mode) - , frameTimeMs(0) - , animationHook(NULL) , prepareTextures(mode == MODE_FULL) , runAnimations(true) , damageAccumulator(NULL) , renderState(renderState) , renderer(NULL) , errorHandler(NULL) + , canvasContext(NULL) {} explicit TreeInfo(TraversalMode mode, const TreeInfo& clone) : mode(mode) - , frameTimeMs(clone.frameTimeMs) - , animationHook(clone.animationHook) , prepareTextures(mode == MODE_FULL) , runAnimations(clone.runAnimations) , damageAccumulator(clone.damageAccumulator) , renderState(clone.renderState) , renderer(clone.renderer) , errorHandler(clone.errorHandler) + , canvasContext(clone.canvasContext) {} const TraversalMode mode; - nsecs_t frameTimeMs; - AnimationHook* animationHook; // TODO: Remove this? Currently this is used to signal to stop preparing // textures if we run out of cache space. bool prepareTextures; @@ -104,6 +95,8 @@ public: // layer updates or similar. May be NULL. OpenGLRenderer* renderer; ErrorHandler* errorHandler; + // TODO: Remove this? May be NULL + renderthread::CanvasContext* canvasContext; struct Out { Out() diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 4bf5a8a..491a295 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -16,11 +16,13 @@ #include "CanvasContext.h" +#include <algorithm> #include <private/hwui/DrawGlInfo.h> #include <strings.h> #include "EglManager.h" #include "RenderThread.h" +#include "../AnimationContext.h" #include "../Caches.h" #include "../DeferredLayerUpdater.h" #include "../RenderState.h" @@ -35,7 +37,8 @@ namespace android { namespace uirenderer { namespace renderthread { -CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode) +CanvasContext::CanvasContext(RenderThread& thread, bool translucent, + RenderNode* rootRenderNode, IContextFactory* contextFactory) : mRenderThread(thread) , mEglManager(thread.eglManager()) , mEglSurface(EGL_NO_SURFACE) @@ -44,11 +47,14 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* , mCanvas(NULL) , mHaveNewSurface(false) , mRootRenderNode(rootRenderNode) { + mAnimationContext = contextFactory->createAnimationContext(mRenderThread.timeLord()); } CanvasContext::~CanvasContext() { destroyCanvasAndSurface(); mRenderThread.removeFrameCallback(this); + delete mAnimationContext; + freePrefetechedLayers(); } void CanvasContext::destroyCanvasAndSurface() { @@ -136,10 +142,18 @@ void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) { void CanvasContext::prepareTree(TreeInfo& info) { mRenderThread.removeFrameCallback(this); - info.frameTimeMs = mRenderThread.timeLord().frameTimeMs(); info.damageAccumulator = &mDamageAccumulator; info.renderer = mCanvas; + if (mPrefetechedLayers.size() && info.mode == TreeInfo::MODE_FULL) { + info.canvasContext = this; + } + mAnimationContext->startFrame(); mRootRenderNode->prepareTree(info); + mAnimationContext->runRemainingAnimations(info); + + if (info.canvasContext) { + freePrefetechedLayers(); + } int runningBehind = 0; // TODO: This query is moderately expensive, investigate adding some sort @@ -244,6 +258,26 @@ void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) { thread.renderState().invokeFunctor(functor, mode, NULL); } +void CanvasContext::markLayerInUse(RenderNode* node) { + if (mPrefetechedLayers.erase(node)) { + node->decStrong(0); + } +} + +static void destroyPrefetechedNode(RenderNode* node) { + ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...", node->getName()); + node->destroyHardwareResources(); + node->decStrong(0); +} + +void CanvasContext::freePrefetechedLayers() { + if (mPrefetechedLayers.size()) { + requireGlContext(); + std::for_each(mPrefetechedLayers.begin(), mPrefetechedLayers.end(), destroyPrefetechedNode); + mPrefetechedLayers.clear(); + } +} + void CanvasContext::buildLayer(RenderNode* node) { ATRACE_CALL(); if (!mEglManager.hasEglContext() || !mCanvas) { @@ -254,7 +288,6 @@ void CanvasContext::buildLayer(RenderNode* node) { stopDrawing(); TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState()); - info.frameTimeMs = mRenderThread.timeLord().frameTimeMs(); info.damageAccumulator = &mDamageAccumulator; info.renderer = mCanvas; info.runAnimations = false; @@ -266,6 +299,9 @@ void CanvasContext::buildLayer(RenderNode* node) { node->setPropertyFieldsDirty(RenderNode::GENERIC); mCanvas->flushLayerUpdates(); + + node->incStrong(0); + mPrefetechedLayers.insert(node); } bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { @@ -278,6 +314,7 @@ void CanvasContext::destroyHardwareResources() { stopDrawing(); if (mEglManager.hasEglContext()) { requireGlContext(); + freePrefetechedLayers(); mRootRenderNode->destroyHardwareResources(); Caches::getInstance().flush(Caches::kFlushMode_Layers); } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 0cbed6f..7c27190 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -17,6 +17,8 @@ #ifndef CANVASCONTEXT_H_ #define CANVASCONTEXT_H_ +#include <set> + #include <cutils/compiler.h> #include <EGL/egl.h> #include <SkBitmap.h> @@ -25,6 +27,7 @@ #include "../DamageAccumulator.h" #include "../DrawProfiler.h" +#include "../IContextFactory.h" #include "../RenderNode.h" #include "RenderTask.h" #include "RenderThread.h" @@ -34,6 +37,7 @@ namespace android { namespace uirenderer { +class AnimationContext; class DeferredLayerUpdater; class OpenGLRenderer; class Rect; @@ -45,9 +49,11 @@ class EglManager; // This per-renderer class manages the bridge between the global EGL context // and the render surface. +// TODO: Rename to Renderer or some other per-window, top-level manager class CanvasContext : public IFrameCallback { public: - CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode); + CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, + IContextFactory* contextFactory); virtual ~CanvasContext(); bool initialize(ANativeWindow* window); @@ -67,6 +73,7 @@ public: void buildLayer(RenderNode* node); bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); + void markLayerInUse(RenderNode* node); void destroyHardwareResources(); static void trimMemory(RenderThread& thread, int level); @@ -95,6 +102,8 @@ private: void requireGlContext(); + void freePrefetechedLayers(); + RenderThread& mRenderThread; EglManager& mEglManager; sp<ANativeWindow> mNativeWindow; @@ -105,10 +114,13 @@ private: OpenGLRenderer* mCanvas; bool mHaveNewSurface; DamageAccumulator mDamageAccumulator; + AnimationContext* mAnimationContext; const sp<RenderNode> mRootRenderNode; DrawProfiler mProfiler; + + std::set<RenderNode*> mPrefetechedLayers; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 37f8e60..e030cdb 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "EglContext" - #include "EglManager.h" #include <cutils/log.h> diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 405ce24..3d04316 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -52,17 +52,20 @@ namespace renderthread { MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \ ARGS(method) *args = (ARGS(method) *) task->payload() -CREATE_BRIDGE3(createContext, RenderThread* thread, bool translucent, RenderNode* rootRenderNode) { - return new CanvasContext(*args->thread, args->translucent, args->rootRenderNode); +CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent, + RenderNode* rootRenderNode, IContextFactory* contextFactory) { + return new CanvasContext(*args->thread, args->translucent, + args->rootRenderNode, args->contextFactory); } -RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode) +RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) : mRenderThread(RenderThread::getInstance()) , mContext(0) { SETUP_TASK(createContext); args->translucent = translucent; args->rootRenderNode = rootRenderNode; args->thread = &mRenderThread; + args->contextFactory = contextFactory; mContext = (CanvasContext*) postAndWait(task); mDrawFrameTask.setContext(&mRenderThread, mContext); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index eea3674..9e6bcf5 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -30,6 +30,7 @@ #include <utils/Vector.h> #include "../Caches.h" +#include "../IContextFactory.h" #include "DrawFrameTask.h" namespace android { @@ -58,7 +59,7 @@ class RenderProxyBridge; */ class ANDROID_API RenderProxy { public: - ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode); + ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode, IContextFactory* contextFactory); ANDROID_API virtual ~RenderProxy(); ANDROID_API void setFrameInterval(nsecs_t frameIntervalNanos); diff --git a/libs/hwui/renderthread/TimeLord.cpp b/libs/hwui/renderthread/TimeLord.cpp index 758d96e..cf3d039 100644 --- a/libs/hwui/renderthread/TimeLord.cpp +++ b/libs/hwui/renderthread/TimeLord.cpp @@ -30,7 +30,7 @@ void TimeLord::vsyncReceived(nsecs_t vsync) { } } -nsecs_t TimeLord::frameTimeMs() { +nsecs_t TimeLord::computeFrameTimeMs() { // Logic copied from Choreographer.java nsecs_t now = systemTime(CLOCK_MONOTONIC); nsecs_t jitterNanos = now - mFrameTimeNanos; diff --git a/libs/hwui/renderthread/TimeLord.h b/libs/hwui/renderthread/TimeLord.h index 52c6d9e..8b0372c 100644 --- a/libs/hwui/renderthread/TimeLord.h +++ b/libs/hwui/renderthread/TimeLord.h @@ -30,7 +30,7 @@ class TimeLord { public: void setFrameInterval(nsecs_t intervalNanos) { mFrameIntervalNanos = intervalNanos; } void vsyncReceived(nsecs_t vsync); - nsecs_t frameTimeMs(); + nsecs_t computeFrameTimeMs(); private: friend class RenderThread; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e69c456..e2770b4 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1688,11 +1688,19 @@ public class AudioManager { /** * Return a new audio session identifier not associated with any player or effect. - * It can for instance be used to create one of the {@link android.media.audiofx.AudioEffect} - * objects or specify a session for speech synthesis in - * {@link android.speech.tts.TextToSpeech.Engine}. + * An audio session identifier is a system wide unique identifier for a set of audio streams + * (one or more mixed together). + * <p>The primary use of the audio session ID is to associate audio effects to audio players, + * such as {@link MediaPlayer} or {@link AudioTrack}: all audio effects sharing the same audio + * session ID will be applied to the mixed audio content of the players that share the same + * audio session. + * <p>This method can for instance be used when creating one of the + * {@link android.media.audiofx.AudioEffect} objects to define the audio session of the effect, + * or to specify a session for a speech synthesis utterance + * in {@link android.speech.tts.TextToSpeech.Engine}. * @return a new unclaimed and unused audio session identifier, or {@link #ERROR} when the - * system failed to generate a new session. + * system failed to generate a new session, a condition in which audio playback or recording + * will subsequently fail as well. */ public int generateAudioSessionId() { int session = AudioSystem.newAudioSessionId(); diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index dae539b..d002924 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -200,6 +200,7 @@ public class AudioService extends IAudioService.Stub { private static final int MSG_UNLOAD_SOUND_EFFECTS = 20; private static final int MSG_SYSTEM_READY = 21; private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22; + private static final int MSG_PERSIST_MICROPHONE_MUTE = 23; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) @@ -873,6 +874,10 @@ public class AudioService extends IAudioService.Stub { AudioSystem.setMasterMute(masterMute); broadcastMasterMuteStatus(masterMute); + boolean microphoneMute = + System.getIntForUser(cr, System.MICROPHONE_MUTE, 0, UserHandle.USER_CURRENT) == 1; + AudioSystem.muteMicrophone(microphoneMute); + // Each stream will read its own persisted settings // Broadcast the sticky intent @@ -1447,17 +1452,15 @@ public class AudioService extends IAudioService.Stub { if (mUseFixedVolume) { return; } - if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } - if (state != AudioSystem.getMasterMute()) { AudioSystem.setMasterMute(state); // Post a persist master volume msg sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, state ? 1 - : 0, 0, null, PERSIST_DELAY); + : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY); sendMasterMuteUpdate(state, flags); } } @@ -1563,6 +1566,9 @@ public class AudioService extends IAudioService.Stub { } AudioSystem.muteMicrophone(on); + // Post a persist microphone msg. + sendMsg(mAudioHandler, MSG_PERSIST_MICROPHONE_MUTE, SENDMSG_REPLACE, on ? 1 + : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY); } /** @see AudioManager#getRingerMode() */ @@ -3486,13 +3492,28 @@ public class AudioService extends IAudioService.Stub { private void dump(PrintWriter pw) { pw.print(" Mute count: "); pw.println(muteCount()); + pw.print(" Max: "); + pw.println((mIndexMax + 5) / 10); pw.print(" Current: "); Set set = mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); - pw.print(Integer.toHexString(((Integer)entry.getKey()).intValue()) - + ": " + ((((Integer)entry.getValue()).intValue() + 5) / 10)+", "); + final int device = (Integer) entry.getKey(); + pw.print(Integer.toHexString(device)); + final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default" + : AudioSystem.getOutputDeviceName(device); + if (!deviceName.isEmpty()) { + pw.print(" ("); + pw.print(deviceName); + pw.print(")"); + } + pw.print(": "); + final int index = (((Integer) entry.getValue()) + 5) / 10; + pw.print(index); + if (i.hasNext()) { + pw.print(", "); + } } } } @@ -3819,7 +3840,6 @@ public class AudioService extends IAudioService.Stub { @Override public void handleMessage(Message msg) { - switch (msg.what) { case MSG_SET_DEVICE_VOLUME: @@ -3851,7 +3871,7 @@ public class AudioService extends IAudioService.Stub { Settings.System.putIntForUser(mContentResolver, Settings.System.VOLUME_MASTER_MUTE, msg.arg1, - UserHandle.USER_CURRENT); + msg.arg2); break; case MSG_PERSIST_RINGER_MODE: @@ -4046,6 +4066,12 @@ public class AudioService extends IAudioService.Stub { Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs, UserHandle.USER_CURRENT); break; + case MSG_PERSIST_MICROPHONE_MUTE: + Settings.System.putIntForUser(mContentResolver, + Settings.System.MICROPHONE_MUTE, + msg.arg1, + msg.arg2); + break; } } } @@ -4290,7 +4316,8 @@ public class AudioService extends IAudioService.Stub { if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) { int devices = 0; for (int dev : mConnectedDevices.keySet()) { - if ((dev & mBecomingNoisyIntentDevices) != 0) { + if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) && + ((dev & mBecomingNoisyIntentDevices) != 0)) { devices |= dev; } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index e11aab1..9a76f94 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -371,6 +371,7 @@ public class AudioSystem public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc"; public static final String DEVICE_OUT_SPDIF_NAME = "spdif"; public static final String DEVICE_OUT_FM_NAME = "fm_transmitter"; + public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line"; public static String getOutputDeviceName(int device) { @@ -417,6 +418,8 @@ public class AudioSystem return DEVICE_OUT_SPDIF_NAME; case DEVICE_OUT_FM: return DEVICE_OUT_FM_NAME; + case DEVICE_OUT_AUX_LINE: + return DEVICE_OUT_AUX_LINE_NAME; case DEVICE_OUT_DEFAULT: default: return ""; diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index 2856edb..522e45d 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -123,7 +123,7 @@ public abstract class Image implements AutoCloseable { */ public abstract long getTimestamp(); - protected Rect mCropRect; + private Rect mCropRect; /** * Get the crop rectangle associated with this frame. diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 96e6ab9..032f07f 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -665,7 +665,7 @@ final public class MediaCodec { * Thrown when an internal codec error occurs. */ public final static class CodecException extends IllegalStateException { - public CodecException(int errorCode, int actionCode, String detailMessage) { + CodecException(int errorCode, int actionCode, String detailMessage) { super(detailMessage); mErrorCode = errorCode; mActionCode = actionCode; @@ -1729,7 +1729,7 @@ final public class MediaCodec { if (cropRect != null) { cropRect.offset(-xOffset, -yOffset); } - mCropRect = cropRect; + setCropRect(cropRect); // save offsets and info mXOffset = xOffset; diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index acb2186..323a3e3 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -70,7 +70,7 @@ public final class MediaCodecInfo { mIsEncoder = isEncoder; mCaps = new HashMap<String, CodecCapabilities>(); for (CodecCapabilities c: caps) { - mCaps.put(c.getMime(), c); + mCaps.put(c.getMimeType(), c); } } @@ -98,6 +98,43 @@ public final class MediaCodecInfo { return types; } + private static int checkPowerOfTwo(int value, String message) { + if ((value & (value - 1)) != 0) { + throw new IllegalArgumentException(message); + } + return value; + } + + private static class Feature { + public String mName; + public int mValue; + public boolean mDefault; + public Feature(String name, int value, boolean def) { + mName = name; + mValue = value; + mDefault = def; + } + } + + // COMMON CONSTANTS + private static final Range<Integer> POSITIVE_INTEGERS = + Range.create(1, Integer.MAX_VALUE); + private static final Range<Long> POSITIVE_LONGS = + Range.create(1l, Long.MAX_VALUE); + private static final Range<Rational> POSITIVE_RATIONALS = + Range.create(new Rational(1, Integer.MAX_VALUE), + new Rational(Integer.MAX_VALUE, 1)); + private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768); + private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960); + + // found stuff that is not supported by framework (=> this should not happen) + private static final int ERROR_UNRECOGNIZED = (1 << 0); + // found profile/level for which we don't have capability estimates + private static final int ERROR_UNSUPPORTED = (1 << 1); + // have not found any profile/level for which we don't have capability estimate + private static final int ERROR_NONE_SUPPORTED = (1 << 2); + + /** * Encapsulates the capabilities of a given codec component. * For example, what profile/level combinations it supports and what colorspaces @@ -222,17 +259,6 @@ public final class MediaCodecInfo { return checkFeature(name, mFlagsRequired); } - private static class Feature { - public String mName; - public int mValue; - public boolean mDefault; - public Feature(String name, int value, boolean def) { - mName = name; - mValue = value; - mDefault = def; - } - } - private static final Feature[] decoderFeatures = { new Feature(FEATURE_AdaptivePlayback, (1 << 0), true), new Feature(FEATURE_SecurePlayback, (1 << 1), false), @@ -312,1669 +338,1645 @@ public final class MediaCodecInfo { return true; } - // errors while reading profile levels - private int mError; - // found stuff that is not supported by framework (=> this should not happen) - private static final int ERROR_UNRECOGNIZED = (1 << 0); - // found profile/level for which we don't have capability estimates - private static final int ERROR_UNSUPPORTED = (1 << 1); - // have not found any profile/level for which we don't have capability estimate - private static final int ERROR_NONE_SUPPORTED = (1 << 2); - - - // UTILITY METHODS - private static final Range<Integer> POSITIVE_INTEGERS = - Range.create(1, Integer.MAX_VALUE); - private static final Range<Long> POSITIVE_LONGS = - Range.create(1l, Long.MAX_VALUE); - private static final Range<Rational> POSITIVE_RATIONALS = - Range.create(new Rational(1, Integer.MAX_VALUE), - new Rational(Integer.MAX_VALUE, 1)); - private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768); - private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960); + // errors while reading profile levels - accessed from sister capabilities + int mError; private static final String TAG = "CodecCapabilities"; // NEW-STYLE CAPABILITIES + private AudioCapabilities mAudioCaps; + private VideoCapabilities mVideoCaps; + private EncoderCapabilities mEncoderCaps; + private MediaFormat mDefaultFormat; /** * Returns a MediaFormat object with default values for configurations that have * defaults. */ - public final MediaFormat getDefaultFormat() { + public MediaFormat getDefaultFormat() { return mDefaultFormat; } - private MediaFormat mDefaultFormat; /** * Returns the mime type for which this codec-capability object was created. */ - public final String getMime() { + public String getMimeType() { return mMime; } + private boolean isAudio() { + return mAudioCaps != null; + } + /** - * Returns the encoding capabilities or {@code null} if this is not an encoder. + * Returns the audio capabilities or {@code null} if this is not an audio codec. */ - public final EncoderCapabilities getEncoderCapabilities() { - return mEncoderCaps; + public AudioCapabilities getAudioCapabilities() { + return mAudioCaps; } - private EncoderCapabilities mEncoderCaps; private boolean isEncoder() { return mEncoderCaps != null; } /** - * A class that supports querying the encoding capabilities of a codec. + * Returns the encoding capabilities or {@code null} if this is not an encoder. */ - public static final class EncoderCapabilities { - /** - * Returns the supported range of quality values. - */ - public final Range<Integer> getQualityRange() { - return mQualityRange; - } - - /** - * Returns the supported range of encoder complexity values. - * <p> - * Some codecs may support multiple complexity levels, where higher - * complexity values use more encoder tools (e.g. perform more - * intensive calculations) to improve the quality or the compression - * ratio. Use a lower value to save power and/or time. - */ - public final Range<Integer> getComplexityRange() { - return mComplexityRange; - } + public EncoderCapabilities getEncoderCapabilities() { + return mEncoderCaps; + } - /** Constant quality mode */ - public static final int BITRATE_MODE_CQ = 0; - /** Variable bitrate mode */ - public static final int BITRATE_MODE_VBR = 1; - /** Constant bitrate mode */ - public static final int BITRATE_MODE_CBR = 2; - - private static final Feature[] bitrates = new Feature[] { - new Feature("VBR", BITRATE_MODE_VBR, true), - new Feature("CBR", BITRATE_MODE_CBR, false), - new Feature("CQ", BITRATE_MODE_CQ, false) - }; - - private static int parseBitrateMode(String mode) { - for (Feature feat: bitrates) { - if (feat.mName.equalsIgnoreCase(mode)) { - return feat.mValue; - } - } - return 0; - } + private boolean isVideo() { + return mVideoCaps != null; + } - /** - * Query whether a bitrate mode is supported. - */ - public final boolean isBitrateModeSupported(int mode) { - for (Feature feat: bitrates) { - if (mode == feat.mValue) { - return (mBitControl & (1 << mode)) != 0; - } - } - return false; - } + /** + * Returns the video capabilities or {@code null} if this is not a video codec. + */ + public VideoCapabilities getVideoCapabilities() { + return mVideoCaps; + } - private Range<Integer> mQualityRange; - private Range<Integer> mComplexityRange; - private CodecCapabilities mParent; + /** @hide */ + public CodecCapabilities dup() { + return new CodecCapabilities( + // clone writable arrays + Arrays.copyOf(profileLevels, profileLevels.length), + Arrays.copyOf(colorFormats, colorFormats.length), + isEncoder(), + mFlagsVerified, + mDefaultFormat, + mCapabilitiesInfo); + } - /* no public constructor */ - private EncoderCapabilities() { } + /** + * Retrieve the codec capabilities for a certain {@code mime type}, {@code + * profile} and {@code level}. If the type, or profile-level combination + * is not understood by the framework, it returns null. + */ + public static CodecCapabilities CreateFromProfileLevel( + String mime, int profile, int level) { + CodecProfileLevel pl = new CodecProfileLevel(); + pl.profile = profile; + pl.level = level; + MediaFormat defaultFormat = new MediaFormat(); + defaultFormat.setString(MediaFormat.KEY_MIME, mime); - /** @hide */ - public static EncoderCapabilities create( - MediaFormat info, CodecCapabilities parent) { - EncoderCapabilities caps = new EncoderCapabilities(); - caps.init(info, parent); - return caps; + CodecCapabilities ret = new CodecCapabilities( + new CodecProfileLevel[] { pl }, new int[0], true /* encoder */, + 0 /* flags */, defaultFormat, new MediaFormat() /* info */); + if (ret.mError != 0) { + return null; } + return ret; + } + + /* package private */ CodecCapabilities( + CodecProfileLevel[] profLevs, int[] colFmts, + boolean encoder, int flags, + Map<String, Object>defaultFormatMap, + Map<String, Object>capabilitiesMap) { + this(profLevs, colFmts, encoder, flags, + new MediaFormat(defaultFormatMap), + new MediaFormat(capabilitiesMap)); + } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { - // no support for complexity or quality yet - mParent = parent; - mComplexityRange = Range.create(0, 0); - mQualityRange = Range.create(0, 0); - mBitControl = (1 << BITRATE_MODE_VBR); + private MediaFormat mCapabilitiesInfo; + + /* package private */ CodecCapabilities( + CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, int flags, + MediaFormat defaultFormat, MediaFormat info) { + final Map<String, Object> map = info.getMap(); + profileLevels = profLevs; + colorFormats = colFmts; + mFlagsVerified = flags; + mDefaultFormat = defaultFormat; + mCapabilitiesInfo = info; + mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME); - applyLevelLimits(); - parseFromInfo(info); + if (mMime.toLowerCase().startsWith("audio/")) { + mAudioCaps = AudioCapabilities.create(info, this); + mAudioCaps.setDefaultFormat(mDefaultFormat); + } else if (mMime.toLowerCase().startsWith("video/")) { + mVideoCaps = VideoCapabilities.create(info, this); + } + if (encoder) { + mEncoderCaps = EncoderCapabilities.create(info, this); + mEncoderCaps.setDefaultFormat(mDefaultFormat); } - private void applyLevelLimits() { - String mime = mParent.getMime(); - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { - mComplexityRange = Range.create(0, 8); - mBitControl = (1 << BITRATE_MODE_CQ); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { - mBitControl = (1 << BITRATE_MODE_CBR); + for (Feature feat: getValidFeatures()) { + String key = MediaFormat.KEY_FEATURE_ + feat.mName; + Integer yesNo = (Integer)map.get(key); + if (yesNo == null) { + continue; + } else if (yesNo > 0) { + mFlagsRequired |= feat.mValue; + mDefaultFormat.setInteger(key, 1); + } else { + mFlagsSupported |= feat.mValue; + mDefaultFormat.setInteger(key, 1); } + // TODO restrict features by mFlagsVerified once all codecs reliably verify them } + } + } - private int mBitControl; - private Integer mDefaultComplexity; - private Integer mDefaultQuality; - private String mQualityScale; + /** + * A class that supports querying the audio capabilities of a codec. + */ + public static final class AudioCapabilities { + private static final String TAG = "AudioCapabilities"; + private CodecCapabilities mParent; + private Range<Integer> mBitrateRange; - private void parseFromInfo(MediaFormat info) { - Map<String, Object> map = info.getMap(); + private int[] mSampleRates; + private Range<Integer>[] mSampleRateRanges; + private int mMaxInputChannelCount; - if (info.containsKey("complexity-range")) { - mComplexityRange = Utils - .parseIntRange(info.getString("complexity-range"), mComplexityRange); - // TODO should we limit this to level limits? - } - if (info.containsKey("quality-range")) { - mQualityRange = Utils - .parseIntRange(info.getString("quality-range"), mQualityRange); - } - if (info.containsKey("feature-bitrate-control")) { - for (String mode: info.getString("feature-bitrate-control").split(",")) { - mBitControl |= parseBitrateMode(mode); - } - } + private static final int MAX_INPUT_CHANNEL_COUNT = 30; - try { - mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default")); - } catch (NumberFormatException e) { } + /** + * Returns the range of supported bitrates in bits/second. + */ + public Range<Integer> getBitrateRange() { + return mBitrateRange; + } - try { - mDefaultQuality = Integer.parseInt((String)map.get("quality-default")); - } catch (NumberFormatException e) { } + /** + * Returns the array of supported sample rates if the codec + * supports only discrete values. Otherwise, it returns + * {@code null}. The array is sorted in ascending order. + */ + public int[] getSupportedSampleRates() { + return Arrays.copyOf(mSampleRates, mSampleRates.length); + } - mQualityScale = (String)map.get("quality-scale"); - } + /** + * Returns the array of supported sample rate ranges. The + * array is sorted in ascending order, and the ranges are + * distinct. + */ + public Range<Integer>[] getSupportedSampleRateRanges() { + return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); + } - private boolean supports( - Integer complexity, Integer quality, Integer profile) { - boolean ok = true; - if (ok && complexity != null) { - ok = mComplexityRange.contains(complexity); - } - if (ok && quality != null) { - ok = mQualityRange.contains(quality); - } - if (ok && profile != null) { - for (CodecProfileLevel pl: mParent.profileLevels) { - if (pl.profile == profile) { - profile = null; - break; - } - } - ok = profile == null; - } - return ok; - } + /** + * Returns the maximum number of input channels supported. The codec + * supports any number of channels between 1 and this maximum value. + */ + public int getMaxInputChannelCount() { + return mMaxInputChannelCount; + } - /** @hide */ - public void setDefaultFormat(MediaFormat format) { - // don't list trivial quality/complexity as default for now - if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) - && mDefaultQuality != null) { - format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality); - } - if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower()) - && mDefaultComplexity != null) { - format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity); - } - // bitrates are listed in order of preference - for (Feature feat: bitrates) { - if ((mBitControl & (1 << feat.mValue)) != 0) { - format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue); - break; - } - } - } + /* no public constructor */ + private AudioCapabilities() { } + + /** @hide */ + public static AudioCapabilities create( + MediaFormat info, CodecCapabilities parent) { + AudioCapabilities caps = new AudioCapabilities(); + caps.init(info, parent); + return caps; + } + + /** @hide */ + public void init(MediaFormat info, CodecCapabilities parent) { + mParent = parent; + initWithPlatformLimits(); + applyLevelLimits(); + parseFromInfo(info); + } - /** @hide */ - public boolean supportsFormat(MediaFormat format) { - final Map<String, Object> map = format.getMap(); - final String mime = mParent.getMime(); + private void initWithPlatformLimits() { + mBitrateRange = Range.create(0, Integer.MAX_VALUE); + mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT; + // mBitrateRange = Range.create(1, 320000); + mSampleRateRanges = new Range[] { Range.create(8000, 96000) }; + mSampleRates = null; + } - Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE); - if (mode != null && !isBitrateModeSupported(mode)) { + private boolean supports(Integer sampleRate, Integer inputChannels) { + // channels and sample rates are checked orthogonally + if (inputChannels != null && + (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) { + return false; + } + if (sampleRate != null) { + int ix = Utils.binarySearchDistinctRanges( + mSampleRateRanges, sampleRate); + if (ix < 0) { return false; } + } + return true; + } - Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY); - if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) { - Integer flacComplexity = - (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL); - if (complexity == null) { - complexity = flacComplexity; - } else if (flacComplexity != null && complexity != flacComplexity) { - throw new IllegalArgumentException( - "conflicting values for complexity and " + - "flac-compression-level"); - } - } + /** + * Query whether the sample rate is supported by the codec. + */ + public boolean isSampleRateSupported(int sampleRate) { + return supports(sampleRate, null); + } - // other audio parameters - Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); - if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) { - Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE); - if (profile == null) { - profile = aacProfile; - } else if (aacProfile != null && aacProfile != profile) { - throw new IllegalArgumentException( - "conflicting values for profile and aac-profile"); - } + /** modifies rates */ + private void limitSampleRates(int[] rates) { + Arrays.sort(rates); + ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>(); + for (int rate: rates) { + if (supports(rate, null /* channels */)) { + ranges.add(Range.create(rate, rate)); } + } + mSampleRateRanges = ranges.toArray(new Range[ranges.size()]); + createDiscreteSampleRates(); + } + + private void createDiscreteSampleRates() { + mSampleRates = new int[mSampleRateRanges.length]; + for (int i = 0; i < mSampleRateRanges.length; i++) { + mSampleRates[i] = mSampleRateRanges[i].getLower(); + } + } - Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY); + /** modifies rateRanges */ + private void limitSampleRates(Range<Integer>[] rateRanges) { + sortDistinctRanges(rateRanges); + mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); - return supports(complexity, quality, profile); + // check if all values are discrete + for (Range<Integer> range: mSampleRateRanges) { + if (!range.getLower().equals(range.getUpper())) { + mSampleRates = null; + return; + } } - }; + createDiscreteSampleRates(); + } - /** - * A class that supports querying basic capabilities of a codec. - */ - public static class BaseCapabilities { - /** - * Returns the range of supported bitrates in bits/second. - */ - public final Range<Integer> getBitrateRange() { - return mBitrateRange; + private void applyLevelLimits() { + int[] sampleRates = null; + Range<Integer> sampleRateRange = null, bitRates = null; + int maxChannels = 0; + String mime = mParent.getMimeType(); + + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) { + sampleRates = new int[] { + 8000, 11025, 12000, + 16000, 22050, 24000, + 32000, 44100, 48000 }; + bitRates = Range.create(8000, 320000); + maxChannels = 2; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(4750, 12200); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { + sampleRates = new int[] { 16000 }; + bitRates = Range.create(6600, 23850); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { + sampleRates = new int[] { + 7350, 8000, + 11025, 12000, 16000, + 22050, 24000, 32000, + 44100, 48000, 64000, + 88200, 96000 }; + bitRates = Range.create(8000, 510000); + maxChannels = 48; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) { + bitRates = Range.create(32000, 500000); + sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 }; + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) { + bitRates = Range.create(6000, 510000); + sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 }; + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { + sampleRateRange = Range.create(1, 96000); + bitRates = Range.create(1, 10000000); + maxChannels = 8; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { + sampleRateRange = Range.create(1, 655350); + // lossless codec, so bitrate is ignored + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(64000, 64000); + // platform allows multiple channels for this format + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(13000, 13000); + maxChannels = 1; + } else { + Log.w(TAG, "Unsupported mime " + mime); + mParent.mError |= ERROR_UNSUPPORTED; + } + + // restrict ranges + if (sampleRates != null) { + limitSampleRates(sampleRates); + } else if (sampleRateRange != null) { + limitSampleRates(new Range[] { sampleRateRange }); + } + applyLimits(maxChannels, bitRates); + } + + private void applyLimits(int maxInputChannels, Range<Integer> bitRates) { + mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount) + .clamp(maxInputChannels); + if (bitRates != null) { + mBitrateRange = mBitrateRange.intersect(bitRates); } + } - /** @hide */ - protected Range<Integer> mBitrateRange; + private void parseFromInfo(MediaFormat info) { + int maxInputChannels = MAX_INPUT_CHANNEL_COUNT; + Range<Integer> bitRates = POSITIVE_INTEGERS; - /** @hide */ - protected CodecCapabilities mParent; + if (info.containsKey("sample-rate-ranges")) { + String[] rateStrings = info.getString("sample-rate-ranges").split(","); + Range<Integer>[] rateRanges = new Range[rateStrings.length]; + for (int i = 0; i < rateStrings.length; i++) { + rateRanges[i] = Utils.parseIntRange(rateStrings[i], null); + } + limitSampleRates(rateRanges); + } + if (info.containsKey("max-channel-count")) { + maxInputChannels = Utils.parseIntSafely( + info.getString("max-channel-count"), maxInputChannels); + } + if (info.containsKey("bitrate-range")) { + bitRates = bitRates.intersect( + Utils.parseIntRange(info.getString("bitrate"), bitRates)); + } + applyLimits(maxInputChannels, bitRates); + } - /** @hide */ - protected BaseCapabilities() { + /** @hide */ + public void setDefaultFormat(MediaFormat format) { + // report settings that have only a single choice + if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { + format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); } + if (mMaxInputChannelCount == 1) { + // mono-only format + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); + } + if (mSampleRates != null && mSampleRates.length == 1) { + format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]); + } + } - /** @hide */ - protected void init(MediaFormat info, CodecCapabilities parent) { - mParent = parent; - mBitrateRange = Range.create(0, Integer.MAX_VALUE); + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + Map<String, Object> map = format.getMap(); + Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE); + Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT); + if (!supports(sampleRate, channels)) { + return false; } + + // nothing to do for: + // KEY_CHANNEL_MASK: codecs don't get this + // KEY_IS_ADTS: required feature for all AAC decoders + return true; } + } + + /** + * A class that supports querying the video capabilities of a codec. + */ + public static final class VideoCapabilities { + private static final String TAG = "VideoCapabilities"; + private CodecCapabilities mParent; + private Range<Integer> mBitrateRange; + + private Range<Integer> mHeightRange; + private Range<Integer> mWidthRange; + private Range<Integer> mBlockCountRange; + private Range<Integer> mHorizontalBlockRange; + private Range<Integer> mVerticalBlockRange; + private Range<Rational> mAspectRatioRange; + private Range<Rational> mBlockAspectRatioRange; + private Range<Long> mBlocksPerSecondRange; + private Range<Integer> mFrameRateRange; + + private int mBlockWidth; + private int mBlockHeight; + private int mWidthAlignment; + private int mHeightAlignment; + private int mSmallerDimensionUpperLimit; /** - * A class that supports querying the video capabilities of a codec. + * Returns the range of supported bitrates in bits/second. */ - public static final class VideoCapabilities extends BaseCapabilities { - private static final String TAG = "VideoCapabilities"; - private Range<Integer> mHeightRange; - private Range<Integer> mWidthRange; - private Range<Integer> mBlockCountRange; - private Range<Integer> mHorizontalBlockRange; - private Range<Integer> mVerticalBlockRange; - private Range<Rational> mAspectRatioRange; - private Range<Rational> mBlockAspectRatioRange; - private Range<Long> mBlocksPerSecondRange; - private Range<Integer> mFrameRateRange; - - private int mBlockWidth; - private int mBlockHeight; - private int mWidthAlignment; - private int mHeightAlignment; - private int mSmallerDimensionUpperLimit; - - /** - * Returns the range of supported video widths. - */ - public final Range<Integer> getSupportedWidths() { - return mWidthRange; - } + public Range<Integer> getBitrateRange() { + return mBitrateRange; + } - /** - * Returns the range of supported video heights. - */ - public final Range<Integer> getSupportedHeights() { - return mHeightRange; - } + /** + * Returns the range of supported video widths. + */ + public Range<Integer> getSupportedWidths() { + return mWidthRange; + } - /** - * Returns the alignment requirement for video width. - */ - public final int getWidthAlignment() { - return mWidthAlignment; - } + /** + * Returns the range of supported video heights. + */ + public Range<Integer> getSupportedHeights() { + return mHeightRange; + } - /** - * Returns the alignment requirement for video height. - */ - public final int getHeightAlignment() { - return mHeightAlignment; - } + /** + * Returns the alignment requirement for video width (in pixels). + * + * This is a power-of-2 value that video width must be a + * multiple of. + */ + public int getWidthAlignment() { + return mWidthAlignment; + } - /** - * Return the upper limit on the smaller dimension of width or height. - * <p></p> - * Some codecs have a limit on the smaller dimension, whether it be - * the width or the height. E.g. a codec may only be able to handle - * up to 1920x1080 both in landscape and portrait mode (1080x1920). - * In this case the maximum width and height are both 1920, but the - * smaller dimension limit will be 1080. For other codecs, this is - * {@code Math.min(getSupportedWidths().getUpper(), - * getSupportedHeights().getUpper())}. - * - * @hide - */ - public int getSmallerDimensionUpperLimit() { - return mSmallerDimensionUpperLimit; - } + /** + * Returns the alignment requirement for video height (in pixels). + * + * This is a power-of-2 value that video height must be a + * multiple of. + */ + public int getHeightAlignment() { + return mHeightAlignment; + } - /** - * Returns the range of supported frame rates. - * <p> - * This is not a performance indicator. Rather, it expresses the - * limits specified in the coding standard, based on the complexities - * of encoding material for later playback at a certain frame rate, - * or the decoding of such material in non-realtime. - */ - public final Range<Integer> getSupportedFrameRates() { - return mFrameRateRange; - } + /** + * Return the upper limit on the smaller dimension of width or height. + * <p></p> + * Some codecs have a limit on the smaller dimension, whether it be + * the width or the height. E.g. a codec may only be able to handle + * up to 1920x1080 both in landscape and portrait mode (1080x1920). + * In this case the maximum width and height are both 1920, but the + * smaller dimension limit will be 1080. For other codecs, this is + * {@code Math.min(getSupportedWidths().getUpper(), + * getSupportedHeights().getUpper())}. + * + * @hide + */ + public int getSmallerDimensionUpperLimit() { + return mSmallerDimensionUpperLimit; + } - /** - * Returns the range of supported video widths for a video height. - * @param height the height of the video - */ - public final Range<Integer> getSupportedWidthsFor(int height) { - try { - Range<Integer> range = mWidthRange; - if (!mHeightRange.contains(height) - || (height % mHeightAlignment) != 0) { - throw new IllegalArgumentException("unsupported height"); - } - final int heightInBlocks = Utils.divUp(height, mBlockHeight); - - // constrain by block count and by block aspect ratio - final int minWidthInBlocks = Math.max( - Utils.divUp(mBlockCountRange.getLower(), heightInBlocks), - (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue() - * heightInBlocks)); - final int maxWidthInBlocks = Math.min( - mBlockCountRange.getUpper() / heightInBlocks, - (int)(mBlockAspectRatioRange.getUpper().doubleValue() - * heightInBlocks)); - range = range.intersect( - (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment, - maxWidthInBlocks * mBlockWidth); - - // constrain by smaller dimension limit - if (height > mSmallerDimensionUpperLimit) { - range = range.intersect(1, mSmallerDimensionUpperLimit); - } + /** + * Returns the range of supported frame rates. + * <p> + * This is not a performance indicator. Rather, it expresses the + * limits specified in the coding standard, based on the complexities + * of encoding material for later playback at a certain frame rate, + * or the decoding of such material in non-realtime. + */ + public Range<Integer> getSupportedFrameRates() { + return mFrameRateRange; + } - // constrain by aspect ratio - range = range.intersect( - (int)Math.ceil(mAspectRatioRange.getLower().doubleValue() - * height), - (int)(mAspectRatioRange.getUpper().doubleValue() * height)); - return range; - } catch (IllegalArgumentException e) { - // should not be here - Log.w(TAG, "could not get supported widths for " + height , e); + /** + * Returns the range of supported video widths for a video height. + * @param height the height of the video + */ + public Range<Integer> getSupportedWidthsFor(int height) { + try { + Range<Integer> range = mWidthRange; + if (!mHeightRange.contains(height) + || (height % mHeightAlignment) != 0) { throw new IllegalArgumentException("unsupported height"); } - } - - /** - * Returns the range of supported video heights for a video width - * @param width the width of the video - */ - public final Range<Integer> getSupportedHeightsFor(int width) { - try { - Range<Integer> range = mHeightRange; - if (!mWidthRange.contains(width) - || (width % mWidthAlignment) != 0) { - throw new IllegalArgumentException("unsupported width"); - } - final int widthInBlocks = Utils.divUp(width, mBlockWidth); - - // constrain by block count and by block aspect ratio - final int minHeightInBlocks = Math.max( - Utils.divUp(mBlockCountRange.getLower(), widthInBlocks), - (int)Math.ceil(widthInBlocks / - mBlockAspectRatioRange.getUpper().doubleValue())); - final int maxHeightInBlocks = Math.min( - mBlockCountRange.getUpper() / widthInBlocks, - (int)(widthInBlocks / - mBlockAspectRatioRange.getLower().doubleValue())); - range = range.intersect( - (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment, - maxHeightInBlocks * mBlockHeight); - - // constrain by smaller dimension limit - if (width > mSmallerDimensionUpperLimit) { - range = range.intersect(1, mSmallerDimensionUpperLimit); - } - - // constrain by aspect ratio - range = range.intersect( - (int)Math.ceil(width / - mAspectRatioRange.getUpper().doubleValue()), - (int)(width / mAspectRatioRange.getLower().doubleValue())); - return range; - } catch (IllegalArgumentException e) { - // should not be here - Log.w(TAG, "could not get supported heights for " + width , e); - throw new IllegalArgumentException("unsupported width"); + final int heightInBlocks = Utils.divUp(height, mBlockHeight); + + // constrain by block count and by block aspect ratio + final int minWidthInBlocks = Math.max( + Utils.divUp(mBlockCountRange.getLower(), heightInBlocks), + (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue() + * heightInBlocks)); + final int maxWidthInBlocks = Math.min( + mBlockCountRange.getUpper() / heightInBlocks, + (int)(mBlockAspectRatioRange.getUpper().doubleValue() + * heightInBlocks)); + range = range.intersect( + (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment, + maxWidthInBlocks * mBlockWidth); + + // constrain by smaller dimension limit + if (height > mSmallerDimensionUpperLimit) { + range = range.intersect(1, mSmallerDimensionUpperLimit); } + + // constrain by aspect ratio + range = range.intersect( + (int)Math.ceil(mAspectRatioRange.getLower().doubleValue() + * height), + (int)(mAspectRatioRange.getUpper().doubleValue() * height)); + return range; + } catch (IllegalArgumentException e) { + // should not be here + Log.w(TAG, "could not get supported widths for " + height , e); + throw new IllegalArgumentException("unsupported height"); } + } - /** - * Returns the range of supported video frame rates for a video size. - * <p> - * This is not a performance indicator. Rather, it expresses the limits specified in - * the coding standard, based on the complexities of encoding material of a given - * size for later playback at a certain frame rate, or the decoding of such material - * in non-realtime. - - * @param width the width of the video - * @param height the height of the video - */ - public final Range<Double> getSupportedFrameRatesFor(int width, int height) { + /** + * Returns the range of supported video heights for a video width + * @param width the width of the video + */ + public Range<Integer> getSupportedHeightsFor(int width) { + try { Range<Integer> range = mHeightRange; - if (!supports(width, height, null)) { - throw new IllegalArgumentException("unsupported size"); + if (!mWidthRange.contains(width) + || (width % mWidthAlignment) != 0) { + throw new IllegalArgumentException("unsupported width"); + } + final int widthInBlocks = Utils.divUp(width, mBlockWidth); + + // constrain by block count and by block aspect ratio + final int minHeightInBlocks = Math.max( + Utils.divUp(mBlockCountRange.getLower(), widthInBlocks), + (int)Math.ceil(widthInBlocks / + mBlockAspectRatioRange.getUpper().doubleValue())); + final int maxHeightInBlocks = Math.min( + mBlockCountRange.getUpper() / widthInBlocks, + (int)(widthInBlocks / + mBlockAspectRatioRange.getLower().doubleValue())); + range = range.intersect( + (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment, + maxHeightInBlocks * mBlockHeight); + + // constrain by smaller dimension limit + if (width > mSmallerDimensionUpperLimit) { + range = range.intersect(1, mSmallerDimensionUpperLimit); } - final int blockCount = - Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); - - return Range.create( - Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount, - (double) mFrameRateRange.getLower()), - Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount, - (double) mFrameRateRange.getUpper())); - } - /** - * Returns whether a given video size ({@code width} and - * {@code height}) and {@code frameRate} combination is supported. - */ - public final boolean areSizeAndRateSupported( - int width, int height, double frameRate) { - return supports(width, height, frameRate); + // constrain by aspect ratio + range = range.intersect( + (int)Math.ceil(width / + mAspectRatioRange.getUpper().doubleValue()), + (int)(width / mAspectRatioRange.getLower().doubleValue())); + return range; + } catch (IllegalArgumentException e) { + // should not be here + Log.w(TAG, "could not get supported heights for " + width , e); + throw new IllegalArgumentException("unsupported width"); } + } - /** - * Returns whether a given video size ({@code width} and - * {@code height}) is supported. - */ - public final boolean isSizeSupported(int width, int height) { - return supports(width, height, null); - } + /** + * Returns the range of supported video frame rates for a video size. + * <p> + * This is not a performance indicator. Rather, it expresses the limits specified in + * the coding standard, based on the complexities of encoding material of a given + * size for later playback at a certain frame rate, or the decoding of such material + * in non-realtime. - private final boolean supports( - Integer width, Integer height, Double rate) { - boolean ok = true; + * @param width the width of the video + * @param height the height of the video + */ + public Range<Double> getSupportedFrameRatesFor(int width, int height) { + Range<Integer> range = mHeightRange; + if (!supports(width, height, null)) { + throw new IllegalArgumentException("unsupported size"); + } + final int blockCount = + Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); + + return Range.create( + Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount, + (double) mFrameRateRange.getLower()), + Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount, + (double) mFrameRateRange.getUpper())); + } - if (ok && width != null) { - ok = mWidthRange.contains(width) - && (width % mWidthAlignment == 0); - } - if (ok && height != null) { - ok = mHeightRange.contains(height) - && (height % mHeightAlignment == 0); - } + /** + * Returns whether a given video size ({@code width} and + * {@code height}) and {@code frameRate} combination is supported. + */ + public boolean areSizeAndRateSupported( + int width, int height, double frameRate) { + return supports(width, height, frameRate); + } + + /** + * Returns whether a given video size ({@code width} and + * {@code height}) is supported. + */ + public boolean isSizeSupported(int width, int height) { + return supports(width, height, null); + } + + private boolean supports( + Integer width, Integer height, Double rate) { + boolean ok = true; + + if (ok && width != null) { + ok = mWidthRange.contains(width) + && (width % mWidthAlignment == 0); + } + if (ok && height != null) { + ok = mHeightRange.contains(height) + && (height % mHeightAlignment == 0); + } + if (ok && rate != null) { + ok = mFrameRateRange.contains(Utils.intRangeFor(rate)); + } + if (ok && height != null && width != null) { + ok = Math.min(height, width) <= mSmallerDimensionUpperLimit; + + final int widthInBlocks = Utils.divUp(width, mBlockWidth); + final int heightInBlocks = Utils.divUp(height, mBlockHeight); + final int blockCount = widthInBlocks * heightInBlocks; + ok = ok && mBlockCountRange.contains(blockCount) + && mBlockAspectRatioRange.contains( + new Rational(widthInBlocks, heightInBlocks)) + && mAspectRatioRange.contains(new Rational(width, height)); if (ok && rate != null) { - ok = mFrameRateRange.contains(Utils.intRangeFor(rate)); - } - if (ok && height != null && width != null) { - ok = Math.min(height, width) <= mSmallerDimensionUpperLimit; - - final int widthInBlocks = Utils.divUp(width, mBlockWidth); - final int heightInBlocks = Utils.divUp(height, mBlockHeight); - final int blockCount = widthInBlocks * heightInBlocks; - ok = ok && mBlockCountRange.contains(blockCount) - && mBlockAspectRatioRange.contains( - new Rational(widthInBlocks, heightInBlocks)) - && mAspectRatioRange.contains(new Rational(width, height)); - if (ok && rate != null) { - double blocksPerSec = blockCount * rate; - ok = mBlocksPerSecondRange.contains( - Utils.longRangeFor(blocksPerSec)); - } + double blocksPerSec = blockCount * rate; + ok = mBlocksPerSecondRange.contains( + Utils.longRangeFor(blocksPerSec)); } - return ok; } + return ok; + } - /** - * @hide - * @throws java.lang.ClassCastException */ - public boolean supportsFormat(MediaFormat format) { - final Map<String, Object> map = format.getMap(); - Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH); - Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT); - Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE); + /** + * @hide + * @throws java.lang.ClassCastException */ + public boolean supportsFormat(MediaFormat format) { + final Map<String, Object> map = format.getMap(); + Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH); + Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT); + Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE); - // we ignore color-format for now as it is not reliably reported by codec + // we ignore color-format for now as it is not reliably reported by codec - return supports(width, height, rate); - } + return supports(width, height, rate); + } - /* no public constructor */ - private VideoCapabilities() { } + /* no public constructor */ + private VideoCapabilities() { } - /** @hide */ - public static VideoCapabilities create( - MediaFormat info, CodecCapabilities parent) { - VideoCapabilities caps = new VideoCapabilities(); - caps.init(info, parent); - return caps; - } + /** @hide */ + public static VideoCapabilities create( + MediaFormat info, CodecCapabilities parent) { + VideoCapabilities caps = new VideoCapabilities(); + caps.init(info, parent); + return caps; + } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { - super.init(info, parent); - initWithPlatformLimits(); - applyLevelLimits(); - parseFromInfo(info); - updateLimits(); - } + /** @hide */ + public void init(MediaFormat info, CodecCapabilities parent) { + mParent = parent; + initWithPlatformLimits(); + applyLevelLimits(); + parseFromInfo(info); + updateLimits(); + } - /** @hide */ - public Size getBlockSize() { - return new Size(mBlockWidth, mBlockHeight); - } + /** @hide */ + public Size getBlockSize() { + return new Size(mBlockWidth, mBlockHeight); + } - /** @hide */ - public Range<Integer> getBlockCountRange() { - return mBlockCountRange; - } + /** @hide */ + public Range<Integer> getBlockCountRange() { + return mBlockCountRange; + } - /** @hide */ - public Range<Long> getBlocksPerSecondRange() { - return mBlocksPerSecondRange; - } + /** @hide */ + public Range<Long> getBlocksPerSecondRange() { + return mBlocksPerSecondRange; + } - /** @hide */ - public Range<Rational> getAspectRatioRange(boolean blocks) { - return blocks ? mBlockAspectRatioRange : mAspectRatioRange; - } + /** @hide */ + public Range<Rational> getAspectRatioRange(boolean blocks) { + return blocks ? mBlockAspectRatioRange : mAspectRatioRange; + } - private void initWithPlatformLimits() { - mWidthRange = SIZE_RANGE; - mHeightRange = SIZE_RANGE; - mFrameRateRange = FRAME_RATE_RANGE; + private void initWithPlatformLimits() { + mBitrateRange = Range.create(0, Integer.MAX_VALUE); - mHorizontalBlockRange = SIZE_RANGE; - mVerticalBlockRange = SIZE_RANGE; + mWidthRange = SIZE_RANGE; + mHeightRange = SIZE_RANGE; + mFrameRateRange = FRAME_RATE_RANGE; - // full positive ranges are supported as these get calculated - mBlockCountRange = POSITIVE_INTEGERS; - mBlocksPerSecondRange = POSITIVE_LONGS; + mHorizontalBlockRange = SIZE_RANGE; + mVerticalBlockRange = SIZE_RANGE; - mBlockAspectRatioRange = POSITIVE_RATIONALS; - mAspectRatioRange = POSITIVE_RATIONALS; + // full positive ranges are supported as these get calculated + mBlockCountRange = POSITIVE_INTEGERS; + mBlocksPerSecondRange = POSITIVE_LONGS; - // YUV 4:2:0 requires 2:2 alignment - mWidthAlignment = 2; - mHeightAlignment = 2; - mBlockWidth = 2; - mBlockHeight = 2; - mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper(); - } + mBlockAspectRatioRange = POSITIVE_RATIONALS; + mAspectRatioRange = POSITIVE_RATIONALS; - private void parseFromInfo(MediaFormat info) { - final Map<String, Object> map = info.getMap(); - Size blockSize = new Size(mBlockWidth, mBlockHeight); - Size alignment = new Size(mWidthAlignment, mHeightAlignment); - Range<Integer> counts = null, widths = null, heights = null; - Range<Integer> frameRates = null; - Range<Long> blockRates = null; - Range<Rational> ratios = null, blockRatios = null; - - blockSize = Utils.parseSize(map.get("block-size"), blockSize); - alignment = Utils.parseSize(map.get("alignment"), alignment); - counts = Utils.parseIntRange(map.get("block-count-range"), null); - blockRates = - Utils.parseLongRange(map.get("blocks-per-second-range"), null); - { - Object o = map.get("size-range"); - Pair<Size, Size> sizeRange = Utils.parseSizeRange(o); - if (sizeRange != null) { - try { - widths = Range.create( - sizeRange.first.getWidth(), - sizeRange.second.getWidth()); - heights = Range.create( - sizeRange.first.getHeight(), - sizeRange.second.getHeight()); - } catch (IllegalArgumentException e) { - Log.w(TAG, "could not parse size range '" + o + "'"); - widths = null; - heights = null; - } - } - } - // for now this just means using the smaller max size as 2nd - // upper limit. - // for now we are keeping the profile specific "width/height - // in macroblocks" limits. - if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) { - if (widths != null) { - mSmallerDimensionUpperLimit = - Math.min(widths.getUpper(), heights.getUpper()); - widths = heights = widths.extend(heights); - } else { - Log.w(TAG, "feature can-swap-width-height is best used with size-range"); - mSmallerDimensionUpperLimit = - Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()); - mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); - } - } + // YUV 4:2:0 requires 2:2 alignment + mWidthAlignment = 2; + mHeightAlignment = 2; + mBlockWidth = 2; + mBlockHeight = 2; + mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper(); + } - ratios = Utils.parseRationalRange( - map.get("block-aspect-ratio-range"), null); - blockRatios = Utils.parseRationalRange( - map.get("pixel-aspect-ratio-range"), null); - frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null); - if (frameRates != null) { + private void parseFromInfo(MediaFormat info) { + final Map<String, Object> map = info.getMap(); + Size blockSize = new Size(mBlockWidth, mBlockHeight); + Size alignment = new Size(mWidthAlignment, mHeightAlignment); + Range<Integer> counts = null, widths = null, heights = null; + Range<Integer> frameRates = null; + Range<Long> blockRates = null; + Range<Rational> ratios = null, blockRatios = null; + + blockSize = Utils.parseSize(map.get("block-size"), blockSize); + alignment = Utils.parseSize(map.get("alignment"), alignment); + counts = Utils.parseIntRange(map.get("block-count-range"), null); + blockRates = + Utils.parseLongRange(map.get("blocks-per-second-range"), null); + { + Object o = map.get("size-range"); + Pair<Size, Size> sizeRange = Utils.parseSizeRange(o); + if (sizeRange != null) { try { - frameRates = frameRates.intersect(FRAME_RATE_RANGE); + widths = Range.create( + sizeRange.first.getWidth(), + sizeRange.second.getWidth()); + heights = Range.create( + sizeRange.first.getHeight(), + sizeRange.second.getHeight()); } catch (IllegalArgumentException e) { - Log.w(TAG, "frame rate range (" + frameRates - + ") is out of limits: " + FRAME_RATE_RANGE); - frameRates = null; + Log.w(TAG, "could not parse size range '" + o + "'"); + widths = null; + heights = null; } } - - checkPowerOfTwo( - blockSize.getWidth(), "block-size width must be power of two"); - checkPowerOfTwo( - blockSize.getHeight(), "block-size height must be power of two"); - - checkPowerOfTwo( - alignment.getWidth(), "alignment width must be power of two"); - checkPowerOfTwo( - alignment.getHeight(), "alignment height must be power of two"); - - // update block-size and alignment - applyMacroBlockLimits( - Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, - Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(), - alignment.getWidth(), alignment.getHeight()); - - if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { - // codec supports profiles that we don't know. - // Use supplied values clipped to platform limits - if (widths != null) { - mWidthRange = SIZE_RANGE.intersect(widths); - } - if (heights != null) { - mHeightRange = SIZE_RANGE.intersect(heights); - } - if (counts != null) { - mBlockCountRange = POSITIVE_INTEGERS.intersect( - Utils.factorRange(counts, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRates != null) { - mBlocksPerSecondRange = POSITIVE_LONGS.intersect( - Utils.factorRange(blockRates, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRatios != null) { - mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( - Utils.scaleRange(blockRatios, - mBlockHeight / blockSize.getHeight(), - mBlockWidth / blockSize.getWidth())); - } - if (ratios != null) { - mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios); - } - if (frameRates != null) { - mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates); - } + } + // for now this just means using the smaller max size as 2nd + // upper limit. + // for now we are keeping the profile specific "width/height + // in macroblocks" limits. + if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) { + if (widths != null) { + mSmallerDimensionUpperLimit = + Math.min(widths.getUpper(), heights.getUpper()); + widths = heights = widths.extend(heights); } else { - // no unsupported profile/levels, so restrict values to known limits - if (widths != null) { - mWidthRange = mWidthRange.intersect(widths); - } - if (heights != null) { - mHeightRange = mHeightRange.intersect(heights); - } - if (counts != null) { - mBlockCountRange = mBlockCountRange.intersect( - Utils.factorRange(counts, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRates != null) { - mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( - Utils.factorRange(blockRates, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRatios != null) { - mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( - Utils.scaleRange(blockRatios, - mBlockHeight / blockSize.getHeight(), - mBlockWidth / blockSize.getWidth())); - } - if (ratios != null) { - mAspectRatioRange = mAspectRatioRange.intersect(ratios); - } - if (frameRates != null) { - mFrameRateRange = mFrameRateRange.intersect(frameRates); - } + Log.w(TAG, "feature can-swap-width-height is best used with size-range"); + mSmallerDimensionUpperLimit = + Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()); + mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); } - updateLimits(); } - private int checkPowerOfTwo(int value, String message) { - if ((value & (value - 1)) != 0) { - throw new IllegalArgumentException(message); + ratios = Utils.parseRationalRange( + map.get("block-aspect-ratio-range"), null); + blockRatios = Utils.parseRationalRange( + map.get("pixel-aspect-ratio-range"), null); + frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null); + if (frameRates != null) { + try { + frameRates = frameRates.intersect(FRAME_RATE_RANGE); + } catch (IllegalArgumentException e) { + Log.w(TAG, "frame rate range (" + frameRates + + ") is out of limits: " + FRAME_RATE_RANGE); + frameRates = null; } - return value; } - private void applyBlockLimits( - int blockWidth, int blockHeight, - Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) { - checkPowerOfTwo(blockWidth, "blockWidth must be a power of two"); - checkPowerOfTwo(blockHeight, "blockHeight must be a power of two"); - - final int newBlockWidth = Math.max(blockWidth, mBlockWidth); - final int newBlockHeight = Math.max(blockHeight, mBlockHeight); - - // factor will always be a power-of-2 - int factor = - newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight; - if (factor != 1) { - mBlockCountRange = Utils.factorRange(mBlockCountRange, factor); - mBlocksPerSecondRange = Utils.factorRange( - mBlocksPerSecondRange, factor); - mBlockAspectRatioRange = Utils.scaleRange( - mBlockAspectRatioRange, - newBlockHeight / mBlockHeight, - newBlockWidth / mBlockWidth); - mHorizontalBlockRange = Utils.factorRange( - mHorizontalBlockRange, newBlockWidth / mBlockWidth); - mVerticalBlockRange = Utils.factorRange( - mVerticalBlockRange, newBlockHeight / mBlockHeight); + checkPowerOfTwo( + blockSize.getWidth(), "block-size width must be power of two"); + checkPowerOfTwo( + blockSize.getHeight(), "block-size height must be power of two"); + + checkPowerOfTwo( + alignment.getWidth(), "alignment width must be power of two"); + checkPowerOfTwo( + alignment.getHeight(), "alignment height must be power of two"); + + // update block-size and alignment + applyMacroBlockLimits( + Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, + Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(), + alignment.getWidth(), alignment.getHeight()); + + if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { + // codec supports profiles that we don't know. + // Use supplied values clipped to platform limits + if (widths != null) { + mWidthRange = SIZE_RANGE.intersect(widths); + } + if (heights != null) { + mHeightRange = SIZE_RANGE.intersect(heights); + } + if (counts != null) { + mBlockCountRange = POSITIVE_INTEGERS.intersect( + Utils.factorRange(counts, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRates != null) { + mBlocksPerSecondRange = POSITIVE_LONGS.intersect( + Utils.factorRange(blockRates, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRatios != null) { + mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( + Utils.scaleRange(blockRatios, + mBlockHeight / blockSize.getHeight(), + mBlockWidth / blockSize.getWidth())); + } + if (ratios != null) { + mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios); + } + if (frameRates != null) { + mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates); + } + } else { + // no unsupported profile/levels, so restrict values to known limits + if (widths != null) { + mWidthRange = mWidthRange.intersect(widths); + } + if (heights != null) { + mHeightRange = mHeightRange.intersect(heights); + } + if (counts != null) { + mBlockCountRange = mBlockCountRange.intersect( + Utils.factorRange(counts, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRates != null) { + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( + Utils.factorRange(blockRates, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRatios != null) { + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( + Utils.scaleRange(blockRatios, + mBlockHeight / blockSize.getHeight(), + mBlockWidth / blockSize.getWidth())); + } + if (ratios != null) { + mAspectRatioRange = mAspectRatioRange.intersect(ratios); } - factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight; - if (factor != 1) { - counts = Utils.factorRange(counts, factor); - rates = Utils.factorRange(rates, factor); - ratios = Utils.scaleRange( - ratios, newBlockHeight / blockHeight, - newBlockWidth / blockWidth); + if (frameRates != null) { + mFrameRateRange = mFrameRateRange.intersect(frameRates); } - mBlockCountRange = mBlockCountRange.intersect(counts); - mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates); - mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios); - mBlockWidth = newBlockWidth; - mBlockHeight = newBlockHeight; } + updateLimits(); + } - private void applyAlignment(int widthAlignment, int heightAlignment) { - checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two"); - checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two"); - - if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) { - // maintain assumption that 0 < alignment <= block-size - applyBlockLimits( - Math.max(widthAlignment, mBlockWidth), - Math.max(heightAlignment, mBlockHeight), - POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS); - } + private void applyBlockLimits( + int blockWidth, int blockHeight, + Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) { + checkPowerOfTwo(blockWidth, "blockWidth must be a power of two"); + checkPowerOfTwo(blockHeight, "blockHeight must be a power of two"); + + final int newBlockWidth = Math.max(blockWidth, mBlockWidth); + final int newBlockHeight = Math.max(blockHeight, mBlockHeight); + + // factor will always be a power-of-2 + int factor = + newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight; + if (factor != 1) { + mBlockCountRange = Utils.factorRange(mBlockCountRange, factor); + mBlocksPerSecondRange = Utils.factorRange( + mBlocksPerSecondRange, factor); + mBlockAspectRatioRange = Utils.scaleRange( + mBlockAspectRatioRange, + newBlockHeight / mBlockHeight, + newBlockWidth / mBlockWidth); + mHorizontalBlockRange = Utils.factorRange( + mHorizontalBlockRange, newBlockWidth / mBlockWidth); + mVerticalBlockRange = Utils.factorRange( + mVerticalBlockRange, newBlockHeight / mBlockHeight); + } + factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight; + if (factor != 1) { + counts = Utils.factorRange(counts, factor); + rates = Utils.factorRange(rates, factor); + ratios = Utils.scaleRange( + ratios, newBlockHeight / blockHeight, + newBlockWidth / blockWidth); + } + mBlockCountRange = mBlockCountRange.intersect(counts); + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates); + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios); + mBlockWidth = newBlockWidth; + mBlockHeight = newBlockHeight; + } - mWidthAlignment = Math.max(widthAlignment, mWidthAlignment); - mHeightAlignment = Math.max(heightAlignment, mHeightAlignment); + private void applyAlignment(int widthAlignment, int heightAlignment) { + checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two"); + checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two"); - mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment); - mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment); + if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) { + // maintain assumption that 0 < alignment <= block-size + applyBlockLimits( + Math.max(widthAlignment, mBlockWidth), + Math.max(heightAlignment, mBlockHeight), + POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS); } - private void updateLimits() { - // pixels -> blocks <- counts - mHorizontalBlockRange = mHorizontalBlockRange.intersect( - Utils.factorRange(mWidthRange, mBlockWidth)); - mHorizontalBlockRange = mHorizontalBlockRange.intersect( - Range.create( - mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(), - mBlockCountRange.getUpper() / mVerticalBlockRange.getLower())); - mVerticalBlockRange = mVerticalBlockRange.intersect( - Utils.factorRange(mHeightRange, mBlockHeight)); - mVerticalBlockRange = mVerticalBlockRange.intersect( - Range.create( - mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(), - mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower())); - mBlockCountRange = mBlockCountRange.intersect( - Range.create( - mHorizontalBlockRange.getLower() - * mVerticalBlockRange.getLower(), - mHorizontalBlockRange.getUpper() - * mVerticalBlockRange.getUpper())); - mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( - new Rational( - mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()), - new Rational( - mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower())); - - // blocks -> pixels - mWidthRange = mWidthRange.intersect( - (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment, - mHorizontalBlockRange.getUpper() * mBlockWidth); - mHeightRange = mHeightRange.intersect( - (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment, - mVerticalBlockRange.getUpper() * mBlockHeight); - mAspectRatioRange = mAspectRatioRange.intersect( - new Rational(mWidthRange.getLower(), mHeightRange.getUpper()), - new Rational(mWidthRange.getUpper(), mHeightRange.getLower())); - - mSmallerDimensionUpperLimit = Math.min( - mSmallerDimensionUpperLimit, - Math.min(mWidthRange.getUpper(), mHeightRange.getUpper())); - - // blocks -> rate - mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( - mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(), - mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper()); - mFrameRateRange = mFrameRateRange.intersect( - (int)(mBlocksPerSecondRange.getLower() - / mBlockCountRange.getUpper()), - (int)(mBlocksPerSecondRange.getUpper() - / (double)mBlockCountRange.getLower())); - } + mWidthAlignment = Math.max(widthAlignment, mWidthAlignment); + mHeightAlignment = Math.max(heightAlignment, mHeightAlignment); - private void applyMacroBlockLimits( - int maxHorizontalBlocks, int maxVerticalBlocks, - int maxBlocks, long maxBlocksPerSecond, - int blockWidth, int blockHeight, - int widthAlignment, int heightAlignment) { - applyAlignment(widthAlignment, heightAlignment); - applyBlockLimits( - blockWidth, blockHeight, Range.create(1, maxBlocks), - Range.create(1L, maxBlocksPerSecond), - Range.create( - new Rational(1, maxVerticalBlocks), - new Rational(maxHorizontalBlocks, 1))); - mHorizontalBlockRange = - mHorizontalBlockRange.intersect( - 1, maxHorizontalBlocks / (mBlockWidth / blockWidth)); - mVerticalBlockRange = - mVerticalBlockRange.intersect( - 1, maxVerticalBlocks / (mBlockHeight / blockHeight)); - } + mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment); + mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment); + } - private void applyLevelLimits() { - int maxBlocksPerSecond = 0; - int maxBlocks = 0; - int maxBps = 0; - int maxDPBBlocks = 0; - - int errors = ERROR_NONE_SUPPORTED; - CodecProfileLevel[] profileLevels = mParent.profileLevels; - String mime = mParent.getMime(); - - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) { - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - maxDPBBlocks = 396; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, DPB = 0; - boolean supported = true; - switch (profileLevel.level) { - case CodecProfileLevel.AVCLevel1: - MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; - case CodecProfileLevel.AVCLevel1b: - MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; - case CodecProfileLevel.AVCLevel11: - MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; - case CodecProfileLevel.AVCLevel12: - MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; - case CodecProfileLevel.AVCLevel13: - MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; - case CodecProfileLevel.AVCLevel2: - MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; - case CodecProfileLevel.AVCLevel21: - MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; - case CodecProfileLevel.AVCLevel22: - MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; - case CodecProfileLevel.AVCLevel3: - MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; - case CodecProfileLevel.AVCLevel31: - MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; - case CodecProfileLevel.AVCLevel32: - MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; - case CodecProfileLevel.AVCLevel4: - MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; - case CodecProfileLevel.AVCLevel41: - MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; - case CodecProfileLevel.AVCLevel42: - MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; - case CodecProfileLevel.AVCLevel5: - MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; - case CodecProfileLevel.AVCLevel51: - MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; - case CodecProfileLevel.AVCLevel52: - MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.AVCProfileHigh: - BR *= 1250; break; - case CodecProfileLevel.AVCProfileHigh10: - BR *= 3000; break; - case CodecProfileLevel.AVCProfileExtended: - case CodecProfileLevel.AVCProfileHigh422: - case CodecProfileLevel.AVCProfileHigh444: - Log.w(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - // fall through - treat as base profile - case CodecProfileLevel.AVCProfileBaseline: - case CodecProfileLevel.AVCProfileMain: - BR *= 1000; break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - BR *= 1000; - } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; - } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR, maxBps); - maxDPBBlocks = Math.max(maxDPBBlocks, DPB); - } + private void updateLimits() { + // pixels -> blocks <- counts + mHorizontalBlockRange = mHorizontalBlockRange.intersect( + Utils.factorRange(mWidthRange, mBlockWidth)); + mHorizontalBlockRange = mHorizontalBlockRange.intersect( + Range.create( + mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(), + mBlockCountRange.getUpper() / mVerticalBlockRange.getLower())); + mVerticalBlockRange = mVerticalBlockRange.intersect( + Utils.factorRange(mHeightRange, mBlockHeight)); + mVerticalBlockRange = mVerticalBlockRange.intersect( + Range.create( + mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(), + mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower())); + mBlockCountRange = mBlockCountRange.intersect( + Range.create( + mHorizontalBlockRange.getLower() + * mVerticalBlockRange.getLower(), + mHorizontalBlockRange.getUpper() + * mVerticalBlockRange.getUpper())); + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( + new Rational( + mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()), + new Rational( + mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower())); + + // blocks -> pixels + mWidthRange = mWidthRange.intersect( + (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment, + mHorizontalBlockRange.getUpper() * mBlockWidth); + mHeightRange = mHeightRange.intersect( + (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment, + mVerticalBlockRange.getUpper() * mBlockHeight); + mAspectRatioRange = mAspectRatioRange.intersect( + new Rational(mWidthRange.getLower(), mHeightRange.getUpper()), + new Rational(mWidthRange.getUpper(), mHeightRange.getLower())); + + mSmallerDimensionUpperLimit = Math.min( + mSmallerDimensionUpperLimit, + Math.min(mWidthRange.getUpper(), mHeightRange.getUpper())); + + // blocks -> rate + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( + mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(), + mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper()); + mFrameRateRange = mFrameRateRange.intersect( + (int)(mBlocksPerSecondRange.getLower() + / mBlockCountRange.getUpper()), + (int)(mBlocksPerSecondRange.getUpper() + / (double)mBlockCountRange.getLower())); + } - int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; - boolean supported = true; - switch (profileLevel.profile) { - case CodecProfileLevel.MPEG4ProfileSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG4Level0: - FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; - case CodecProfileLevel.MPEG4Level1: - FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; - case CodecProfileLevel.MPEG4Level0b: - FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; - case CodecProfileLevel.MPEG4Level2: - FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; - case CodecProfileLevel.MPEG4Level3: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; - case CodecProfileLevel.MPEG4Level4: - case CodecProfileLevel.MPEG4Level4a: - case CodecProfileLevel.MPEG4Level5: - // While MPEG4 SP does not have level 4 or 5, some vendors - // report it. Use the same limits as level 3, but mark as - // unsupported. - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; - supported = false; - break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG4ProfileAdvancedSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG4Level0: - case CodecProfileLevel.MPEG4Level1: - FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; - case CodecProfileLevel.MPEG4Level2: - FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; - case CodecProfileLevel.MPEG4Level3: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; - // case CodecProfileLevel.MPEG4Level3b: - // TODO: MPEG4 level 3b is not defined in OMX - // MBPS = 11880; FS = 396; BR = 1500; break; - case CodecProfileLevel.MPEG4Level4: - case CodecProfileLevel.MPEG4Level4a: - // TODO: MPEG4 level 4a is not defined in spec - FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; - case CodecProfileLevel.MPEG4Level5: - FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG4ProfileMain: // 2-4 - case CodecProfileLevel.MPEG4ProfileNbit: // 2 - case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4 - case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3 - case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4 - case CodecProfileLevel.MPEG4ProfileCore: // 1-2 - case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4 - case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2 - case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3 - case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2 - case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2 - case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1 - case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2 - case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2 - Log.i(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; - } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); + private void applyMacroBlockLimits( + int maxHorizontalBlocks, int maxVerticalBlocks, + int maxBlocks, long maxBlocksPerSecond, + int blockWidth, int blockHeight, + int widthAlignment, int heightAlignment) { + applyAlignment(widthAlignment, heightAlignment); + applyBlockLimits( + blockWidth, blockHeight, Range.create(1, maxBlocks), + Range.create(1L, maxBlocksPerSecond), + Range.create( + new Rational(1, maxVerticalBlocks), + new Rational(maxHorizontalBlocks, 1))); + mHorizontalBlockRange = + mHorizontalBlockRange.intersect( + 1, maxHorizontalBlocks / (mBlockWidth / blockWidth)); + mVerticalBlockRange = + mVerticalBlockRange.intersect( + 1, maxVerticalBlocks / (mBlockHeight / blockHeight)); + } + + private void applyLevelLimits() { + int maxBlocksPerSecond = 0; + int maxBlocks = 0; + int maxBps = 0; + int maxDPBBlocks = 0; + + int errors = ERROR_NONE_SUPPORTED; + CodecProfileLevel[] profileLevels = mParent.profileLevels; + String mime = mParent.getMimeType(); + + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) { + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + maxDPBBlocks = 396; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, DPB = 0; + boolean supported = true; + switch (profileLevel.level) { + case CodecProfileLevel.AVCLevel1: + MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; + case CodecProfileLevel.AVCLevel1b: + MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; + case CodecProfileLevel.AVCLevel11: + MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; + case CodecProfileLevel.AVCLevel12: + MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; + case CodecProfileLevel.AVCLevel13: + MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; + case CodecProfileLevel.AVCLevel2: + MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; + case CodecProfileLevel.AVCLevel21: + MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; + case CodecProfileLevel.AVCLevel22: + MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; + case CodecProfileLevel.AVCLevel3: + MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; + case CodecProfileLevel.AVCLevel31: + MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; + case CodecProfileLevel.AVCLevel32: + MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; + case CodecProfileLevel.AVCLevel4: + MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; + case CodecProfileLevel.AVCLevel41: + MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; + case CodecProfileLevel.AVCLevel42: + MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; + case CodecProfileLevel.AVCLevel5: + MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; + case CodecProfileLevel.AVCLevel51: + MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; + case CodecProfileLevel.AVCLevel52: + MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - applyMacroBlockLimits(maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - mFrameRateRange = mFrameRateRange.intersect(12, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0; - switch (profileLevel.level) { - case CodecProfileLevel.H263Level10: - FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level20: - // only supports CIF, 0..QCIF - FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level30: - // only supports CIF, 0..QCIF - FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level40: - // only supports CIF, 0..QCIF - FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level45: - // only implies level 10 support - FR = 30; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level50: - // only supports 50fps for H > 15 - FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; - case CodecProfileLevel.H263Level60: - // only supports 50fps for H > 15 - FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; - case CodecProfileLevel.H263Level70: - // only supports 50fps for H > 30 - FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; - default: - Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile - + "/" + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.H263ProfileBackwardCompatible: - case CodecProfileLevel.H263ProfileBaseline: - case CodecProfileLevel.H263ProfileH320Coding: - case CodecProfileLevel.H263ProfileHighCompression: - case CodecProfileLevel.H263ProfileHighLatency: - case CodecProfileLevel.H263ProfileInterlace: - case CodecProfileLevel.H263ProfileInternet: - case CodecProfileLevel.H263ProfileISWV2: - case CodecProfileLevel.H263ProfileISWV3: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(W * H, maxBlocks); - maxBps = Math.max(BR * 64000, maxBps); - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); + switch (profileLevel.profile) { + case CodecProfileLevel.AVCProfileHigh: + BR *= 1250; break; + case CodecProfileLevel.AVCProfileHigh10: + BR *= 3000; break; + case CodecProfileLevel.AVCProfileExtended: + case CodecProfileLevel.AVCProfileHigh422: + case CodecProfileLevel.AVCProfileHigh444: + Log.w(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + // fall through - treat as base profile + case CodecProfileLevel.AVCProfileBaseline: + case CodecProfileLevel.AVCProfileMain: + BR *= 1000; break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + BR *= 1000; } - applyMacroBlockLimits(maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - mFrameRateRange = Range.create(1, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) || - mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { - maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE; - - // TODO: set to 100Mbps for now, need a number for VPX - maxBps = 100000000; - - // profile levels are not indicative for VPx, but verify - // them nonetheless - for (CodecProfileLevel profileLevel: profileLevels) { - switch (profileLevel.level) { - case CodecProfileLevel.VP8Level_Version0: - case CodecProfileLevel.VP8Level_Version1: - case CodecProfileLevel.VP8Level_Version2: - case CodecProfileLevel.VP8Level_Version3: - break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.VP8ProfileMain: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } + if (supported) { errors &= ~ERROR_NONE_SUPPORTED; } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR, maxBps); + maxDPBBlocks = Math.max(maxDPBBlocks, DPB); + } - final int blockSize = - mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8; - applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE, - maxBlocks, maxBlocksPerSecond, blockSize, blockSize, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { - maxBlocks = 36864; - maxBlocksPerSecond = maxBlocks * 15; - maxBps = 128000; - for (CodecProfileLevel profileLevel: profileLevels) { - double FR = 0; - int FS = 0; - int BR = 0; - switch (profileLevel.level) { - case CodecProfileLevel.HEVCMainTierLevel1: - case CodecProfileLevel.HEVCHighTierLevel1: - FR = 15; FS = 36864; BR = 128; break; - case CodecProfileLevel.HEVCMainTierLevel2: - case CodecProfileLevel.HEVCHighTierLevel2: - FR = 30; FS = 122880; BR = 1500; break; - case CodecProfileLevel.HEVCMainTierLevel21: - case CodecProfileLevel.HEVCHighTierLevel21: - FR = 30; FS = 245760; BR = 3000; break; - case CodecProfileLevel.HEVCMainTierLevel3: - case CodecProfileLevel.HEVCHighTierLevel3: - FR = 30; FS = 552960; BR = 6000; break; - case CodecProfileLevel.HEVCMainTierLevel31: - case CodecProfileLevel.HEVCHighTierLevel31: - FR = 33.75; FS = 983040; BR = 10000; break; - case CodecProfileLevel.HEVCMainTierLevel4: - FR = 30; FS = 2228224; BR = 12000; break; - case CodecProfileLevel.HEVCHighTierLevel4: - FR = 30; FS = 2228224; BR = 30000; break; - case CodecProfileLevel.HEVCMainTierLevel41: - FR = 60; FS = 2228224; BR = 20000; break; - case CodecProfileLevel.HEVCHighTierLevel41: - FR = 60; FS = 2228224; BR = 50000; break; - case CodecProfileLevel.HEVCMainTierLevel5: - FR = 30; FS = 8912896; BR = 25000; break; - case CodecProfileLevel.HEVCHighTierLevel5: - FR = 30; FS = 8912896; BR = 100000; break; - case CodecProfileLevel.HEVCMainTierLevel51: - FR = 60; FS = 8912896; BR = 40000; break; - case CodecProfileLevel.HEVCHighTierLevel51: - FR = 60; FS = 8912896; BR = 160000; break; - case CodecProfileLevel.HEVCMainTierLevel52: - FR = 120; FS = 8912896; BR = 60000; break; - case CodecProfileLevel.HEVCHighTierLevel52: - FR = 120; FS = 8912896; BR = 240000; break; - case CodecProfileLevel.HEVCMainTierLevel6: - FR = 30; FS = 35651584; BR = 60000; break; - case CodecProfileLevel.HEVCHighTierLevel6: - FR = 30; FS = 35651584; BR = 240000; break; - case CodecProfileLevel.HEVCMainTierLevel61: - FR = 60; FS = 35651584; BR = 120000; break; - case CodecProfileLevel.HEVCHighTierLevel61: - FR = 60; FS = 35651584; BR = 480000; break; - case CodecProfileLevel.HEVCMainTierLevel62: - FR = 120; FS = 35651584; BR = 240000; break; - case CodecProfileLevel.HEVCHighTierLevel62: - FR = 120; FS = 35651584; BR = 800000; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.HEVCProfileMain: - case CodecProfileLevel.HEVCProfileMain10: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - - /* DPB logic: - if (width * height <= FS / 4) DPB = 16; - else if (width * height <= FS / 2) DPB = 12; - else if (width * height <= FS * 0.75) DPB = 8; - else DPB = 6; - */ - + int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; + boolean supported = true; + switch (profileLevel.profile) { + case CodecProfileLevel.MPEG4ProfileSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG4Level0: + FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; + case CodecProfileLevel.MPEG4Level1: + FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; + case CodecProfileLevel.MPEG4Level0b: + FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; + case CodecProfileLevel.MPEG4Level2: + FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; + case CodecProfileLevel.MPEG4Level3: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; + case CodecProfileLevel.MPEG4Level4: + case CodecProfileLevel.MPEG4Level4a: + case CodecProfileLevel.MPEG4Level5: + // While MPEG4 SP does not have level 4 or 5, some vendors + // report it. Use the same limits as level 3, but mark as + // unsupported. + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; + supported = false; + break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG4ProfileAdvancedSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG4Level0: + case CodecProfileLevel.MPEG4Level1: + FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; + case CodecProfileLevel.MPEG4Level2: + FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; + case CodecProfileLevel.MPEG4Level3: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; + // case CodecProfileLevel.MPEG4Level3b: + // TODO: MPEG4 level 3b is not defined in OMX + // MBPS = 11880; FS = 396; BR = 1500; break; + case CodecProfileLevel.MPEG4Level4: + case CodecProfileLevel.MPEG4Level4a: + // TODO: MPEG4 level 4a is not defined in spec + FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; + case CodecProfileLevel.MPEG4Level5: + FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG4ProfileMain: // 2-4 + case CodecProfileLevel.MPEG4ProfileNbit: // 2 + case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4 + case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3 + case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4 + case CodecProfileLevel.MPEG4ProfileCore: // 1-2 + case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4 + case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2 + case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3 + case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2 + case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2 + case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1 + case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2 + case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2 + Log.i(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + if (supported) { errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); + } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + } + applyMacroBlockLimits(maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + mFrameRateRange = mFrameRateRange.intersect(12, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0; + switch (profileLevel.level) { + case CodecProfileLevel.H263Level10: + FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level20: + // only supports CIF, 0..QCIF + FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level30: + // only supports CIF, 0..QCIF + FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level40: + // only supports CIF, 0..QCIF + FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level45: + // only implies level 10 support + FR = 30; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level50: + // only supports 50fps for H > 15 + FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; + case CodecProfileLevel.H263Level60: + // only supports 50fps for H > 15 + FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; + case CodecProfileLevel.H263Level70: + // only supports 50fps for H > 30 + FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; + default: + Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile + + "/" + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.H263ProfileBackwardCompatible: + case CodecProfileLevel.H263ProfileBaseline: + case CodecProfileLevel.H263ProfileH320Coding: + case CodecProfileLevel.H263ProfileHighCompression: + case CodecProfileLevel.H263ProfileHighLatency: + case CodecProfileLevel.H263ProfileInterlace: + case CodecProfileLevel.H263ProfileInternet: + case CodecProfileLevel.H263ProfileISWV2: + case CodecProfileLevel.H263ProfileISWV3: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(W * H, maxBlocks); + maxBps = Math.max(BR * 64000, maxBps); + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + } + applyMacroBlockLimits(maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + mFrameRateRange = Range.create(1, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) || + mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { + maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE; + + // TODO: set to 100Mbps for now, need a number for VPX + maxBps = 100000000; + + // profile levels are not indicative for VPx, but verify + // them nonetheless + for (CodecProfileLevel profileLevel: profileLevels) { + switch (profileLevel.level) { + case CodecProfileLevel.VP8Level_Version0: + case CodecProfileLevel.VP8Level_Version1: + case CodecProfileLevel.VP8Level_Version2: + case CodecProfileLevel.VP8Level_Version3: + break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.VP8ProfileMain: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + } + + final int blockSize = + mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8; + applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE, + maxBlocks, maxBlocksPerSecond, blockSize, blockSize, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { + maxBlocks = 36864; + maxBlocksPerSecond = maxBlocks * 15; + maxBps = 128000; + for (CodecProfileLevel profileLevel: profileLevels) { + double FR = 0; + int FS = 0; + int BR = 0; + switch (profileLevel.level) { + case CodecProfileLevel.HEVCMainTierLevel1: + case CodecProfileLevel.HEVCHighTierLevel1: + FR = 15; FS = 36864; BR = 128; break; + case CodecProfileLevel.HEVCMainTierLevel2: + case CodecProfileLevel.HEVCHighTierLevel2: + FR = 30; FS = 122880; BR = 1500; break; + case CodecProfileLevel.HEVCMainTierLevel21: + case CodecProfileLevel.HEVCHighTierLevel21: + FR = 30; FS = 245760; BR = 3000; break; + case CodecProfileLevel.HEVCMainTierLevel3: + case CodecProfileLevel.HEVCHighTierLevel3: + FR = 30; FS = 552960; BR = 6000; break; + case CodecProfileLevel.HEVCMainTierLevel31: + case CodecProfileLevel.HEVCHighTierLevel31: + FR = 33.75; FS = 983040; BR = 10000; break; + case CodecProfileLevel.HEVCMainTierLevel4: + FR = 30; FS = 2228224; BR = 12000; break; + case CodecProfileLevel.HEVCHighTierLevel4: + FR = 30; FS = 2228224; BR = 30000; break; + case CodecProfileLevel.HEVCMainTierLevel41: + FR = 60; FS = 2228224; BR = 20000; break; + case CodecProfileLevel.HEVCHighTierLevel41: + FR = 60; FS = 2228224; BR = 50000; break; + case CodecProfileLevel.HEVCMainTierLevel5: + FR = 30; FS = 8912896; BR = 25000; break; + case CodecProfileLevel.HEVCHighTierLevel5: + FR = 30; FS = 8912896; BR = 100000; break; + case CodecProfileLevel.HEVCMainTierLevel51: + FR = 60; FS = 8912896; BR = 40000; break; + case CodecProfileLevel.HEVCHighTierLevel51: + FR = 60; FS = 8912896; BR = 160000; break; + case CodecProfileLevel.HEVCMainTierLevel52: + FR = 120; FS = 8912896; BR = 60000; break; + case CodecProfileLevel.HEVCHighTierLevel52: + FR = 120; FS = 8912896; BR = 240000; break; + case CodecProfileLevel.HEVCMainTierLevel6: + FR = 30; FS = 35651584; BR = 60000; break; + case CodecProfileLevel.HEVCHighTierLevel6: + FR = 30; FS = 35651584; BR = 240000; break; + case CodecProfileLevel.HEVCMainTierLevel61: + FR = 60; FS = 35651584; BR = 120000; break; + case CodecProfileLevel.HEVCHighTierLevel61: + FR = 60; FS = 35651584; BR = 480000; break; + case CodecProfileLevel.HEVCMainTierLevel62: + FR = 120; FS = 35651584; BR = 240000; break; + case CodecProfileLevel.HEVCHighTierLevel62: + FR = 120; FS = 35651584; BR = 800000; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.HEVCProfileMain: + case CodecProfileLevel.HEVCProfileMain10: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); - // CTBs are at least 8x8 - maxBlocks = Utils.divUp(maxBlocks, 8 * 8); - maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8); - maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8); - - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - 8 /* blockWidth */, 8 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else { - Log.w(TAG, "Unsupported mime " + mime); - // using minimal bitrate here. should be overriden by - // info from media_codecs.xml - maxBps = 64000; - errors |= ERROR_UNSUPPORTED; + /* DPB logic: + if (width * height <= FS / 4) DPB = 16; + else if (width * height <= FS / 2) DPB = 12; + else if (width * height <= FS * 0.75) DPB = 8; + else DPB = 6; + */ + + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); } - mBitrateRange = Range.create(1, maxBps); - mParent.mError |= errors; - } - }; - VideoCapabilities mVideoCaps; + int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); + // CTBs are at least 8x8 + maxBlocks = Utils.divUp(maxBlocks, 8 * 8); + maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8); + maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8); - private boolean isVideo() { - return mVideoCaps != null; + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + 8 /* blockWidth */, 8 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else { + Log.w(TAG, "Unsupported mime " + mime); + // using minimal bitrate here. should be overriden by + // info from media_codecs.xml + maxBps = 64000; + errors |= ERROR_UNSUPPORTED; + } + mBitrateRange = Range.create(1, maxBps); + mParent.mError |= errors; } + } + /** + * A class that supports querying the encoding capabilities of a codec. + */ + public static final class EncoderCapabilities { /** - * Returns the video capabilities or {@code null} if this is not a video codec. + * Returns the supported range of quality values. + * + * @hide */ - public final VideoCapabilities getVideoCapabilities() { - return mVideoCaps; + public Range<Integer> getQualityRange() { + return mQualityRange; } /** - * Retrieve the codec capabilities for a certain {@code mime type}, {@code - * profile} and {@code level}. If the type, or profile-level combination - * is not understood by the framework, it returns null. + * Returns the supported range of encoder complexity values. + * <p> + * Some codecs may support multiple complexity levels, where higher + * complexity values use more encoder tools (e.g. perform more + * intensive calculations) to improve the quality or the compression + * ratio. Use a lower value to save power and/or time. */ - public static final CodecCapabilities CreateFromProfileLevel( - String mime, int profile, int level) { - CodecProfileLevel pl = new CodecProfileLevel(); - pl.profile = profile; - pl.level = level; - MediaFormat defaultFormat = new MediaFormat(); - defaultFormat.setString(MediaFormat.KEY_MIME, mime); - - CodecCapabilities ret = new CodecCapabilities( - new CodecProfileLevel[] { pl }, new int[0], true /* encoder */, - 0 /* flags */, defaultFormat, new MediaFormat() /* info */); - if (ret.mError != 0) { - return null; - } - return ret; - } - - /* package private */ CodecCapabilities( - CodecProfileLevel[] profLevs, int[] colFmts, - boolean encoder, int flags, - Map<String, Object>defaultFormatMap, - Map<String, Object>capabilitiesMap) { - this(profLevs, colFmts, encoder, flags, - new MediaFormat(defaultFormatMap), - new MediaFormat(capabilitiesMap)); + public Range<Integer> getComplexityRange() { + return mComplexityRange; } - private MediaFormat mCapabilitiesInfo; - - /* package private */ CodecCapabilities( - CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, int flags, - MediaFormat defaultFormat, MediaFormat info) { - final Map<String, Object> map = info.getMap(); - profileLevels = profLevs; - colorFormats = colFmts; - mFlagsVerified = flags; - mDefaultFormat = defaultFormat; - mCapabilitiesInfo = info; - mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME); - - if (mMime.toLowerCase().startsWith("audio/")) { - mAudioCaps = AudioCapabilities.create(info, this); - mAudioCaps.setDefaultFormat(mDefaultFormat); - } else if (mMime.toLowerCase().startsWith("video/")) { - mVideoCaps = VideoCapabilities.create(info, this); - } - if (encoder) { - mEncoderCaps = EncoderCapabilities.create(info, this); - mEncoderCaps.setDefaultFormat(mDefaultFormat); - } + /** Constant quality mode */ + public static final int BITRATE_MODE_CQ = 0; + /** Variable bitrate mode */ + public static final int BITRATE_MODE_VBR = 1; + /** Constant bitrate mode */ + public static final int BITRATE_MODE_CBR = 2; + + private static final Feature[] bitrates = new Feature[] { + new Feature("VBR", BITRATE_MODE_VBR, true), + new Feature("CBR", BITRATE_MODE_CBR, false), + new Feature("CQ", BITRATE_MODE_CQ, false) + }; - for (Feature feat: getValidFeatures()) { - String key = MediaFormat.KEY_FEATURE_ + feat.mName; - Integer yesNo = (Integer)map.get(key); - if (yesNo == null) { - continue; - } else if (yesNo > 0) { - mFlagsRequired |= feat.mValue; - mDefaultFormat.setInteger(key, 1); - } else { - mFlagsSupported |= feat.mValue; - mDefaultFormat.setInteger(key, 1); + private static int parseBitrateMode(String mode) { + for (Feature feat: bitrates) { + if (feat.mName.equalsIgnoreCase(mode)) { + return feat.mValue; } - // TODO restrict features by mFlagsVerified once all codecs reliably verify them } + return 0; } /** - * A class that supports querying the audio capabilities of a codec. + * Query whether a bitrate mode is supported. */ - public static final class AudioCapabilities extends BaseCapabilities { - private static final String TAG = "AudioCapabilities"; - - private int[] mSampleRates; - private Range<Integer>[] mSampleRateRanges; - private int mMaxInputChannelCount; - - private static final int MAX_INPUT_CHANNEL_COUNT = 30; - - /** - * Returns the array of supported sample rates if the codec - * supports only discrete values. Otherwise, it returns - * {@code null}. The array is sorted in ascending order. - */ - public final int[] getSupportedSampleRates() { - return Arrays.copyOf(mSampleRates, mSampleRates.length); + public boolean isBitrateModeSupported(int mode) { + for (Feature feat: bitrates) { + if (mode == feat.mValue) { + return (mBitControl & (1 << mode)) != 0; + } } + return false; + } - /** - * Returns the array of supported sample rate ranges. The - * array is sorted in ascending order, and the ranges are - * distinct. - */ - public final Range<Integer>[] getSupportedSampleRateRanges() { - return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); - } + private Range<Integer> mQualityRange; + private Range<Integer> mComplexityRange; + private CodecCapabilities mParent; - /** - * Returns the maximum number of input channels supported. The codec - * supports any number of channels between 1 and this maximum value. - */ - public final int getMaxInputChannelCount() { - return mMaxInputChannelCount; - } + /* no public constructor */ + private EncoderCapabilities() { } - /* no public constructor */ - private AudioCapabilities() { } + /** @hide */ + public static EncoderCapabilities create( + MediaFormat info, CodecCapabilities parent) { + EncoderCapabilities caps = new EncoderCapabilities(); + caps.init(info, parent); + return caps; + } - /** @hide */ - public static AudioCapabilities create( - MediaFormat info, CodecCapabilities parent) { - AudioCapabilities caps = new AudioCapabilities(); - caps.init(info, parent); - return caps; - } + /** @hide */ + public void init(MediaFormat info, CodecCapabilities parent) { + // no support for complexity or quality yet + mParent = parent; + mComplexityRange = Range.create(0, 0); + mQualityRange = Range.create(0, 0); + mBitControl = (1 << BITRATE_MODE_VBR); + + applyLevelLimits(); + parseFromInfo(info); + } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { - super.init(info, parent); - initWithPlatformLimits(); - applyLevelLimits(); - parseFromInfo(info); + private void applyLevelLimits() { + String mime = mParent.getMimeType(); + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { + mComplexityRange = Range.create(0, 8); + mBitControl = (1 << BITRATE_MODE_CQ); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { + mBitControl = (1 << BITRATE_MODE_CBR); } + } - private void initWithPlatformLimits() { - mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT; - // mBitrateRange = Range.create(1, 320000); - mSampleRateRanges = new Range[] { Range.create(8000, 96000) }; - mSampleRates = null; - } + private int mBitControl; + private Integer mDefaultComplexity; + private Integer mDefaultQuality; + private String mQualityScale; - private boolean supports(Integer sampleRate, Integer inputChannels) { - // channels and sample rates are checked orthogonally - if (inputChannels != null && - (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) { - return false; - } - if (sampleRate != null) { - int ix = Utils.binarySearchDistinctRanges( - mSampleRateRanges, sampleRate); - if (ix < 0) { - return false; - } - } - return true; - } + private void parseFromInfo(MediaFormat info) { + Map<String, Object> map = info.getMap(); - /** - * Query whether the sample rate is supported by the codec. - */ - public final boolean isSampleRateSupported(int sampleRate) { - return supports(sampleRate, null); + if (info.containsKey("complexity-range")) { + mComplexityRange = Utils + .parseIntRange(info.getString("complexity-range"), mComplexityRange); + // TODO should we limit this to level limits? } - - /** modifies rates */ - private void limitSampleRates(int[] rates) { - Arrays.sort(rates); - ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>(); - for (int rate: rates) { - if (supports(rate, null /* channels */)) { - ranges.add(Range.create(rate, rate)); - } - } - mSampleRateRanges = ranges.toArray(new Range[ranges.size()]); - createDiscreteSampleRates(); + if (info.containsKey("quality-range")) { + mQualityRange = Utils + .parseIntRange(info.getString("quality-range"), mQualityRange); } - - private void createDiscreteSampleRates() { - mSampleRates = new int[mSampleRateRanges.length]; - for (int i = 0; i < mSampleRateRanges.length; i++) { - mSampleRates[i] = mSampleRateRanges[i].getLower(); + if (info.containsKey("feature-bitrate-control")) { + for (String mode: info.getString("feature-bitrate-control").split(",")) { + mBitControl |= parseBitrateMode(mode); } } - /** modifies rateRanges */ - private void limitSampleRates(Range<Integer>[] rateRanges) { - sortDistinctRanges(rateRanges); - mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); + try { + mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default")); + } catch (NumberFormatException e) { } - // check if all values are discrete - for (Range<Integer> range: mSampleRateRanges) { - if (!range.getLower().equals(range.getUpper())) { - mSampleRates = null; - return; - } - } - createDiscreteSampleRates(); - } + try { + mDefaultQuality = Integer.parseInt((String)map.get("quality-default")); + } catch (NumberFormatException e) { } - private void applyLevelLimits() { - int[] sampleRates = null; - Range<Integer> sampleRateRange = null, bitRates = null; - int maxChannels = 0; - String mime = mParent.getMime(); - - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) { - sampleRates = new int[] { - 8000, 11025, 12000, - 16000, 22050, 24000, - 32000, 44100, 48000 }; - bitRates = Range.create(8000, 320000); - maxChannels = 2; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(4750, 12200); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { - sampleRates = new int[] { 16000 }; - bitRates = Range.create(6600, 23850); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { - sampleRates = new int[] { - 7350, 8000, - 11025, 12000, 16000, - 22050, 24000, 32000, - 44100, 48000, 64000, - 88200, 96000 }; - bitRates = Range.create(8000, 510000); - maxChannels = 48; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) { - bitRates = Range.create(32000, 500000); - sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 }; - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) { - bitRates = Range.create(6000, 510000); - sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 }; - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { - sampleRateRange = Range.create(1, 96000); - bitRates = Range.create(1, 10000000); - maxChannels = 8; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { - sampleRateRange = Range.create(1, 655350); - // lossless codec, so bitrate is ignored - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(64000, 64000); - // platform allows multiple channels for this format - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(13000, 13000); - maxChannels = 1; - } else { - Log.w(TAG, "Unsupported mime " + mime); - mParent.mError |= ERROR_UNSUPPORTED; - } + mQualityScale = (String)map.get("quality-scale"); + } - // restrict ranges - if (sampleRates != null) { - limitSampleRates(sampleRates); - } else if (sampleRateRange != null) { - limitSampleRates(new Range[] { sampleRateRange }); + private boolean supports( + Integer complexity, Integer quality, Integer profile) { + boolean ok = true; + if (ok && complexity != null) { + ok = mComplexityRange.contains(complexity); + } + if (ok && quality != null) { + ok = mQualityRange.contains(quality); + } + if (ok && profile != null) { + for (CodecProfileLevel pl: mParent.profileLevels) { + if (pl.profile == profile) { + profile = null; + break; + } } - applyLimits(maxChannels, bitRates); + ok = profile == null; } + return ok; + } - private void applyLimits(int maxInputChannels, Range<Integer> bitRates) { - mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount) - .clamp(maxInputChannels); - if (bitRates != null) { - mBitrateRange = mBitrateRange.intersect(bitRates); + /** @hide */ + public void setDefaultFormat(MediaFormat format) { + // don't list trivial quality/complexity as default for now + if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) + && mDefaultQuality != null) { + format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality); + } + if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower()) + && mDefaultComplexity != null) { + format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity); + } + // bitrates are listed in order of preference + for (Feature feat: bitrates) { + if ((mBitControl & (1 << feat.mValue)) != 0) { + format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue); + break; } } + } - private void parseFromInfo(MediaFormat info) { - int maxInputChannels = MAX_INPUT_CHANNEL_COUNT; - Range<Integer> bitRates = POSITIVE_INTEGERS; + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + final Map<String, Object> map = format.getMap(); + final String mime = mParent.getMimeType(); - if (info.containsKey("sample-rate-ranges")) { - String[] rateStrings = info.getString("sample-rate-ranges").split(","); - Range<Integer>[] rateRanges = new Range[rateStrings.length]; - for (int i = 0; i < rateStrings.length; i++) { - rateRanges[i] = Utils.parseIntRange(rateStrings[i], null); - } - limitSampleRates(rateRanges); - } - if (info.containsKey("max-channel-count")) { - maxInputChannels = Utils.parseIntSafely( - info.getString("max-channel-count"), maxInputChannels); - } - if (info.containsKey("bitrate-range")) { - bitRates = bitRates.intersect( - Utils.parseIntRange(info.getString("bitrate"), bitRates)); - } - applyLimits(maxInputChannels, bitRates); + Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE); + if (mode != null && !isBitrateModeSupported(mode)) { + return false; } - /** @hide */ - public void setDefaultFormat(MediaFormat format) { - // report settings that have only a single choice - if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { - format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); - } - if (mMaxInputChannelCount == 1) { - // mono-only format - format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); - } - if (mSampleRates != null && mSampleRates.length == 1) { - format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]); + Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY); + if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) { + Integer flacComplexity = + (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL); + if (complexity == null) { + complexity = flacComplexity; + } else if (flacComplexity != null && complexity != flacComplexity) { + throw new IllegalArgumentException( + "conflicting values for complexity and " + + "flac-compression-level"); } } - /** @hide */ - public boolean supportsFormat(MediaFormat format) { - Map<String, Object> map = format.getMap(); - Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE); - Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT); - if (!supports(sampleRate, channels)) { - return false; + // other audio parameters + Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); + if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) { + Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE); + if (profile == null) { + profile = aacProfile; + } else if (aacProfile != null && aacProfile != profile) { + throw new IllegalArgumentException( + "conflicting values for profile and aac-profile"); } - - // nothing to do for: - // KEY_CHANNEL_MASK: codecs don't get this - // KEY_IS_ADTS: required feature for all AAC decoders - return true; } - }; - - AudioCapabilities mAudioCaps; - private boolean isAudio() { - return mAudioCaps != null; - } - /** - * Returns the audio capabilities or {@code null} if this is not an audio codec. - */ - public final AudioCapabilities getAudioCapabilities() { - return mAudioCaps; - } + Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY); - /** @hide */ - public CodecCapabilities dup() { - return new CodecCapabilities( - // clone writable arrays - Arrays.copyOf(profileLevels, profileLevels.length), - Arrays.copyOf(colorFormats, colorFormats.length), - isEncoder(), - mFlagsVerified, - mDefaultFormat, - mCapabilitiesInfo); + return supports(complexity, quality, profile); } }; diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java index d74f22d..5084c5c 100644 --- a/media/java/android/media/MediaCodecList.java +++ b/media/java/android/media/MediaCodecList.java @@ -36,6 +36,8 @@ final public class MediaCodecList { /** * Count the number of available (regular) codecs. * + * @deprecated Use {@link #getCodecInfos} instead. + * * @see #REGULAR_CODECS */ public static final int getCodecCount() { @@ -49,6 +51,8 @@ final public class MediaCodecList { * Return the {@link MediaCodecInfo} object for the codec at * the given {@code index} in the regular list. * + * @deprecated Use {@link #getCodecInfos} instead. + * * @see #REGULAR_CODECS */ public static final MediaCodecInfo getCodecInfoAt(int index) { @@ -116,13 +120,22 @@ final public class MediaCodecList { /** * Use in {@link #MediaCodecList} to enumerate only codecs that are suitable - * for normal playback and recording. + * for regular (buffer-to-buffer) decoding or encoding. + * + * <em>NOTE:</em> These are the codecs that are returned prior to API 21, + * using the now deprecated static methods. */ public static final int REGULAR_CODECS = 0; /** * Use in {@link #MediaCodecList} to enumerate all codecs, even ones that are - * not suitable for normal playback or recording. + * not suitable for regular (buffer-to-buffer) decoding or encoding. These + * include codecs, for example, that only work with special input or output + * surfaces, such as secure-only or tunneled-only codecs. + * + * @see MediaCodecInfo.CodecCapabilities#isFormatSupported + * @see MediaCodecInfo.CodecCapabilities#FEATURE_SecurePlayback + * @see MediaCodecInfo.CodecCapabilities#FEATURE_TunneledPlayback */ public static final int ALL_CODECS = 1; diff --git a/media/java/android/media/MediaDescription.aidl b/media/java/android/media/MediaDescription.aidl new file mode 100644 index 0000000..6f934f7 --- /dev/null +++ b/media/java/android/media/MediaDescription.aidl @@ -0,0 +1,18 @@ +/* Copyright 2014, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.media; + +parcelable MediaDescription; diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java new file mode 100644 index 0000000..4399c0d --- /dev/null +++ b/media/java/android/media/MediaDescription.java @@ -0,0 +1,276 @@ +package android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Size; + +/** + * A simple set of metadata for a media item suitable for display. This can be + * created using the Builder or retrieved from existing metadata using + * {@link MediaMetadata#getDescription()}. + */ +public class MediaDescription implements Parcelable { + /** + * A unique persistent id for the content or null. + */ + private final String mMediaId; + /** + * A primary title suitable for display or null. + */ + private final CharSequence mTitle; + /** + * A subtitle suitable for display or null. + */ + private final CharSequence mSubtitle; + /** + * A description suitable for display or null. + */ + private final CharSequence mDescription; + /** + * A bitmap icon suitable for display or null. + */ + private final Bitmap mIcon; + /** + * A Uri for an icon suitable for display or null. + */ + private final Uri mIconUri; + /** + * Extras for opaque use by apps/system. + */ + private final Bundle mExtras; + + private MediaDescription(String mediaId, CharSequence title, CharSequence subtitle, + CharSequence description, Bitmap icon, Uri iconUri, Bundle extras) { + mMediaId = mediaId; + mTitle = title; + mSubtitle = subtitle; + mDescription = description; + mIcon = icon; + mIconUri = iconUri; + mExtras = extras; + } + + private MediaDescription(Parcel in) { + mMediaId = in.readString(); + mTitle = in.readCharSequence(); + mSubtitle = in.readCharSequence(); + mDescription = in.readCharSequence(); + mIcon = in.readParcelable(null); + mIconUri = in.readParcelable(null); + mExtras = in.readBundle(); + } + + /** + * Returns the media id or null. See + * {@link MediaMetadata#METADATA_KEY_MEDIA_ID}. + */ + public @Nullable String getMediaId() { + return mMediaId; + } + + /** + * Returns a title suitable for display or null. + * + * @return A title or null. + */ + public @Nullable CharSequence getTitle() { + return mTitle; + } + + /** + * Returns a subtitle suitable for display or null. + * + * @return A subtitle or null. + */ + public @Nullable CharSequence getSubtitle() { + return mSubtitle; + } + + /** + * Returns a description suitable for display or null. + * + * @return A description or null. + */ + public @Nullable CharSequence getDescription() { + return mDescription; + } + + /** + * Returns a bitmap icon suitable for display or null. + * + * @return An icon or null. + */ + public @Nullable Bitmap getIconBitmap() { + return mIcon; + } + + /** + * Returns a Uri for an icon suitable for display or null. + * + * @return An icon uri or null. + */ + public @Nullable Uri getIconUri() { + return mIconUri; + } + + /** + * Returns any extras that were added to the description. + * + * @return A bundle of extras or null. + */ + public @Nullable Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mMediaId); + dest.writeCharSequence(mTitle); + dest.writeCharSequence(mSubtitle); + dest.writeCharSequence(mDescription); + dest.writeParcelable(mIcon, flags); + dest.writeParcelable(mIconUri, flags); + dest.writeBundle(mExtras); + } + + @Override + public String toString() { + return mTitle + ", " + mSubtitle + ", " + mDescription; + } + + public static final Parcelable.Creator<MediaDescription> CREATOR = + new Parcelable.Creator<MediaDescription>() { + @Override + public MediaDescription createFromParcel(Parcel in) { + return new MediaDescription(in); + } + + @Override + public MediaDescription[] newArray(int size) { + return new MediaDescription[size]; + } + }; + + /** + * Builder for {@link MediaDescription} objects. + */ + public static class Builder { + private String mMediaId; + private CharSequence mTitle; + private CharSequence mSubtitle; + private CharSequence mDescription; + private Bitmap mIcon; + private Uri mIconUri; + private Bundle mExtras; + + /** + * Creates an initially empty builder. + */ + public Builder() { + } + + /** + * Sets the media id. + * + * @param mediaId The unique id for the item or null. + * @return this + */ + public Builder setMediaId(@Nullable String mediaId) { + mMediaId = mediaId; + return this; + } + + /** + * Sets the title. + * + * @param title A title suitable for display to the user or null. + * @return this + */ + public Builder setTitle(@Nullable CharSequence title) { + mTitle = title; + return this; + } + + /** + * Sets the subtitle. + * + * @param subtitle A subtitle suitable for display to the user or null. + * @return this + */ + public Builder setSubtitle(@Nullable CharSequence subtitle) { + mSubtitle = subtitle; + return this; + } + + /** + * Sets the description. + * + * @param description A description suitable for display to the user or + * null. + * @return this + */ + public Builder setDescription(@Nullable CharSequence description) { + mDescription = description; + return this; + } + + /** + * Sets the icon. + * + * @param icon A {@link Bitmap} icon suitable for display to the user or + * null. + * @return this + */ + public Builder setIconBitmap(@Nullable Bitmap icon) { + mIcon = icon; + return this; + } + + /** + * Sets the icon uri. + * + * @param iconUri A {@link Uri} for an icon suitable for display to the + * user or null. + * @return this + */ + public Builder setIconUri(@Nullable Uri iconUri) { + mIconUri = iconUri; + return this; + } + + /** + * Sets a bundle of extras. + * + * @param extras The extras to include with this description or null. + * @return this + */ + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + public MediaDescription build() { + return new MediaDescription(mMediaId, mTitle, mSubtitle, mDescription, mIcon, mIconUri, + mExtras); + } + } +} diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index fd79495..2036533 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -120,19 +120,6 @@ public final class MediaFormat { private Map<String, Object> mMap; /** - * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities} - * feature name describing a required or optional feature for a codec capabilities - * query. - * The associated value is an integer, where non-0 value means the feature is - * requested to be present, while 0 value means the feature is requested to be not - * present. - * @see MediaCodecList#findDecoderForFormat - * @see MediaCodecList#findEncoderForFormat - * @see MediaCodecInfo.CodecCapabilities#isFormatSupported - */ - public static final String KEY_FEATURE_ = "feature-"; - - /** * A key describing the mime type of the MediaFormat. * The associated value is a string. */ @@ -422,6 +409,8 @@ public final class MediaFormat { * codec specific, but lower values generally result in more efficient * (smaller-sized) encoding. * + * @hide + * * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#getQualityRange */ public static final String KEY_QUALITY = "quality"; @@ -510,6 +499,21 @@ public final class MediaFormat { } /** + * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities} + * feature name describing a required or optional feature for a codec capabilities + * query. + * The associated value is an integer, where non-0 value means the feature is + * requested to be present, while 0 value means the feature is requested to be not + * present. + * @see MediaCodecList#findDecoderForFormat + * @see MediaCodecList#findEncoderForFormat + * @see MediaCodecInfo.CodecCapabilities#isFormatSupported + * + * @hide + */ + public static final String KEY_FEATURE_ = "feature-"; + + /** * Returns the value of an integer key. */ public final int getInteger(String name) { @@ -559,6 +563,23 @@ public final class MediaFormat { } /** + * Returns whether a feature is to be enabled ({@code true}) or disabled + * ({@code false}). + * + * @param feature the name of a {@link MediaCodecInfo.CodecCapabilities} feature. + * + * @throws IllegalArgumentException if the feature was neither set to be enabled + * nor to be disabled. + */ + public boolean getFeatureEnabled(String feature) { + Integer enabled = (Integer)mMap.get(KEY_FEATURE_ + feature); + if (enabled == null) { + throw new IllegalArgumentException("feature is not specified"); + } + return enabled != 0; + } + + /** * Sets the value of an integer key. */ public final void setInteger(String name, int value) { @@ -594,6 +615,23 @@ public final class MediaFormat { } /** + * Sets whether a feature is to be enabled ({@code true}) or disabled + * ({@code false}). + * + * If {@code enabled} is {@code true}, the feature is requested to be present. + * Otherwise, the feature is requested to be not present. + * + * @param feature the name of a {@link MediaCodecInfo.CodecCapabilities} feature. + * + * @see MediaCodecList#findDecoderForFormat + * @see MediaCodecList#findEncoderForFormat + * @see MediaCodecInfo.CodecCapabilities#isFormatSupported + */ + public void setFeatureEnabled(String feature, boolean enabled) { + setInteger(KEY_FEATURE_ + feature, enabled ? 1 : 0); + } + + /** * Creates a minimal audio format. * @param mime The mime type of the content. * @param sampleRate The sampling rate of the content. diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java index 74f7a96..b4e6033 100644 --- a/media/java/android/media/MediaMetadata.java +++ b/media/java/android/media/MediaMetadata.java @@ -17,15 +17,22 @@ package android.media; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ContentProviderClient; +import android.content.ContentResolver; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.browse.MediaBrowser; +import android.media.session.MediaController; import android.net.Uri; import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.text.format.Time; import android.util.ArrayMap; import android.util.Log; +import android.util.Size; import android.util.SparseArray; import java.util.Set; @@ -119,7 +126,9 @@ public final class MediaMetadata implements Parcelable { public static final String METADATA_KEY_ART = "android.media.metadata.ART"; /** - * The artwork for the media as a Uri. + * The artwork for the media as a Uri formatted String. The artwork can be + * loaded using a combination of {@link ContentResolver#openInputStream} and + * {@link BitmapFactory#decodeStream}. */ public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI"; @@ -130,7 +139,10 @@ public final class MediaMetadata implements Parcelable { public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART"; /** - * The artwork for the album of the media's original source as a Uri. + * The artwork for the album of the media's original source as a Uri + * formatted String. The artwork can be loaded using a combination of + * {@link ContentResolver#openInputStream} and + * {@link BitmapFactory#decodeStream}. */ public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI"; @@ -181,13 +193,26 @@ public final class MediaMetadata implements Parcelable { = "android.media.metadata.DISPLAY_ICON"; /** - * An icon or thumbnail that is suitable for display to the user. When - * displaying more information for media described by this metadata the - * display description should be preferred to other fields when present. + * A Uri formatted String for an icon or thumbnail that is suitable for + * display to the user. When displaying more information for media described + * by this metadata the display description should be preferred to other + * fields when present. The icon can be loaded using a combination of + * {@link ContentResolver#openInputStream} and + * {@link BitmapFactory#decodeStream}. */ public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI"; + /** + * A String key for identifying the content. This value is specific to the + * service providing the content. If used, this should be a persistent + * unique key for the underlying content. It may be used with + * {@link MediaController.TransportControls#playFromMediaId(String, Bundle)} + * to initiate playback when provided by a {@link MediaBrowser} connected to + * the same app. + */ + public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID"; + private static final String[] PREFERRED_DESCRIPTION_ORDER = { METADATA_KEY_TITLE, METADATA_KEY_ARTIST, @@ -277,7 +302,7 @@ public final class MediaMetadata implements Parcelable { } private final Bundle mBundle; - private Description mDescription; + private MediaDescription mDescription; private MediaMetadata(Bundle bundle) { mBundle = new Bundle(bundle); @@ -406,11 +431,13 @@ public final class MediaMetadata implements Parcelable { * * @return A simple description of this metadata. */ - public @NonNull Description getDescription() { + public @NonNull MediaDescription getDescription() { if (mDescription != null) { return mDescription; } + String mediaId = getString(METADATA_KEY_MEDIA_ID); + CharSequence[] text = new CharSequence[3]; Bitmap icon = null; Uri iconUri = null; @@ -454,7 +481,15 @@ public final class MediaMetadata implements Parcelable { } } - mDescription = new Description(text[0], text[1], text[2], icon, iconUri); + MediaDescription.Builder bob = new MediaDescription.Builder(); + bob.setMediaId(mediaId); + bob.setTitle(text[0]); + bob.setSubtitle(text[1]); + bob.setDescription(text[2]); + bob.setIconBitmap(icon); + bob.setIconUri(iconUri); + mDescription = bob.build(); + return mDescription; } @@ -668,90 +703,4 @@ public final class MediaMetadata implements Parcelable { return new MediaMetadata(mBundle); } } - - /** - * A simple form of the metadata that can be used for display. - */ - public final class Description { - /** - * A primary title suitable for display or null. - */ - private final CharSequence mTitle; - /** - * A subtitle suitable for display or null. - */ - private final CharSequence mSubtitle; - /** - * A description suitable for display or null. - */ - private final CharSequence mDescription; - /** - * A bitmap icon suitable for display or null. - */ - private final Bitmap mIcon; - /** - * A Uri for an icon suitable for display or null. - */ - private final Uri mIconUri; - - /** - * Returns the best available title or null. - * - * @return A title or null. - */ - public @Nullable CharSequence getTitle() { - return mTitle; - } - - /** - * Returns the best available subtitle or null. - * - * @return A subtitle or null. - */ - public @Nullable CharSequence getSubtitle() { - return mSubtitle; - } - - /** - * Returns the best available description or null. - * - * @return A description or null. - */ - public @Nullable CharSequence getDescription() { - return mDescription; - } - - /** - * Returns the best available icon or null. - * - * @return An icon or null. - */ - public @Nullable Bitmap getIcon() { - return mIcon; - } - - /** - * Returns the best available icon Uri or null. - * - * @return An icon uri or null. - */ - public @Nullable Uri getIconUri() { - return mIconUri; - } - - private Description(CharSequence title, CharSequence subtitle, CharSequence description, - Bitmap icon, Uri iconUri) { - mTitle = title; - mSubtitle = subtitle; - mDescription = description; - mIcon = icon; - mIconUri = iconUri; - } - - @Override - public String toString() { - return mTitle + ", " + mSubtitle + ", " + mDescription; - } - } - } diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index a221104..7d075ba 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -115,6 +115,15 @@ public class Ringtone { } /** + * Returns the {@link AudioAttributes} used by this object. + * @return the {@link AudioAttributes} that were set with + * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set. + */ + public AudioAttributes getAudioAttributes() { + return mAudioAttributes; + } + + /** * Returns a human-presentable title for ringtone. Looks in media * content provider. If not in either, uses the filename * diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java index 9b9c767..4f74bdd 100644 --- a/media/java/android/media/SoundPool.java +++ b/media/java/android/media/SoundPool.java @@ -151,6 +151,10 @@ public class SoundPool { /** * Constructs a new Builder with the defaults format values. + * If not provided, the maximum number of streams is 1 (see {@link #setMaxStreams(int)} to + * change it), and the audio attributes have a usage value of + * {@link AudioAttributes#USAGE_MEDIA} (see {@link #setAudioAttributes(AudioAttributes)} to + * change them). */ public Builder() { } diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index 1c6d81f..debaf45 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -16,6 +16,7 @@ package android.media.browse; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -23,26 +24,27 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ParceledListSlice; -import android.content.res.Configuration; -import android.graphics.Bitmap; +import android.media.MediaDescription; import android.media.session.MediaSession; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; +import android.service.media.MediaBrowserService; +import android.service.media.IMediaBrowserService; +import android.service.media.IMediaBrowserServiceCallbacks; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; -import android.util.SparseArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Objects; -import java.util.Set; /** * Browses media content offered by a link MediaBrowserService. @@ -67,8 +69,6 @@ public final class MediaBrowser { private final Handler mHandler = new Handler(); private final ArrayMap<Uri,Subscription> mSubscriptions = new ArrayMap<Uri, MediaBrowser.Subscription>(); - private final SparseArray<IconRequest> mIconRequests = - new SparseArray<IconRequest>(); private int mState = CONNECT_STATE_DISCONNECTED; private MediaServiceConnection mServiceConnection; @@ -77,7 +77,6 @@ public final class MediaBrowser { private Uri mRootUri; private MediaSession.Token mMediaSessionToken; private Bundle mExtras; - private int mNextSeq; /** * Creates a media browser for the specified media browse service. @@ -363,49 +362,6 @@ public final class MediaBrowser { } /** - * Loads the icon of a media item. - * - * @param uri The uri of the Icon. - * @param width The preferred width of the icon in dp. - * @param height The preferred width of the icon in dp. - * @param callback The callback to receive the icon. - */ - public void loadIcon(final @NonNull Uri uri, final int width, final int height, - final @NonNull IconCallback callback) { - if (uri == null) { - throw new IllegalArgumentException("Icon uri cannot be null"); - } - if (callback == null) { - throw new IllegalArgumentException("Icon callback cannot be null"); - } - mHandler.post(new Runnable() { - @Override - public void run() { - for (int i = 0; i < mIconRequests.size(); i++) { - IconRequest existingRequest = mIconRequests.valueAt(i); - if (existingRequest.isSameRequest(uri, width, height)) { - existingRequest.addCallback(callback); - return; - } - } - final int seq = mNextSeq++; - IconRequest request = new IconRequest(seq, uri, width, height); - request.addCallback(callback); - mIconRequests.put(seq, request); - if (mState == CONNECT_STATE_CONNECTED) { - try { - mServiceBinder.loadIcon(seq, uri, width, height, mServiceCallbacks); - } catch (RemoteException e) { - // Process is crashing. We will disconnect, and upon reconnect we will - // automatically reload the icons. So nothing to do here. - Log.d(TAG, "loadIcon failed with RemoteException uri=" + uri); - } - } - } - }); - } - - /** * For debugging. */ private static String getStateLabel(int state) { @@ -461,18 +417,6 @@ public final class MediaBrowser { Log.d(TAG, "addSubscription failed with RemoteException parentUri=" + uri); } } - - for (int i = 0; i < mIconRequests.size(); i++) { - IconRequest request = mIconRequests.valueAt(i); - try { - mServiceBinder.loadIcon(request.mSeq, request.mUri, - request.mWidth, request.mHeight, mServiceCallbacks); - } catch (RemoteException e) { - // Process is crashing. We will disconnect, and upon reconnect we will - // automatically reload. So nothing to do here. - Log.d(TAG, "loadIcon failed with RemoteException request=" + request); - } - } } }); } @@ -515,7 +459,7 @@ public final class MediaBrowser { return; } - List<MediaBrowserItem> data = list.getList(); + List<MediaItem> data = list.getList(); if (DBG) { Log.d(TAG, "onLoadChildren for " + mServiceComponent + " uri=" + uri); } @@ -539,32 +483,6 @@ public final class MediaBrowser { }); } - private final void onLoadIcon(final IMediaBrowserServiceCallbacks callback, - final int seqNum, final Bitmap bitmap) { - mHandler.post(new Runnable() { - @Override - public void run() { - // Check that there hasn't been a disconnect or a different - // ServiceConnection. - if (!isCurrent(callback, "onLoadIcon")) { - return; - } - - IconRequest request = mIconRequests.get(seqNum); - if (request == null) { - Log.d(TAG, "onLoadIcon called for seqNum=" + seqNum + " request=" - + request + " but the request is not registered"); - return; - } - mIconRequests.delete(seqNum); - for (IconCallback IconCallback : request.getCallbacks()) { - IconCallback.onIconLoaded(request.mUri, bitmap); - } - } - }); - } - - /** * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not. */ @@ -600,130 +518,169 @@ public final class MediaBrowser { Log.d(TAG, " mMediaSessionToken=" + mMediaSessionToken); } + public static class MediaItem implements Parcelable { + private final int mFlags; + private final MediaDescription mDescription; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) + public @interface Flags { } - /** - * Callbacks for connection related events. - */ - public static class ConnectionCallback { /** - * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed. + * Flag: Indicates that the item has children of its own. */ - public void onConnected() { + public static final int FLAG_BROWSABLE = 1 << 0; + + /** + * Flag: Indicates that the item is playable. + * <p> + * The Uri of this item may be passed to link android.media.session.MediaController#play(Uri) + * to start playing it. + * </p> + */ + public static final int FLAG_PLAYABLE = 1 << 1; + + /** + * Create a new MediaItem for use in browsing media. + * + * @param flags The flags for this item. + * @param description The description of the media, which must include a + * media id. + */ + public MediaItem(@Flags int flags, @NonNull MediaDescription description) { + if (description == null) { + throw new IllegalArgumentException("description cannot be null"); + } + if (TextUtils.isEmpty(description.getMediaId())) { + throw new IllegalArgumentException("description must have a non-empty media id"); + } + mFlags = flags; + mDescription = description; } /** - * Invoked when the client is disconnected from the media browser. + * Private constructor. */ - public void onConnectionSuspended() { + private MediaItem(Parcel in) { + mFlags = in.readInt(); + mDescription = MediaDescription.CREATOR.createFromParcel(in); } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mFlags); + mDescription.writeToParcel(out, flags); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("MediaItem{"); + sb.append("mFlags=").append(mFlags); + sb.append(", mDescription=").append(mDescription); + sb.append('}'); + return sb.toString(); + } + + public static final Parcelable.Creator<MediaItem> CREATOR = + new Parcelable.Creator<MediaItem>() { + @Override + public MediaItem createFromParcel(Parcel in) { + return new MediaItem(in); + } + + @Override + public MediaItem[] newArray(int size) { + return new MediaItem[size]; + } + }; + /** - * Invoked when the connection to the media browser failed. + * Gets the flags of the item. */ - public void onConnectionFailed() { + public @Flags int getFlags() { + return mFlags; } - } - /** - * Callbacks for subscription related events. - */ - public static abstract class SubscriptionCallback { /** - * Called when the list of children is loaded or updated. + * Returns whether this item is browsable. + * @see #FLAG_BROWSABLE */ - public void onChildrenLoaded(@NonNull Uri parentUri, - @NonNull List<MediaBrowserItem> children) { + public boolean isBrowsable() { + return (mFlags & FLAG_BROWSABLE) != 0; } /** - * Called when the Uri doesn't exist or other errors in subscribing. - * <p> - * If this is called, the subscription remains until {@link MediaBrowser#unsubscribe} - * called, because some errors may heal themselves. - * </p> + * Returns whether this item is playable. + * @see #FLAG_PLAYABLE */ - public void onError(@NonNull Uri uri) { + public boolean isPlayable() { + return (mFlags & FLAG_PLAYABLE) != 0; } - } - /** - * Callbacks for icon loading. - */ - public static abstract class IconCallback { /** - * Called when the icon is loaded. + * Returns the description of the media. */ - public void onIconLoaded(@NonNull Uri uri, @NonNull Bitmap bitmap) { + public @NonNull MediaDescription getDescription() { + return mDescription; } /** - * Called when the Uri doesn’t exist or the bitmap cannot be loaded. + * Returns the media id for this item. */ - public void onError(@NonNull Uri uri) { + public @NonNull String getMediaId() { + return mDescription.getMediaId(); } } - private static class IconRequest { - final int mSeq; - final Uri mUri; - final int mWidth; - final int mHeight; - final List<IconCallback> mCallbacks; + /** + * Callbacks for connection related events. + */ + public static class ConnectionCallback { /** - * Constructs an icon request. - * @param seq The unique sequence number assigned to the request by the media browser. - * @param uri The Uri for the icon. - * @param width The width for the icon. - * @param height The height for the icon. + * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed. */ - IconRequest(int seq, @NonNull Uri uri, int width, int height) { - if (uri == null) { - throw new IllegalArgumentException("Icon uri cannot be null"); - } - this.mSeq = seq; - this.mUri = uri; - this.mWidth = width; - this.mHeight = height; - mCallbacks = new ArrayList<IconCallback>(); + public void onConnected() { } /** - * Adds a callback to the icon request. - * If the callback already exists, it will not be added again. + * Invoked when the client is disconnected from the media browser. */ - public void addCallback(@NonNull IconCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null in IconRequest"); - } - if (!mCallbacks.contains(callback)) { - mCallbacks.add(callback); - } + public void onConnectionSuspended() { } /** - * Checks if the icon request has the same uri, width, and height as the given values. + * Invoked when the connection to the media browser failed. */ - public boolean isSameRequest(@Nullable Uri uri, int width, int height) { - return Objects.equals(mUri, uri) && mWidth == width && mHeight == height; + public void onConnectionFailed() { } + } - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("IconRequest{"); - sb.append("uri=").append(mUri); - sb.append(", width=").append(mWidth); - sb.append(", height=").append(mHeight); - sb.append(", seq=").append(mSeq); - sb.append('}'); - return sb.toString(); + /** + * Callbacks for subscription related events. + */ + public static abstract class SubscriptionCallback { + /** + * Called when the list of children is loaded or updated. + */ + public void onChildrenLoaded(@NonNull Uri parentUri, + @NonNull List<MediaItem> children) { } /** - * Gets an unmodifiable view of the list of callbacks associated with the request. + * Called when the Uri doesn't exist or other errors in subscribing. + * <p> + * If this is called, the subscription remains until {@link MediaBrowser#unsubscribe} + * called, because some errors may heal themselves. + * </p> */ - public List<IconCallback> getCallbacks() { - return Collections.unmodifiableList(mCallbacks); + public void onError(@NonNull Uri uri) { } } @@ -809,7 +766,7 @@ public final class MediaBrowser { } return true; } - }; + } /** * Callbacks from the service. @@ -852,14 +809,6 @@ public final class MediaBrowser { mediaBrowser.onLoadChildren(this, uri, list); } } - - @Override - public void onLoadIcon(final int seqNum, final Bitmap bitmap) { - MediaBrowser mediaBrowser = mMediaBrowser.get(); - if (mediaBrowser != null) { - mediaBrowser.onLoadIcon(this, seqNum, bitmap); - } - } } private static class Subscription { diff --git a/media/java/android/media/browse/MediaBrowserItem.java b/media/java/android/media/browse/MediaBrowserItem.java deleted file mode 100644 index 47ec46b..0000000 --- a/media/java/android/media/browse/MediaBrowserItem.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.browse; - -import android.annotation.DrawableRes; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.net.Uri; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Describes a media item in the list of items offered by a {@link MediaBrowserService}. - */ -public final class MediaBrowserItem implements Parcelable { - private final Uri mUri; - private final int mFlags; - private final CharSequence mTitle; - private final CharSequence mSummary; - private final Uri mIconUri; - private final int mIconResourceId; - private final Bundle mExtras; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) - public @interface Flags { } - - /** - * Flag: Indicates that the item has children of its own. - */ - public static final int FLAG_BROWSABLE = 1 << 0; - - /** - * Flag: Indicates that the item is playable. - * <p> - * The Uri of this item may be passed to link android.media.session.MediaController#play(Uri) - * to start playing it. - * </p> - */ - public static final int FLAG_PLAYABLE = 1 << 1; - - /** - * Initialize a MediaBrowserItem object. - */ - private MediaBrowserItem(@NonNull Uri uri, int flags, @NonNull CharSequence title, - CharSequence summary, @Nullable Uri iconUri, int iconResourceId, Bundle extras) { - if (uri == null) { - throw new IllegalArgumentException("uri can not be null"); - } - if (title == null) { - throw new IllegalArgumentException("title can not be null"); - } - mUri = uri; - mFlags = flags; - mTitle = title; - mSummary = summary; - mIconUri = iconUri; - mIconResourceId = iconResourceId; - mExtras = extras; - } - - /** - * Private constructor. - */ - private MediaBrowserItem(Parcel in) { - mUri = Uri.CREATOR.createFromParcel(in); - mFlags = in.readInt(); - mTitle = in.readCharSequence(); - if (in.readInt() != 0) { - mSummary = in.readCharSequence(); - } else { - mSummary = null; - } - if (in.readInt() != 0) { - mIconUri = Uri.CREATOR.createFromParcel(in); - } else { - mIconUri = null; - } - mIconResourceId = in.readInt(); - if (in.readInt() != 0) { - mExtras = Bundle.CREATOR.createFromParcel(in); - } else { - mExtras = null; - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - mUri.writeToParcel(out, flags); - out.writeInt(mFlags); - out.writeCharSequence(mTitle); - if (mSummary != null) { - out.writeInt(1); - out.writeCharSequence(mSummary); - } else { - out.writeInt(0); - } - if (mIconUri != null) { - out.writeInt(1); - mIconUri.writeToParcel(out, flags); - } else { - out.writeInt(0); - } - out.writeInt(mIconResourceId); - if (mExtras != null) { - out.writeInt(1); - mExtras.writeToParcel(out, flags); - } else { - out.writeInt(0); - } - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("MediaBrowserItem{"); - sb.append("mUri=").append(mUri); - sb.append(", mFlags=").append(mFlags); - sb.append(", mTitle=").append(mTitle); - sb.append(", mSummary=").append(mSummary); - sb.append(", mIconUri=").append(mIconUri); - sb.append(", mIconResourceId=").append(mIconResourceId); - sb.append('}'); - return sb.toString(); - } - - public static final Parcelable.Creator<MediaBrowserItem> CREATOR = - new Parcelable.Creator<MediaBrowserItem>() { - @Override - public MediaBrowserItem createFromParcel(Parcel in) { - return new MediaBrowserItem(in); - } - - @Override - public MediaBrowserItem[] newArray(int size) { - return new MediaBrowserItem[size]; - } - }; - - /** - * Gets the Uri of the item. - */ - public @NonNull Uri getUri() { - return mUri; - } - - /** - * Gets the flags of the item. - */ - public @Flags int getFlags() { - return mFlags; - } - - /** - * Returns whether this item is browsable. - * @see #FLAG_BROWSABLE - */ - public boolean isBrowsable() { - return (mFlags & FLAG_BROWSABLE) != 0; - } - - /** - * Returns whether this item is playable. - * @see #FLAG_PLAYABLE - */ - public boolean isPlayable() { - return (mFlags & FLAG_PLAYABLE) != 0; - } - - /** - * Gets the title of the item. - * @more - * The title will be shown as the first line of text when - * describing each item to the user. - */ - public @NonNull CharSequence getTitle() { - return mTitle; - } - - /** - * Gets summary of the item, or null if none. - * @more - * The summary will be shown as the second line of text when - * describing each item to the user. - */ - public @Nullable CharSequence getSummary() { - return mSummary; - } - - /** - * Gets the Uri of the icon. - */ - public @Nullable Uri getIconUri() { - return mIconUri; - } - - /** - * Gets the resource id of the icon. - */ - public @DrawableRes int getIconResourceId() { - return mIconResourceId; - } - - /** - * Gets additional service-specified extras about the - * item or its content, or null if none. - */ - public @Nullable Bundle getExtras() { - return mExtras; - } - - /** - * Builder for {@link MediaBrowserItem} objects. - */ - public static final class Builder { - private final Uri mUri; - private final int mFlags; - private final CharSequence mTitle; - private CharSequence mSummary; - private Uri mIconUri; - private int mIconResourceId; - private Bundle mExtras; - - /** - * Creates an item builder. - */ - public Builder(@NonNull Uri uri, @Flags int flags, @NonNull CharSequence title) { - if (uri == null) { - throw new IllegalArgumentException("uri can not be null"); - } - if (title == null) { - throw new IllegalArgumentException("title can not be null"); - } - mUri = uri; - mFlags = flags; - mTitle = title; - } - - /** - * Sets summary of the item, or null if none. - */ - public @NonNull Builder setSummary(@Nullable CharSequence summary) { - mSummary = summary; - return this; - } - - /** - * Sets the uri of the icon. - * <p> - * Either {@link #setIconUri(Uri)} or {@link #setIconResourceId(int)} should be called. - * If both are specified, the resource id will be used to load the icon. - * </p> - */ - public @NonNull Builder setIconUri(@Nullable Uri iconUri) { - mIconUri = iconUri; - return this; - } - - /** - * Sets the resource id of the icon. - * <p> - * Either {@link #setIconUri(Uri)} or {@link #setIconResourceId(int)} should be specified. - * If both are specified, the resource id will be used to load the icon. - * </p> - */ - public @NonNull Builder setIconResourceId(@DrawableRes int ResourceId) { - mIconResourceId = ResourceId; - return this; - } - - /** - * Sets additional service-specified extras about the - * item or its content. - */ - public @NonNull Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Builds the item. - */ - public @NonNull MediaBrowserItem build() { - return new MediaBrowserItem(mUri, mFlags, mTitle, mSummary, mIconUri, - mIconResourceId, mExtras); - } - } -} - diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 9911129..49087b0 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -30,7 +30,7 @@ oneway interface ISessionCallback { // These callbacks are for the TransportPerformer void onPlay(); - void onPlayUri(in Uri uri, in Bundle extras); + void onPlayFromMediaId(String uri, in Bundle extras); void onPlayFromSearch(String query, in Bundle extras); void onSkipToTrack(long id); void onPause(); diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index 6b80477..d684688 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -51,9 +51,9 @@ interface ISessionController { // These commands are for the TransportControls void play(); - void playUri(in Uri uri, in Bundle extras); + void playFromMediaId(String uri, in Bundle extras); void playFromSearch(String string, in Bundle extras); - void skipToTrack(long id); + void skipToQueueItem(long id); void pause(); void stop(); void next(); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index d7baaa9..9641f83 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -173,7 +173,7 @@ public final class MediaController { * * @return The current play queue or null. */ - public @Nullable List<MediaSession.Item> getQueue() { + public @Nullable List<MediaSession.QueueItem> getQueue() { try { ParceledListSlice queue = mSessionBinder.getQueue(); if (queue != null) { @@ -538,9 +538,9 @@ public final class MediaController { * @param queue A list of items in the current play queue. It should * include the currently playing item as well as previous and * upcoming items if applicable. - * @see MediaSession.Item + * @see MediaSession.QueueItem */ - public void onQueueChanged(@Nullable List<MediaSession.Item> queue) { + public void onQueueChanged(@Nullable List<MediaSession.QueueItem> queue) { } /** @@ -594,18 +594,19 @@ public final class MediaController { /** * Request that the player start playback for a specific {@link Uri}. * - * @param uri The uri of the requested media. + * @param mediaId The uri of the requested media. * @param extras Optional extras that can include extra information about the media item * to be played. */ - public void playUri(Uri uri, Bundle extras) { - if (uri == null) { - throw new IllegalArgumentException("You must specify a non-null Uri for playUri."); + public void playFromMediaId(String mediaId, Bundle extras) { + if (TextUtils.isEmpty(mediaId)) { + throw new IllegalArgumentException( + "You must specify a non-empty String for playFromMediaId."); } try { - mSessionBinder.playUri(uri, extras); + mSessionBinder.playFromMediaId(mediaId, extras); } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play(" + uri + ").", e); + Log.wtf(TAG, "Error calling play(" + mediaId + ").", e); } } @@ -631,9 +632,9 @@ public final class MediaController { * Play an item with a specific id in the play queue. If you specify an * id that is not in the play queue, the behavior is undefined. */ - public void skipToItem(long id) { + public void skipToQueueItem(long id) { try { - mSessionBinder.skipToTrack(id); + mSessionBinder.skipToQueueItem(id); } catch (RemoteException e) { Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e); } @@ -904,7 +905,7 @@ public final class MediaController { @Override public void onQueueChanged(ParceledListSlice parceledQueue) { - List<MediaSession.Item> queue = parceledQueue.getList(); + List<MediaSession.QueueItem> queue = parceledQueue.getList(); MediaController controller = mController.get(); if (controller != null) { controller.postMessage(MSG_UPDATE_QUEUE, queue, null); @@ -960,7 +961,7 @@ public final class MediaController { mCallback.onMetadataChanged((MediaMetadata) msg.obj); break; case MSG_UPDATE_QUEUE: - mCallback.onQueueChanged((List<MediaSession.Item>) msg.obj); + mCallback.onQueueChanged((List<MediaSession.QueueItem>) msg.obj); break; case MSG_UPDATE_QUEUE_TITLE: mCallback.onQueueTitleChanged((CharSequence) msg.obj); diff --git a/media/java/android/media/session/MediaSession.aidl b/media/java/android/media/session/MediaSession.aidl index 0ad58c4..f657cef 100644 --- a/media/java/android/media/session/MediaSession.aidl +++ b/media/java/android/media/session/MediaSession.aidl @@ -16,4 +16,4 @@ package android.media.session; parcelable MediaSession.Token; -parcelable MediaSession.Track;
\ No newline at end of file +parcelable MediaSession.QueueItem;
\ No newline at end of file diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 1670097..711831b 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; +import android.media.MediaDescription; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; @@ -38,6 +39,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; +import android.service.media.MediaBrowserService; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -180,18 +182,26 @@ public final class MediaSession { * @param handler The handler that events should be posted on. */ public void setCallback(@Nullable Callback callback, @Nullable Handler handler) { - if (callback == null) { - mCallback = null; - return; - } synchronized (mLock) { - if (mCallback != null && mCallback.mCallback == callback) { - Log.w(TAG, "Tried to set same callback, ignoring"); + if (callback == null) { + if (mCallback != null) { + mCallback.mCallback.mSession = null; + } + mCallback = null; return; } + if (mCallback != null) { + if (mCallback.mCallback == callback) { + Log.w(TAG, "Tried to set same callback, ignoring"); + return; + } + // We're changing callbacks, clear the session from the old one. + mCallback.mCallback.mSession = null; + } if (handler == null) { handler = new Handler(); } + callback.mSession = this; CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(), callback); mCallback = msgHandler; @@ -418,9 +428,9 @@ public final class MediaSession { * * @param queue A list of items in the play queue. */ - public void setQueue(@Nullable List<Item> queue) { + public void setQueue(@Nullable List<QueueItem> queue) { try { - mBinder.setQueue(new ParceledListSlice<Item>(queue)); + mBinder.setQueue(new ParceledListSlice<QueueItem>(queue)); } catch (RemoteException e) { Log.wtf("Dead object in setQueue.", e); } @@ -478,8 +488,8 @@ public final class MediaSession { postToCallback(CallbackMessageHandler.MSG_PLAY); } - private void dispatchPlayUri(Uri uri, Bundle extras) { - postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras); + private void dispatchPlayFromMediaId(String mediaId, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras); } private void dispatchPlayFromSearch(String query, Bundle extras) { @@ -744,9 +754,10 @@ public final class MediaSession { } /** - * Override to handle requests to play a specific {@link Uri}. + * Override to handle requests to play a specific mediaId that was + * provided by your app's {@link MediaBrowserService}. */ - public void onPlayUri(Uri uri, Bundle extras) { + public void onPlayFromMediaId(String mediaId, Bundle extras) { } /** @@ -759,7 +770,7 @@ public final class MediaSession { * Override to handle requests to play an item with a given id from the * play queue. */ - public void onSkipToItem(long id) { + public void onSkipToQueueItem(long id) { } /** @@ -824,10 +835,6 @@ public final class MediaSession { */ public void onCustomAction(@NonNull String action, @Nullable Bundle extras) { } - - private void setSession(MediaSession session) { - mSession = session; - } } /** @@ -872,10 +879,10 @@ public final class MediaSession { } @Override - public void onPlayUri(Uri uri, Bundle extras) { + public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPlayUri(uri, extras); + session.dispatchPlayFromMediaId(mediaId, extras); } } @@ -990,125 +997,59 @@ public final class MediaSession { } /** - * A single item that is part of the play queue. It contains information - * necessary to display a single item in the queue. + * A single item that is part of the play queue. It contains a description + * of the item and its id in the queue. */ - public static final class Item implements Parcelable { + public static final class QueueItem implements Parcelable { /** * This id is reserved. No items can be explicitly asigned this id. */ public static final int UNKNOWN_ID = -1; - private final MediaMetadata mMetadata; + private final MediaDescription mDescription; private final long mId; - private final Uri mUri; - private final Bundle mExtras; /** - * Create a new {@link MediaSession.Item}. + * Create a new {@link MediaSession.QueueItem}. * - * @param metadata The metadata for this item. + * @param description The {@link MediaDescription} for this item. * @param id An identifier for this item. It must be unique within the - * play queue. - * @param uri The uri for this item. - * @param extras A bundle of extras that can be used to add extra - * information about this item. + * play queue and cannot be {@link #UNKNOWN_ID}. */ - private Item(MediaMetadata metadata, long id, Uri uri, Bundle extras) { - mMetadata = metadata; + public QueueItem(MediaDescription description, long id) { + if (description == null) { + throw new IllegalArgumentException("Description cannot be null."); + } + if (id == UNKNOWN_ID) { + throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); + } + mDescription = description; mId = id; - mUri = uri; - mExtras = extras; } - private Item(Parcel in) { - mMetadata = MediaMetadata.CREATOR.createFromParcel(in); + private QueueItem(Parcel in) { + mDescription = MediaDescription.CREATOR.createFromParcel(in); mId = in.readLong(); - mUri = Uri.CREATOR.createFromParcel(in); - mExtras = in.readBundle(); } /** - * Get the metadata for this item. + * Get the description for this item. */ - public MediaMetadata getMetadata() { - return mMetadata; + public MediaDescription getDescription() { + return mDescription; } /** - * Get the id for this item. + * Get the queue id for this item. */ - public long getId() { + public long getQueueId() { return mId; } - /** - * Get the Uri for this item. - */ - public Uri getUri() { - return mUri; - } - - /** - * Get the extras for this item. - */ - public Bundle getExtras() { - return mExtras; - } - - /** - * Builder for {@link MediaSession.Item} objects. - */ - public static final class Builder { - private final MediaMetadata mMetadata; - private final long mId; - private final Uri mUri; - - private Bundle mExtras; - - /** - * Create a builder with the metadata, id, and uri already set. - */ - public Builder(MediaMetadata metadata, long id, Uri uri) { - if (metadata == null) { - throw new IllegalArgumentException( - "You must specify a non-null MediaMetadata to build an Item."); - } - if (uri == null) { - throw new IllegalArgumentException( - "You must specify a non-null Uri to build an Item."); - } - if (id == UNKNOWN_ID) { - throw new IllegalArgumentException( - "You must specify an id other than UNKNOWN_ID to build an Item."); - } - mMetadata = metadata; - mId = id; - mUri = uri; - } - - /** - * Set optional extras for the item. - */ - public MediaSession.Item.Builder setExtras(Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Create the {@link Item}. - */ - public MediaSession.Item build() { - return new MediaSession.Item(mMetadata, mId, mUri, mExtras); - } - } - @Override public void writeToParcel(Parcel dest, int flags) { - mMetadata.writeToParcel(dest, flags); + mDescription.writeToParcel(dest, flags); dest.writeLong(mId); - mUri.writeToParcel(dest, flags); - dest.writeBundle(mExtras); } @Override @@ -1116,28 +1057,24 @@ public final class MediaSession { return 0; } - public static final Creator<MediaSession.Item> CREATOR - = new Creator<MediaSession.Item>() { + public static final Creator<MediaSession.QueueItem> CREATOR = new Creator<MediaSession.QueueItem>() { @Override - public MediaSession.Item createFromParcel(Parcel p) { - return new MediaSession.Item(p); + public MediaSession.QueueItem createFromParcel(Parcel p) { + return new MediaSession.QueueItem(p); } @Override - public MediaSession.Item[] newArray(int size) { - return new MediaSession.Item[size]; + public MediaSession.QueueItem[] newArray(int size) { + return new MediaSession.QueueItem[size]; } }; @Override public String toString() { - return "MediaSession.Item {" + - "Metadata=" + mMetadata + - ", Id=" + mId + - ", Uri=" + mUri + - ", Extras=" + mExtras + - " }"; + return "MediaSession.QueueItem {" + + "Description=" + mDescription + + ", Id=" + mId + " }"; } } @@ -1156,7 +1093,7 @@ public final class MediaSession { private class CallbackMessageHandler extends Handler { private static final int MSG_PLAY = 1; - private static final int MSG_PLAY_URI = 2; + private static final int MSG_PLAY_MEDIA_ID = 2; private static final int MSG_PLAY_SEARCH = 3; private static final int MSG_SKIP_TO_ITEM = 4; private static final int MSG_PAUSE = 5; @@ -1202,14 +1139,14 @@ public final class MediaSession { case MSG_PLAY: mCallback.onPlay(); break; - case MSG_PLAY_URI: - mCallback.onPlayUri((Uri) msg.obj, msg.getData()); + case MSG_PLAY_MEDIA_ID: + mCallback.onPlayFromMediaId((String) msg.obj, msg.getData()); break; case MSG_PLAY_SEARCH: mCallback.onPlayFromSearch((String) msg.obj, msg.getData()); break; case MSG_SKIP_TO_ITEM: - mCallback.onSkipToItem((Long) msg.obj); + mCallback.onSkipToQueueItem((Long) msg.obj); case MSG_PAUSE: mCallback.onPause(); break; diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 2ca97dd..267d1ff 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -36,95 +36,95 @@ public final class PlaybackState implements Parcelable { private static final String TAG = "PlaybackState"; /** - * Indicates this performer supports the stop command. + * Indicates this session supports the stop command. * * @see Builder#setActions(long) */ public static final long ACTION_STOP = 1 << 0; /** - * Indicates this performer supports the pause command. + * Indicates this session supports the pause command. * * @see Builder#setActions(long) */ public static final long ACTION_PAUSE = 1 << 1; /** - * Indicates this performer supports the play command. + * Indicates this session supports the play command. * * @see Builder#setActions(long) */ public static final long ACTION_PLAY = 1 << 2; /** - * Indicates this performer supports the rewind command. + * Indicates this session supports the rewind command. * * @see Builder#setActions(long) */ public static final long ACTION_REWIND = 1 << 3; /** - * Indicates this performer supports the previous command. + * Indicates this session supports the previous command. * * @see Builder#setActions(long) */ public static final long ACTION_SKIP_TO_PREVIOUS = 1 << 4; /** - * Indicates this performer supports the next command. + * Indicates this session supports the next command. * * @see Builder#setActions(long) */ public static final long ACTION_SKIP_TO_NEXT = 1 << 5; /** - * Indicates this performer supports the fast forward command. + * Indicates this session supports the fast forward command. * * @see Builder#setActions(long) */ public static final long ACTION_FAST_FORWARD = 1 << 6; /** - * Indicates this performer supports the set rating command. + * Indicates this session supports the set rating command. * * @see Builder#setActions(long) */ public static final long ACTION_SET_RATING = 1 << 7; /** - * Indicates this performer supports the seek to command. + * Indicates this session supports the seek to command. * * @see Builder#setActions(long) */ public static final long ACTION_SEEK_TO = 1 << 8; /** - * Indicates this performer supports the play/pause toggle command. + * Indicates this session supports the play/pause toggle command. * * @see Builder#setActions(long) */ public static final long ACTION_PLAY_PAUSE = 1 << 9; /** - * Indicates this performer supports the play from uri command. + * Indicates this session supports the play from media id command. * * @see Builder#setActions(long) */ - public static final long ACTION_PLAY_URI = 1 << 10; + public static final long ACTION_PLAY_FROM_MEDIA_ID = 1 << 10; /** - * Indicates this performer supports the play from search command. + * Indicates this session supports the play from search command. * * @see Builder#setActions(long) */ public static final long ACTION_PLAY_FROM_SEARCH = 1 << 11; /** - * Indicates this performer supports the skip to item command. + * Indicates this session supports the skip to queue item command. * * @see Builder#setActions(long) */ - public static final long ACTION_SKIP_TO_ITEM = 1 << 12; + public static final long ACTION_SKIP_TO_QUEUE_ITEM = 1 << 12; /** * This is the default playback state and indicates that no media has been @@ -211,6 +211,14 @@ public final class PlaybackState implements Parcelable { public final static int STATE_SKIPPING_TO_NEXT = 10; /** + * State indicating the player is currently skipping to a specific item in + * the queue. + * + * @see Builder#setState + */ + public final static int STATE_SKIPPING_TO_QUEUE_ITEM = 11; + + /** * Use this value for the position to indicate the position is not known. */ public final static long PLAYBACK_POSITION_UNKNOWN = -1; @@ -374,6 +382,18 @@ public final class PlaybackState implements Parcelable { } /** + * Get the id of the currently active item in the queue. If there is no + * queue or a queue is not supported by the session this will be + * {@link MediaSession.QueueItem#UNKNOWN_ID}. + * + * @return The id of the currently active item in the queue or + * {@link MediaSession.QueueItem#UNKNOWN_ID}. + */ + public long getActiveQueueItemId() { + return mActiveItemId; + } + + /** * Get the {@link PlaybackState} state for the given * {@link RemoteControlClient} state. * @@ -716,7 +736,7 @@ public final class PlaybackState implements Parcelable { private long mActions; private CharSequence mErrorMessage; private long mUpdateTime; - private long mActiveItemId = MediaSession.Item.UNKNOWN_ID; + private long mActiveItemId = MediaSession.QueueItem.UNKNOWN_ID; /** * Creates an initially empty state builder. @@ -904,12 +924,12 @@ public final class PlaybackState implements Parcelable { /** * Set the active item in the play queue by specifying its id. The - * default value is {@link MediaSession.Item#UNKNOWN_ID} + * default value is {@link MediaSession.QueueItem#UNKNOWN_ID} * * @param id The id of the active item. * @return this */ - public Builder setActiveItem(long id) { + public Builder setActiveQueueItemId(long id) { mActiveItemId = id; return this; } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 20b8e7c..6ca794e 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -22,6 +22,7 @@ import android.media.tv.ITvInputClient; import android.media.tv.ITvInputHardware; import android.media.tv.ITvInputHardwareCallback; import android.media.tv.ITvInputManagerCallback; +import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvStreamConfig; @@ -38,7 +39,7 @@ interface ITvInputManager { List<TvInputInfo> getTvInputList(int userId); TvInputInfo getTvInputInfo(in String inputId, int userId); - List<Uri> getTvContentRatingSystemXmls(int userId); + List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId); void registerCallback(in ITvInputManagerCallback callback, int userId); void unregisterCallback(in ITvInputManagerCallback callback, int userId); diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java index bbf7399..0bda2b3 100644 --- a/media/java/android/media/tv/TvContentRating.java +++ b/media/java/android/media/tv/TvContentRating.java @@ -33,10 +33,9 @@ import java.util.Objects; * {@link #createRating TvContentRating.createRating} method with valid rating system string * constants. * <p> - * It is possible for a TV input to define its own content rating system by supplying a content - * rating system definition XML resource (see example below) and having the - * {@link android.R.attr#tvContentRatingDescription tvContentRatingDescription} attribute in - * {@link TvInputService#SERVICE_META_DATA} of the TV input point to it. + * It is possible for an application to define its own content rating system by supplying a content + * rating system definition XML resource (see example below) and declaring a broadcast receiver that + * filters {@link TvInputManager#ACTION_QUERY_CONTENT_RATING_SYSTEMS} in its manifest. * </p> * <h3> Example: Rating system definition for the TV Parental Guidelines</h3> * The following XML example shows how the TV Parental Guidelines in the United States can be diff --git a/media/java/android/media/tv/TvContentRatingSystemInfo.aidl b/media/java/android/media/tv/TvContentRatingSystemInfo.aidl new file mode 100644 index 0000000..957be62 --- /dev/null +++ b/media/java/android/media/tv/TvContentRatingSystemInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +parcelable TvContentRatingSystemInfo;
\ No newline at end of file diff --git a/media/java/android/media/tv/TvContentRatingSystemInfo.java b/media/java/android/media/tv/TvContentRatingSystemInfo.java new file mode 100644 index 0000000..f2e5b08 --- /dev/null +++ b/media/java/android/media/tv/TvContentRatingSystemInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.annotation.SystemApi; +import android.content.ContentResolver; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * TvContentRatingSystemInfo class provides information about a specific TV content rating system + * defined either by a system app or by a third-party app. + * + * @hide + */ +@SystemApi +public final class TvContentRatingSystemInfo implements Parcelable { + private final Uri mXmlUri; + + private final ApplicationInfo mApplicationInfo; + + /** + * Creates a TvContentRatingSystemInfo object with given resource ID and receiver info. + * + * @param xmlResourceId The ID of an XML resource whose root element is + * <code> <rating-system-definitions></code> + * @param applicationInfo Information about the application that provides the TV content rating + * system definition. + */ + public static final TvContentRatingSystemInfo createTvContentRatingSystemInfo(int xmlResourceId, + ApplicationInfo applicationInfo) { + Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(applicationInfo.packageName) + .appendPath(String.valueOf(xmlResourceId)) + .build(); + return new TvContentRatingSystemInfo(uri, applicationInfo); + } + + private TvContentRatingSystemInfo(Uri xmlUri, ApplicationInfo applicationInfo) { + mXmlUri = xmlUri; + mApplicationInfo = applicationInfo; + } + + /** + * Returns {@code true} if the TV content rating system is defined by a system app, + * {@code false} otherwise. + */ + public final boolean isSystemDefined() { + return (mApplicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + /** + * Returns the URI to the XML resource that defines the TV content rating system. + * + * TODO: Remove. Instead, parse the XML resource and provide an interface to directly access + * parsed information. + */ + public final Uri getXmlUri() { + return mXmlUri; + } + + /** + * Used to make this class parcelable. + * @hide + */ + public static final Parcelable.Creator<TvContentRatingSystemInfo> CREATOR = + new Parcelable.Creator<TvContentRatingSystemInfo>() { + @Override + public TvContentRatingSystemInfo createFromParcel(Parcel in) { + return new TvContentRatingSystemInfo(in); + } + + @Override + public TvContentRatingSystemInfo[] newArray(int size) { + return new TvContentRatingSystemInfo[size]; + } + }; + + private TvContentRatingSystemInfo(Parcel in) { + mXmlUri = in.readParcelable(null); + mApplicationInfo = in.readParcelable(null); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mXmlUri, flags); + dest.writeParcelable(mApplicationInfo, flags); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 00c7ad4..7f1c304 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -680,7 +680,7 @@ public final class TvContract { * <p> * A value of 1 indicates the channel is included in the channel list that applications use * to browse channels, a value of 0 indicates the channel is not included in the list. If - * not specified, this value is set to 1 (browsable) by default. + * not specified, this value is set to 0 (not browsable) by default. * </p><p> * Type: INTEGER (boolean) * </p> diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 106e1dc..00183bb 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -18,7 +18,6 @@ package android.media.tv; import android.annotation.SystemApi; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -125,7 +124,6 @@ public final class TvInputInfo implements Parcelable { private String mLabel; private Uri mIconUri; private boolean mIsConnectedToHdmiSwitch; - private Uri mRatingSystemXmlUri; static { sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE, @@ -245,19 +243,6 @@ public final class TvInputInfo implements Parcelable { Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for " + si.name); } - int tvContentRatingDescription = sa.getResourceId( - com.android.internal.R.styleable.TvInputService_tvContentRatingDescription, -1); - if (tvContentRatingDescription != -1) { - input.mRatingSystemXmlUri = new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(si.packageName) - .appendPath(Integer.toString(tvContentRatingDescription)) - .build(); - if (DEBUG) { - Log.d(TAG, "Content rating xml loaded. [" + tvContentRatingDescription - + "] for " + si.name); - } - } sa.recycle(); input.mLabel = label; @@ -367,15 +352,6 @@ public final class TvInputInfo implements Parcelable { } /** - * Returns the resource uri for the rating system xml of this TV input service. - * @hide - */ - @SystemApi - public Uri getRatingSystemXmlUri() { - return mRatingSystemXmlUri; - } - - /** * Returns {@code true} if this TV input is pass-though which does not have any real channels * in TvProvider. {@code false} otherwise. * @@ -508,7 +484,6 @@ public final class TvInputInfo implements Parcelable { dest.writeParcelable(mIconUri, flags); dest.writeString(mLabel); dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0); - dest.writeParcelable(mRatingSystemXmlUri, flags); } private Drawable loadDefaultIcon(Context context) { @@ -581,7 +556,6 @@ public final class TvInputInfo implements Parcelable { mIconUri = in.readParcelable(null); mLabel = in.readString(); mIsConnectedToHdmiSwitch = in.readByte() == 1 ? true : false; - mRatingSystemXmlUri = in.readParcelable(null); } /** diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 13937e2..6d1f0e4 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -101,14 +101,58 @@ public final class TvInputManager { * {@link #isRatingBlocked}. */ public static final String ACTION_BLOCKED_RATINGS_CHANGED = - "android.media.tv.action.BLOCKED_RATINGS_CHANGED"; + "android.media.tv.TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED"; /** * Broadcast intent action when the parental controls enabled state changes. For use with the * {@link #isParentalControlsEnabled}. */ public static final String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = - "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; + "android.media.tv.TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED"; + + /** + * Broadcast intent action used to query available content rating systems. + * <p> + * The TV input manager service locates available content rating systems by querying broadcast + * receivers that are registered for this action. An application can offer additional content + * rating systems to the user by declaring a suitable broadcast receiver in its manifest. + * </p><p> + * Here is an example broadcast receiver declaration that an application might include in its + * AndroidManifest.xml to advertise custom content rating systems. The meta-data specifies a + * resource that contains a description of each content rating system that is provided by the + * application. + * <p><pre class="prettyprint"> + * {@literal + * <receiver android:name=".TvInputReceiver"> + * <intent-filter> + * <action android:name= + * "android.media.tv.TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS" /> + * </intent-filter> + * <meta-data + * android:name="android.media.tv.TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS" + * android:resource="@xml/tv_content_rating_systems" /> + * </receiver>}</pre></p> + * In the above example, the <code>@xml/tv_content_rating_systems</code> resource refers to an + * XML resource whose root element is <code><rating-system-definitions></code> that + * contains zero or more <code><rating-system-definition></code> elements. Each <code> + * <rating-system-definition></code> element specifies the ratings, sub-ratings and rating + * orders of a particular content rating system. + * </p> + * + * @see TvContentRating + */ + public static final String ACTION_QUERY_CONTENT_RATING_SYSTEMS = + "android.media.tv.TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS"; + + /** + * Content rating systems metadata associated with {@link #ACTION_QUERY_CONTENT_RATING_SYSTEMS}. + * <p> + * Specifies the resource ID of an XML resource that describes the content rating systems that + * are provided by the application. + * </p> + */ + public static final String META_DATA_CONTENT_RATING_SYSTEMS = + "android.media.tv.TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS"; private final ITvInputManager mService; @@ -863,13 +907,13 @@ public final class TvInputManager { } /** - * Returns the list of xml resource uris for TV content rating systems. + * Returns the list of all TV content rating systems defined. * @hide */ @SystemApi - public List<Uri> getTvContentRatingSystemXmls() { + public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() { try { - return mService.getTvContentRatingSystemXmls(mUserId); + return mService.getTvContentRatingSystemList(mUserId); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/media/java/android/media/browse/IMediaBrowserService.aidl b/media/java/android/service/media/IMediaBrowserService.aidl index 8acd724..2631ddd 100644 --- a/media/java/android/media/browse/IMediaBrowserService.aidl +++ b/media/java/android/service/media/IMediaBrowserService.aidl @@ -1,9 +1,9 @@ // Copyright 2014 Google Inc. All Rights Reserved. -package android.media.browse; +package android.service.media; import android.content.res.Configuration; -import android.media.browse.IMediaBrowserServiceCallbacks; +import android.service.media.IMediaBrowserServiceCallbacks; import android.net.Uri; import android.os.Bundle; @@ -18,6 +18,4 @@ oneway interface IMediaBrowserService { void addSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks); void removeSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks); - void loadIcon(in int seqNum, in Uri uri, int width, int height, - IMediaBrowserServiceCallbacks callbacks); }
\ No newline at end of file diff --git a/media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl index 06fabcc..7702a50 100644 --- a/media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl +++ b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl @@ -1,6 +1,6 @@ // Copyright 2014 Google Inc. All Rights Reserved. -package android.media.browse; +package android.service.media; import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; @@ -24,5 +24,4 @@ oneway interface IMediaBrowserServiceCallbacks { void onConnect(in Uri root, in MediaSession.Token session, in Bundle extras); void onConnectFailed(); void onLoadChildren(in Uri uri, in ParceledListSlice list); - void onLoadIcon(int seqNum, in Bitmap bitmap); } diff --git a/media/java/android/media/browse/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index 99126c9..4d6fd7b 100644 --- a/media/java/android/media/browse/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.media.browse; +package android.service.media; import android.annotation.IntDef; import android.annotation.NonNull; @@ -27,6 +27,8 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.media.browse.MediaBrowser; +import android.media.browse.MediaBrowser.MediaItem; import android.media.session.MediaSession; import android.net.Uri; import android.os.Binder; @@ -34,6 +36,9 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Handler; import android.os.RemoteException; +import android.service.media.IMediaBrowserService; +import android.service.media.IMediaBrowserServiceCallbacks; +import android.service.media.IMediaBrowserService.Stub; import android.util.ArrayMap; import android.util.Log; @@ -264,59 +269,6 @@ public abstract class MediaBrowserService extends Service { } }); } - - @Override - public void loadIcon(final int seq, final Uri uri, final int width, final int height, - final IMediaBrowserServiceCallbacks callbacks) { - if (uri == null) { - throw new IllegalStateException("loadIcon sent null list for uri " + uri); - } - mHandler.post(new Runnable() { - @Override - public void run() { - // In theory we could return a result to a new connection, but in practice - // the other side in MediaBrowser uses a new IMediaBrowserServiceCallbacks - // object every time it calls connect(), so as long as it does that we won't - // see results sent for the wrong connection. - final ConnectionRecord connection = mConnections.get(callbacks.asBinder()); - if (connection == null) { - if (DBG) { - Log.d(TAG, "Not loading bitmap for invalid connection. uri=" + uri); - } - return; - } - - final Result<Bitmap> result = new Result<Bitmap>(uri) { - @Override - void onResultSent(Bitmap bitmap) { - if (mConnections.get(connection.callbacks.asBinder()) != connection) { - if (DBG) { - Log.d(TAG, "Not sending onLoadIcon result for connection" - + " that has been disconnected. pkg=" + connection.pkg - + " uri=" + uri); - } - return; - } - - try { - callbacks.onLoadIcon(seq, bitmap); - } catch (RemoteException e) { - // The other side is in the process of crashing. - Log.w(TAG, "RemoteException in calling onLoadIcon", e); - } - } - }; - - onLoadIcon(uri, width, height, result); - - if (!result.isDone()) { - throw new IllegalStateException("onLoadIcon must call detach() or" - + " sendResult() before returning for package=" + connection.pkg - + " uri=" + uri); - } - } - }); - } } @Override @@ -372,26 +324,7 @@ public abstract class MediaBrowserService extends Service { * @return The list of children, or null if the uri is invalid. */ public abstract void onLoadChildren(@NonNull Uri parentUri, - @NonNull Result<List<MediaBrowserItem>> result); - - /** - * Called to get the icon of a particular media item. - * <p> - * Implementations must call result.{@link Result#sendResult result.sendResult} with the bitmap. - * If loading the bitmap will be an expensive operation that should be performed - * on another thread, result.{@link Result#detach result.detach} may be called before returning - * from this function, and then {@link Result#sendResult result.sendResult} called when - * the loading is complete. - * - * @param uri The uri of the media item. - * @param width The requested width of the icon in dp. - * @param height The requested height of the icon in dp. - * - * @return The file descriptor of the icon, which may then be loaded - * using a bitmap factory, or null if the item does not have an icon. - */ - public abstract void onLoadIcon(@NonNull Uri uri, int width, int height, - @NonNull Result<Bitmap> result); + @NonNull Result<List<MediaBrowser.MediaItem>> result); /** * Call to set the media session. @@ -478,9 +411,11 @@ public abstract class MediaBrowserService extends Service { * Callers must make sure that this connection is still connected. */ private void performLoadChildren(final Uri uri, final ConnectionRecord connection) { - final Result<List<MediaBrowserItem>> result = new Result<List<MediaBrowserItem>>(uri) { + final Result<List<MediaBrowser.MediaItem>> result + = new Result<List<MediaBrowser.MediaItem>>( + uri) { @Override - void onResultSent(List<MediaBrowserItem> list) { + void onResultSent(List<MediaBrowser.MediaItem> list) { if (list == null) { throw new IllegalStateException("onLoadChildren sent null list for uri " + uri); } @@ -492,7 +427,7 @@ public abstract class MediaBrowserService extends Service { return; } - final ParceledListSlice<MediaBrowserItem> pls = new ParceledListSlice(list); + final ParceledListSlice<MediaBrowser.MediaItem> pls = new ParceledListSlice(list); try { connection.callbacks.onLoadChildren(uri, pls); } catch (RemoteException ex) { diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index c678ac7..0fed27e 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -263,7 +263,7 @@ static bool throwExceptionAsNecessary( String8 vendorMessage; if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) { - vendorMessage.format("DRM vendor-defined error: %d", err); + vendorMessage = String8::format("DRM vendor-defined error: %d", err); drmMessage = vendorMessage.string(); } @@ -285,7 +285,7 @@ static bool throwExceptionAsNecessary( if (msg == NULL) { msg = drmMessage; } else { - errbuf.format("%s: %s", msg, drmMessage); + errbuf = String8::format("%s: %s", msg, drmMessage); msg = errbuf.string(); } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java index 38d7e3e..86c23c7 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java @@ -25,6 +25,9 @@ package com.android.mediaframeworktest; public class MediaNames { // A directory to hold all kinds of media files public static final String MEDIA_SAMPLE_POOL = "/sdcard/media_api/samples/"; + // A file to hold all streaming URLs + public static final String MEDIA_STREAMING_SRC = "/sdcard/media_api/streaming.txt"; + // Audio files public static final String MP3CBR = "/sdcard/media_api/music/MP3_48KHz_128kbps_s_1_17_CBR.mp3"; public static final String MP3VBR = "/sdcard/media_api/music/MP3_48KHz_128kbps_s_1_17_VBR.mp3"; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java index e84f762..66ed933 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java @@ -42,13 +42,13 @@ import java.io.FileOutputStream; import java.util.Random; /** * Junit / Instrumentation test case for the media player api - - */ -public class CodecTest { + + */ +public class CodecTest { private static String TAG = "CodecTest"; private static MediaPlayer mMediaPlayer; private MediaPlayer.OnPreparedListener mOnPreparedListener; - + private static int WAIT_FOR_COMMAND_TO_COMPLETE = 60000; //1 min max. private static boolean mInitialized = false; private static boolean mPrepareReset = false; @@ -66,18 +66,18 @@ public class CodecTest { public static int mMediaInfoNotSeekableCount = 0; public static int mMediaInfoMetdataUpdateCount = 0; - public static String printCpuInfo(){ + public static String printCpuInfo(){ String cm = "dumpsys cpuinfo"; String cpuinfo =null; int ch; try{ Process p = Runtime.getRuntime().exec(cm); - InputStream in = p.getInputStream(); + InputStream in = p.getInputStream(); StringBuffer sb = new StringBuffer(512); - while ( ( ch = in.read() ) != -1 ){ - sb.append((char) ch); + while ( ( ch = in.read() ) != -1 ){ + sb.append((char) ch); } - cpuinfo = sb.toString(); + cpuinfo = sb.toString(); }catch (IOException e){ Log.v(TAG, e.toString()); } @@ -90,14 +90,14 @@ public class CodecTest { MediaPlayer mp = new MediaPlayer(); try{ mp.setDataSource(filePath); - mp.prepare(); + mp.prepare(); }catch (Exception e){ Log.v(TAG, e.toString()); } int duration = mp.getDuration(); Log.v(TAG, "Duration " + duration); mp.release(); - Log.v(TAG, "release"); + Log.v(TAG, "release"); return duration; } @@ -122,7 +122,7 @@ public class CodecTest { } currentPosition = mp.getCurrentPosition(); mp.stop(); - mp.release(); + mp.release(); Log.v(TAG, "mp currentPositon = " + currentPosition + " play duration = " + (t2-t1)); //The currentposition should be within 10% of the sleep time //For the very short mp3, it should return the length instead of 10 seconds @@ -130,11 +130,11 @@ public class CodecTest { if (currentPosition < 1000 ) return true; } - if ((currentPosition < ((t2-t1) *1.2)) && (currentPosition > 0)) + if ((currentPosition < ((t2-t1) *1.2)) && (currentPosition > 0)) return true; else return false; - } + } public static boolean seekTo(String filePath){ Log.v(TAG, "seekTo " + filePath); @@ -149,12 +149,12 @@ public class CodecTest { currentPosition = mp.getCurrentPosition(); }catch (Exception e){ Log.v(TAG, e.getMessage()); - } + } mp.stop(); mp.release(); Log.v(TAG, "CurrentPosition = " + currentPosition); //The currentposition should be at least greater than the 80% of seek time - if ((currentPosition > MediaNames.SEEK_TIME *0.8)) + if ((currentPosition > MediaNames.SEEK_TIME *0.8)) return true; else return false; @@ -170,7 +170,7 @@ public class CodecTest { try{ mp.setDataSource(filePath); mp.prepare(); - duration = mp.getDuration(); + duration = mp.getDuration(); Log.v(TAG, "setLooping duration " + duration); mp.setLooping(true); mp.start(); @@ -180,14 +180,14 @@ public class CodecTest { Thread.sleep(20000); t2=SystemClock.uptimeMillis(); Log.v(TAG, "pause"); - //Bug# 1106852 - IllegalStateException will be thrown if pause is called + //Bug# 1106852 - IllegalStateException will be thrown if pause is called //in here //mp.pause(); currentPosition = mp.getCurrentPosition(); Log.v(TAG, "looping position " + currentPosition + "duration = " + (t2-t1)); }catch (Exception e){ Log.v(TAG, "Exception : " + e.toString()); - } + } mp.stop(); mp.release(); //The current position should be within 20% of the sleep time @@ -196,7 +196,7 @@ public class CodecTest { return true; else return false; - } + } public static boolean pause(String filePath) throws Exception { Log.v(TAG, "pause - " + filePath); @@ -206,7 +206,7 @@ public class CodecTest { long t2=0; MediaPlayer mp = new MediaPlayer(); mp.setDataSource(filePath); - mp.prepare(); + mp.prepare(); int duration = mp.getDuration(); mp.start(); t1=SystemClock.uptimeMillis(); @@ -244,7 +244,7 @@ public class CodecTest { mp.pause(); mp.release(); } - + static MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { @@ -258,7 +258,7 @@ public class CodecTest { //Register the videoSizeChanged listener public static int videoHeight(String filePath) throws Exception { Log.v(TAG, "videoHeight - " + filePath); - int videoHeight = 0; + int videoHeight = 0; synchronized (lock) { initializeMessageLooper(); try { @@ -286,7 +286,7 @@ public class CodecTest { } catch (Exception e) { Log.e(TAG, e.getMessage()); } - + return videoHeight; } @@ -321,12 +321,12 @@ public class CodecTest { terminateMessageLooper(); } catch (Exception e) { Log.e(TAG, e.getMessage()); - } + } return videoWidth; } //This also test the streaming video which may take a long - //time to start the playback. + //time to start the playback. public static boolean videoSeekTo(String filePath) throws Exception { Log.v(TAG, "videoSeekTo - " + filePath); int currentPosition = 0; @@ -392,12 +392,12 @@ public class CodecTest { currentPosition = mp.getCurrentPosition(); Log.v(TAG, "seekToEnd currentPosition= " + currentPosition + " isPlaying = " + isPlaying); mp.stop(); - mp.release(); + mp.release(); Log.v(TAG, "duration = " + duration); if (currentPosition < 0.9 * duration || isPlaying) return false; else - return true; + return true; } public static boolean shortMediaStop(String filePath){ @@ -419,12 +419,12 @@ public class CodecTest { currentPosition = mp.getCurrentPosition(); Log.v(TAG, "seekToEnd currentPosition= " + currentPosition + " isPlaying = " + isPlaying); mp.stop(); - mp.release(); + mp.release(); Log.v(TAG, "duration = " + duration); if (currentPosition > duration || isPlaying) return false; else - return true; + return true; } public static boolean playToEnd(String filePath){ @@ -449,13 +449,13 @@ public class CodecTest { //updateDuration = mp.getDuration(); Log.v(TAG, "seekToEnd currentPosition= " + currentPosition + " isPlaying = " + isPlaying); mp.stop(); - mp.release(); + mp.release(); //Log.v(TAG, "duration = " + duration); //Log.v(TAG, "Update duration = " + updateDuration); if (currentPosition > duration || isPlaying) return false; else - return true; + return true; } public static boolean seektoBeforeStart(String filePath){ @@ -478,7 +478,7 @@ public class CodecTest { if (currentPosition < duration/2) return false; else - return true; + return true; } public static boolean mediaRecorderRecord(String filePath){ @@ -499,7 +499,7 @@ public class CodecTest { mRecorder.release(); }catch (Exception e){ Log.v(TAG, e.toString()); - } + } //Verify the recorded file MediaPlayer mp = new MediaPlayer(); @@ -540,7 +540,7 @@ public class CodecTest { } Bitmap outThumbnail = mMediaMetadataRetriever.getFrameAtTime(-1); - //Verify the thumbnail + //Verify the thumbnail Bitmap goldenBitmap = mBitmapFactory.decodeFile(goldenPath); outputWidth = outThumbnail.getWidth(); outputHeight = outThumbnail.getHeight(); @@ -586,8 +586,8 @@ public class CodecTest { return false; } - public static boolean prepareAsyncReset(String filePath){ - //preparesAsync + public static boolean prepareAsyncReset(String filePath){ + //preparesAsync try{ MediaPlayer mp = new MediaPlayer(); mp.setDataSource(filePath); @@ -602,9 +602,9 @@ public class CodecTest { } - public static boolean isLooping(String filePath) { + public static boolean isLooping(String filePath) { MediaPlayer mp = null; - + try { mp = new MediaPlayer(); if (mp.isLooping()) { @@ -619,7 +619,7 @@ public class CodecTest { Log.v(TAG, "MediaPlayer.isLooping() returned false after setLooping(true)"); return false; } - + mp.setLooping(false); if (mp.isLooping()) { Log.v(TAG, "MediaPlayer.isLooping() returned true after setLooping(false)"); @@ -659,9 +659,9 @@ public class CodecTest { return true; } - + /* - * Initializes the message looper so that the mediaPlayer object can + * Initializes the message looper so that the mediaPlayer object can * receive the callback messages. */ private static void initializeMessageLooper() { @@ -672,10 +672,10 @@ public class CodecTest { // Set up a looper to be used by camera. Looper.prepare(); Log.v(TAG, "start loopRun"); - // Save the looper so that we can terminate this thread + // Save the looper so that we can terminate this thread // after we are done with it. - mLooper = Looper.myLooper(); - mMediaPlayer = new MediaPlayer(); + mLooper = Looper.myLooper(); + mMediaPlayer = new MediaPlayer(); synchronized (lock) { mInitialized = true; lock.notify(); @@ -685,7 +685,7 @@ public class CodecTest { } }.start(); } - + /* * Terminates the message looper thread. */ @@ -693,7 +693,7 @@ public class CodecTest { mLooper.quit(); mMediaPlayer.release(); } - + static MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { synchronized (prepareDone) { @@ -707,14 +707,14 @@ public class CodecTest { } } }; - + public static boolean prepareAsyncCallback(String filePath, boolean reset) throws Exception { //Added the PrepareReset flag which allow us to switch to different //test case. if (reset){ mPrepareReset = true; } - + synchronized (lock) { initializeMessageLooper(); try { @@ -728,18 +728,18 @@ public class CodecTest { mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setDataSource(filePath); mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder()); - mMediaPlayer.prepareAsync(); + mMediaPlayer.prepareAsync(); synchronized (prepareDone) { try { prepareDone.wait(WAIT_FOR_COMMAND_TO_COMPLETE); } catch (Exception e) { Log.v(TAG, "wait was interrupted."); } - } + } terminateMessageLooper(); }catch (Exception e){ Log.v(TAG,e.getMessage()); - } + } return onPrepareSuccess; } @@ -784,8 +784,12 @@ public class CodecTest { } }; - // For each media file, forward twice and backward once, then play to the end public static boolean playMediaSamples(String filePath) throws Exception { + return playMediaSamples(filePath, 2000); + } + + // For each media file, forward twice and backward once, then play to the end + public static boolean playMediaSamples(String filePath, int buffertime) throws Exception { int duration = 0; int curPosition = 0; int nextPosition = 0; @@ -822,14 +826,14 @@ public class CodecTest { waittime = duration - mMediaPlayer.getCurrentPosition(); synchronized(onCompletion){ try { - onCompletion.wait(waittime + 2000); + onCompletion.wait(waittime + buffertime); }catch (Exception e) { Log.v(TAG, "playMediaSamples are interrupted"); return false; } } terminateMessageLooper(); - }catch (Exception e) { + } catch (Exception e) { Log.v(TAG, "playMediaSamples:" + e.getMessage()); } return onCompleteSuccess; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java index b6bb578..cc50c43 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java @@ -245,7 +245,7 @@ public class CameraBinderTest extends AndroidTestCase { * android.hardware.camera2.CaptureResultExtras) */ @Override - public void onCameraError(int errorCode, CaptureResultExtras resultExtras) + public void onDeviceError(int errorCode, CaptureResultExtras resultExtras) throws RemoteException { // TODO Auto-generated method stub @@ -283,7 +283,7 @@ public class CameraBinderTest extends AndroidTestCase { * @see android.hardware.camera2.ICameraDeviceCallbacks#onCameraIdle() */ @Override - public void onCameraIdle() throws RemoteException { + public void onDeviceIdle() throws RemoteException { // TODO Auto-generated method stub } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java index 7b2e7dd..3cae19d 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -88,10 +88,10 @@ public class CameraDeviceBinderTest extends AndroidTestCase { /* * (non-Javadoc) * @see - * android.hardware.camera2.ICameraDeviceCallbacks#onCameraError(int, + * android.hardware.camera2.ICameraDeviceCallbacks#onDeviceError(int, * android.hardware.camera2.CaptureResultExtras) */ - public void onCameraError(int errorCode, CaptureResultExtras resultExtras) + public void onDeviceError(int errorCode, CaptureResultExtras resultExtras) throws RemoteException { // TODO Auto-generated method stub @@ -99,9 +99,9 @@ public class CameraDeviceBinderTest extends AndroidTestCase { /* * (non-Javadoc) - * @see android.hardware.camera2.ICameraDeviceCallbacks#onCameraIdle() + * @see android.hardware.camera2.ICameraDeviceCallbacks#onDeviceIdle() */ - public void onCameraIdle() throws RemoteException { + public void onDeviceIdle() throws RemoteException { // TODO Auto-generated method stub } @@ -432,7 +432,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase { // Cancel and make sure we eventually quiesce status = mCameraUser.cancelRequest(streamingId, null); - verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onCameraIdle(); + verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onDeviceIdle(); // Submit a few capture requests int requestId1 = submitCameraRequest(request, /* streaming */false); @@ -442,7 +442,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase { int requestId5 = submitCameraRequest(request, /* streaming */false); // And wait for more idle - verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onCameraIdle(); + verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onDeviceIdle(); } @@ -472,7 +472,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase { status = mCameraUser.flush(null); assertEquals(CameraBinderTestUtils.NO_ERROR, status); - verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onCameraIdle(); + verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onDeviceIdle(); // Now a streaming request int streamingId = submitCameraRequest(request, /* streaming */true); @@ -484,7 +484,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase { status = mCameraUser.flush(null); assertEquals(CameraBinderTestUtils.NO_ERROR, status); - verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onCameraIdle(); + verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onDeviceIdle(); // TODO: When errors are hooked up, count that errors + successful // requests equal to 5. diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStreamingStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStreamingStressTest.java new file mode 100644 index 0000000..d92c857 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStreamingStressTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediaframeworktest.stress; + +import com.android.mediaframeworktest.MediaFrameworkTest; +import com.android.mediaframeworktest.MediaPlayerStressTestRunner; + +import android.os.Bundle; +import android.os.Environment; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + +import com.android.mediaframeworktest.MediaNames; +import com.android.mediaframeworktest.functional.CodecTest; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.Writer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Junit / Instrumentation test case for the media player + */ +public class MediaPlayerStreamingStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "MediaPlayerStreamingStressTest"; + private String mStreamingSrc; + + public MediaPlayerStreamingStressTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + protected void setUp() throws Exception { + //Insert a 2 second before launching the test activity. This is + //the workaround for the race condition of requesting the updated surface. + Thread.sleep(2000); + getActivity(); + MediaPlayerStressTestRunner mRunner = (MediaPlayerStressTestRunner)getInstrumentation(); + Bundle arguments = mRunner.getArguments(); + mStreamingSrc = arguments.getString("streaming-source"); + if (mStreamingSrc == null) { + mStreamingSrc = MediaNames.MEDIA_STREAMING_SRC; + } + super.setUp(); + } + + private int mTotalPlaybackError = 0; + private int mTotalComplete = 0; + private int mTotalInfoUnknown = 0; + private int mTotalVideoTrackLagging = 0; + private int mTotalBadInterleaving = 0; + private int mTotalNotSeekable = 0; + private int mTotalMetaDataUpdate = 0; + + //Test result output file + private static final String PLAYBACK_RESULT = "StreamingTestResult.txt"; + + private void writeTestOutput(String filename, Writer output) throws Exception{ + output.write("URL: " + filename); + output.write(" Complete: " + CodecTest.onCompleteSuccess); + output.write(" Error: " + CodecTest.mPlaybackError); + output.write(" Unknown Info: " + CodecTest.mMediaInfoUnknownCount); + output.write(" Track Lagging: " + CodecTest.mMediaInfoVideoTrackLaggingCount); + output.write(" Bad Interleaving: " + CodecTest.mMediaInfoBadInterleavingCount); + output.write(" Not Seekable: " + CodecTest.mMediaInfoNotSeekableCount); + output.write(" Info Meta data update: " + CodecTest.mMediaInfoMetdataUpdateCount); + output.write("\n"); + } + + private void writeTestSummary(Writer output) throws Exception{ + output.write("Total Result:\n"); + output.write("Total Complete: " + mTotalComplete + "\n"); + output.write("Total Error: " + mTotalPlaybackError + "\n"); + output.write("Total Unknown Info: " + mTotalInfoUnknown + "\n"); + output.write("Total Track Lagging: " + mTotalVideoTrackLagging + "\n" ); + output.write("Total Bad Interleaving: " + mTotalBadInterleaving + "\n"); + output.write("Total Not Seekable: " + mTotalNotSeekable + "\n"); + output.write("Total Info Meta data update: " + mTotalMetaDataUpdate + "\n"); + output.write("\n"); + } + + private void updateTestResult(){ + if (CodecTest.onCompleteSuccess){ + mTotalComplete++; + } + else if (CodecTest.mPlaybackError){ + mTotalPlaybackError++; + } + mTotalInfoUnknown += CodecTest.mMediaInfoUnknownCount; + mTotalVideoTrackLagging += CodecTest.mMediaInfoVideoTrackLaggingCount; + mTotalBadInterleaving += CodecTest.mMediaInfoBadInterleavingCount; + mTotalNotSeekable += CodecTest.mMediaInfoNotSeekableCount; + mTotalMetaDataUpdate += CodecTest.mMediaInfoMetdataUpdateCount; + } + + //Test that will start the playback for all the videos + //under the samples folder + @LargeTest + public void testVideoPlayback() throws Exception { + String fileWithError = "Filename:\n"; + File playbackOutput = new File(Environment.getExternalStorageDirectory(), PLAYBACK_RESULT); + Writer output = new BufferedWriter(new FileWriter(playbackOutput, true)); + + boolean testResult = true; + // load directory files + boolean onCompleteSuccess = false; + + + Log.i(TAG, "Streaming src file: " + mStreamingSrc); + //TODO: add try catch + + File f = new File(mStreamingSrc); + BufferedReader br = new BufferedReader(new FileReader(f)); + List<String> urls = new ArrayList<String>(); + String line; + while ((line = br.readLine()) != null) { + urls.add(line.trim()); + } + br.close(); + if (urls == null) { + Log.v("MediaPlayerStreamingTest:testVideoPlayback", "no url found"); + return; + } else { + for (int i = 0; i < urls.size(); i++) { + //Get url + String filename = urls.get(i); + onCompleteSuccess = + CodecTest.playMediaSamples(filename, 60000); + if (!onCompleteSuccess){ + //Don't fail the test right away, print out the failure file. + fileWithError += filename + '\n'; + Log.v(TAG, "Failure File : " + fileWithError); + testResult = false; + } + Thread.sleep(3000); + //Write test result to an output file + writeTestOutput(filename,output); + //Get the summary + updateTestResult(); + } + writeTestSummary(output); + output.close(); + assertTrue("testMediaSamples", testResult); + } + } +} diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml index ee5d42a..761d58a 100644 --- a/packages/PrintSpooler/res/layout/print_activity.xml +++ b/packages/PrintSpooler/res/layout/print_activity.xml @@ -27,6 +27,7 @@ android:id="@+id/static_content" android:layout_width="fill_parent" android:layout_height="wrap_content" + android:paddingStart="8dip" android:elevation="@dimen/preview_controls_elevation" android:background="?android:attr/colorPrimary"> diff --git a/packages/PrintSpooler/res/layout/print_activity_controls.xml b/packages/PrintSpooler/res/layout/print_activity_controls.xml index 0629481..c2a0da9 100644 --- a/packages/PrintSpooler/res/layout/print_activity_controls.xml +++ b/packages/PrintSpooler/res/layout/print_activity_controls.xml @@ -55,8 +55,7 @@ android:text="@string/label_copies"> </TextView> - <view - class="com.android.printspooler.widget.FirstFocusableEditText" + <EditText android:id="@+id/copies_edittext" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -64,7 +63,7 @@ android:singleLine="true" android:ellipsize="end" android:inputType="numberDecimal"> - </view> + </EditText> </LinearLayout> @@ -198,8 +197,7 @@ android:visibility="visible"> </TextView> - <view - class="com.android.printspooler.widget.FirstFocusableEditText" + <EditText android:id="@+id/page_range_edittext" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -208,7 +206,7 @@ android:ellipsize="end" android:visibility="visible" android:inputType="textNoSuggestions"> - </view> + </EditText> </LinearLayout> diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml index 43d8aaf..4381a7a 100644 --- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml +++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml @@ -17,8 +17,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:paddingStart="16dip" - android:paddingEnd="16dip" + android:paddingStart="8dip" + android:paddingEnd="8dip" android:minHeight="56dip" android:orientation="horizontal" android:gravity="start|center_vertical"> diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml index 173057b..77c500a 100644 --- a/packages/PrintSpooler/res/layout/select_printer_activity.xml +++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml @@ -23,8 +23,6 @@ android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:paddingStart="@dimen/printer_list_view_padding_start" - android:paddingEnd="@dimen/printer_list_view_padding_end" android:scrollbarStyle="outsideOverlay" android:cacheColorHint="@android:color/transparent" android:scrollbarAlwaysDrawVerticalTrack="true" > diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml deleted file mode 100644 index 14403a1..0000000 --- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml +++ /dev/null @@ -1,52 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:orientation="vertical" - android:gravity="start|center_vertical"> - - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="?android:attr/spinnerDropDownItemStyle" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textColor="?android:attr/textColorPrimary" - android:singleLine="true" - android:ellipsize="end" - android:textIsSelectable="false" - android:gravity="top|left" - android:duplicateParentState="true"> - </TextView> - - <TextView - android:id="@+id/subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="?android:attr/spinnerDropDownItemStyle" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="?android:attr/textColorPrimary" - android:singleLine="true" - android:ellipsize="end" - android:textIsSelectable="false" - android:visibility="gone" - android:duplicateParentState="true"> - </TextView> - -</LinearLayout> diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml index a4666a5..6cf9754 100644 --- a/packages/PrintSpooler/res/values-land/constants.xml +++ b/packages/PrintSpooler/res/values-land/constants.xml @@ -16,10 +16,6 @@ <resources> - <dimen name="printer_list_view_padding_start">48dip</dimen> - - <dimen name="printer_list_view_padding_end">48dip</dimen> - <integer name="preview_page_per_row_count">2</integer> <integer name="print_option_column_count">3</integer> diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml index b95703b..b4e4777 100644 --- a/packages/PrintSpooler/res/values/constants.xml +++ b/packages/PrintSpooler/res/values/constants.xml @@ -28,9 +28,6 @@ <dimen name="print_dialog_frame_max_width_dip">400dip</dimen> - <dimen name="printer_list_view_padding_start">16dip</dimen> - <dimen name="printer_list_view_padding_end">16dip</dimen> - <dimen name="selected_page_elevation">6dip</dimen> <dimen name="unselected_page_elevation">2dip</dimen> @@ -46,6 +43,6 @@ <fraction name="page_unselected_alpha">50%</fraction> <dimen name="preview_page_footer_height">32dip</dimen> - <dimen name="preview_page_min_width">130dip</dimen> + <dimen name="preview_page_min_width">128dip</dimen> </resources> diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml index db319e9..532b01f 100644 --- a/packages/PrintSpooler/res/values/themes.xml +++ b/packages/PrintSpooler/res/values/themes.xml @@ -16,7 +16,10 @@ <resources> - <style name="PrintActivity" parent="@android:style/Theme.Material.Settings"> + <style name="PrintActivity" parent="@android:style/Theme.Material"> + <item name="android:colorPrimary">@*android:color/material_blue_grey_900</item> + <item name="android:colorPrimaryDark">@*android:color/material_blue_grey_950</item> + <item name="android:colorAccent">@*android:color/material_deep_teal_500</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java index 5bcdb9f..d949673 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java @@ -289,15 +289,11 @@ public final class PageAdapter extends Adapter implements + " for position: " + position); } - final int pageCount = getItemCount(); MyViewHolder myHolder = (MyViewHolder) holder; View page = holder.itemView; - if (pageCount > 1) { - page.setOnClickListener(mPageClickListener); - } else { - page.setOnClickListener(null); - } + page.setOnClickListener(mPageClickListener); + page.setTag(holder); myHolder.mPageInAdapter = position; @@ -339,16 +335,9 @@ public final class PageAdapter extends Adapter implements } content.init(provider, mMediaSize, mMinMargins); - View pageSelector = page.findViewById(R.id.page_selector); pageSelector.setTag(myHolder); - if (pageCount > 1) { - pageSelector.setOnClickListener(mPageClickListener); - pageSelector.setVisibility(View.VISIBLE); - } else { - pageSelector.setOnClickListener(null); - pageSelector.setVisibility(View.GONE); - } + pageSelector.setOnClickListener(mPageClickListener); if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) { pageSelector.setSelected(true); @@ -449,8 +438,9 @@ public final class PageAdapter extends Adapter implements final int verticalPadding; if (mPageContentHeight + mFooterHeight + mPreviewListPadding > availableHeight) { - verticalPadding = Math.max(mPreviewPageMargin, - (availableHeight - totalContentHeight) / 2); + verticalPadding = Math.max(0, + (availableHeight - mPageContentHeight - mFooterHeight) / 2 + - mPreviewPageMargin); } else { verticalPadding = Math.max(mPreviewListPadding, (availableHeight - totalContentHeight) / 2); @@ -791,6 +781,9 @@ public final class PageAdapter extends Adapter implements page.animate().translationZ(mSelectedPageElevation) .alpha(mSelectedPageAlpha); } else { + if (mConfirmedPagesInDocument.size() <= 1) { + return; + } mConfirmedPagesInDocument.remove(pageInDocument); pageSelector.setSelected(false); page.animate().translationZ(mUnselectedPageElevation) diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index fe17516..01c9746 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -982,21 +982,21 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat // Media size. mMediaSizeSpinnerAdapter = new ArrayAdapter<>( - this, R.layout.spinner_dropdown_item, R.id.title); + this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner); mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter); mMediaSizeSpinner.setOnItemSelectedListener(itemSelectedListener); // Color mode. mColorModeSpinnerAdapter = new ArrayAdapter<>( - this, R.layout.spinner_dropdown_item, R.id.title); + this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner); mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter); mColorModeSpinner.setOnItemSelectedListener(itemSelectedListener); // Orientation mOrientationSpinnerAdapter = new ArrayAdapter<>( - this, R.layout.spinner_dropdown_item, R.id.title); + this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); String[] orientationLabels = getResources().getStringArray( R.array.orientation_labels); mOrientationSpinnerAdapter.add(new SpinnerItem<>( @@ -1008,8 +1008,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat mOrientationSpinner.setOnItemSelectedListener(itemSelectedListener); // Range options - ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = - new ArrayAdapter<>(this, R.layout.spinner_dropdown_item, R.id.title); + ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter = new ArrayAdapter<>( + this, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1); mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner); mRangeOptionsSpinner.setAdapter(rangeOptionsSpinnerAdapter); mRangeOptionsSpinner.setOnItemSelectedListener(itemSelectedListener); @@ -1075,6 +1075,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat mDestinationSpinner.setEnabled(false); } mCopiesEditText.setEnabled(false); + mCopiesEditText.setFocusable(false); mMediaSizeSpinner.setEnabled(false); mColorModeSpinner.setEnabled(false); mOrientationSpinner.setEnabled(false); @@ -1089,6 +1090,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat // available, we disable all print options except the destination. if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) { mCopiesEditText.setEnabled(false); + mCopiesEditText.setFocusable(false); mMediaSizeSpinner.setEnabled(false); mColorModeSpinner.setEnabled(false); mOrientationSpinner.setEnabled(false); @@ -1316,8 +1318,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat // Copies if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) { mCopiesEditText.setEnabled(true); + mCopiesEditText.setFocusableInTouchMode(true); } else { mCopiesEditText.setEnabled(false); + mCopiesEditText.setFocusable(false); } if (mCopiesEditText.getError() == null && TextUtils.isEmpty(mCopiesEditText.getText())) { diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java b/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java deleted file mode 100644 index d6bb7c8..0000000 --- a/packages/PrintSpooler/src/com/android/printspooler/widget/FirstFocusableEditText.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.printspooler.widget; - -import android.content.Context; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.widget.EditText; - -/** - * An instance of this class class is intended to be the first focusable - * in a layout to which the system automatically gives focus. It performs - * some voodoo to avoid the first tap on it to start an edit mode, rather - * to bring up the IME, i.e. to get the behavior as if the view was not - * focused. - */ -public final class FirstFocusableEditText extends EditText { - private boolean mClickedBeforeFocus; - private CharSequence mError; - - public FirstFocusableEditText(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean performClick() { - super.performClick(); - if (isFocused() && !mClickedBeforeFocus) { - clearFocus(); - requestFocus(); - } - mClickedBeforeFocus = true; - return true; - } - - @Override - public CharSequence getError() { - return mError; - } - - @Override - public void setError(CharSequence error, Drawable icon) { - setCompoundDrawables(null, null, icon, null); - mError = error; - } - - protected void onFocusChanged(boolean gainFocus, int direction, - Rect previouslyFocusedRect) { - if (!gainFocus) { - mClickedBeforeFocus = false; - } - super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - } -}
\ No newline at end of file diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java index e428948..c84b06a 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java +++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java @@ -152,6 +152,17 @@ public final class PrintContentView extends ViewGroup implements View.OnClickLis // Make sure we start in a closed options state. onDragProgress(1.0f); + + // The framework gives focus to the frist focusable and we + // do not want that, hence we will take focus instead. + setFocusableInTouchMode(true); + } + + @Override + public void focusableViewAvailable(View v) { + // The framework gives focus to the frist focusable and we + // do not want that, hence do not announce new focusables. + return; } @Override @@ -309,6 +320,7 @@ public final class PrintContentView extends ViewGroup implements View.OnClickLis mSummaryContent.setLayerType(View.LAYER_TYPE_NONE, null); mDraggableContent.setLayerType(View.LAYER_TYPE_NONE, null); mMoreOptionsButton.setLayerType(View.LAYER_TYPE_NONE, null); + mMoreOptionsButton.setLayerType(View.LAYER_TYPE_NONE, null); } mDragProgress = progress; @@ -320,7 +332,6 @@ public final class PrintContentView extends ViewGroup implements View.OnClickLis mMoreOptionsButton.setAlpha(inverseAlpha); mEmbeddedContentScrim.setBackgroundColor(computeScrimColor()); - if (progress == 0) { if (mOptionsStateChangeListener != null) { mOptionsStateChangeListener.onOptionsOpened(); @@ -354,14 +365,15 @@ public final class PrintContentView extends ViewGroup implements View.OnClickLis } private void ensureImeClosedAndInputFocusCleared() { - View focus = findFocus(); - if (focus != null) { + View focused = findFocus(); + + if (focused != null && focused.isFocused()) { InputMethodManager imm = (InputMethodManager) mContext.getSystemService( Context.INPUT_METHOD_SERVICE); - if (imm.isActive(focus)) { + if (imm.isActive(focused)) { imm.hideSoftInputFromWindow(getWindowToken(), 0); } - focus.clearFocus(); + focused.clearFocus(); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 87c015c..4ef2189 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -318,10 +318,10 @@ public class SettingsProvider extends ContentProvider { } } - private void checkUserRestrictions(String setting) { + private void checkUserRestrictions(String setting, int userId) { String userRestriction = sRestrictedKeys.get(setting); if (!TextUtils.isEmpty(userRestriction) - && mUserManager.hasUserRestriction(userRestriction)) { + && mUserManager.hasUserRestriction(userRestriction, new UserHandle(userId))) { throw new SecurityException( "Permission denial: user is restricted from changing this setting."); } @@ -936,7 +936,7 @@ public class SettingsProvider extends ContentProvider { try { int numValues = values.length; for (int i = 0; i < numValues; i++) { - checkUserRestrictions(values[i].getAsString(Settings.Secure.NAME)); + checkUserRestrictions(values[i].getAsString(Settings.Secure.NAME), callingUser); if (db.insert(args.table, null, values[i]) < 0) return 0; SettingsCache.populate(cache, values[i]); if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + values[i]); @@ -1067,7 +1067,7 @@ public class SettingsProvider extends ContentProvider { // Check write permissions only after determining which table the insert will touch checkWritePermissions(args); - checkUserRestrictions(name); + checkUserRestrictions(name, desiredUserHandle); // The global table is stored under the owner, always if (TABLE_GLOBAL.equals(args.table)) { @@ -1143,7 +1143,7 @@ public class SettingsProvider extends ContentProvider { callingUser = UserHandle.USER_OWNER; } checkWritePermissions(args); - checkUserRestrictions(initialValues.getAsString(Settings.Secure.NAME)); + checkUserRestrictions(initialValues.getAsString(Settings.Secure.NAME), callingUser); final AtomicInteger mutationCount = sKnownMutationsInFlight.get(callingUser); mutationCount.incrementAndGet(); diff --git a/packages/SystemUI/res/layout/data_usage.xml b/packages/SystemUI/res/layout/data_usage.xml index 8831a05..c943f3d 100644 --- a/packages/SystemUI/res/layout/data_usage.xml +++ b/packages/SystemUI/res/layout/data_usage.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.systemui.qs.tiles.DataUsageDetailView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="16dp" @@ -82,4 +82,4 @@ android:textAppearance="@style/TextAppearance.QS.DataUsage.Secondary" /> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</com.android.systemui.qs.tiles.DataUsageDetailView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index ef0c9bb..ca07c87 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -67,6 +67,6 @@ android:src="@drawable/ic_lock_24dp" android:scaleType="center" android:tint="#ffffffff" - android:contentDescription="@string/accessibility_unlock_button_not_secured" /> + android:contentDescription="@string/accessibility_unlock_button" /> </com.android.systemui.statusbar.phone.KeyguardBottomAreaView> diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml index d65a23e..566c304 100644 --- a/packages/SystemUI/res/layout/notification_guts.xml +++ b/packages/SystemUI/res/layout/notification_guts.xml @@ -53,7 +53,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" android:layout_weight="1" - android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Title" + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title" android:textColor="@color/notification_guts_title_color" /> <DateTimeView @@ -62,7 +62,7 @@ android:layout_height="wrap_content" android:layout_weight="0" android:layout_gravity="center_vertical|start" - android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Time" + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Time" android:textColor="@color/notification_guts_text_color" /> <TextView @@ -70,7 +70,7 @@ android:layout_height="wrap_content" android:id="@+id/debug_info" android:layout_weight="0" - android:textAppearance="@*android:style/TextAppearance.StatusBar.Material.EventContent.Time" + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Time" android:layout_gravity="bottom|start" android:visibility="gone" android:textColor="@color/notification_guts_text_color" diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml index 2322f16..af22f03 100644 --- a/packages/SystemUI/res/layout/qs_user_detail_item.xml +++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml @@ -44,7 +44,7 @@ android:id="@+id/user_name" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="14sp" + android:textSize="@dimen/qs_detail_item_secondary_text_size" android:textColor="@color/qs_user_detail_name" android:gravity="center_horizontal" /> diff --git a/packages/SystemUI/res/layout/split_clock_view.xml b/packages/SystemUI/res/layout/split_clock_view.xml index 87b7051..808460a 100644 --- a/packages/SystemUI/res/layout/split_clock_view.xml +++ b/packages/SystemUI/res/layout/split_clock_view.xml @@ -39,6 +39,7 @@ <!-- Empty text view so we have the same height when expanded/collapsed--> <TextView + android:id="@+id/empty_time_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="invisible" diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml index ef8a426..f9e7852 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml @@ -87,6 +87,7 @@ android:visibility="gone" android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly" android:text="@*android:string/emergency_calls_only" + android:singleLine="true" android:gravity="center_vertical" /> <FrameLayout diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml index eff3758..351177b 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml @@ -38,8 +38,8 @@ android:id="@+id/more_text" android:layout_width="32dp" android:layout_height="32dp" - android:layout_marginStart="20dp" - android:layout_marginEnd="16dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="12dp" android:layout_gravity="center_vertical" android:background="@drawable/keyguard_overflow_number_background" android:gravity="center" diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml index 3949d7d..14bf10e 100644 --- a/packages/SystemUI/res/layout/zen_mode_panel.xml +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -88,14 +88,4 @@ android:orientation="vertical" android:paddingBottom="@dimen/qs_panel_padding" /> - <TextView - android:id="@+id/zen_alarm_warning" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/qs_panel_padding" - android:paddingLeft="@dimen/qs_panel_padding" - android:paddingRight="@dimen/qs_panel_padding" - android:paddingTop="4dp" - android:paddingBottom="4dp" - android:textAppearance="@style/TextAppearance.QS.Subhead" /> </com.android.systemui.volume.ZenModePanel> diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index 3cd5f67..dd158c2 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -58,6 +58,9 @@ <!-- Size of fading edge for scrolling --> <dimen name="status_bar_recents_scroll_fading_edge_length">10dip</dimen> + <!-- The radius of the rounded corners on a task view. --> + <dimen name="recents_task_view_rounded_corners_radius">3dp</dimen> + <!-- Where to place the app icon over the thumbnail --> <dimen name="status_bar_recents_app_icon_left_margin">0dp</dimen> <dimen name="status_bar_recents_app_icon_top_margin">8dp</dimen> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index e940b18..0e18979 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -62,6 +62,9 @@ <!-- Vibration duration for GlowPadView used in SearchPanelView --> <integer translatable="false" name="config_search_panel_view_vibration_duration">20</integer> + <!-- Show camera affordance on Keyguard --> + <bool name="config_keyguardShowCameraAffordance">true</bool> + <!-- The length of the vibration when the notification pops open. --> <integer name="one_finger_pop_duration_ms">10</integer> @@ -123,10 +126,10 @@ <!-- The min animation duration for animating views that are newly visible. --> <integer name="recents_filter_animate_new_views_duration">250</integer> <!-- The min animation duration for animating the task bar in. --> - <integer name="recents_animate_task_bar_enter_duration">275</integer> + <integer name="recents_animate_task_bar_enter_duration">225</integer> <!-- The animation delay for animating the first task in. This should roughly be the animation duration of the transition in to recents. --> - <integer name="recents_animate_task_bar_enter_delay">300</integer> + <integer name="recents_animate_task_bar_enter_delay">275</integer> <!-- The min animation duration for animating the task bar out. --> <integer name="recents_animate_task_exit_to_home_duration">225</integer> <!-- The min animation duration for animating the task bar out. --> @@ -143,6 +146,8 @@ <integer name="recents_nav_bar_scrim_enter_duration">400</integer> <!-- The animation duration for animating the removal of a task view. --> <integer name="recents_animate_task_view_remove_duration">250</integer> + <!-- The animation duration for scrolling the stack to a particular item. --> + <integer name="recents_animate_task_stack_scroll_duration">225</integer> <!-- The minimum alpha for the dim applied to cards that go deeper into the stack. --> <integer name="recents_max_task_stack_view_dim">96</integer> <!-- The delay to enforce between each alt-tab key press. --> @@ -195,5 +200,8 @@ <!-- Tiles with feature timeouts: number of days to show after feature is used. --> <integer name="days_to_show_timeout_tiles">30</integer> + + <!-- Number of times to show the strong alarm warning text in the volume dialog --> + <integer name="zen_mode_alarm_warning_threshold">5</integer> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index bc941ca..162c277 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -166,6 +166,7 @@ <dimen name="qs_dual_tile_padding_vertical">8dp</dimen> <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen> <dimen name="qs_tile_padding_top">16dp</dimen> + <dimen name="qs_tile_padding_top_large_text">4dp</dimen> <dimen name="qs_tile_padding_below_icon">12dp</dimen> <dimen name="qs_tile_padding_bottom">16dp</dimen> <dimen name="qs_tile_spacing">4dp</dimen> @@ -173,7 +174,13 @@ <dimen name="qs_detail_item_height">48dp</dimen> <dimen name="qs_detail_item_height_twoline">72dp</dimen> <dimen name="qs_brightness_padding_top">6dp</dimen> - + <dimen name="qs_detail_header_text_size">20sp</dimen> + <dimen name="qs_detail_button_text_size">14sp</dimen> + <dimen name="qs_detail_item_primary_text_size">16sp</dimen> + <dimen name="qs_detail_item_secondary_text_size">14sp</dimen> + <dimen name="qs_detail_empty_text_size">14sp</dimen> + <dimen name="qs_data_usage_text_size">14sp</dimen> + <dimen name="qs_data_usage_usage_text_size">36sp</dimen> <dimen name="segmented_button_spacing">4dp</dimen> <dimen name="segmented_button_radius">2dp</dimen> @@ -195,17 +202,14 @@ <!-- The size of the application icon in the recents task view. --> <dimen name="recents_task_view_application_icon_size">48dp</dimen> - <!-- The size of the activity icon in the recents task view. --> - <dimen name="recents_task_view_activity_icon_size">60dp</dimen> - <!-- The radius of the rounded corners on a task view. --> <dimen name="recents_task_view_rounded_corners_radius">2dp</dimen> <!-- The min translation in the Z index for the last task. --> - <dimen name="recents_task_view_z_min">25dp</dimen> + <dimen name="recents_task_view_z_min">20dp</dimen> <!-- The max translation in the Z index for the last task. --> - <dimen name="recents_task_view_z_max">100dp</dimen> + <dimen name="recents_task_view_z_max">80dp</dimen> <!-- The amount to translate when animating the removal of a task. --> <dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen> @@ -231,6 +235,9 @@ <!-- The side padding for the task stack as a percentage of the width. --> <item name="recents_stack_width_padding_percentage" format="float" type="dimen">0.04444</item> + <!-- The overscroll percentage allowed on the stack. --> + <item name="recents_stack_overscroll_percentage" format="float" type="dimen">0.0875</item> + <!-- The top offset for the task stack. --> <dimen name="recents_stack_top_padding">16dp</dimen> @@ -368,6 +375,9 @@ <!-- The padding bottom of the clock group when QS is collapsed. --> <dimen name="clock_collapsed_bottom_margin">10dp</dimen> + <!-- The padding bottom of the clock group when QS is collapsed for large text --> + <dimen name="clock_collapsed_bottom_margin_large_text">6dp</dimen> + <!-- The width of the multi user switch on keyguard and collapsed QS header. --> <dimen name="multi_user_switch_width_collapsed">34dp</dimen> @@ -389,6 +399,12 @@ <!-- The font size of the time when expanded in QS --> <dimen name="qs_time_expanded_size">20sp</dimen> + <!-- The font size of the "emergency calls only" label in QS --> + <dimen name="qs_emergency_calls_only_text_size">12sp</dimen> + + <!-- The font size of the date in QS --> + <dimen name="qs_date_collapsed_size">14sp</dimen> + <!-- Battery level padding end when in expanded QS (but not on Keyguard) --> <dimen name="battery_level_padding_end">4dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 0fe389a..07943e1 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -213,6 +213,8 @@ <string name="accessibility_camera_button">Camera</string> <!-- Content description of the phone button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_phone_button">Phone</string> + <!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_unlock_button">Unlock</string> <!-- Click action label for accessibility for the unlock button. [CHAR LIMIT=NONE] --> <string name="unlock_label">unlock</string> <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] --> @@ -220,17 +222,6 @@ <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] --> <string name="camera_label">open camera</string> - <!-- Content description of the lock icon when device is secured (lock closed) and trust not managed (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_unlock_button_secured">Device secured.</string> - <!-- Content description of the lock icon when device is not secured (lock open) and trust not managed (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_unlock_button_not_secured">Device not secured.</string> - <!-- Content description of the lock icon when device is secured (lock closed) and trust managed (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_unlock_button_secured_trust_managed">Device secured, trust agent active.</string> - <!-- Content description of the lock icon when device is not secured (lock open) and trust managed (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_unlock_button_not_secured_trust_managed">Device not secured, trust agent active.</string> - <!-- Content description of the lock icon when face unlock is running (face icon) and trust managed (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_unlock_button_face_unlock_running">Face detection running, trust agent active.</string> - <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_ime_switch_button">Switch input method button.</string> <!-- Content description of the compatibility zoom button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> @@ -697,7 +688,7 @@ <string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> <!-- Zen mode: No interruptions title, with a warning about alarms. [CHAR LIMIT=60] --> - <string name="zen_no_interruptions_with_warning">No interruptions, including alarms</string> + <string name="zen_no_interruptions_with_warning">No interruptions. Not even alarms.</string> <!-- Zen mode: No interruptions. [CHAR LIMIT=40] --> <string name="zen_no_interruptions">No interruptions</string> @@ -837,12 +828,18 @@ <!-- Footer device owned text [CHAR LIMIT=50] --> <string name="device_owned_footer">Device may be monitored</string> + <!-- Footer profile owned text [CHAR LIMIT=50] --> + <string name="profile_owned_footer">Profile may be monitored</string> + <!-- Footer vpn present text [CHAR LIMIT=50] --> <string name="vpn_footer">Network may be monitored</string> <!-- Monitoring dialog title for device owned devices [CHAR LIMIT=35] --> <string name="monitoring_title_device_owned">Device monitoring</string> + <!-- Monitoring dialog title for profile owned devices [CHAR LIMIT=35] --> + <string name="monitoring_title_profile_owned">Profile monitoring</string> + <!-- Monitoring dialog title for normal devices [CHAR LIMIT=35]--> <string name="monitoring_title">Network monitoring</string> @@ -867,6 +864,24 @@ <!-- Monitoring dialog legacy VPN with device owner text [CHAR LIMIT=300] --> <string name="monitoring_description_legacy_vpn_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you\'re connected to a VPN (\"<xliff:g id="application">%2$s</xliff:g>\"). Your VPN service provider can monitor network activity too.</string> + <!-- Monitoring dialog profile owner body text [CHAR LIMIT=300] --> + <string name="monitoring_description_profile_owned">This profile is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator can monitor your device and network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string> + + <!-- Monitoring dialog device and profile owner body text [CHAR LIMIT=300] --> + <string name="monitoring_description_device_and_profile_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\nYour profile is managed by:\n<xliff:g id="organization">%2$s</xliff:g>\n\nYour administrator can monitor your device and network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string> + + <!-- Monitoring dialog non-legacy VPN with profile owner text [CHAR LIMIT=300] --> + <string name="monitoring_description_vpn_profile_owned">This profile is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you gave \"<xliff:g id="application">%2$s</xliff:g>\" permission to set up a VPN connection. This app can monitor network activity too.</string> + + <!-- Monitoring dialog legacy VPN with profile owner text [CHAR LIMIT=300] --> + <string name="monitoring_description_legacy_vpn_profile_owned">This profile is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you\'re connected to a VPN (\"<xliff:g id="application">%2$s</xliff:g>\"). Your VPN service provider can monitor network activity too.</string> + + <!-- Monitoring dialog non-legacy VPN with device and profile owner text [CHAR LIMIT=300] --> + <string name="monitoring_description_vpn_device_and_profile_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\nYour profile is managed by:\n<xliff:g id="organization">%2$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you gave \"<xliff:g id="application">%3$s</xliff:g>\" permission to set up a VPN connection. This app can monitor network activity too.</string> + + <!-- Monitoring dialog legacy VPN with device and profile owner text [CHAR LIMIT=300] --> + <string name="monitoring_description_legacy_vpn_device_and_profile_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\nYour profile is managed by:\n<xliff:g id="organization">%2$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you\'re connected to a VPN (\"<xliff:g id="application">%3$s</xliff:g>\"). Your VPN service provider can monitor network activity too.</string> + <!-- Indication on the keyguard that appears when the user disables trust agents until the next time they unlock manually. [CHAR LIMIT=NONE] --> <string name="keyguard_indication_trust_disabled">Device will stay locked until you manually unlock</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index b39fe24..efd4fb4 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -33,8 +33,7 @@ <item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item> - - <item name="android:ambientShadowAlpha">0.30</item> + <item name="android:ambientShadowAlpha">0.35</item> </style> <!-- Animations for a non-full-screen window or activity. --> @@ -85,13 +84,13 @@ </style> <style name="TextAppearance.StatusBar.Expanded.Date"> - <item name="android:textSize">14sp</item> + <item name="android:textSize">@dimen/qs_date_collapsed_size</item> <item name="android:textStyle">normal</item> <item name="android:textColor">#b2ffffff</item> </style> <style name="TextAppearance.StatusBar.Expanded.AboveDateTime"> - <item name="android:textSize">12dp</item> + <item name="android:textSize">@dimen/qs_emergency_calls_only_text_size</item> <item name="android:textStyle">normal</item> <item name="android:textColor">#66ffffff</item> </style> @@ -125,16 +124,16 @@ </style> <style name="TextAppearance.QS.DetailHeader"> - <item name="android:textSize">20sp</item> + <item name="android:textSize">@dimen/qs_detail_header_text_size</item> <item name="android:fontFamily">sans-serif-medium</item> </style> <style name="TextAppearance.QS.DetailItemPrimary"> - <item name="android:textSize">16sp</item> + <item name="android:textSize">@dimen/qs_detail_item_primary_text_size</item> </style> <style name="TextAppearance.QS.DetailItemSecondary"> - <item name="android:textSize">14sp</item> + <item name="android:textSize">@dimen/qs_detail_item_secondary_text_size</item> <item name="android:textColor">@color/system_accent_color</item> </style> @@ -144,14 +143,14 @@ </style> <style name="TextAppearance.QS.DetailButton"> - <item name="android:textSize">14sp</item> + <item name="android:textSize">@dimen/qs_detail_button_text_size</item> <item name="android:textAllCaps">true</item> <item name="android:fontFamily">sans-serif-medium</item> <item name="android:gravity">center</item> </style> <style name="TextAppearance.QS.DetailEmpty"> - <item name="android:textSize">14sp</item> + <item name="android:textSize">@dimen/qs_detail_empty_text_size</item> <item name="android:textColor">@color/qs_subhead</item> </style> @@ -167,11 +166,11 @@ </style> <style name="TextAppearance.QS.DataUsage"> - <item name="android:textSize">14sp</item> + <item name="android:textSize">@dimen/qs_data_usage_text_size</item> </style> <style name="TextAppearance.QS.DataUsage.Usage"> - <item name="android:textSize">36sp</item> + <item name="android:textSize">@dimen/qs_data_usage_usage_text_size</item> <item name="android:textColor">@color/system_accent_color</item> </style> @@ -235,8 +234,8 @@ <item name="android:windowExitAnimation">@*android:anim/popup_exit_material</item> </style> - <style name="TextAppearance.StatusBar.Material.EventContent.Parenthetical" - parent="@*android:style/TextAppearance.StatusBar.Material.EventContent"> + <style name="TextAppearance.Material.Notification.Parenthetical" + parent="@*android:style/TextAppearance.Material.Notification"> <item name="android:textStyle">italic</item> <item name="android:textColor">#60000000</item> </style> diff --git a/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java b/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java new file mode 100644 index 0000000..35a70a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/FontSizeUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui; + +import android.util.TypedValue; +import android.view.View; +import android.widget.TextView; + +/** + * Utility class to update the font size when the configuration has changed. + */ +public class FontSizeUtils { + + public static final float LARGE_TEXT_SCALE = 1.3f; + + public static void updateFontSize(View parent, int viewId, int dimensId) { + updateFontSize((TextView) parent.findViewById(viewId), dimensId); + } + + public static void updateFontSize(TextView v, int dimensId) { + if (v != null) { + v.setTextSize(TypedValue.COMPLEX_UNIT_PX, + v.getResources().getDimensionPixelSize(dimensId)); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java index 24c1378..ce0d5f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java @@ -17,12 +17,14 @@ package com.android.systemui.qs; import android.content.Context; +import android.content.res.Configuration; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -31,6 +33,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; /** @@ -76,6 +79,20 @@ public class QSDetailItems extends FrameLayout { mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + FontSizeUtils.updateFontSize(mEmptyText, R.dimen.qs_detail_empty_text_size); + int count = mItems.getChildCount(); + for (int i = 0; i < count; i++) { + View item = mItems.getChildAt(i); + FontSizeUtils.updateFontSize(item, android.R.id.title, + R.dimen.qs_detail_item_primary_text_size); + FontSizeUtils.updateFontSize(item, android.R.id.summary, + R.dimen.qs_detail_item_secondary_text_size); + } + } + public void setTagSuffix(String suffix) { mTag = TAG + "." + suffix; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index a8199fa..a0b6e82 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -18,23 +18,23 @@ package com.android.systemui.qs; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.content.res.Configuration; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; -import android.view.ContextThemeWrapper; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; -import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.statusbar.phone.QSTileHost; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.statusbar.policy.SecurityController.VpnCallback; public class QSFooter implements OnClickListener, DialogInterface.OnClickListener { protected static final String TAG = "QSFooter"; @@ -50,6 +50,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene private AlertDialog mDialog; private QSTileHost mHost; private Handler mHandler; + private final Handler mMainHandler; private boolean mIsVisible; private boolean mIsIconVisible; @@ -62,6 +63,7 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene mFooterText = (TextView) mRootView.findViewById(R.id.footer_text); mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon); mContext = context; + mMainHandler = new Handler(); } public void setHost(QSTileHost host) { @@ -78,6 +80,10 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } } + public void onConfigurationChanged() { + FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size); + } + public View getView() { return mRootView; } @@ -106,14 +112,19 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene mFooterTextId = R.string.device_owned_footer; mIsVisible = true; mIsIconVisible = false; + } else if (mSecurityController.hasProfileOwner()) { + mFooterTextId = R.string.profile_owned_footer; + mIsVisible = true; + mIsIconVisible = false; } else if (mSecurityController.isVpnEnabled()) { mFooterTextId = R.string.vpn_footer; mIsVisible = true; mIsIconVisible = true; } else { mIsVisible = false; + mIsIconVisible = false; } - mRootView.post(mUpdateDisplayState); + mMainHandler.post(mUpdateDisplayState); } @Override @@ -148,20 +159,61 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene private String getMessage() { if (mSecurityController.hasDeviceOwner()) { + if (mSecurityController.hasProfileOwner()) { + if (mSecurityController.isVpnEnabled()) { + if (mSecurityController.isLegacyVpn()) { + return mContext.getString( + R.string.monitoring_description_legacy_vpn_device_and_profile_owned, + mSecurityController.getDeviceOwnerName(), + mSecurityController.getProfileOwnerName(), + mSecurityController.getLegacyVpnName()); + } else { + return mContext.getString( + R.string.monitoring_description_vpn_device_and_profile_owned, + mSecurityController.getDeviceOwnerName(), + mSecurityController.getProfileOwnerName(), + mSecurityController.getVpnApp()); + } + } else { + return mContext.getString( + R.string.monitoring_description_device_and_profile_owned, + mSecurityController.getDeviceOwnerName(), + mSecurityController.getProfileOwnerName()); + } + } else { + if (mSecurityController.isVpnEnabled()) { + if (mSecurityController.isLegacyVpn()) { + return mContext.getString( + R.string.monitoring_description_legacy_vpn_device_owned, + mSecurityController.getDeviceOwnerName(), + mSecurityController.getLegacyVpnName()); + } else { + return mContext.getString(R.string.monitoring_description_vpn_device_owned, + mSecurityController.getDeviceOwnerName(), + mSecurityController.getVpnApp()); + } + } else { + return mContext.getString(R.string.monitoring_description_device_owned, + mSecurityController.getDeviceOwnerName()); + } + } + } else if (mSecurityController.hasProfileOwner()) { if (mSecurityController.isVpnEnabled()) { if (mSecurityController.isLegacyVpn()) { return mContext.getString( - R.string.monitoring_description_legacy_vpn_device_owned, - mSecurityController.getDeviceOwnerName(), + R.string.monitoring_description_legacy_vpn_profile_owned, + mSecurityController.getProfileOwnerName(), mSecurityController.getLegacyVpnName()); } else { - return mContext.getString(R.string.monitoring_description_vpn_device_owned, - mSecurityController.getDeviceOwnerName(), + return mContext.getString( + R.string.monitoring_description_vpn_profile_owned, + mSecurityController.getProfileOwnerName(), mSecurityController.getVpnApp()); } } else { - return mContext.getString(R.string.monitoring_description_device_owned, - mSecurityController.getDeviceOwnerName()); + return mContext.getString( + R.string.monitoring_description_profile_owned, + mSecurityController.getProfileOwnerName()); } } else { if (mSecurityController.isLegacyVpn()) { @@ -179,6 +231,9 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene if (mSecurityController.hasDeviceOwner()) { return R.string.monitoring_title_device_owned; } + if (mSecurityController.hasProfileOwner()) { + return R.string.monitoring_title_profile_owned; + } return R.string.monitoring_title; } @@ -193,9 +248,9 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } }; - private class Callback implements VpnCallback { + private class Callback implements SecurityController.SecurityControllerCallback { @Override - public void onVpnStateChanged() { + public void onStateChanged() { refreshState(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index a3ffc4e..6bfe0a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -21,15 +21,19 @@ import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.content.res.Resources; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.TextView; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.qs.QSTile.DetailAdapter; import com.android.systemui.settings.BrightnessController; @@ -48,8 +52,8 @@ public class QSPanel extends ViewGroup { private final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>(); private final View mDetail; private final ViewGroup mDetailContent; - private final View mDetailSettingsButton; - private final View mDetailDoneButton; + private final TextView mDetailSettingsButton; + private final TextView mDetailDoneButton; private final View mBrightnessView; private final QSDetailClipper mClipper; private final H mHandler = new H(); @@ -83,8 +87,8 @@ public class QSPanel extends ViewGroup { mDetail = LayoutInflater.from(context).inflate(R.layout.qs_detail, this, false); mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content); - mDetailSettingsButton = mDetail.findViewById(android.R.id.button2); - mDetailDoneButton = mDetail.findViewById(android.R.id.button1); + mDetailSettingsButton = (TextView) mDetail.findViewById(android.R.id.button2); + mDetailDoneButton = (TextView) mDetail.findViewById(android.R.id.button1); mDetail.setVisibility(GONE); mDetail.setClickable(true); mBrightnessView = LayoutInflater.from(context).inflate( @@ -148,6 +152,24 @@ public class QSPanel extends ViewGroup { } } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + FontSizeUtils.updateFontSize(mDetailDoneButton, R.dimen.qs_detail_button_text_size); + FontSizeUtils.updateFontSize(mDetailSettingsButton, R.dimen.qs_detail_button_text_size); + + // We need to poke the detail views as well as they might not be attached to the view + // hierarchy but reused at a later point. + int count = mRecords.size(); + for (int i = 0; i < count; i++) { + View detailView = mRecords.get(i).detailView; + if (detailView != null) { + detailView.dispatchConfigurationChanged(newConfig); + } + } + mFooter.onConfigurationChanged(); + } + public void setExpanded(boolean expanded) { if (mExpanded == expanded) return; mExpanded = expanded; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 9321614..e6175d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -17,6 +17,7 @@ package com.android.systemui.qs; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Typeface; @@ -25,6 +26,7 @@ import android.graphics.drawable.RippleDrawable; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.util.MathUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -33,6 +35,7 @@ import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.TextView; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.qs.QSTile.State; @@ -47,7 +50,7 @@ public class QSTileView extends ViewGroup { private final H mHandler = new H(); private final int mIconSizePx; private final int mTileSpacingPx; - private final int mTilePaddingTopPx; + private int mTilePaddingTopPx; private final int mTilePaddingBelowIconPx; private final int mDualTileVerticalPaddingPx; private final View mTopBackgroundView; @@ -66,7 +69,6 @@ public class QSTileView extends ViewGroup { final Resources res = context.getResources(); mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size); mTileSpacingPx = res.getDimensionPixelSize(R.dimen.qs_tile_spacing); - mTilePaddingTopPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top); mTilePaddingBelowIconPx = res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon); mDualTileVerticalPaddingPx = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical); @@ -86,6 +88,29 @@ public class QSTileView extends ViewGroup { addView(mDivider); setClickable(true); + + updateTopPadding(); + } + + private void updateTopPadding() { + Resources res = getResources(); + int padding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top); + int largePadding = res.getDimensionPixelSize(R.dimen.qs_tile_padding_top_large_text); + float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale, + 1.0f, FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f); + mTilePaddingTopPx = Math.round((1 - largeFactor) * padding + largeFactor * largePadding); + requestLayout(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateTopPadding(); + FontSizeUtils.updateFontSize(mLabel, R.dimen.qs_tile_text_size); + if (mDualLabel != null) { + mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, + getResources().getDimensionPixelSize(R.dimen.qs_tile_text_size)); + } } private void recreateLabel() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 25bcfd2..98adf81 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -190,11 +190,6 @@ public class CellularTile extends QSTile<QSTile.SignalState> { }; private final class CellularDetailAdapter implements DetailAdapter { - private static final double KB = 1024; - private static final double MB = 1024 * KB; - private static final double GB = 1024 * MB; - - private final DecimalFormat FORMAT = new DecimalFormat("#.##"); @Override public int getTitle() { @@ -218,80 +213,17 @@ public class CellularTile extends QSTile<QSTile.SignalState> { @Override public View createDetailView(Context context, View convertView, ViewGroup parent) { - final View v = convertView != null ? convertView : LayoutInflater.from(mContext) - .inflate(R.layout.data_usage, parent, false); + final DataUsageDetailView v = (DataUsageDetailView) (convertView != null + ? convertView + : LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false)); final DataUsageInfo info = mController.getDataUsageInfo(); if (info == null) return v; - final Resources res = mContext.getResources(); - final int titleId; - final long bytes; - int usageColor = R.color.system_accent_color; - final String top; - String bottom = null; - if (info.usageLevel < info.warningLevel || info.limitLevel <= 0) { - // under warning, or no limit - titleId = R.string.quick_settings_cellular_detail_data_usage; - bytes = info.usageLevel; - top = res.getString(R.string.quick_settings_cellular_detail_data_warning, - formatBytes(info.warningLevel)); - } else if (info.usageLevel <= info.limitLevel) { - // over warning, under limit - titleId = R.string.quick_settings_cellular_detail_remaining_data; - bytes = info.limitLevel - info.usageLevel; - top = res.getString(R.string.quick_settings_cellular_detail_data_used, - formatBytes(info.usageLevel)); - bottom = res.getString(R.string.quick_settings_cellular_detail_data_limit, - formatBytes(info.limitLevel)); - } else { - // over limit - titleId = R.string.quick_settings_cellular_detail_over_limit; - bytes = info.usageLevel - info.limitLevel; - top = res.getString(R.string.quick_settings_cellular_detail_data_used, - formatBytes(info.usageLevel)); - bottom = res.getString(R.string.quick_settings_cellular_detail_data_limit, - formatBytes(info.limitLevel)); - usageColor = R.color.system_warning_color; - } - - final TextView title = (TextView) v.findViewById(android.R.id.title); - title.setText(titleId); - final TextView usage = (TextView) v.findViewById(R.id.usage_text); - usage.setText(formatBytes(bytes)); - usage.setTextColor(res.getColor(usageColor)); - final DataUsageGraph graph = (DataUsageGraph) v.findViewById(R.id.usage_graph); - graph.setLevels(info.limitLevel, info.warningLevel, info.usageLevel); - final TextView carrier = (TextView) v.findViewById(R.id.usage_carrier_text); - carrier.setText(info.carrier); - final TextView period = (TextView) v.findViewById(R.id.usage_period_text); - period.setText(info.period); - final TextView infoTop = (TextView) v.findViewById(R.id.usage_info_top_text); - infoTop.setVisibility(top != null ? View.VISIBLE : View.GONE); - infoTop.setText(top); - final TextView infoBottom = (TextView) v.findViewById(R.id.usage_info_bottom_text); - infoBottom.setVisibility(bottom != null ? View.VISIBLE : View.GONE); - infoBottom.setText(bottom); + v.bind(info); return v; } public void setMobileDataEnabled(boolean enabled) { fireToggleStateChanged(enabled); } - - private String formatBytes(long bytes) { - final long b = Math.abs(bytes); - double val; - String suffix; - if (b > 100 * MB) { - val = b / GB; - suffix = "GB"; - } else if (b > 100 * KB) { - val = b / MB; - suffix = "MB"; - } else { - val = b / KB; - suffix = "KB"; - } - return FORMAT.format(val * (bytes < 0 ? -1 : 1)) + " " + suffix; - } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java new file mode 100644 index 0000000..7bdb58f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataUsageDetailView.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.qs.tiles; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.systemui.FontSizeUtils; +import com.android.systemui.R; +import com.android.systemui.qs.DataUsageGraph; +import com.android.systemui.statusbar.policy.NetworkController; + +import java.text.DecimalFormat; + +/** + * Layout for the data usage detail in quick settings. + */ +public class DataUsageDetailView extends LinearLayout { + + private static final double KB = 1024; + private static final double MB = 1024 * KB; + private static final double GB = 1024 * MB; + + private final DecimalFormat FORMAT = new DecimalFormat("#.##"); + + public DataUsageDetailView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + FontSizeUtils.updateFontSize(this, android.R.id.title, R.dimen.qs_data_usage_text_size); + FontSizeUtils.updateFontSize(this, R.id.usage_text, R.dimen.qs_data_usage_usage_text_size); + FontSizeUtils.updateFontSize(this, R.id.usage_carrier_text, + R.dimen.qs_data_usage_text_size); + FontSizeUtils.updateFontSize(this, R.id.usage_info_top_text, + R.dimen.qs_data_usage_text_size); + FontSizeUtils.updateFontSize(this, R.id.usage_period_text, R.dimen.qs_data_usage_text_size); + FontSizeUtils.updateFontSize(this, R.id.usage_info_bottom_text, + R.dimen.qs_data_usage_text_size); + } + + public void bind(NetworkController.DataUsageInfo info) { + final Resources res = mContext.getResources(); + final int titleId; + final long bytes; + int usageColor = R.color.system_accent_color; + final String top; + String bottom = null; + if (info.usageLevel < info.warningLevel || info.limitLevel <= 0) { + // under warning, or no limit + titleId = R.string.quick_settings_cellular_detail_data_usage; + bytes = info.usageLevel; + top = res.getString(R.string.quick_settings_cellular_detail_data_warning, + formatBytes(info.warningLevel)); + } else if (info.usageLevel <= info.limitLevel) { + // over warning, under limit + titleId = R.string.quick_settings_cellular_detail_remaining_data; + bytes = info.limitLevel - info.usageLevel; + top = res.getString(R.string.quick_settings_cellular_detail_data_used, + formatBytes(info.usageLevel)); + bottom = res.getString(R.string.quick_settings_cellular_detail_data_limit, + formatBytes(info.limitLevel)); + } else { + // over limit + titleId = R.string.quick_settings_cellular_detail_over_limit; + bytes = info.usageLevel - info.limitLevel; + top = res.getString(R.string.quick_settings_cellular_detail_data_used, + formatBytes(info.usageLevel)); + bottom = res.getString(R.string.quick_settings_cellular_detail_data_limit, + formatBytes(info.limitLevel)); + usageColor = R.color.system_warning_color; + } + + final TextView title = (TextView) findViewById(android.R.id.title); + title.setText(titleId); + final TextView usage = (TextView) findViewById(R.id.usage_text); + usage.setText(formatBytes(bytes)); + usage.setTextColor(res.getColor(usageColor)); + final DataUsageGraph graph = (DataUsageGraph) findViewById(R.id.usage_graph); + graph.setLevels(info.limitLevel, info.warningLevel, info.usageLevel); + final TextView carrier = (TextView) findViewById(R.id.usage_carrier_text); + carrier.setText(info.carrier); + final TextView period = (TextView) findViewById(R.id.usage_period_text); + period.setText(info.period); + final TextView infoTop = (TextView) findViewById(R.id.usage_info_top_text); + infoTop.setVisibility(top != null ? View.VISIBLE : View.GONE); + infoTop.setText(top); + final TextView infoBottom = (TextView) findViewById(R.id.usage_info_bottom_text); + infoBottom.setVisibility(bottom != null ? View.VISIBLE : View.GONE); + infoBottom.setText(bottom); + } + + private String formatBytes(long bytes) { + final long b = Math.abs(bytes); + double val; + String suffix; + if (b > 100 * MB) { + val = b / GB; + suffix = "GB"; + } else if (b > 100 * KB) { + val = b / MB; + suffix = "MB"; + } else { + val = b / KB; + suffix = "KB"; + } + return FORMAT.format(val * (bytes < 0 ? -1 : 1)) + " " + suffix; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index 7ac6644..46d8a9b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -54,12 +54,7 @@ public class LocationTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { final boolean wasEnabled = (Boolean) mState.value; - final boolean changed = mController.setLocationEnabled(!wasEnabled); - if (!wasEnabled && changed) { - // If we've successfully switched from location off to on, close the - // notifications tray to show the network location provider consent dialog. - mHost.collapsePanels(); - } + mController.setLocationEnabled(!wasEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java index a56b7a7..c55cbcc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java @@ -17,15 +17,18 @@ package com.android.systemui.qs.tiles; import com.android.internal.util.ArrayUtils; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.statusbar.phone.UserAvatarView; import android.content.Context; +import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -107,6 +110,12 @@ public class UserDetailItemView extends LinearLayout { } @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + FontSizeUtils.updateFontSize(mName, R.dimen.qs_detail_item_secondary_text_size); + } + + @Override protected void drawableStateChanged() { super.drawableStateChanged(); updateTypeface(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java index 0b36bdb..ec39d77 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -22,7 +22,6 @@ import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; @@ -36,7 +35,6 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskGrouping; @@ -74,6 +72,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta static RecentsComponent.Callbacks sRecentsComponentCallbacks; Context mContext; + LayoutInflater mInflater; SystemServicesProxy mSystemServicesProxy; Handler mHandler; boolean mBootCompleted; @@ -99,32 +98,21 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta long mLastToggleTime; public AlternateRecentsComponent(Context context) { - Resources res = context.getResources(); + RecentsTaskLoader.initialize(context); + mInflater = LayoutInflater.from(context); mContext = context; mSystemServicesProxy = new SystemServicesProxy(context); mHandler = new Handler(); - mConfig = RecentsConfiguration.reinitialize(context, mSystemServicesProxy); - mWindowRect = mSystemServicesProxy.getWindowRect(); mTaskStackBounds = new Rect(); - mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); - mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); - mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight, - mNavBarWidth, mTaskStackBounds); - if (mConfig.isLandscape && mConfig.transposeRecentsLayoutWithOrientation) { - mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0); - } else { - mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight); - } } - public void onStart() { + public void onStart() {} + + public void onBootCompleted() { // Initialize some static datastructures TaskStackViewLayoutAlgorithm.initializeCurve(); + // Load the header bar layout reloadHeaderBarLayout(); - } - - public void onBootCompleted() { mBootCompleted = true; } @@ -176,8 +164,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } void showRelativeAffiliatedTask(boolean showNextTask) { - TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy, - Integer.MAX_VALUE, mContext.getResources()); + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(), + -1, -1, false, null, null); // Return early if there are no tasks if (stack.getTaskCount() == 0) return; @@ -235,9 +224,19 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } public void onConfigurationChanged(Configuration newConfig) { + reloadHeaderBarLayout(); + sLastScreenshot = null; + } + + /** Prepares the header bar layout. */ + void reloadHeaderBarLayout() { + Resources res = mContext.getResources(); + mWindowRect = mSystemServicesProxy.getWindowRect(); + mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); + mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy); mConfig.updateOnConfigurationChange(); - mWindowRect = mSystemServicesProxy.getWindowRect(); mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight, mNavBarWidth, mTaskStackBounds); if (mConfig.isLandscape && mConfig.transposeRecentsLayoutWithOrientation) { @@ -245,14 +244,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } else { mSystemInsets.set(0, mStatusBarHeight, 0, mNavBarHeight); } - sLastScreenshot = null; - reloadHeaderBarLayout(); - } - /** Prepares the header bar layout. */ - void reloadHeaderBarLayout() { // Inflate the header bar layout so that we can rebind and draw it for the transition - Resources res = mContext.getResources(); TaskStack stack = new TaskStack(); mDummyStackView = new TaskStackView(mContext, stack); TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); @@ -261,8 +254,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds); Rect taskViewSize = algo.getUntransformedTaskViewSize(); int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height); - LayoutInflater inflater = LayoutInflater.from(mContext); - mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header, null, + mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null, false); mHeaderBar.measure( View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY), @@ -385,16 +377,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta toTask); if (toTransform != null && toTask.key != null) { Rect toTaskRect = toTransform.rect; - ActivityInfo info = mSystemServicesProxy.getActivityInfo( - toTask.key.baseIntent.getComponent(), toTask.key.userId); - if (toTask.activityIcon == null) { - toTask.activityIcon = mSystemServicesProxy.getActivityIcon(info, - toTask.key.userId); - } - if (toTask.activityLabel == null) { - toTask.activityLabel = mSystemServicesProxy.getActivityLabel(info); - } + // XXX: Reduce the memory usage the to the task bar height Bitmap thumbnail = Bitmap.createBitmap(toTaskRect.width(), toTaskRect.height(), Bitmap.Config.ARGB_8888); if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) { @@ -420,16 +404,13 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome, Task runningTaskOut) { // Get the stack of tasks that we are animating into - TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy, -1, - mContext.getResources()); + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(), + runningTaskId, -1, false, null, null); if (stack.getTaskCount() == 0) { return null; } - // Get the stack - mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); - mDummyStackView.getScroller().setStackScrollToInitialState(); - // Find the running task in the TaskStack Task task = null; ArrayList<Task> tasks = stack.getTasks(); @@ -451,6 +432,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } // Get the transform for the running task + mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); + mDummyStackView.getScroller().setStackScrollToInitialState(); mTmpTransform = mDummyStackView.getStackAlgorithm().getStackTransform(task, mDummyStackView.getScroller().getStackScroll(), mTmpTransform, null); return mTmpTransform; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index a5b845d..2f9715f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -343,7 +343,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView super.onCreate(savedInstanceState); // Initialize the loader and the configuration - RecentsTaskLoader.initialize(this); mConfig = RecentsConfiguration.reinitialize(this, RecentsTaskLoader.getInstance().getSystemServicesProxy()); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 9803687..5d8181c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -67,9 +67,11 @@ public class RecentsConfiguration { public int searchBarSpaceHeightPx; /** Task stack */ + public int taskStackScrollDuration; public int taskStackMaxDim; public int taskStackTopPaddingPx; public float taskStackWidthPaddingPct; + public float taskStackOverscrollPct; /** Task view animation and styles */ public int taskViewEnterFromHomeDuration; @@ -195,9 +197,14 @@ public class RecentsConfiguration { searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1); // Task stack + taskStackScrollDuration = + res.getInteger(R.integer.recents_animate_task_stack_scroll_duration); TypedValue widthPaddingPctValue = new TypedValue(); res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true); taskStackWidthPaddingPct = widthPaddingPctValue.getFloat(); + TypedValue stackOverscrollPctValue = new TypedValue(); + res.getValue(R.dimen.recents_stack_overscroll_percentage, stackOverscrollPctValue, true); + taskStackOverscrollPct = stackOverscrollPctValue.getFloat(); taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim); taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java index 31011ae..60e89bf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java @@ -16,7 +16,6 @@ package com.android.systemui.recents.model; -import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.os.Looper; @@ -36,7 +35,7 @@ public class RecentsPackageMonitor extends PackageMonitor { } PackageCallbacks mCb; - List<ActivityManager.RecentTaskInfo> mTasks; + List<Task.TaskKey> mTasks; SystemServicesProxy mSystemServicesProxy; /** Registers the broadcast receivers with the specified callbacks. */ @@ -64,7 +63,7 @@ public class RecentsPackageMonitor extends PackageMonitor { } /** Sets the list of tasks to match against package broadcast changes. */ - void setTasks(List<ActivityManager.RecentTaskInfo> tasks) { + void setTasks(List<Task.TaskKey> tasks) { mTasks = tasks; } @@ -75,7 +74,7 @@ public class RecentsPackageMonitor extends PackageMonitor { // Identify all the tasks that should be removed as a result of the package being removed. // Using a set to ensure that we callback once per unique component. HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>(); - for (ActivityManager.RecentTaskInfo t : mTasks) { + for (Task.TaskKey t : mTasks) { ComponentName cn = t.baseIntent.getComponent(); if (cn.getPackageName().equals(packageName)) { componentsToRemove.add(cn); @@ -99,7 +98,7 @@ public class RecentsPackageMonitor extends PackageMonitor { // Using a set to ensure that we callback once per unique component. HashSet<ComponentName> componentsKnownToExist = new HashSet<ComponentName>(); HashSet<ComponentName> componentsToRemove = new HashSet<ComponentName>(); - for (ActivityManager.RecentTaskInfo t : mTasks) { + for (Task.TaskKey t : mTasks) { ComponentName cn = t.baseIntent.getComponent(); if (cn.getPackageName().equals(packageName)) { if (componentsKnownToExist.contains(cn)) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index b93c126..f7ad35b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -18,7 +18,6 @@ package com.android.systemui.recents.model; import android.app.ActivityManager; import android.content.ComponentCallbacks2; -import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; @@ -28,6 +27,8 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.HandlerThread; import android.os.UserHandle; +import android.util.Log; + import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -35,11 +36,16 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.HashMap; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; +/** Handle to an ActivityInfo */ +class ActivityInfoHandle { + ActivityInfo info; +} + /** A bitmap load queue */ class TaskResourceLoadQueue { ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>(); @@ -227,10 +233,11 @@ class TaskResourceLoader implements Runnable { /* Recents task loader * NOTE: We should not hold any references to a Context from a static instance */ public class RecentsTaskLoader { + private static final String TAG = "RecentsTaskLoader"; + static RecentsTaskLoader sInstance; SystemServicesProxy mSystemServicesProxy; - DrawableLruCache mTaskDescriptionIconCache; DrawableLruCache mApplicationIconCache; BitmapLruCache mThumbnailCache; StringLruCache mActivityLabelCache; @@ -274,7 +281,6 @@ public class RecentsTaskLoader { mSystemServicesProxy = new SystemServicesProxy(context); mPackageMonitor = new RecentsPackageMonitor(); mLoadQueue = new TaskResourceLoadQueue(); - mTaskDescriptionIconCache = new DrawableLruCache(iconCacheSize); mApplicationIconCache = new DrawableLruCache(iconCacheSize); mThumbnailCache = new BitmapLruCache(thumbnailCacheSize); mActivityLabelCache = new StringLruCache(100); @@ -301,103 +307,162 @@ public class RecentsTaskLoader { } /** Gets the list of recent tasks, ordered from back to front. */ - private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp, - int numTasks) { - // Set a default number of tasks to query if none is provided - if (numTasks < 0) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - numTasks = config.maxNumTasksToLoad; - } + private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp) { + RecentsConfiguration config = RecentsConfiguration.getInstance(); List<ActivityManager.RecentTaskInfo> tasks = - ssp.getRecentTasks(numTasks, UserHandle.CURRENT.getIdentifier()); + ssp.getRecentTasks(config.maxNumTasksToLoad, + UserHandle.CURRENT.getIdentifier()); Collections.reverse(tasks); return tasks; } + /** Returns the activity icon using as many cached values as we can. */ + public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, + ActivityManager.TaskDescription td, SystemServicesProxy ssp, + Resources res, ActivityInfoHandle infoHandle, boolean preloadTask) { + // Return the cached activity icon if it exists + Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey); + if (icon != null) { + return icon; + } + // Return the task description icon if it exists + if (td != null && td.getIcon() != null) { + icon = ssp.getBadgedIcon(new BitmapDrawable(res, td.getIcon()), taskKey.userId); + mApplicationIconCache.put(taskKey, icon); + return icon; + } + // If we are preloading this task, continue to load the activity icon + if (preloadTask) { + // All short paths failed, load the icon from the activity info and cache it + if (infoHandle.info == null) { + infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(), + taskKey.userId); + } + if (infoHandle.info != null) { + icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId); + if (icon != null) { + mApplicationIconCache.put(taskKey, icon); + return icon; + } + } + } + // If we couldn't load any icon, return null + return null; + } + + /** Returns the activity label using as many cached values as we can. */ + public String getAndUpdateActivityLabel(Task.TaskKey taskKey, + ActivityManager.TaskDescription td, SystemServicesProxy ssp, + ActivityInfoHandle infoHandle) { + // Return the task description label if it exists + if (td != null && td.getLabel() != null) { + return td.getLabel(); + } + // Return the cached activity label if it exists + String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey); + if (label != null) { + return label; + } + // All short paths failed, load the label from the activity info and cache it + if (infoHandle.info == null) { + infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(), + taskKey.userId); + } + if (infoHandle.info != null) { + label = ssp.getActivityLabel(infoHandle.info); + mActivityLabelCache.put(taskKey, label); + } else { + Log.w(TAG, "Missing ActivityInfo for " + taskKey.baseIntent.getComponent() + + " u=" + taskKey.userId); + } + return label; + } + + /** Returns the activity's primary color. */ + public int getActivityPrimaryColor(ActivityManager.TaskDescription td, + RecentsConfiguration config) { + if (td != null && td.getPrimaryColor() != 0) { + return td.getPrimaryColor(); + } + return config.taskBarViewDefaultBackgroundColor; + } + /** Reload the set of recent tasks */ public SpaceNode reload(Context context, int preloadCount) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - Resources res = context.getResources(); - LinkedHashSet<Task> tasksToLoad = new LinkedHashSet<Task>(); - ArrayList<Task> tasksToAdd = new ArrayList<Task>(); - TaskStack stack = new TaskStack(); + ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>(); + ArrayList<Task> tasksToLoad = new ArrayList<Task>(); + TaskStack stack = getTaskStack(mSystemServicesProxy, context.getResources(), + -1, preloadCount, true, taskKeys, tasksToLoad); SpaceNode root = new SpaceNode(); root.setStack(stack); - // Get the recent tasks - SystemServicesProxy ssp = mSystemServicesProxy; - List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp, -1); + // Start the task loader and add all the tasks we need to load + mLoader.start(context); + mLoadQueue.addTasks(tasksToLoad); + + // Update the package monitor with the list of packages to listen for + mPackageMonitor.setTasks(taskKeys); + + return root; + } + + /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */ + public TaskStack getTaskStack(SystemServicesProxy ssp, Resources res, + int preloadTaskId, int preloadTaskCount, + boolean loadTaskThumbnails, List<Task.TaskKey> taskKeysOut, + List<Task> tasksToLoadOut) { + RecentsConfiguration config = RecentsConfiguration.getInstance(); + List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp); + HashMap<Task.ComponentNameKey, ActivityInfoHandle> activityInfoCache = + new HashMap<Task.ComponentNameKey, ActivityInfoHandle>(); + ArrayList<Task> tasksToAdd = new ArrayList<Task>(); + TaskStack stack = new TaskStack(); - // From back to front, add each task to the task stack int taskCount = tasks.size(); for (int i = 0; i < taskCount; i++) { ActivityManager.RecentTaskInfo t = tasks.get(i); + + // Compose the task key Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId, t.firstActiveTime, t.lastActiveTime); - ComponentName cn = t.baseIntent.getComponent(); - ActivityInfo info = null; - - ActivityManager.TaskDescription av = t.taskDescription; - String activityLabel = null; - Drawable activityIcon = mDefaultApplicationIcon; - int activityColor = config.taskBarViewDefaultBackgroundColor; - boolean loadedActivityIcon = false; - if (av != null) { - activityLabel = av.getLabel(); - activityIcon = mTaskDescriptionIconCache.getAndInvalidateIfModified(taskKey); - if (activityIcon == null) { - activityIcon = (av.getIcon() != null) ? - ssp.getBadgedIcon(new BitmapDrawable(res, av.getIcon()), t.userId) : null; - if (activityIcon != null) { - mTaskDescriptionIconCache.put(taskKey, activityIcon); - } - } - if (av.getPrimaryColor() != 0) { - activityColor = av.getPrimaryColor(); - } - loadedActivityIcon = (activityIcon != null); + + // Get an existing activity info handle if possible + Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); + ActivityInfoHandle infoHandle; + boolean hasCachedActivityInfo = false; + if (activityInfoCache.containsKey(cnKey)) { + infoHandle = activityInfoCache.get(cnKey); + hasCachedActivityInfo = true; + } else { + infoHandle = new ActivityInfoHandle(); } - // If there is no activity label, then try and read it from the label cache before - // loading it from the system - if (activityLabel == null) { - activityLabel = mActivityLabelCache.getAndInvalidateIfModified(taskKey); - if (activityLabel == null) { - if (info == null) { - info = ssp.getActivityInfo(cn, t.userId); - } - activityLabel = ssp.getActivityLabel(info); - mActivityLabelCache.put(taskKey, activityLabel); - } + + // Determine whether to preload this task + boolean preloadTask = false; + if (preloadTaskId > 0) { + preloadTask = (t.id == preloadTaskId); + } else if (preloadTaskCount > 0) { + preloadTask = (i >= (taskCount - preloadTaskCount)); } - // Create a new task + // Load the label, icon, and color + String activityLabel = getAndUpdateActivityLabel(taskKey, t.taskDescription, + ssp, infoHandle); + Drawable activityIcon = getAndUpdateActivityIcon(taskKey, t.taskDescription, + ssp, res, infoHandle, preloadTask); + int activityColor = getActivityPrimaryColor(t.taskDescription, config); + + // Update the activity info cache + if (!hasCachedActivityInfo && infoHandle.info != null) { + activityInfoCache.put(cnKey, infoHandle); + } + + // Add the task to the stack Task task = new Task(taskKey, (t.id > -1), t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, activityIcon, activityColor, (i == (taskCount - 1)), config.lockToAppEnabled); - // Preload the specified number of apps - if (i >= (taskCount - preloadCount)) { - // Load the icon from the cache if possible (only if we don't have an activity icon) - if (!loadedActivityIcon) { - task.applicationIcon = - mApplicationIconCache.getAndInvalidateIfModified(taskKey); - if (task.applicationIcon == null) { - // Load the icon from the system - if (info == null) { - info = ssp.getActivityInfo(cn, t.userId); - } - task.applicationIcon = ssp.getActivityIcon(info, taskKey.userId); - if (task.applicationIcon != null) { - mApplicationIconCache.put(taskKey, task.applicationIcon); - } - } - if (task.applicationIcon == null) { - // Either the task has changed since the last active time, or it was not - // previously cached, so try and load the task anew. - tasksToLoad.add(task); - } - } - + if (preloadTask && loadTaskThumbnails) { // Load the thumbnail from the cache if possible task.thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey); if (task.thumbnail == null) { @@ -408,54 +473,20 @@ public class RecentsTaskLoader { mThumbnailCache.put(taskKey, task.thumbnail); } } - if (task.thumbnail == null) { + if (task.thumbnail == null && tasksToLoadOut != null) { // Either the task has changed since the last active time, or it was not // previously cached, so try and load the task anew. - tasksToLoad.add(task); + tasksToLoadOut.add(task); } } + // Add to the list of task keys + if (taskKeysOut != null) { + taskKeysOut.add(taskKey); + } // Add the task to the stack tasksToAdd.add(task); } - - // Simulate the groupings that we describe - stack.setTasks(tasksToAdd); - stack.createAffiliatedGroupings(config); - - // Start the task loader and add all the tasks we need to load - mLoader.start(context); - mLoadQueue.addTasks(tasksToLoad); - - // Update the package monitor with the list of packages to listen for - mPackageMonitor.setTasks(tasks); - - return root; - } - - /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */ - public static TaskStack getShallowTaskStack(SystemServicesProxy ssp, int numTasks, - Resources resources) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp, numTasks); - ArrayList<Task> tasksToAdd = new ArrayList<Task>(); - TaskStack stack = new TaskStack(); - - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = tasks.get(i); - ActivityManager.TaskDescription av = t.taskDescription; - - BitmapDrawable icon = null; - if (av.getIcon() != null) { - icon = new BitmapDrawable(resources, av.getIcon()); - } - Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId, - t.firstActiveTime, t.lastActiveTime); - tasksToAdd.add(new Task(taskKey, true, t.affiliatedTaskId, t.affiliatedTaskColor, - av.getLabel(), icon, av.getPrimaryColor(), (i == (taskCount - 1)), - config.lockToAppEnabled)); - } stack.setTasks(tasksToAdd); stack.createAffiliatedGroupings(config); return stack; @@ -525,21 +556,18 @@ public class RecentsTaskLoader { // We are leaving recents, so trim the data a bit mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2); mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2); - mTaskDescriptionIconCache.trimToSize(mMaxIconCacheSize / 2); break; case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: case ComponentCallbacks2.TRIM_MEMORY_MODERATE: // We are going to be low on memory mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4); mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4); - mTaskDescriptionIconCache.trimToSize(mMaxIconCacheSize / 4); break; case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: // We are low on memory, so release everything mThumbnailCache.evictAll(); mApplicationIconCache.evictAll(); - mTaskDescriptionIconCache.evictAll(); // The cache is small, only clear the label cache when we are critical mActivityLabelCache.evictAll(); break; diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 977db60..406e03f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -16,12 +16,15 @@ package com.android.systemui.recents.model; +import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; import com.android.systemui.recents.misc.Utilities; +import java.util.Objects; + /** * A task represents the top most task in the system's task stack. @@ -35,8 +38,35 @@ public class Task { public void onTaskDataUnloaded(); } + /** The ComponentNameKey represents the unique primary key for a component + * belonging to a specified user. */ + public static class ComponentNameKey { + final ComponentName component; + final int userId; + + public ComponentNameKey(ComponentName cn, int user) { + component = cn; + userId = user; + } + + @Override + public int hashCode() { + return Objects.hash(component, userId); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ComponentNameKey)) { + return false; + } + return component.equals(((ComponentNameKey) o).component) && + userId == ((ComponentNameKey) o).userId; + } + } + /* The Task Key represents the unique primary key for the task */ public static class TaskKey { + final ComponentNameKey mComponentNameKey; public final int id; public final Intent baseIntent; public final int userId; @@ -44,6 +74,7 @@ public class Task { public long lastActiveTime; public TaskKey(int id, Intent intent, int userId, long firstActiveTime, long lastActiveTime) { + mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId); this.id = id; this.baseIntent = intent; this.userId = userId; @@ -51,6 +82,11 @@ public class Task { this.lastActiveTime = lastActiveTime; } + /** Returns the component name key for this task. */ + public ComponentNameKey getComponentNameKey() { + return mComponentNameKey; + } + @Override public boolean equals(Object o) { if (!(o instanceof TaskKey)) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java index 34f73c6..d6889d0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java @@ -33,6 +33,8 @@ public class AnimateableViewBounds extends ViewOutlineProvider { Rect mClipRect = new Rect(); Rect mOutlineClipRect = new Rect(); int mCornerRadius; + float mAlpha = 1f; + final float mMinAlpha = 0.25f; ObjectAnimator mClipTopAnimator; ObjectAnimator mClipRightAnimator; @@ -51,6 +53,7 @@ public class AnimateableViewBounds extends ViewOutlineProvider { @Override public void getOutline(View view, Outline outline) { + outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha)); outline.setRoundRect(Math.max(mClipRect.left, mOutlineClipRect.left), Math.max(mClipRect.top, mOutlineClipRect.top), mSourceView.getMeasuredWidth() - Math.max(mClipRect.right, mOutlineClipRect.right), @@ -58,6 +61,14 @@ public class AnimateableViewBounds extends ViewOutlineProvider { mCornerRadius); } + /** Sets the view outline alpha. */ + void setAlpha(float alpha) { + if (Float.compare(alpha, mAlpha) != 0) { + mAlpha = alpha; + mSourceView.invalidateOutline(); + } + } + /** Animates the top clip. */ void animateClipTop(int top, int duration, ValueAnimator.AnimatorUpdateListener updateListener) { if (mClipTopAnimator != null) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 47fda5b..1ac3bc3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -477,10 +477,10 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV if (!task.group.isFrontMostTask(task)) { // For affiliated tasks that are behind other tasks, we must animate the front cards // out of view before starting the task transition - stackView.startLaunchTaskAnimation(tv, launchRunnable); + stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask); } else { // Otherwise, we can start the task transition immediately - stackView.startLaunchTaskAnimation(tv, null); + stackView.startLaunchTaskAnimation(tv, null, lockToTask); postDelayed(launchRunnable, 17); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java index e0298ab..fa44551 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java @@ -28,6 +28,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.animation.LinearInterpolator; +import com.android.systemui.recents.RecentsConfiguration; /** * This class facilitates swipe to dismiss. It defines an interface to be implemented by the @@ -50,7 +51,7 @@ public class SwipeHelper { private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms private int MAX_DISMISS_VELOCITY = 2000; // dp/sec - private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms + private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width // where fade starts @@ -265,6 +266,7 @@ public class SwipeHelper { ValueAnimator anim = createTranslationAnimation(view, 0); int duration = SNAP_ANIM_LEN; anim.setDuration(duration); + anim.setInterpolator(RecentsConfiguration.getInstance().linearOutSlowInInterpolator); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 861011f..dbed136 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -105,7 +105,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool); mStackScroller = new TaskStackViewScroller(context, mConfig, mLayoutAlgorithm); mStackScroller.setCallbacks(this); - mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller); + mTouchHandler = new TaskStackViewTouchHandler(context, this, mConfig, mStackScroller); mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() { @Override public void run() { @@ -647,17 +647,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** Animates a task view in this stack as it launches. */ - public void startLaunchTaskAnimation(TaskView tv, final Runnable r) { + public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) { Task launchTargetTask = tv.getTask(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { TaskView t = (TaskView) getChildAt(i); if (t == tv) { - t.startLaunchTaskAnimation(r, true, true); + t.startLaunchTaskAnimation(r, true, true, lockToTask); } else { boolean occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(t.getTask(), launchTargetTask); - t.startLaunchTaskAnimation(null, false, occludesLaunchTarget); + t.startLaunchTaskAnimation(null, false, occludesLaunchTarget, lockToTask); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index 2c0dc44..5852b88 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -133,12 +133,8 @@ public class TaskStackViewScroller { stopBoundScrollAnimation(); mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll); - mScrollAnimator.setDuration(200); - // We would have to project the difference into the screen coords, and then use that as the - // duration -// mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll - -// curScroll, 250)); - mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator); + mScrollAnimator.setDuration(mConfig.taskStackScrollDuration); + mScrollAnimator.setInterpolator(mConfig.linearOutSlowInInterpolator); mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 374a27f..8f9b4c2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -23,11 +23,13 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.RecentsConfiguration; /* Handles touch events for a TaskStackView. */ class TaskStackViewTouchHandler implements SwipeHelper.Callback { static int INACTIVE_POINTER_ID = -1; + RecentsConfiguration mConfig; TaskStackView mSv; TaskStackViewScroller mScroller; VelocityTracker mVelocityTracker; @@ -52,7 +54,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { SwipeHelper mSwipeHelper; boolean mInterceptedBySwipeHelper; - public TaskStackViewTouchHandler(Context context, TaskStackView sv, TaskStackViewScroller scroller) { + public TaskStackViewTouchHandler(Context context, TaskStackView sv, + RecentsConfiguration config, TaskStackViewScroller scroller) { ViewConfiguration configuration = ViewConfiguration.get(context); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); @@ -60,6 +63,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); mSv = sv; mScroller = scroller; + mConfig = config; float densityScale = context.getResources().getDisplayMetrics().density; mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop); @@ -258,7 +262,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { if (Float.compare(overScrollAmount, 0f) != 0) { // Bound the overscroll to a fixed amount, and inversely scale the y-movement // relative to how close we are to the max overscroll - float maxOverScroll = 0.25f; + float maxOverScroll = mConfig.taskStackOverscrollPct; deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount) / maxOverScroll)); } @@ -280,7 +284,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int velocity = (int) velocityTracker.getYVelocity(mActivePointerId); if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { - // XXX: Should this be calculated as a percentage of a curve? int overscrollRange = (int) (Math.min(1f, Math.abs((float) velocity / mMaximumVelocity)) * Constants.Values.TaskStackView.TaskStackOverscrollRange); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index dfbcce1..eecc170 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -471,7 +471,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, /** Animates this task view as it exits recents */ void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask, - boolean occludesLaunchTarget) { + boolean occludesLaunchTarget, boolean lockToTask) { if (isLaunchingTask) { // Disable the thumbnail clip mThumbnailView.disableTaskBarClip(); @@ -487,11 +487,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } // Animate the action button away - float toScale = 0.9f; + if (!lockToTask) { + float toScale = 0.9f; + mActionButtonView.animate() + .scaleX(toScale) + .scaleY(toScale); + } mActionButtonView.animate() .alpha(0f) - .scaleX(toScale) - .scaleY(toScale) .setStartDelay(0) .setDuration(mConfig.taskBarExitAnimDuration) .setInterpolator(mConfig.fastOutLinearInInterpolator) @@ -621,6 +624,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, /** Sets the current task progress. */ public void setTaskProgress(float p) { mTaskProgress = p; + mViewBounds.setAlpha(p); updateDimFromTaskProgress(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index fef4a39..500bf45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -461,6 +461,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (mDrawingAppearAnimation) { startAppearAnimation(false /* isAppearing */, translationDirection, 0, duration, onFinishedRunnable); + } else if (onFinishedRunnable != null) { + onFinishedRunnable.run(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index df005a8..c8adf61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -276,7 +276,7 @@ public abstract class BaseStatusBar extends SystemUI implements // Wait for activity start. return handled; } - }); + }, false /* afterKeyguardGone */); return true; } else { return super.onClickHandler(view, pendingIntent, fillInIntent); @@ -526,8 +526,9 @@ public abstract class BaseStatusBar extends SystemUI implements /** * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. * @param action A dismiss action that is called if it's safe to start the activity. + * @param afterKeyguardGone Whether the action should be executed after the Keyguard is gone. */ - protected void dismissKeyguardThenExecute(OnDismissAction action) { + protected void dismissKeyguardThenExecute(OnDismissAction action, boolean afterKeyguardGone) { action.onDismiss(); } @@ -581,9 +582,11 @@ public abstract class BaseStatusBar extends SystemUI implements protected void applyColorsAndBackgrounds(StatusBarNotification sbn, NotificationData.Entry entry) { + PackageManager pmUser = getPackageManagerForUser( + entry.notification.getUser().getIdentifier()); int version = 0; try { - ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0); + ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); version = info.targetSdkVersion; } catch (NameNotFoundException ex) { Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); @@ -646,7 +649,7 @@ public abstract class BaseStatusBar extends SystemUI implements animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); return true; } - }); + }, false /* afterKeyguardGone */); } protected SwipeHelper.LongPressListener getNotificationLongClicker() { @@ -1231,7 +1234,7 @@ public abstract class BaseStatusBar extends SystemUI implements if (text != null) { text.setText(R.string.notification_hidden_text); text.setTextAppearance(mContext, - R.style.TextAppearance_StatusBar_Material_EventContent_Parenthetical); + R.style.TextAppearance_Material_Notification_Parenthetical); } int topPadding = Notification.Builder.calculateTopPadding(mContext, @@ -1339,7 +1342,7 @@ public abstract class BaseStatusBar extends SystemUI implements return mIntent != null && mIntent.isActivity(); } - }); + }, false /* afterKeyguardGone */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java index 4a6bfa10..5878ae1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java @@ -163,6 +163,9 @@ public class KeyguardAffordanceView extends ImageView { public void setPreviewView(View v) { mPreviewView = v; + if (mPreviewView != null) { + mPreviewView.setVisibility(INVISIBLE); + } } private void drawArrow(Canvas canvas) { @@ -295,7 +298,7 @@ public class KeyguardAffordanceView extends ImageView { duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION); animator.setDuration(duration); animator.start(); - if (mPreviewView != null) { + if (mPreviewView != null && mPreviewView.getVisibility() == View.VISIBLE) { mPreviewView.setVisibility(View.VISIBLE); mPreviewClipper = ViewAnimationUtils.createCircularReveal( mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index e6eca22..8b4c2c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -202,7 +202,7 @@ public class NotificationContentView extends FrameLayout { } public void setDark(boolean dark, boolean fade) { - if (mDark == dark) return; + if (mDark == dark || mContractedChild == null) return; mDark = dark; setImageViewDark(dark, fade, com.android.internal.R.id.right_icon); setImageViewDark(dark, fade, com.android.internal.R.id.icon); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index fb13126..9da209a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -45,6 +45,7 @@ public class SignalClusterView private boolean mIsAirplaneMode = false; private int mAirplaneIconId = 0; private String mWifiDescription, mMobileDescription, mMobileTypeDescription; + private boolean mRoaming; ViewGroup mWifiGroup, mMobileGroup; ImageView mWifi, mMobile, mMobileType, mAirplane; @@ -106,12 +107,13 @@ public class SignalClusterView @Override public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon, - String contentDescription, String typeContentDescription) { + String contentDescription, String typeContentDescription, boolean roaming) { mMobileVisible = visible; mMobileStrengthId = strengthIcon; mMobileTypeId = typeIcon; mMobileDescription = contentDescription; mMobileTypeDescription = typeContentDescription; + mRoaming = roaming; apply(); } @@ -207,8 +209,7 @@ public class SignalClusterView (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId)); - mMobileType.setVisibility( - !mWifiVisible ? View.VISIBLE : View.GONE); + mMobileType.setVisibility(!mWifiVisible || mRoaming ? View.VISIBLE : View.GONE); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java index 23810f9..9dfbb27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java @@ -24,5 +24,5 @@ import android.content.Intent; * Keyguard. */ public interface ActivityStarter { - public void startActivity(Intent intent, boolean dismissShade); + public void startActivity(Intent intent, boolean dismissShade, boolean afterKeyguardGone); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index c0b171a..f9da30f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -222,7 +222,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(), PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser()); - boolean visible = !isCameraDisabledByDpm() && resolved != null; + boolean visible = !isCameraDisabledByDpm() && resolved != null + && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance); mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } @@ -314,11 +315,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void launchCamera() { mFlashlightController.killFlashlight(); Intent intent = getCameraIntent(); - if (intent == SECURE_CAMERA_INTENT && - !mPreviewInflater.wouldLaunchResolverActivity(intent)) { + boolean wouldLaunchResolverActivity = mPreviewInflater.wouldLaunchResolverActivity(intent); + if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) { mContext.startActivityAsUser(intent, UserHandle.CURRENT); } else { - mActivityStarter.startActivity(intent, false /* dismissShade */); + + // We need to delay starting the activity because ResolverActivity finishes itself if + // launched behind lockscreen. + mActivityStarter.startActivity(intent, false /* dismissShade */, + wouldLaunchResolverActivity /* afterKeyguardGone */); } } @@ -332,7 +337,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } }); } else { - mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */); + mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */, + mPreviewInflater.wouldLaunchResolverActivity(PHONE_INTENT)); } } @@ -374,21 +380,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLockIcon.setImageResource(iconRes); boolean trustManaged = mUnlockMethodCache.isTrustManaged(); mTrustDrawable.setTrustManaged(trustManaged); - updateLockIconClickability(); - updateLockIconContentDescription(mUnlockMethodCache.isFaceUnlockRunning(), - mUnlockMethodCache.isMethodInsecure(), trustManaged); } - private void updateLockIconContentDescription(boolean faceUnlockRunning, boolean insecure, - boolean trustManaged) { - mLockIcon.setContentDescription(getResources().getString( - faceUnlockRunning ? R.string.accessibility_unlock_button_face_unlock_running - : insecure && !trustManaged ? R.string.accessibility_unlock_button_not_secured - : insecure ? R.string.accessibility_unlock_button_not_secured_trust_managed - : !trustManaged ? R.string.accessibility_unlock_button_secured - : R.string.accessibility_unlock_button_secured_trust_managed)); - } + public KeyguardAffordanceView getPhoneView() { return mPhoneImageView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 788d5ce..cd0fb6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -2039,8 +2039,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void startActivity(Intent intent, boolean dismissShade) { - startActivityDismissingKeyguard(intent, false, dismissShade); + public void startActivity(Intent intent, boolean dismissShade, boolean afterKeyguardGone) { + startActivityDismissingKeyguard(intent, false, dismissShade, afterKeyguardGone); } public ScrimController getScrimController() { @@ -2929,7 +2929,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, - final boolean dismissShade) { + final boolean dismissShade, final boolean afterKeyguardGone) { if (onlyProvisioned && !isDeviceProvisioned()) return; final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); @@ -2939,7 +2939,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, AsyncTask.execute(new Runnable() { public void run() { try { - if (keyguardShowing) { + if (keyguardShowing && !afterKeyguardGone) { ActivityManagerNative.getDefault() .keyguardWaitingForActivityDrawn(); } @@ -2947,7 +2947,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); mContext.startActivityAsUser( intent, new UserHandle(UserHandle.USER_CURRENT)); - overrideActivityPendingAppTransition(keyguardShowing); + overrideActivityPendingAppTransition( + keyguardShowing && !afterKeyguardGone); } catch (RemoteException e) { } } @@ -2957,7 +2958,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } return true; } - }); + }, afterKeyguardGone); } private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -3019,10 +3020,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - protected void dismissKeyguardThenExecute(final OnDismissAction action) { + protected void dismissKeyguardThenExecute(final OnDismissAction action, + boolean afterKeyguardGone) { if (mStatusBarKeyguardViewManager.isShowing()) { if (UnlockMethodCache.getInstance(mContext).isMethodInsecure() - && mNotificationPanel.isLaunchTransitionRunning()) { + && mNotificationPanel.isLaunchTransitionRunning() && !afterKeyguardGone) { action.onDismiss(); mNotificationPanel.setLaunchTransitionEndRunnable(new Runnable() { @Override @@ -3031,7 +3033,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }); } else { - mStatusBarKeyguardViewManager.dismissWithAction(action); + mStatusBarKeyguardViewManager.dismissWithAction(action, afterKeyguardGone); } } else { action.onDismiss(); @@ -3270,7 +3272,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void handleStartSettingsActivity(Intent intent, boolean onlyProvisioned) { - startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */); + startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */, + false /* afterKeyguardGone */); } private static class FastColorDrawable extends Drawable { @@ -3609,7 +3612,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } public boolean onSpacePressed() { - if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { + if (mScreenOn != null && mScreenOn + && (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) { animateCollapsePanels(0 /* flags */, true /* force */); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 729d459..2dc08d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -119,6 +119,7 @@ public class QSTileHost implements QSTile.Host { for (QSTile<?> tile : mTiles.values()) { tile.userSwitch(newUserId); } + mSecurity.onUserSwitched(newUserId); mObserver.register(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java index 938d888..b6792f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -20,11 +20,14 @@ import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.MathUtils; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; @@ -36,6 +39,7 @@ import android.widget.Switch; import android.widget.TextView; import com.android.keyguard.KeyguardStatusView; +import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QSTile; @@ -57,11 +61,11 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private View mDateGroup; private View mClock; private TextView mTime; - private View mAmPm; + private TextView mAmPm; private MultiUserSwitch mMultiUserSwitch; private ImageView mMultiUserAvatar; - private View mDateCollapsed; - private View mDateExpanded; + private TextView mDateCollapsed; + private TextView mDateExpanded; private LinearLayout mSystemIcons; private View mStatusIcons; private View mSignalCluster; @@ -70,7 +74,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private TextView mQsDetailHeaderTitle; private Switch mQsDetailHeaderSwitch; private ImageView mQsDetailHeaderProgress; - private View mEmergencyCallsOnly; + private TextView mEmergencyCallsOnly; private TextView mBatteryLevel; private TextView mAlarmStatus; @@ -129,11 +133,11 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mDateGroup = findViewById(R.id.date_group); mClock = findViewById(R.id.clock); mTime = (TextView) findViewById(R.id.time_view); - mAmPm = findViewById(R.id.am_pm_view); + mAmPm = (TextView) findViewById(R.id.am_pm_view); mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch); mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar); - mDateCollapsed = findViewById(R.id.date_collapsed); - mDateExpanded = findViewById(R.id.date_expanded); + mDateCollapsed = (TextView) findViewById(R.id.date_collapsed); + mDateExpanded = (TextView) findViewById(R.id.date_expanded); mSettingsButton = findViewById(R.id.settings_button); mSettingsButton.setOnClickListener(this); mQsDetailHeader = findViewById(R.id.qs_detail_header); @@ -141,7 +145,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title); mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle); mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress); - mEmergencyCallsOnly = findViewById(R.id.header_emergency_calls_only); + mEmergencyCallsOnly = (TextView) findViewById(R.id.header_emergency_calls_only); mBatteryLevel = (TextView) findViewById(R.id.battery_level); mAlarmStatus = (TextView) findViewById(R.id.alarm_status); mAlarmStatus.setOnClickListener(this); @@ -187,6 +191,39 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mAlarmStatus.setX(mDateGroup.getLeft() + mDateCollapsed.getRight()); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + FontSizeUtils.updateFontSize(mBatteryLevel, R.dimen.battery_level_text_size); + FontSizeUtils.updateFontSize(mEmergencyCallsOnly, + R.dimen.qs_emergency_calls_only_text_size); + FontSizeUtils.updateFontSize(mDateCollapsed, R.dimen.qs_date_collapsed_size); + FontSizeUtils.updateFontSize(mDateExpanded, R.dimen.qs_date_collapsed_size); + FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size); + FontSizeUtils.updateFontSize(this, android.R.id.title, R.dimen.qs_detail_header_text_size); + FontSizeUtils.updateFontSize(this, android.R.id.toggle, R.dimen.qs_detail_header_text_size); + FontSizeUtils.updateFontSize(mAmPm, R.dimen.qs_time_collapsed_size); + FontSizeUtils.updateFontSize(this, R.id.empty_time_view, R.dimen.qs_time_expanded_size); + + mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size); + mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size); + mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize; + + updateClockScale(); + updateClockCollapsedMargin(); + } + + private void updateClockCollapsedMargin() { + Resources res = getResources(); + int padding = res.getDimensionPixelSize(R.dimen.clock_collapsed_bottom_margin); + int largePadding = res.getDimensionPixelSize( + R.dimen.clock_collapsed_bottom_margin_large_text); + float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale, 1.0f, + FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f); + mClockMarginBottomCollapsed = Math.round((1 - largeFactor) * padding + largeFactor * largePadding); + requestLayout(); + } + private void requestCaptureValues() { mCaptureValues = true; requestLayout(); @@ -200,11 +237,9 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin); mMultiUserCollapsedMargin = getResources().getDimensionPixelSize(R.dimen.multi_user_switch_collapsed_margin); - mClockMarginBottomExpanded = getResources().getDimensionPixelSize(R.dimen.clock_expanded_bottom_margin); - mClockMarginBottomCollapsed = - getResources().getDimensionPixelSize(R.dimen.clock_collapsed_bottom_margin); + updateClockCollapsedMargin(); mMultiUserSwitchWidthCollapsed = getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_collapsed); mMultiUserSwitchWidthExpanded = @@ -489,19 +524,20 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL } else if (v == mAlarmStatus && mNextAlarm != null) { PendingIntent showIntent = mNextAlarm.getShowIntent(); if (showIntent != null && showIntent.isActivity()) { - mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */); + mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */, + false /* afterKeyguardGone */); } } } private void startSettingsActivity() { mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), - true /* dismissShade */); + true /* dismissShade */, false /* afterKeyguardGone */); } private void startBatteryActivity() { mActivityStarter.startActivity(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY), - true /* dismissShade */); + true /* dismissShade */, false /* afterKeyguardGone */); } public void setQSPanel(QSPanel qsp) { @@ -732,7 +768,9 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL final boolean showingDetail = detail != null; transition(mClock, !showingDetail); transition(mDateGroup, !showingDetail); - transition(mAlarmStatus, !showingDetail); + if (mAlarmShowing) { + transition(mAlarmStatus, !showingDetail); + } transition(mQsDetailHeader, showingDetail); mShowingDetail = showingDetail; if (showingDetail) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 3338f6a..b4e2d57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -65,6 +65,7 @@ public class StatusBarKeyguardViewManager { private boolean mLastOccluded; private boolean mLastBouncerShowing; private boolean mLastBouncerDismissible; + private OnDismissAction mAfterKeyguardGoneAction; public StatusBarKeyguardViewManager(Context context, ViewMediatorCallback callback, LockPatternUtils lockPatternUtils) { @@ -118,9 +119,14 @@ public class StatusBarKeyguardViewManager { updateStates(); } - public void dismissWithAction(OnDismissAction r) { + public void dismissWithAction(OnDismissAction r, boolean afterKeyguardGone) { if (mShowing) { - mBouncer.showWithDismissAction(r); + if (!afterKeyguardGone) { + mBouncer.showWithDismissAction(r); + } else { + mBouncer.show(); + mAfterKeyguardGoneAction = r; + } } updateStates(); } @@ -245,6 +251,7 @@ public class StatusBarKeyguardViewManager { mPhoneStatusBar.hideKeyguard(); mStatusBarWindowManager.setKeyguardFadingAway(false); mViewMediatorCallback.keyguardGone(); + executeAfterKeyguardGoneAction(); } }); } else { @@ -266,11 +273,19 @@ public class StatusBarKeyguardViewManager { mStatusBarWindowManager.setKeyguardShowing(false); mBouncer.hide(true /* destroyView */); mViewMediatorCallback.keyguardGone(); + executeAfterKeyguardGoneAction(); updateStates(); } } + private void executeAfterKeyguardGoneAction() { + if (mAfterKeyguardGoneAction != null) { + mAfterKeyguardGoneAction.onDismiss(); + mAfterKeyguardGoneAction = null; + } + } + /** * Dismisses the keyguard by going to the next screen or making it gone. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 15a7047..9b59814 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -64,6 +64,7 @@ public class NetworkControllerImpl extends BroadcastReceiver static final boolean CHATTY = false; // additional diagnostics, but not logspew private static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_signal_flightmode; + private static final int ROAMING_ICON = R.drawable.stat_sys_data_fully_connected_roam; // telephony boolean mHspaDataDistinguishable; @@ -164,7 +165,7 @@ public class NetworkControllerImpl extends BroadcastReceiver public interface SignalCluster { void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription); void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon, - String contentDescription, String typeContentDescription); + String contentDescription, String typeContentDescription, boolean roaming); void setIsAirplaneMode(boolean is, int airplaneIcon); } @@ -372,7 +373,8 @@ public class NetworkControllerImpl extends BroadcastReceiver mAlwaysShowCdmaRssi ? mPhoneSignalIconId : mWimaxIconId, mDataTypeIconId, mContentDescriptionWimax, - mContentDescriptionDataType); + mContentDescriptionDataType, + mDataTypeIconId == ROAMING_ICON); } else { // normal mobile data cluster.setMobileDataIndicators( @@ -380,7 +382,8 @@ public class NetworkControllerImpl extends BroadcastReceiver mShowPhoneRSSIForData ? mPhoneSignalIconId : mDataSignalIconId, mDataTypeIconId, mContentDescriptionPhoneSignal, - mContentDescriptionDataType); + mContentDescriptionDataType, + mDataTypeIconId == ROAMING_ICON); } cluster.setIsAirplaneMode(mAirplaneMode, mAirplaneIconId); } @@ -777,11 +780,11 @@ public class NetworkControllerImpl extends BroadcastReceiver if (isCdma()) { if (isCdmaEri()) { - mDataTypeIconId = R.drawable.stat_sys_data_fully_connected_roam; + mDataTypeIconId = ROAMING_ICON; mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition]; } } else if (mPhone.isNetworkRoaming()) { - mDataTypeIconId = R.drawable.stat_sys_data_fully_connected_roam; + mDataTypeIconId = ROAMING_ICON; mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition]; } } @@ -1195,11 +1198,11 @@ public class NetworkControllerImpl extends BroadcastReceiver mQSDataTypeIconId = 0; if (isCdma()) { if (isCdmaEri()) { - mDataTypeIconId = R.drawable.stat_sys_data_fully_connected_roam; + mDataTypeIconId = ROAMING_ICON; mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition]; } } else if (mPhone.isNetworkRoaming()) { - mDataTypeIconId = R.drawable.stat_sys_data_fully_connected_roam; + mDataTypeIconId = ROAMING_ICON; mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition]; } } @@ -1544,8 +1547,7 @@ public class NetworkControllerImpl extends BroadcastReceiver datatype.equals("g") ? R.drawable.stat_sys_data_fully_connected_g : datatype.equals("h") ? R.drawable.stat_sys_data_fully_connected_h : datatype.equals("lte") ? R.drawable.stat_sys_data_fully_connected_lte : - datatype.equals("roam") - ? R.drawable.stat_sys_data_fully_connected_roam : + datatype.equals("roam") ? ROAMING_ICON : 0; mDemoQSDataTypeIconId = datatype.equals("1x") ? R.drawable.ic_qs_signal_1x : @@ -1572,7 +1574,8 @@ public class NetworkControllerImpl extends BroadcastReceiver iconId, mDemoDataTypeIconId, "Demo", - "Demo"); + "Demo", + mDemoDataTypeIconId == ROAMING_ICON); } refreshViews(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java index 3a5a53b..6148feb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java @@ -18,18 +18,21 @@ package com.android.systemui.statusbar.policy; public interface SecurityController { boolean hasDeviceOwner(); + boolean hasProfileOwner(); String getDeviceOwnerName(); + String getProfileOwnerName(); boolean isVpnEnabled(); String getVpnApp(); boolean isLegacyVpn(); String getLegacyVpnName(); void disconnectFromVpn(); + void onUserSwitched(int newUserId); - void addCallback(VpnCallback callback); - void removeCallback(VpnCallback callback); + void addCallback(SecurityControllerCallback callback); + void removeCallback(SecurityControllerCallback callback); - public interface VpnCallback { - void onVpnStateChanged(); + public interface SecurityControllerCallback { + void onStateChanged(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 499fe0b..a15ddaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.policy; +import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; @@ -52,11 +53,13 @@ public class SecurityControllerImpl implements SecurityController { private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); private final DevicePolicyManager mDevicePolicyManager; - private final ArrayList<VpnCallback> mCallbacks = new ArrayList<VpnCallback>(); + private final ArrayList<SecurityControllerCallback> mCallbacks + = new ArrayList<SecurityControllerCallback>(); private VpnConfig mVpnConfig; private String mVpnName; private int mCurrentVpnNetworkId = NO_NETWORK; + private int mCurrentUserId; public SecurityControllerImpl(Context context) { mContext = context; @@ -67,6 +70,7 @@ public class SecurityControllerImpl implements SecurityController { // TODO: re-register network callback on user change. mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback); + mCurrentUserId = ActivityManager.getCurrentUser(); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -82,11 +86,22 @@ public class SecurityControllerImpl implements SecurityController { } @Override + public boolean hasProfileOwner() { + return !TextUtils.isEmpty(mDevicePolicyManager.getProfileOwnerNameAsUser(mCurrentUserId)); + } + + @Override public String getDeviceOwnerName() { return mDevicePolicyManager.getDeviceOwnerName(); } @Override + public String getProfileOwnerName() { + return mDevicePolicyManager.getProfileOwnerNameAsUser(mCurrentUserId); + } + + + @Override public boolean isVpnEnabled() { return mCurrentVpnNetworkId != NO_NETWORK; } @@ -124,19 +139,25 @@ public class SecurityControllerImpl implements SecurityController { } @Override - public void addCallback(VpnCallback callback) { + public void addCallback(SecurityControllerCallback callback) { if (callback == null) return; if (DEBUG) Log.d(TAG, "removeCallback " + callback); mCallbacks.remove(callback); } @Override - public void removeCallback(VpnCallback callback) { + public void removeCallback(SecurityControllerCallback callback) { if (callback == null || mCallbacks.contains(callback)) return; if (DEBUG) Log.d(TAG, "addCallback " + callback); mCallbacks.add(callback); } + @Override + public void onUserSwitched(int newUserId) { + mCurrentUserId = newUserId; + fireCallbacks(); + } + private void setCurrentNetid(int netId) { if (netId != mCurrentVpnNetworkId) { mCurrentVpnNetworkId = netId; @@ -146,8 +167,8 @@ public class SecurityControllerImpl implements SecurityController { } private void fireCallbacks() { - for (VpnCallback callback : mCallbacks) { - callback.onVpnStateChanged(); + for (SecurityControllerCallback callback : mCallbacks) { + callback.onStateChanged(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index afd7216..cd3c1a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -747,6 +747,7 @@ public class StackStateAnimator { } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { if (changingView.getVisibility() == View.GONE) { + mHostLayout.getOverlay().remove(changingView); continue; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java index f03c5eb..12c887e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java @@ -26,7 +26,6 @@ import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -46,7 +45,6 @@ import android.net.Uri; import android.os.Handler; import android.os.Message; import android.os.Vibrator; -import android.provider.Settings.Global; import android.util.Log; import android.util.SparseArray; import android.view.KeyEvent; @@ -58,6 +56,7 @@ import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.view.accessibility.AccessibilityManager; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; @@ -97,6 +96,7 @@ public class VolumePanel extends Handler { private static final int TIMEOUT_DELAY_SHORT = 1500; private static final int TIMEOUT_DELAY_COLLAPSED = 4500; private static final int TIMEOUT_DELAY_SAFETY_WARNING = 5000; + private static final int TIMEOUT_DELAY_SAFETY_WARNING_TALKBACK = 25000; private static final int TIMEOUT_DELAY_EXPANDED = 10000; private static final int MSG_VOLUME_CHANGED = 0; @@ -161,6 +161,7 @@ public class VolumePanel extends Handler { private int mActiveStreamType = -1; /** All the slider controls mapped by stream type */ private SparseArray<StreamControl> mStreamControls; + private final AccessibilityManager mAccessibilityManager; private enum StreamResources { BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO, @@ -332,6 +333,8 @@ public class VolumePanel extends Handler { mContext = context; mZenController = zenController; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + mAccessibilityManager = (AccessibilityManager) context.getSystemService( + Context.ACCESSIBILITY_SERVICE); // For now, only show master volume if master volume is supported final Resources res = context.getResources(); @@ -791,7 +794,8 @@ public class VolumePanel extends Handler { } private void updateTimeoutDelay() { - mTimeoutDelay = sSafetyWarning != null ? TIMEOUT_DELAY_SAFETY_WARNING + mTimeoutDelay = sSafetyWarning != null ? mAccessibilityManager.isEnabled() ? + TIMEOUT_DELAY_SAFETY_WARNING_TALKBACK : TIMEOUT_DELAY_SAFETY_WARNING : mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED @@ -1214,6 +1218,7 @@ public class VolumePanel extends Handler { } updateStates(); } + updateTimeoutDelay(); resetTimeout(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 0586a83..acb4827 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -140,7 +140,8 @@ public class VolumeUI extends SystemUI { @Override public void run() { getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard( - ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */); + ZenModePanel.ZEN_SETTINGS, true /* onlyProvisioned */, true /* dismissShade */, + false /* afterKeyguardGone */); mPanel.postDismiss(mDismissDelay); } }; diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index c99e1fd..ac7fc25 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -31,10 +31,9 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; -import android.text.format.DateFormat; -import android.text.format.Time; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.view.LayoutInflater; import android.view.View; import android.view.animation.AnimationUtils; @@ -49,10 +48,7 @@ import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.statusbar.policy.ZenModeController; -import java.text.SimpleDateFormat; import java.util.Arrays; -import java.util.Date; -import java.util.Locale; import java.util.Objects; public class ZenModePanel extends LinearLayout { @@ -79,10 +75,10 @@ public class ZenModePanel extends LinearLayout { private final Context mContext; private final LayoutInflater mInflater; private final H mHandler = new H(); - private final Favorites mFavorites; + private final Prefs mPrefs; private final Interpolator mFastOutSlowInInterpolator; - private final int mHardWarningColor; - private final int mSoftWarningColor; + private final int mSubheadWarningColor; + private final int mSubheadColor; private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this)); @@ -92,7 +88,6 @@ public class ZenModePanel extends LinearLayout { private TextView mZenSubheadExpanded; private View mMoreSettings; private LinearLayout mZenConditions; - private TextView mAlarmWarning; private Callback mCallback; private ZenModeController mController; @@ -103,21 +98,21 @@ public class ZenModePanel extends LinearLayout { private boolean mExpanded; private boolean mHidden = false; private int mSessionZen; + private int mAttachedZen; private Condition mSessionExitCondition; - private long mNextAlarm; private Condition[] mConditions; private Condition mTimeCondition; public ZenModePanel(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; - mFavorites = new Favorites(); + mPrefs = new Prefs(); mInflater = LayoutInflater.from(mContext.getApplicationContext()); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in); final Resources res = mContext.getResources(); - mHardWarningColor = res.getColor(R.color.system_warning_color); - mSoftWarningColor = res.getColor(R.color.qs_subhead); + mSubheadWarningColor = res.getColor(R.color.system_warning_color); + mSubheadColor = res.getColor(R.color.qs_subhead); if (DEBUG) Log.d(mTag, "new ZenModePanel"); } @@ -155,17 +150,16 @@ public class ZenModePanel extends LinearLayout { }); mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions); - mAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (DEBUG) Log.d(mTag, "onAttachedToWindow"); - mSessionZen = getSelectedZen(-1); + mAttachedZen = getSelectedZen(-1); + mSessionZen = mAttachedZen; mSessionExitCondition = copy(mExitCondition); refreshExitConditionText(); - refreshNextAlarm(); updateWidgets(); } @@ -173,6 +167,8 @@ public class ZenModePanel extends LinearLayout { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (DEBUG) Log.d(mTag, "onDetachedFromWindow"); + checkForAttachedZenChange(); + mAttachedZen = -1; mSessionZen = -1; mSessionExitCondition = null; setExpanded(false); @@ -184,6 +180,17 @@ public class ZenModePanel extends LinearLayout { updateWidgets(); } + private void checkForAttachedZenChange() { + final int selectedZen = getSelectedZen(-1); + if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen); + if (selectedZen != mAttachedZen) { + if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen); + if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) { + mPrefs.trackNoneSelected(); + } + } + } + private void setExpanded(boolean expanded) { if (expanded == mExpanded) return; mExpanded = expanded; @@ -234,10 +241,6 @@ public class ZenModePanel extends LinearLayout { updateWidgets(); } - private Uri getExitConditionId() { - return getConditionId(mExitCondition); - } - private static Uri getConditionId(Condition condition) { return condition != null ? condition.id : null; } @@ -301,9 +304,7 @@ public class ZenModePanel extends LinearLayout { final boolean zenOff = zen == Global.ZEN_MODE_OFF; final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS; - final boolean hasNextAlarm = mNextAlarm != 0; final boolean expanded = !mHidden && mExpanded; - final boolean showAlarmWarning = zenNone && expanded && hasNextAlarm; mZenButtons.setVisibility(mHidden ? GONE : VISIBLE); mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE); @@ -311,26 +312,6 @@ public class ZenModePanel extends LinearLayout { mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE); mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE); mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE); - mAlarmWarning.setVisibility(zenNone && expanded && hasNextAlarm ? VISIBLE : GONE); - if (showAlarmWarning) { - final long exitTime = ZenModeConfig.tryParseCountdownConditionId(getExitConditionId()); - final long now = System.currentTimeMillis(); - final boolean alarmToday = time(mNextAlarm).yearDay == time(now).yearDay; - final String skeleton = (alarmToday ? "" : "E") - + (DateFormat.is24HourFormat(mContext) ? "Hm" : "hma"); - final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); - final String alarm = new SimpleDateFormat(pattern).format(new Date(mNextAlarm)); - final boolean isWarning = exitTime > 0 && mNextAlarm > now && mNextAlarm < exitTime; - if (isWarning) { - mAlarmWarning.setText(mContext.getString(R.string.zen_alarm_warning, alarm)); - mAlarmWarning.setTextColor(mHardWarningColor); - } else { - mAlarmWarning.setText(mContext.getString(alarmToday - ? R.string.zen_alarm_information_time - : R.string.zen_alarm_information_day_time, alarm)); - mAlarmWarning.setTextColor(mSoftWarningColor); - } - } if (zenNone) { mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning); @@ -339,12 +320,8 @@ public class ZenModePanel extends LinearLayout { mZenSubheadExpanded.setText(R.string.zen_important_interruptions); mZenSubheadCollapsed.setText(mExitConditionText); } - } - - private static Time time(long millis) { - final Time t = new Time(); - t.set(millis); - return t; + mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous() + ? mSubheadWarningColor : mSubheadColor); } private Condition parseExistingTimeCondition(Condition condition) { @@ -373,15 +350,6 @@ public class ZenModePanel extends LinearLayout { Condition.FLAG_RELEVANT_NOW); } - private void refreshNextAlarm() { - mNextAlarm = mController.getNextAlarm(); - } - - private void handleNextAlarmChanged() { - refreshNextAlarm(); - updateWidgets(); - } - private void handleUpdateConditions(Condition[] conditions) { mConditions = conditions; handleUpdateConditions(); @@ -429,7 +397,7 @@ public class ZenModePanel extends LinearLayout { } } if (DEBUG) Log.d(mTag, "Selecting a default"); - final int favoriteIndex = mFavorites.getMinuteIndex(); + final int favoriteIndex = mPrefs.getMinuteIndex(); if (favoriteIndex == -1) { getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true); } else { @@ -579,9 +547,9 @@ public class ZenModePanel extends LinearLayout { } setExitCondition(condition); if (condition == null) { - mFavorites.setMinuteIndex(-1); + mPrefs.setMinuteIndex(-1); } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) { - mFavorites.setMinuteIndex(mBucketIndex); + mPrefs.setMinuteIndex(mBucketIndex); } mSessionExitCondition = copy(condition); } @@ -618,18 +586,12 @@ public class ZenModePanel extends LinearLayout { public void onExitConditionChanged(Condition exitCondition) { mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget(); } - - @Override - public void onNextAlarmChanged() { - mHandler.sendEmptyMessage(H.NEXT_ALARM_CHANGED); - } }; private final class H extends Handler { private static final int UPDATE_CONDITIONS = 1; private static final int EXIT_CONDITION_CHANGED = 2; private static final int UPDATE_ZEN = 3; - private static final int NEXT_ALARM_CHANGED = 4; private H() { super(Looper.getMainLooper()); @@ -643,8 +605,6 @@ public class ZenModePanel extends LinearLayout { handleExitConditionChanged((Condition) msg.obj); } else if (msg.what == UPDATE_ZEN) { handleUpdateZen(msg.arg1); - } else if (msg.what == NEXT_ALARM_CHANGED) { - handleNextAlarmChanged(); } } } @@ -661,14 +621,32 @@ public class ZenModePanel extends LinearLayout { Condition condition; } - private final class Favorites implements OnSharedPreferenceChangeListener { + private final class Prefs implements OnSharedPreferenceChangeListener { private static final String KEY_MINUTE_INDEX = "minuteIndex"; + private static final String KEY_NONE_SELECTED = "noneSelected"; + + private final int mNoneDangerousThreshold; private int mMinuteIndex; + private int mNoneSelected; - private Favorites() { + private Prefs() { + mNoneDangerousThreshold = mContext.getResources() + .getInteger(R.integer.zen_mode_alarm_warning_threshold); prefs().registerOnSharedPreferenceChangeListener(this); updateMinuteIndex(); + updateNoneSelected(); + } + + public boolean isNoneDangerous() { + return mNoneSelected < mNoneDangerousThreshold; + } + + public void trackNoneSelected() { + mNoneSelected = clampNoneSelected(mNoneSelected + 1); + if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold=" + + mNoneDangerousThreshold); + prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply(); } public int getMinuteIndex() { @@ -676,9 +654,9 @@ public class ZenModePanel extends LinearLayout { } public void setMinuteIndex(int minuteIndex) { - minuteIndex = clamp(minuteIndex); + minuteIndex = clampIndex(minuteIndex); if (minuteIndex == mMinuteIndex) return; - mMinuteIndex = clamp(minuteIndex); + mMinuteIndex = clampIndex(minuteIndex); if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex); prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply(); } @@ -686,6 +664,7 @@ public class ZenModePanel extends LinearLayout { @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { updateMinuteIndex(); + updateNoneSelected(); } private SharedPreferences prefs() { @@ -693,12 +672,21 @@ public class ZenModePanel extends LinearLayout { } private void updateMinuteIndex() { - mMinuteIndex = clamp(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX)); + mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX)); if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex); } - private int clamp(int index) { - return Math.max(-1, Math.min(MINUTE_BUCKETS.length - 1, index)); + private int clampIndex(int index) { + return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1); + } + + private void updateNoneSelected() { + mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0)); + if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected); + } + + private int clampNoneSelected(int noneSelected) { + return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE); } } diff --git a/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java b/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java index 71b0d53..6f79f58 100644 --- a/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java +++ b/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java @@ -83,6 +83,7 @@ public class EnableAccessibilityController { private final Context mContext; + private final Runnable mOnAccessibilityEnabledCallback; private final UserManager mUserManager; private final TextToSpeech mTts; private final Ringtone mTone; @@ -97,8 +98,9 @@ public class EnableAccessibilityController { private float mSecondPointerDownX; private float mSecondPointerDownY; - public EnableAccessibilityController(Context context) { + public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) { mContext = context; + mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback; mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() { @Override @@ -275,5 +277,7 @@ public class EnableAccessibilityController { /* ignore */ } } + + mOnAccessibilityEnabledCallback.run(); } } diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java index ae94654..41695c1 100644 --- a/policy/src/com/android/internal/policy/impl/GlobalActions.java +++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java @@ -1073,7 +1073,13 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac // is dismissed on the first down while the global gesture is a long press // with two fingers anywhere on the screen. if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) { - mEnableAccessibilityController = new EnableAccessibilityController(mContext); + mEnableAccessibilityController = new EnableAccessibilityController(mContext, + new Runnable() { + @Override + public void run() { + dismiss(); + } + }); super.setCanceledOnTouchOutside(false); } else { mEnableAccessibilityController = null; diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index e240127..93591a9 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -2754,11 +2754,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mStatusColorView = updateColorViewInt(mStatusColorView, SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS, mStatusBarColor, mLastTopInset, Gravity.TOP, - STATUS_BAR_BACKGROUND_TRANSITION_NAME); + STATUS_BAR_BACKGROUND_TRANSITION_NAME, + com.android.internal.R.id.statusBarBackground); mNavigationColorView = updateColorViewInt(mNavigationColorView, SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION, mNavigationBarColor, mLastBottomInset, Gravity.BOTTOM, - NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME); + NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME, + com.android.internal.R.id.navigationBarBackground); } if (insets != null) { insets = insets.consumeStableInsets(); @@ -2767,7 +2769,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } private View updateColorViewInt(View view, int systemUiHideFlag, int translucentFlag, - int color, int height, int verticalGravity, String transitionName) { + int color, int height, int verticalGravity, String transitionName, int id) { boolean show = height > 0 && (mLastSystemUiVisibility & systemUiHideFlag) == 0 && (getAttributes().flags & translucentFlag) == 0 && (color & Color.BLACK) != 0 @@ -2778,6 +2780,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { view = new View(mContext); view.setBackgroundColor(color); view.setTransitionName(transitionName); + view.setId(id); addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, height, Gravity.START | verticalGravity)); } @@ -3206,8 +3209,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { Context.WINDOW_SERVICE); if (windowService != null) { final Display display = windowService.getDefaultDisplay(); - if (display.getDisplayId() == Display.DEFAULT_DISPLAY && - a.hasValue(R.styleable.Window_windowOutsetBottom)) { + final boolean shouldUseBottomOutset = + display.getDisplayId() == Display.DEFAULT_DISPLAY + || (getForcedWindowFlags() & FLAG_FULLSCREEN) != 0; + if (shouldUseBottomOutset && a.hasValue(R.styleable.Window_windowOutsetBottom)) { if (mOutsetBottom == null) mOutsetBottom = new TypedValue(); a.getValue(R.styleable.Window_windowOutsetBottom, mOutsetBottom); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 5a836c8..e382a9f 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -545,6 +545,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_DISPATCH_SHOW_RECENTS = 9; private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10; private static final int MSG_HIDE_BOOT_MESSAGE = 11; + private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12; private class PolicyHandler extends Handler { @Override @@ -590,6 +591,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_HIDE_BOOT_MESSAGE: handleHideBootMessage(); break; + case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK: + launchVoiceAssistWithWakeLock(msg.arg1 != 0); + break; } } } @@ -2342,11 +2346,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) { if (!down) { Intent voiceIntent; - if (!keyguardOn && mPowerManager.isInteractive()) { + if (!keyguardOn) { voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); } else { voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); - voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, keyguardOn); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true); } mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF); } @@ -4445,6 +4449,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; } + case KeyEvent.KEYCODE_VOICE_ASSIST: { + // Only do this if we would otherwise not pass it to the user. In that case, + // interceptKeyBeforeDispatching would apply a similar but different policy in + // order to invoke voice assist actions. Note that we need to make a copy of the + // key event here because the original key event will be recycled when we return. + if ((result & ACTION_PASS_TO_USER) == 0 && !down) { + mBroadcastWakeLock.acquire(); + Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK, + keyguardActive ? 1 : 0, 0); + msg.setAsynchronous(true); + msg.sendToTarget(); + } + } } if (useHapticFeedback) { @@ -4551,6 +4568,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + void launchVoiceAssistWithWakeLock(boolean keyguardActive) { + Intent voiceIntent = + new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, keyguardActive); + mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF); + mBroadcastWakeLock.release(); + } + BroadcastReceiver mDockReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index ac0ca0a..af5c13d 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -708,6 +708,7 @@ class TouchExplorer implements EventStreamTransformation { // Send an event to the end of the drag gesture. sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } + mCurrentState = STATE_TOUCH_EXPLORING; } break; case MotionEvent.ACTION_UP: { mAms.onTouchInteractionEnd(); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index de5e7cb..3e7a7e4 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -8634,10 +8634,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF skip = true; } - if (!mAutoRestore || !mProvisioned) { + if (!mAutoRestore) { if (DEBUG) { - Slog.w(TAG, "Non-restorable state: auto=" + mAutoRestore - + " prov=" + mProvisioned); + Slog.w(TAG, "Non-restorable state: auto=" + mAutoRestore); } skip = true; } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 8bb094b..0919f77 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -185,7 +185,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final String TAG = "ConnectivityService"; private static final boolean DBG = true; - private static final boolean VDBG = true; // STOPSHIP + private static final boolean VDBG = false; // network sampling debugging private static final boolean SAMPLE_DBG = false; @@ -830,11 +830,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { // network is blocked; clone and override state info = new NetworkInfo(info); info.setDetailedState(DetailedState.BLOCKED, null, null); - if (VDBG) log("returning Blocked NetworkInfo"); + if (DBG) log("returning Blocked NetworkInfo"); } if (mLockdownTracker != null) { info = mLockdownTracker.augmentNetworkInfo(info); - if (VDBG) log("returning Locked NetworkInfo"); + if (DBG) log("returning Locked NetworkInfo"); } return info; } @@ -1202,7 +1202,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface); } } - if (VDBG) log("Adding " + bestRoute + " for interface " + bestRoute.getInterface()); + if (DBG) log("Adding " + bestRoute + " for interface " + bestRoute.getInterface()); try { mNetd.addLegacyRouteForNetId(netId, bestRoute, uid); } catch (Exception e) { @@ -1401,7 +1401,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mInitialBroadcast = new Intent(intent); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - if (VDBG) { + if (DBG) { log("sendStickyBroadcast: action=" + intent.getAction()); } @@ -1558,7 +1558,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } try { - if (VDBG) log("Setting MTU size: " + iface + ", " + mtu); + if (DBG) log("Setting MTU size: " + iface + ", " + mtu); mNetd.setMtu(iface, mtu); } catch (Exception e) { Slog.e(TAG, "exception in setMtu()" + e); @@ -1579,7 +1579,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (values == null || values.length != 6) { - if (VDBG) log("Invalid tcpBufferSizes string: " + tcpBufferSizes +", using defaults"); + if (DBG) log("Invalid tcpBufferSizes string: " + tcpBufferSizes +", using defaults"); tcpBufferSizes = DEFAULT_TCP_BUFFER_SIZES; values = tcpBufferSizes.split(","); } @@ -1587,7 +1587,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (tcpBufferSizes.equals(mCurrentTcpBufferSizes)) return; try { - if (VDBG) Slog.d(TAG, "Setting tx/rx TCP buffers to " + tcpBufferSizes); + if (DBG) Slog.d(TAG, "Setting tx/rx TCP buffers to " + tcpBufferSizes); final String prefix = "/sys/kernel/ipv4/tcp_"; FileUtils.stringToFile(prefix + "rmem_min", values[0]); @@ -1750,7 +1750,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (officialNai != null && officialNai.equals(nai)) return true; if (officialNai != null || VDBG) { - loge(msg + " - validateNetworkAgent found mismatched netId: " + officialNai + + loge(msg + " - isLiveNetworkAgent found mismatched netId: " + officialNai + " - " + nai); } return false; @@ -1794,7 +1794,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("NetworkAgent not found for EVENT_NETWORK_PROPERTIES_CHANGED"); } else { if (VDBG) { - log("Update of Linkproperties for " + nai.name() + + log("Update of LinkProperties for " + nai.name() + "; created=" + nai.created); } LinkProperties oldLp = nai.linkProperties; @@ -2073,11 +2073,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (int i = 0; i < nai.networkRequests.size(); i++) { NetworkRequest request = nai.networkRequests.valueAt(i); NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId); - if (VDBG) { - log(" checking request " + request + ", currentNetwork = " + - (currentNetwork != null ? currentNetwork.name() : "null")); - } if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) { + if (DBG) { + log("Checking for replacement network to handle request " + request ); + } mNetworkForRequestId.remove(request.requestId); sendUpdatedScoreToFactories(request, 0); NetworkAgentInfo alternative = null; @@ -2091,8 +2090,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { alternative = existing; } } - if (alternative != null && !toActivate.contains(alternative)) { - toActivate.add(alternative); + if (alternative != null) { + if (DBG) log(" found replacement in " + alternative.name()); + if (!toActivate.contains(alternative)) { + toActivate.add(alternative); + } } } } @@ -2115,9 +2117,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Check for the best currently alive network that satisfies this request NetworkAgentInfo bestNetwork = null; for (NetworkAgentInfo network : mNetworkAgentInfos.values()) { - if (VDBG) log("handleRegisterNetworkRequest checking " + network.name()); + if (DBG) log("handleRegisterNetworkRequest checking " + network.name()); if (newCap.satisfiedByNetworkCapabilities(network.networkCapabilities)) { - if (VDBG) log("apparently satisfied. currentScore=" + network.currentScore); + if (DBG) log("apparently satisfied. currentScore=" + network.currentScore); if ((bestNetwork == null) || bestNetwork.currentScore < network.currentScore) { if (!nri.isRequest) { // Not setting bestNetwork here as a listening NetworkRequest may be @@ -2132,7 +2134,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } if (bestNetwork != null) { - if (VDBG) log("using " + bestNetwork.name()); + if (DBG) log("using " + bestNetwork.name()); if (bestNetwork.networkInfo.isConnected()) { // Cancel any lingering so the linger timeout doesn't teardown this network // even though we have a request for it. @@ -2173,7 +2175,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { if (nai.networkRequests.get(nri.request.requestId) != null) { nai.networkRequests.remove(nri.request.requestId); - if (VDBG) { + if (DBG) { log(" Removing from current network " + nai.name() + ", leaving " + nai.networkRequests.size() + " requests."); @@ -3557,7 +3559,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Check for apps that can handle provisioning first Intent provisioningIntent = new Intent(TelephonyIntents.ACTION_CARRIER_SETUP); List<String> carrierPackages = - mTelephonyManager.getCarrierPackageNamesForBroadcastIntent(provisioningIntent); + mTelephonyManager.getCarrierPackageNamesForIntent(provisioningIntent); if (carrierPackages != null && !carrierPackages.isEmpty()) { if (carrierPackages.size() != 1) { if (DBG) log("Multiple matching carrier apps found, launching the first."); @@ -3920,7 +3922,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleNetworkSamplingTimeout() { - log("Sampling interval elapsed, updating statistics .."); + if (SAMPLE_DBG) log("Sampling interval elapsed, updating statistics .."); // initialize list of interfaces .. Map<String, SamplingDataTracker.SamplingSnapshot> mapIfaceToSample = @@ -3951,13 +3953,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - log("Done."); + if (SAMPLE_DBG) log("Done."); int samplingIntervalInSeconds = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS, DEFAULT_SAMPLING_INTERVAL_IN_SECONDS); - if (DBG) log("Setting timer for " + String.valueOf(samplingIntervalInSeconds) + "seconds"); + if (SAMPLE_DBG) { + log("Setting timer for " + String.valueOf(samplingIntervalInSeconds) + "seconds"); + } setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent); } @@ -4125,7 +4129,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) { - if (VDBG) log("Got NetworkFactory Messenger for " + nfi.name); + if (DBG) log("Got NetworkFactory Messenger for " + nfi.name); mNetworkFactoryInfos.put(nfi.messenger, nfi); nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger); } @@ -4139,10 +4143,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleUnregisterNetworkFactory(Messenger messenger) { NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger); if (nfi == null) { - if (VDBG) log("Failed to find Messenger in unregisterNetworkFactory"); + loge("Failed to find Messenger in unregisterNetworkFactory"); return; } - if (VDBG) log("unregisterNetworkFactory for " + nfi.name); + if (DBG) log("unregisterNetworkFactory for " + nfi.name); } /** @@ -4180,7 +4184,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized (this) { nai.networkMonitor.systemReady = mSystemReady; } - if (VDBG) log("registerNetworkAgent " + nai); + if (DBG) log("registerNetworkAgent " + nai); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai)); } @@ -4241,6 +4245,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } for (String iface : interfaceDiff.added) { try { + if (DBG) log("Adding iface " + iface + " to network " + netId); mNetd.addInterfaceToNetwork(iface, netId); } catch (Exception e) { loge("Exception adding interface: " + e); @@ -4248,6 +4253,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } for (String iface : interfaceDiff.removed) { try { + if (DBG) log("Removing iface " + iface + " from network " + netId); mNetd.removeInterfaceFromNetwork(iface, netId); } catch (Exception e) { loge("Exception removing interface: " + e); @@ -4272,22 +4278,29 @@ public class ConnectivityService extends IConnectivityManager.Stub { // do this twice, adding non-nexthop routes first, then routes they are dependent on for (RouteInfo route : routeDiff.added) { if (route.hasGateway()) continue; + if (DBG) log("Adding Route [" + route + "] to network " + netId); try { mNetd.addRoute(netId, route); } catch (Exception e) { - loge("Exception in addRoute for non-gateway: " + e); + if ((route.getDestination().getAddress() instanceof Inet4Address) || VDBG) { + loge("Exception in addRoute for non-gateway: " + e); + } } } for (RouteInfo route : routeDiff.added) { if (route.hasGateway() == false) continue; + if (DBG) log("Adding Route [" + route + "] to network " + netId); try { mNetd.addRoute(netId, route); } catch (Exception e) { - loge("Exception in addRoute for gateway: " + e); + if ((route.getGateway() instanceof Inet4Address) || VDBG) { + loge("Exception in addRoute for gateway: " + e); + } } } for (RouteInfo route : routeDiff.removed) { + if (DBG) log("Removing Route [" + route + "] from network " + netId); try { mNetd.removeRoute(netId, route); } catch (Exception e) { @@ -4306,6 +4319,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("no dns provided for netId " + netId + ", so using defaults"); } } + if (DBG) log("Setting Dns servers for network " + netId + " to " + dnses); try { mNetd.setDnsServersForNetwork(netId, NetworkUtils.makeStrings(dnses), newLp.getDomains()); @@ -4395,7 +4409,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { msg.obj = o; msg.what = notificationType; try { - if (VDBG) log("sending notification " + notificationType + " for " + nri.request); + if (VDBG) { + log("sending notification " + notifyTypeToName(notificationType) + + " for " + nri.request); + } nri.messenger.send(msg); } catch (RemoteException e) { // may occur naturally in the race of binder death. @@ -4418,7 +4435,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void makeDefault(NetworkAgentInfo newNetwork) { - if (VDBG) log("Switching to new default network: " + newNetwork); + if (DBG) log("Switching to new default network: " + newNetwork); mActiveDefaultNetwork = newNetwork.networkInfo.getType(); setupDataActivityTracking(newNetwork); try { @@ -4444,7 +4461,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (NetworkRequestInfo nri : mNetworkRequests.values()) { NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId); if (newNetwork == currentNetwork) { - if (VDBG) log("Network " + newNetwork.name() + " was already satisfying" + + if (DBG) log("Network " + newNetwork.name() + " was already satisfying" + " request " + nri.request.requestId + ". No change."); keep = true; continue; @@ -4468,12 +4485,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (currentNetwork == null || currentNetwork.currentScore < newNetwork.currentScore) { if (currentNetwork != null) { - if (VDBG) log(" accepting network in place of " + currentNetwork.name()); + if (DBG) log(" accepting network in place of " + currentNetwork.name()); currentNetwork.networkRequests.remove(nri.request.requestId); currentNetwork.networkLingered.add(nri.request); affectedNetworks.add(currentNetwork); } else { - if (VDBG) log(" accepting network in place of null"); + if (DBG) log(" accepting network in place of null"); } mNetworkForRequestId.put(nri.request.requestId, newNetwork); newNetwork.addRequest(nri.request); @@ -4574,7 +4591,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge(" " + newNetwork.networkRequests.valueAt(i)); } } - if (VDBG) log("Validated network turns out to be unwanted. Tear it down."); + if (DBG) log("Validated network turns out to be unwanted. Tear it down."); newNetwork.asyncChannel.disconnect(); } } @@ -4745,7 +4762,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) { - if (VDBG) log("notifyType " + notifyType + " for " + networkAgent.name()); + if (DBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name()); for (int i = 0; i < networkAgent.networkRequests.size(); i++) { NetworkRequest nr = networkAgent.networkRequests.valueAt(i); NetworkRequestInfo nri = mNetworkRequests.get(nr); @@ -4754,6 +4771,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + private String notifyTypeToName(int notifyType) { + switch (notifyType) { + case ConnectivityManager.CALLBACK_PRECHECK: return "PRECHECK"; + case ConnectivityManager.CALLBACK_AVAILABLE: return "AVAILABLE"; + case ConnectivityManager.CALLBACK_LOSING: return "LOSING"; + case ConnectivityManager.CALLBACK_LOST: return "LOST"; + case ConnectivityManager.CALLBACK_UNAVAIL: return "UNAVAILABLE"; + case ConnectivityManager.CALLBACK_CAP_CHANGED: return "CAP_CHANGED"; + case ConnectivityManager.CALLBACK_IP_CHANGED: return "IP_CHANGED"; + case ConnectivityManager.CALLBACK_RELEASED: return "RELEASED"; + } + return "UNKNOWN"; + } + private LinkProperties getLinkPropertiesForTypeInternal(int networkType) { NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); if (nai != null) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 972a86d..6310764 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1198,11 +1198,12 @@ public final class ActivityManagerService extends ActivityManagerNative */ private boolean mUserIsMonkey; - /** Flag whether the device has a recents UI */ - final boolean mHasRecents; + /** Flag whether the device has a Recents UI */ + boolean mHasRecents; - final int mThumbnailWidth; - final int mThumbnailHeight; + /** The dimensions of the thumbnails in the Recents UI. */ + int mThumbnailWidth; + int mThumbnailHeight; final ServiceThread mHandlerThread; final MainHandler mHandler; @@ -2257,11 +2258,6 @@ public final class ActivityManagerService extends ActivityManagerNative mConfigurationSeq = mConfiguration.seq = 1; mProcessCpuTracker.init(); - final Resources res = mContext.getResources(); - mHasRecents = res.getBoolean(com.android.internal.R.bool.config_hasRecents); - mThumbnailWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); - mThumbnailHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); - mCompatModePackages = new CompatModePackages(this, systemDir, mHandler); mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mStackSupervisor = new ActivityStackSupervisor(this); @@ -3816,6 +3812,11 @@ public final class ActivityManagerService extends ActivityManagerNative * of affiliations. */ void cleanupRecentTasksLocked(int userId) { + if (mRecentTasks == null) { + // Happens when called from the packagemanager broadcast before boot. + return; + } + final HashMap<ComponentName, ActivityInfo> availActCache = new HashMap<>(); final HashMap<String, ApplicationInfo> availAppCache = new HashMap<>(); final IPackageManager pm = AppGlobals.getPackageManager(); @@ -8947,6 +8948,14 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } + private void checkTime(long startTime, String where) { + long now = SystemClock.elapsedRealtime(); + if ((now-startTime) > 1000) { + // If we are taking more than a second, log about it. + Slog.w(TAG, "Slow operation: " + (now-startTime) + "ms so far, now at " + where); + } + } + private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, String name, IBinder token, boolean stable, int userId) { ContentProviderRecord cpr; @@ -8954,6 +8963,8 @@ public final class ActivityManagerService extends ActivityManagerNative ProviderInfo cpi = null; synchronized(this) { + long startTime = SystemClock.elapsedRealtime(); + ProcessRecord r = null; if (caller != null) { r = getRecordForAppLocked(caller); @@ -8967,6 +8978,8 @@ public final class ActivityManagerService extends ActivityManagerNative boolean checkCrossUser = true; + checkTime(startTime, "getContentProviderImpl: getProviderByName"); + // First check if this content provider has been published... cpr = mProviderMap.getProviderByName(name, userId); // If that didn't work, check if it exists for user 0 and then @@ -8991,10 +9004,12 @@ public final class ActivityManagerService extends ActivityManagerNative if (providerRunning) { cpi = cpr.info; String msg; + checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission"); if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser)) != null) { throw new SecurityException(msg); } + checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission"); if (r != null && cpr.canRunHere(r)) { // This provider has been published or is in the process @@ -9010,6 +9025,8 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); + checkTime(startTime, "getContentProviderImpl: incProviderCountLocked"); + // In this case the provider instance already exists, so we can // return it right away. conn = incProviderCountLocked(r, cpr, token, stable); @@ -9019,7 +9036,9 @@ public final class ActivityManagerService extends ActivityManagerNative // make sure to count it as being accessed and thus // back up on the LRU list. This is good because // content providers are often expensive to start. + checkTime(startTime, "getContentProviderImpl: before updateLruProcess"); updateLruProcessLocked(cpr.proc, false, null); + checkTime(startTime, "getContentProviderImpl: after updateLruProcess"); } } @@ -9032,7 +9051,9 @@ public final class ActivityManagerService extends ActivityManagerNative Process.killProcess(cpr.proc.pid); } } + checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); boolean success = updateOomAdjLocked(cpr.proc); + checkTime(startTime, "getContentProviderImpl: after updateOomAdj"); if (DEBUG_PROVIDER) Slog.i(TAG, "Adjust success: " + success); // NOTE: there is still a race here where a signal could be // pending on the process even though we managed to update its @@ -9047,7 +9068,9 @@ public final class ActivityManagerService extends ActivityManagerNative "Existing provider " + cpr.name.flattenToShortString() + " is crashing; detaching " + r); boolean lastRef = decProviderCountLocked(conn, cpr, token, stable); + checkTime(startTime, "getContentProviderImpl: before appDied"); appDiedLocked(cpr.proc); + checkTime(startTime, "getContentProviderImpl: after appDied"); if (!lastRef) { // This wasn't the last ref our process had on // the provider... we have now been killed, bail. @@ -10675,6 +10698,14 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** Loads resources after the current configuration has been set. */ + private void loadResourcesOnSystemReady() { + final Resources res = mContext.getResources(); + mHasRecents = res.getBoolean(com.android.internal.R.bool.config_hasRecents); + mThumbnailWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + mThumbnailHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); + } + public boolean testIsSystemReady() { // no need to synchronize(this) just to read & return the value return mSystemReady; @@ -10956,6 +10987,7 @@ public final class ActivityManagerService extends ActivityManagerNative } retrieveSettings(); + loadResourcesOnSystemReady(); synchronized (this) { readGrantedUriPermissionsLocked(); @@ -15203,6 +15235,10 @@ public final class ActivityManagerService extends ActivityManagerNative final int is24Hour = intent.getBooleanExtra( Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, false) ? 1 : 0; mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0)); + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + stats.noteCurrentTimeChangedLocked(); + } } if (Intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction())) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index d066940..3efd049 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -229,9 +229,6 @@ final class ActivityStack { private ActivityRecord mLastScreenshotActivity = null; private Bitmap mLastScreenshotBitmap = null; - int mThumbnailWidth = -1; - int mThumbnailHeight = -1; - int mCurrentUser; final int mStackId; @@ -355,10 +352,6 @@ final class ActivityStack { mWindowManager = mService.mWindowManager; mStackId = activityContainer.mStackId; mCurrentUser = mService.mCurrentUserId; - // Get the activity screenshot thumbnail dimensions - Resources res = mService.mContext.getResources(); - mThumbnailWidth = mService.mThumbnailWidth; - mThumbnailHeight = mService.mThumbnailHeight; } /** @@ -773,8 +766,8 @@ final class ActivityStack { return null; } - int w = mThumbnailWidth; - int h = mThumbnailHeight; + int w = mService.mThumbnailWidth; + int h = mService.mThumbnailHeight; if (w > 0) { if (who != mLastScreenshotActivity || mLastScreenshotBitmap == null || mLastScreenshotActivity.state == ActivityState.RESUMED diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 9b32b65..1d2f7a9 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1543,6 +1543,15 @@ public final class ActivityStackSupervisor implements DisplayListener { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; + // In some flows in to this function, we retrieve the task record and hold on to it + // without a lock before calling back in to here... so the task at this point may + // not actually be in recents. Check for that, and if it isn't in recents just + // consider it invalid. + if (inTask != null && !inTask.inRecents) { + Slog.w(TAG, "Starting activity in task not in recents: " + inTask); + inTask = null; + } + final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP; final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE; final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK; @@ -1686,32 +1695,50 @@ public final class ActivityStackSupervisor implements DisplayListener { // If the caller is not coming from another activity, but has given us an // explicit task into which they would like us to launch the new activity, // then let's see about doing that. - if (sourceRecord == null && inTask != null && inTask.stack != null && inTask.inRecents) { + if (sourceRecord == null && inTask != null && inTask.stack != null) { + final Intent baseIntent = inTask.getBaseIntent(); + final ActivityRecord root = inTask.getRootActivity(); + if (baseIntent == null) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Launching into task without base intent: " + + inTask); + } + // If this task is empty, then we are adding the first activity -- it // determines the root, and must be launching as a NEW_TASK. - if (inTask.getRootActivity() == null) { - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 - && !launchSingleInstance && !launchSingleTask) { - throw new IllegalStateException("Caller has inTask " + inTask - + " but target is not a new task"); - } else if (inTask.getBaseIntent() == null || !intent.getComponent().equals( - inTask.getBaseIntent().getComponent())) { - throw new IllegalStateException("Caller requested " + inTask + " is component " - + inTask.getBaseIntent() + " but starting " + intent); + if (launchSingleInstance || launchSingleTask) { + if (!baseIntent.getComponent().equals(r.intent.getComponent())) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Trying to launch singleInstance/Task " + + r + " into different task " + inTask); } + if (root != null) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Caller with inTask " + inTask + + " has root " + root + " but target is singleInstance/Task"); + } + } + + // If task is empty, then adopt the interesting intent launch flags in to the + // activity being started. + if (root == null) { + final int flagsOfInterest = Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT + | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; + launchFlags = (launchFlags&~flagsOfInterest) + | (baseIntent.getFlags()&flagsOfInterest); + intent.setFlags(launchFlags); inTask.setIntent(r); // If the task is not empty, then we are going to add the new activity on top // of the task, so it can not be launching as a new task. - } else { - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 - || launchSingleInstance || launchSingleTask) { - throw new IllegalStateException("Caller has inTask " + inTask - + " but target is a new task"); - } + } else if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + ActivityOptions.abort(options); + throw new IllegalStateException("Caller has inTask " + inTask + + " but target is a new task"); } - sourceStack = inTask.stack; reuseTask = inTask; + addingToTask = true; } else { inTask = null; } @@ -1724,10 +1751,11 @@ public final class ActivityStackSupervisor implements DisplayListener { if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || launchSingleInstance || launchSingleTask) { - // If bring to front is requested, and no result is requested, and + // If bring to front is requested, and no result is requested and we have not + // been given an explicit task to launch in to, and // we can find a task that was started with this same // component, then instead of launching bring that one to the front. - if (r.resultTo == null) { + if (inTask == null && r.resultTo == null) { // See if there is a task to bring to the front. If this is // a SINGLE_INSTANCE activity, there can be one and only one // instance of it in the history, and it is always in its own @@ -1957,13 +1985,8 @@ public final class ActivityStackSupervisor implements DisplayListener { Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } - if (inTask == null) { - // If we have an incoming task, we are just going to use that. - newTask = true; - targetStack = adjustStackFocus(r, newTask); - } else { - targetStack = inTask.stack; - } + newTask = true; + targetStack = adjustStackFocus(r, newTask); if (!launchTaskBehind) { targetStack.moveToFront(); } @@ -2048,8 +2071,27 @@ public final class ActivityStackSupervisor implements DisplayListener { return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } targetStack = inTask.stack; - targetStack.moveToFront(); + targetStack.moveTaskToFrontLocked(inTask, r, options); mWindowManager.moveTaskToTop(targetStack.topTask().taskId); + + // Check whether we should actually launch the new activity in to the task, + // or just reuse the current activity on top. + ActivityRecord top = inTask.getTopActivity(); + if (top != null && top.realActivity.equals(r.realActivity) && top.userId == r.userId) { + if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || launchSingleTop || launchSingleTask) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(callingUid, r.intent); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + r.setTask(inTask, null); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in explicit task " + r.task); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 77c324f..1287dce 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -21,6 +21,7 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import android.app.ActivityManager; +import android.os.SystemClock; import com.android.internal.util.MemInfoReader; import com.android.server.wm.WindowManagerService; @@ -528,12 +529,18 @@ final class ProcessList { if (amt == UNKNOWN_ADJ) return; + long start = SystemClock.elapsedRealtime(); ByteBuffer buf = ByteBuffer.allocate(4 * 4); buf.putInt(LMK_PROCPRIO); buf.putInt(pid); buf.putInt(uid); buf.putInt(amt); writeLmkd(buf); + long now = SystemClock.elapsedRealtime(); + if ((now-start) > 250) { + Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid + + " = " + amt); + } } /* diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 4b812cf..aa1310d 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -248,6 +248,11 @@ final class Constants { static final int DISABLED = 0; static final int ENABLED = 1; + // Property name for the local device configurations. + // TODO(OEM): OEM should provide this property, and the value is the comma separated integer + // values which denotes the device type in HDMI Spec 1.4. + static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type"; + // -------------------------------------------------- // MHL sub command message types. static final int MHL_MSG_MSGE = 0x02; @@ -275,11 +280,6 @@ final class Constants { static final int MHL_CBUS_MODE_ECBUS_S = 2; static final int MHL_CBUS_MODE_ECBUS_D = 3; - // Property name for the local device configurations. - // TODO(OEM): OEM should provide this property, and the value is the comma separated integer - // values which denotes the device type in HDMI Spec 1.4. - static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type"; - // MHL RCPE messages static final int MHL_RCPE_NO_ERROR = 0x00; static final int MHL_RCPE_INEFFECTIVE_KEYCODE = 0x01; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 827b3ed..bb22b4d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -23,6 +23,7 @@ import android.os.MessageQueue; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Predicate; import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; @@ -30,6 +31,8 @@ import com.android.server.hdmi.HdmiControlService.DevicePollingCallback; import libcore.util.EmptyArray; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -446,7 +449,7 @@ final class HdmiCecController { allocated.add(address); } } - mIoThreadLogger.debug("DevicePollingResult:" + allocated); + mIoThreadLogger.debug("[P]:Allocated Address=" + allocated); if (callback != null) { runOnServiceThread(new Runnable() { @Override @@ -548,7 +551,7 @@ final class HdmiCecController { runOnIoThread(new Runnable() { @Override public void run() { - mIoThreadLogger.debug("SendCommand:" + cecMessage); + mIoThreadLogger.debug("[S]:" + cecMessage); byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); int i = 0; int errorCode = Constants.SEND_RESULT_SUCCESS; @@ -583,7 +586,7 @@ final class HdmiCecController { private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { assertRunOnServiceThread(); HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body); - mServiceThreadLogger.debug("ReceiveCommand:" + command); + mServiceThreadLogger.debug("[R]:" + command); onReceiveCommand(command); } @@ -598,6 +601,15 @@ final class HdmiCecController { mService.onHotplug(port, connected); } + void dump(final IndentingPrintWriter pw) { + for (int i = 0; i < mLocalDevices.size(); ++i) { + pw.println("HdmiCecLocalDevice #" + i + ":"); + pw.increaseIndent(); + mLocalDevices.valueAt(i).dump(pw); + pw.decreaseIndent(); + } + } + private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue); private static native int nativeSendCecCommand(long controllerPtr, int srcAddress, int dstAddress, byte[] body); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java index 26d2cde..85f5be2 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java @@ -43,6 +43,9 @@ import java.util.List; */ abstract class HdmiCecFeatureAction { private static final String TAG = "HdmiCecFeatureAction"; + // As all actions run in the same thread (service thread), it's fine to have single logger. + // TODO: create global logger for each threads and use them. + protected static final HdmiLogger DLOGGER = new HdmiLogger(TAG); // Timer handler message used for timeout event protected static final int MSG_TIMEOUT = 100; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index a12e4fc..38addba 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -28,6 +28,7 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import java.util.ArrayList; @@ -97,6 +98,17 @@ abstract class HdmiCecLocalDevice { public int hashCode() { return logicalAddress * 29 + physicalAddress; } + @Override + public String toString() { + StringBuffer s = new StringBuffer(); + String logicalAddressString = (logicalAddress == Constants.ADDR_INVALID) + ? "invalid" : String.format("0x%02x", logicalAddress); + s.append("logical_address: ").append(logicalAddressString); + String physicalAddressString = (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) + ? "invalid" : String.format("0x%04x", physicalAddress); + s.append(", physical_address: ").append(physicalAddressString); + return s.toString(); + } } // Logical address of the active source. @GuardedBy("mLock") @@ -793,4 +805,16 @@ abstract class HdmiCecLocalDevice { protected void sendKeyEvent(int keyCode, boolean isPressed) { Slog.w(TAG, "sendKeyEvent not implemented"); } + + /** + * Dump internal status of HdmiCecLocalDevice object. + */ + protected void dump(final IndentingPrintWriter pw) { + pw.println("mDeviceType: " + mDeviceType); + pw.println("mAddress: " + mAddress); + pw.println("mPreferredAddress: " + mPreferredAddress); + pw.println("mDeviceInfo: " + mDeviceInfo); + pw.println("mActiveSource: " + mActiveSource); + pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath)); + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 5a2fa9c..6603a71 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -23,6 +23,7 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.util.Slog; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; /** @@ -219,4 +220,10 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { mIsActiveSource = false; checkIfPendingActionsCleared(); } + + @Override + protected void dump(final IndentingPrintWriter pw) { + super.dump(pw); + pw.println("mIsActiveSource: " + mIsActiveSource); + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index cd56cfc..1ab8069 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -45,6 +45,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; @@ -1610,4 +1611,16 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); } + + @Override + protected void dump(final IndentingPrintWriter pw) { + super.dump(pw); + pw.println("mArcEstablished: " + mArcEstablished); + pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled); + pw.println("mSystemAudioActivated: " + mSystemAudioActivated); + pw.println("mSystemAudioMute: " + mSystemAudioMute); + pw.println("mAutoDeviceOff: " + mAutoDeviceOff); + pw.println("mAutoWakeup: " + mAutoWakeup); + pw.println("mSkipRoutingControl: " + mSkipRoutingControl); + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index fcccfc0..d13e1de 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -67,6 +67,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.SystemService; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; @@ -75,6 +76,8 @@ import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; import libcore.util.EmptyArray; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -179,11 +182,6 @@ public final class HdmiControlService extends SystemService { private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords = new ArrayList<>(); - // List of records for MHL Scratchpad command listener to handle the caller killed in action. - @GuardedBy("mLock") - private final ArrayList<HdmiMhlScratchpadCommandListenerRecord> - mScratchpadCommandListenerRecords = new ArrayList<>(); - @GuardedBy("mLock") private InputChangeListenerRecord mInputChangeListenerRecord; @@ -201,13 +199,6 @@ public final class HdmiControlService extends SystemService { @GuardedBy("mLock") private boolean mProhibitMode; - // Set to true while the input change by MHL is allowed. - @GuardedBy("mLock") - private boolean mMhlInputChangeEnabled; - - @GuardedBy("mLock") - private List<HdmiDeviceInfo> mMhlDevices; - // List of records for system audio mode change to handle the the caller killed in action. private final ArrayList<SystemAudioModeChangeListenerRecord> mSystemAudioModeChangeListenerRecords = new ArrayList<>(); @@ -223,9 +214,6 @@ public final class HdmiControlService extends SystemService { @Nullable private HdmiCecController mCecController; - @Nullable - private HdmiMhlController mMhlController; - // HDMI port information. Stored in the unmodifiable list to keep the static information // from being modified. private List<HdmiPortInfo> mPortInfo; @@ -256,8 +244,23 @@ public final class HdmiControlService extends SystemService { @ServiceThreadOnly private int mActivePortId = Constants.INVALID_PORT_ID; - // Last input port before switching to the MHL port by way of incoming request RAP[ContentOn]. - // Should switch back to this port when the device sends RAP[ContentOff]. + // Set to true while the input change by MHL is allowed. + @GuardedBy("mLock") + private boolean mMhlInputChangeEnabled; + + // List of records for MHL Scratchpad command listener to handle the caller killed in action. + @GuardedBy("mLock") + private final ArrayList<HdmiMhlScratchpadCommandListenerRecord> + mScratchpadCommandListenerRecords = new ArrayList<>(); + + @GuardedBy("mLock") + private List<HdmiDeviceInfo> mMhlDevices; + + @Nullable + private HdmiMhlController mMhlController; + + // Last input port before switching to the MHL port. Should switch back to this port + // when the mobile device sends the request one touch play with off. // Gets invalidated if we go to other port/input. @ServiceThreadOnly private int mLastInputMhl = Constants.INVALID_PORT_ID; @@ -304,16 +307,17 @@ public final class HdmiControlService extends SystemService { } mMhlController = HdmiMhlController.create(this); - if (mMhlController == null) { + if (!mMhlController.isReady()) { Slog.i(TAG, "Device does not support MHL-control."); } - initPortInfo(); mMhlDevices = Collections.emptyList(); + + initPortInfo(); mMessageValidator = new HdmiCecMessageValidator(this); publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); // Register broadcast receiver for power state change. - if (mCecController != null || mMhlController != null) { + if (mCecController != null) { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); @@ -378,9 +382,7 @@ public final class HdmiControlService extends SystemService { setMhlInputChangeEnabled(enabled); break; case Global.MHL_POWER_CHARGE_ENABLED: - if (mMhlController != null) { - mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled)); - } + mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled)); break; } } @@ -484,30 +486,30 @@ public final class HdmiControlService extends SystemService { mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); - if (mMhlController == null) { - mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo)); - return; - } else { - HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); - ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); - for (HdmiPortInfo info : mhlPortInfo) { - if (info.isMhlSupported()) { - mhlSupportedPorts.add(info.getId()); - } + HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); + ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); + for (HdmiPortInfo info : mhlPortInfo) { + if (info.isMhlSupported()) { + mhlSupportedPorts.add(info.getId()); } + } - // Build HDMI port info list with CEC port info plus MHL supported flag. - ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); - for (HdmiPortInfo info : cecPortInfo) { - if (mhlSupportedPorts.contains(info.getId())) { - result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), - info.isCecSupported(), true, info.isArcSupported())); - } else { - result.add(info); - } + // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use + // cec port info if we do not have have port that supports MHL. + if (mhlSupportedPorts.isEmpty()) { + mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo)); + return; + } + ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); + for (HdmiPortInfo info : cecPortInfo) { + if (mhlSupportedPorts.contains(info.getId())) { + result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), + info.isCecSupported(), true, info.isArcSupported())); + } else { + result.add(info); } - mPortInfo = Collections.unmodifiableList(result); } + mPortInfo = Collections.unmodifiableList(result); } List<HdmiPortInfo> getPortInfo() { @@ -652,18 +654,6 @@ public final class HdmiControlService extends SystemService { sendCecCommand(command, null); } - @ServiceThreadOnly - void sendMhlSubcommand(int portId, HdmiMhlSubcommand command) { - assertRunOnServiceThread(); - sendMhlSubcommand(portId, command, null); - } - - @ServiceThreadOnly - void sendMhlSubcommand(int portId, HdmiMhlSubcommand command, SendMessageCallback callback) { - assertRunOnServiceThread(); - mMhlController.sendSubcommand(portId, command, callback); - } - /** * Send <Feature Abort> command on the given CEC message if possible. * If the aborted message is invalid, then it wont send the message. @@ -796,6 +786,18 @@ public final class HdmiControlService extends SystemService { } @ServiceThreadOnly + void sendMhlSubcommand(int portId, HdmiMhlSubcommand command) { + assertRunOnServiceThread(); + sendMhlSubcommand(portId, command, null); + } + + @ServiceThreadOnly + void sendMhlSubcommand(int portId, HdmiMhlSubcommand command, SendMessageCallback callback) { + assertRunOnServiceThread(); + mMhlController.sendSubcommand(portId, command, callback); + } + + @ServiceThreadOnly boolean handleMhlSubcommand(int portId, HdmiMhlSubcommand message) { assertRunOnServiceThread(); @@ -895,6 +897,19 @@ public final class HdmiControlService extends SystemService { return mMhlDevices; } + private class HdmiMhlScratchpadCommandListenerRecord implements IBinder.DeathRecipient { + private final IHdmiMhlScratchpadCommandListener mListener; + + public HdmiMhlScratchpadCommandListenerRecord(IHdmiMhlScratchpadCommandListener listener) { + mListener = listener; + } + + @Override + public void binderDied() { + mScratchpadCommandListenerRecords.remove(this); + } + } + // Record class that monitors the event of the caller of being killed. Used to clean up // the listener list and record list accordingly. private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { @@ -974,19 +989,6 @@ public final class HdmiControlService extends SystemService { } } - private class HdmiMhlScratchpadCommandListenerRecord implements IBinder.DeathRecipient { - private final IHdmiMhlScratchpadCommandListener mListener; - - public HdmiMhlScratchpadCommandListenerRecord(IHdmiMhlScratchpadCommandListener listener) { - mListener = listener; - } - - @Override - public void binderDied() { - mScratchpadCommandListenerRecords.remove(this); - } - } - private void enforceAccessPermission() { getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); } @@ -1039,20 +1041,18 @@ public final class HdmiControlService extends SystemService { invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); return; } - if (mMhlController != null) { - HdmiMhlLocalDevice device = mMhlController.getLocalDeviceById(deviceId); - if (device != null) { - if (device.getPortId() == tv.getActivePortId()) { - invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); - return; - } - // Upon selecting MHL device, we send RAP[Content On] to wake up - // the connected mobile device, start routing control to switch ports. - // callback is handled by MHL action. - device.turnOn(callback); - tv.doManualPortSwitching(device.getInfo().getPortId(), null); + HdmiMhlLocalDevice device = mMhlController.getLocalDeviceById(deviceId); + if (device != null) { + if (device.getPortId() == tv.getActivePortId()) { + invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); return; } + // Upon selecting MHL device, we send RAP[Content On] to wake up + // the connected mobile device, start routing control to switch ports. + // callback is handled by MHL action. + device.turnOn(callback); + tv.doManualPortSwitching(device.getInfo().getPortId(), null); + return; } tv.deviceSelect(deviceId, callback); } @@ -1086,12 +1086,10 @@ public final class HdmiControlService extends SystemService { runOnServiceThread(new Runnable() { @Override public void run() { - if (mMhlController != null) { - HdmiMhlLocalDevice device = mMhlController.getLocalDevice(mActivePortId); - if (device != null) { - device.sendKeyEvent(keyCode, isPressed); - return; - } + HdmiMhlLocalDevice device = mMhlController.getLocalDevice(mActivePortId); + if (device != null) { + device.sendKeyEvent(keyCode, isPressed); + return; } if (mCecController != null) { HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); @@ -1380,10 +1378,6 @@ public final class HdmiControlService extends SystemService { runOnServiceThread(new Runnable() { @Override public void run() { - if (mMhlController == null) { - Slog.w(TAG, "No Mhl controller available."); - return; - } if (!isControlEnabled()) { Slog.w(TAG, "Hdmi control is disabled."); return ; @@ -1404,6 +1398,28 @@ public final class HdmiControlService extends SystemService { enforceAccessPermission(); HdmiControlService.this.addHdmiMhlScratchpadCommandListener(listener); } + + @Override + protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { + getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + + pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled); + pw.println("mProhibitMode: " + mProhibitMode); + if (mCecController != null) { + pw.println("mCecController: "); + pw.increaseIndent(); + mCecController.dump(pw); + pw.decreaseIndent(); + } + pw.println("mPortInfo: "); + pw.increaseIndent(); + for (HdmiPortInfo hdmiPortInfo : mPortInfo) { + pw.println("- " + hdmiPortInfo); + } + pw.decreaseIndent(); + pw.println("mPowerStatus: " + mPowerStatus); + } } @ServiceThreadOnly @@ -1775,9 +1791,7 @@ public final class HdmiControlService extends SystemService { } } - if (mMhlController != null) { - mMhlController.clearAllLocalDevices(); - } + mMhlController.clearAllLocalDevices(); } @ServiceThreadOnly @@ -1887,9 +1901,7 @@ public final class HdmiControlService extends SystemService { int value = toInt(enabled); mCecController.setOption(OPTION_CEC_ENABLE, value); - if (mMhlController != null) { - mMhlController.setOption(OPTION_MHL_ENABLE, value); - } + mMhlController.setOption(OPTION_MHL_ENABLE, value); synchronized (mLock) { mHdmiControlEnabled = enabled; @@ -1955,7 +1967,7 @@ public final class HdmiControlService extends SystemService { tv().setActivePortId(portId); // The port is either the MHL-enabled port where the mobile device is connected, or - // the last port to go back to when RAP[ContentOff] is received. Note that the last port + // the last port to go back to when turnoff command is received. Note that the last port // may not be the MHL-enabled one. In this case the device info to be passed to // input change listener should be the one describing the corresponding HDMI port. HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); @@ -1966,9 +1978,7 @@ public final class HdmiControlService extends SystemService { } void setMhlInputChangeEnabled(boolean enabled) { - if (mMhlController != null) { - mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); - } + mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); synchronized (mLock) { mMhlInputChangeEnabled = enabled; diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java index ee9379d..c7add75 100644 --- a/services/core/java/com/android/server/hdmi/HdmiLogger.java +++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java @@ -42,7 +42,7 @@ final class HdmiLogger { private final String mTag; HdmiLogger(String tag) { - mTag = tag; + mTag = "HDMI:" + tag; } void warning(String logMessage) { diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index ac2c7b9..d15ffb0 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -73,9 +73,9 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { // Seq #27 protected void sendSystemAudioModeRequest() { - mState = STATE_CHECK_ROUTING_IN_PRGRESS; List<RoutingControlAction> routingActions = getActions(RoutingControlAction.class); if (!routingActions.isEmpty()) { + mState = STATE_CHECK_ROUTING_IN_PRGRESS; // Should have only one Routing Control Action RoutingControlAction routingAction = routingActions.get(0); routingAction.addOnFinishedCallback(this, new Runnable() { @@ -97,20 +97,21 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { sendCommand(command, new HdmiControlService.SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error == Constants.SEND_RESULT_SUCCESS) { - mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE; - addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS); - } else { + if (error != Constants.SEND_RESULT_SUCCESS) { + DLOGGER.debug("Failed to send <System Audio Mode Request>:" + error); setSystemAudioMode(false); finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); } } }); + mState = STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE; + addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS); } private void handleSendSystemAudioModeRequestTimeout() { if (!mTargetAudioStatus // Don't retry for Off case. || mSendRetryCount++ >= MAX_SEND_RETRY_COUNT) { + DLOGGER.debug("[T]:wait for <Set System Audio Mode>."); setSystemAudioMode(false); finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); return; @@ -129,6 +130,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT && (cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST) { + DLOGGER.debug("Failed to start system audio mode request."); setSystemAudioMode(false); finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); return true; @@ -143,6 +145,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { startAudioStatusAction(); return true; } else { + DLOGGER.debug("Unexpected system audio mode request:" + receivedStatus); // Unexpected response, consider the request is newly initiated by AVR. // To return 'false' will initiate new SystemAudioActionFromAvr by the control // service. diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 978a9f4..0da2cfa 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.pm.ParceledListSlice; import android.media.AudioManager; import android.media.AudioManagerInternal; +import android.media.MediaDescription; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; @@ -441,7 +442,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private String getShortMetadataString() { int fields = mMetadata == null ? 0 : mMetadata.size(); - MediaMetadata.Description description = mMetadata == null ? null : mMetadata + MediaDescription description = mMetadata == null ? null : mMetadata .getDescription(); return "size=" + fields + ", description=" + description; } @@ -820,9 +821,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } } - public void playUri(Uri uri, Bundle extras) { + public void playFromMediaId(String mediaId, Bundle extras) { try { - mCb.onPlayUri(uri, extras); + mCb.onPlayFromMediaId(mediaId, extras); } catch (RemoteException e) { Slog.e(TAG, "Remote failure in playUri.", e); } @@ -1042,8 +1043,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void playUri(Uri uri, Bundle extras) throws RemoteException { - mSessionCb.playUri(uri, extras); + public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException { + mSessionCb.playFromMediaId(mediaId, extras); } @Override @@ -1052,7 +1053,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void skipToTrack(long id) { + public void skipToQueueItem(long id) { mSessionCb.skipToTrack(id); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8c0d2c9..4101232 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -17,6 +17,8 @@ package com.android.server.notification; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; +import static android.service.notification.NotificationListenerService.TRIM_FULL; +import static android.service.notification.NotificationListenerService.TRIM_LIGHT; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -57,7 +59,6 @@ import android.os.IBinder; import android.os.IInterface; import android.os.Looper; import android.os.Message; -import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -127,6 +128,7 @@ public class NotificationManagerService extends SystemService { static final int MESSAGE_RANKING_CONFIG_CHANGE = 5; static final int MESSAGE_SEND_RANKING_UPDATE = 6; static final int MESSAGE_LISTENER_HINTS_CHANGED = 7; + static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 8; static final int LONG_DELAY = 3500; // 3.5 seconds static final int SHORT_DELAY = 2000; // 2 seconds @@ -178,6 +180,7 @@ public class NotificationManagerService extends SystemService { private final ArraySet<ManagedServiceInfo> mListenersDisablingEffects = new ArraySet<>(); private ComponentName mEffectsSuppressor; private int mListenerHints; // right now, all hints are global + private int mInterruptionFilter; // current ZEN mode as communicated to listeners // for enabling and disabling notification pulse behavior private boolean mScreenOn = true; @@ -806,7 +809,7 @@ public class NotificationManagerService extends SystemService { @Override void onZenModeChanged() { synchronized(mNotificationList) { - updateListenerHintsLocked(); + updateInterruptionFilterLocked(); } } }); @@ -938,8 +941,7 @@ public class NotificationManagerService extends SystemService { } private void updateListenerHintsLocked() { - final int hints = (mListenersDisablingEffects.isEmpty() ? 0 : HINT_HOST_DISABLE_EFFECTS) | - mZenModeHelper.getZenModeListenerHint(); + final int hints = mListenersDisablingEffects.isEmpty() ? 0 : HINT_HOST_DISABLE_EFFECTS; if (hints == mListenerHints) return; mListenerHints = hints; scheduleListenerHintsChanged(hints); @@ -954,6 +956,13 @@ public class NotificationManagerService extends SystemService { .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)); } + private void updateInterruptionFilterLocked() { + int interruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter(); + if (interruptionFilter == mInterruptionFilter) return; + mInterruptionFilter = interruptionFilter; + scheduleInterruptionFilterChanged(interruptionFilter); + } + private final IBinder mService = new INotificationManager.Stub() { // Toasts // ============================================================================ @@ -1283,24 +1292,23 @@ public class NotificationManagerService extends SystemService { */ @Override public ParceledListSlice<StatusBarNotification> getActiveNotificationsFromListener( - INotificationListener token, String[] keys) { + INotificationListener token, String[] keys, int trim) { synchronized (mNotificationList) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); - final ArrayList<StatusBarNotification> list - = new ArrayList<StatusBarNotification>(); final boolean getKeys = keys != null; final int N = getKeys ? keys.length : mNotificationList.size(); - list.ensureCapacity(N); + final ArrayList<StatusBarNotification> list + = new ArrayList<StatusBarNotification>(N); for (int i=0; i<N; i++) { final NotificationRecord r = getKeys ? mNotificationsByKey.get(keys[i]) : mNotificationList.get(i); - if (r != null) { - StatusBarNotification sbn = r.sbn; - if (isVisibleToListener(sbn, info)) { - list.add(sbn); - } - } + if (r == null) continue; + StatusBarNotification sbn = r.sbn; + if (!isVisibleToListener(sbn, info)) continue; + StatusBarNotification sbnToSend = + (trim == TRIM_FULL) ? sbn : sbn.cloneLight(); + list.add(sbnToSend); } return new ParceledListSlice<StatusBarNotification>(list); } @@ -1318,7 +1326,6 @@ public class NotificationManagerService extends SystemService { } else { mListenersDisablingEffects.remove(info); } - mZenModeHelper.requestFromListener(hints); updateListenerHintsLocked(); updateEffectsSuppressorLocked(); } @@ -1335,6 +1342,39 @@ public class NotificationManagerService extends SystemService { } @Override + public void requestInterruptionFilterFromListener(INotificationListener token, + int interruptionFilter) throws RemoteException { + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mNotificationList) { + mListeners.checkServiceTokenLocked(token); + mZenModeHelper.requestFromListener(interruptionFilter); + updateInterruptionFilterLocked(); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public int getInterruptionFilterFromListener(INotificationListener token) + throws RemoteException { + synchronized (mNotificationLight) { + return mInterruptionFilter; + } + } + + @Override + public void setOnNotificationPostedTrimFromListener(INotificationListener token, int trim) + throws RemoteException { + synchronized (mNotificationList) { + final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + if (info == null) return; + mListeners.setOnNotificationPostedTrimLocked(info, trim); + } + } + + @Override public ZenModeConfig getZenModeConfig() { enforceSystemOrSystemUI("INotificationManager.getZenModeConfig"); return mZenModeHelper.getConfig(); @@ -2058,12 +2098,26 @@ public class NotificationManagerService extends SystemService { mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget(); } + private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) { + mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED); + mHandler.obtainMessage( + MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED, + listenerInterruptionFilter, + 0).sendToTarget(); + } + private void handleListenerHintsChanged(int hints) { synchronized (mNotificationList) { mListeners.notifyListenerHintsChangedLocked(hints); } } + private void handleListenerInterruptionFilterChanged(int interruptionFilter) { + synchronized (mNotificationList) { + mListeners.notifyInterruptionFilterChanged(interruptionFilter); + } + } + private final class WorkerHandler extends Handler { @Override @@ -2083,6 +2137,9 @@ public class NotificationManagerService extends SystemService { case MESSAGE_LISTENER_HINTS_CHANGED: handleListenerHintsChanged(msg.arg1); break; + case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED: + handleListenerInterruptionFilterChanged(msg.arg1); + break; } } @@ -2565,6 +2622,8 @@ public class NotificationManagerService extends SystemService { public class NotificationListeners extends ManagedServices { + private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>(); + public NotificationListeners() { super(getContext(), mHandler, mNotificationList, mUserProfiles); } @@ -2605,6 +2664,20 @@ public class NotificationManagerService extends SystemService { if (mListenersDisablingEffects.remove(removed)) { updateListenerHintsLocked(); } + mLightTrimListeners.remove(removed); + } + + public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) { + if (trim == TRIM_LIGHT) { + mLightTrimListeners.add(info); + } else { + mLightTrimListeners.remove(info); + } + } + + public int getOnNotificationPostedTrim(ManagedServiceInfo info) { + return mLightTrimListeners.contains(info) ? TRIM_LIGHT : TRIM_FULL; + } /** @@ -2615,8 +2688,10 @@ public class NotificationManagerService extends SystemService { * but isn't anymore. */ public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) { - // make a copy in case changes are made to the underlying Notification object - final StatusBarNotification sbnClone = sbn.clone(); + // Lazily initialized snapshots of the notification. + StatusBarNotification sbnClone = null; + StatusBarNotification sbnCloneLight = null; + for (final ManagedServiceInfo info : mServices) { boolean sbnVisible = isVisibleToListener(sbn, info); boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false; @@ -2638,10 +2713,20 @@ public class NotificationManagerService extends SystemService { continue; } + final int trim = mListeners.getOnNotificationPostedTrim(info); + + if (trim == TRIM_LIGHT && sbnCloneLight == null) { + sbnCloneLight = sbn.cloneLight(); + } else if (trim == TRIM_FULL && sbnClone == null) { + sbnClone = sbn.clone(); + } + final StatusBarNotification sbnToPost = + (trim == TRIM_FULL) ? sbnClone : sbnCloneLight; + mHandler.post(new Runnable() { @Override public void run() { - notifyPosted(info, sbnClone, update); + notifyPosted(info, sbnToPost, update); } }); } @@ -2701,6 +2786,20 @@ public class NotificationManagerService extends SystemService { } } + public void notifyInterruptionFilterChanged(final int interruptionFilter) { + for (final ManagedServiceInfo serviceInfo : mServices) { + if (!serviceInfo.isEnabledForCurrentProfiles()) { + continue; + } + mHandler.post(new Runnable() { + @Override + public void run() { + notifyInterruptionFilterChanged(serviceInfo, interruptionFilter); + } + }); + } + } + private void notifyPosted(final ManagedServiceInfo info, final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) { final INotificationListener listener = (INotificationListener)info.service; @@ -2743,6 +2842,16 @@ public class NotificationManagerService extends SystemService { } } + private void notifyInterruptionFilterChanged(ManagedServiceInfo info, + int interruptionFilter) { + final INotificationListener listener = (INotificationListener) info.service; + try { + listener.onInterruptionFilterChanged(interruptionFilter); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify listener (interruption filter): " + listener, ex); + } + } + private boolean isListenerPackage(String packageName) { if (packageName == null) { return false; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 0b93690..7a5336b 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -115,35 +115,35 @@ public class ZenModeHelper { mAudioManager = audioManager; } - public int getZenModeListenerHint() { - switch(mZenMode) { + public int getZenModeListenerInterruptionFilter() { + switch (mZenMode) { case Global.ZEN_MODE_OFF: - return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_ALL; + return NotificationListenerService.INTERRUPTION_FILTER_ALL; case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: - return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_PRIORITY; + return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY; case Global.ZEN_MODE_NO_INTERRUPTIONS: - return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_NONE; + return NotificationListenerService.INTERRUPTION_FILTER_NONE; default: return 0; } } - private static int zenFromListenerHint(int hints, int defValue) { - final int level = hints & NotificationListenerService.HOST_INTERRUPTION_LEVEL_MASK; - switch(level) { - case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_ALL: + private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter, + int defValue) { + switch (listenerInterruptionFilter) { + case NotificationListenerService.INTERRUPTION_FILTER_ALL: return Global.ZEN_MODE_OFF; - case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_PRIORITY: + case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY: return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_NONE: + case NotificationListenerService.INTERRUPTION_FILTER_NONE: return Global.ZEN_MODE_NO_INTERRUPTIONS; default: return defValue; } } - public void requestFromListener(int hints) { - final int newZen = zenFromListenerHint(hints, -1); + public void requestFromListener(int interruptionFilter) { + final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1); if (newZen != -1) { setZenMode(newZen, "listener"); } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 694669c..ca11862 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -224,10 +224,6 @@ public final class Installer extends SystemService { } } - public int pruneDexCache(String cacheSubDir) { - return mInstaller.execute("prunedexcache " + cacheSubDir); - } - public int freeCache(long freeStorageSize) { StringBuilder builder = new StringBuilder("freecache"); builder.append(' '); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5093f97..2cb9077 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1497,29 +1497,6 @@ public class PackageManagerService extends IPackageManager.Stub { } } - if (didDexOptLibraryOrTool) { - // If we dexopted a library or tool, then something on the system has - // changed. Consider this significant, and wipe away all other - // existing dexopt files to ensure we don't leave any dangling around. - // - // TODO: This should be revisited because it isn't as good an indicator - // as it used to be. It used to include the boot classpath but at some point - // DexFile.isDexOptNeeded started returning false for the boot - // class path files in all cases. It is very possible in a - // small maintenance release update that the library and tool - // jars may be unchanged but APK could be removed resulting in - // unused dalvik-cache files. - for (String dexCodeInstructionSet : dexCodeInstructionSets) { - mInstaller.pruneDexCache(dexCodeInstructionSet); - } - - // Additionally, delete all dex files from the root directory - // since there shouldn't be any there anyway, unless we're upgrading - // from an older OS version or a build that contained the "old" style - // flat scheme. - mInstaller.pruneDexCache("."); - } - // Collect vendor overlay packages. // (Do this before scanning any apps.) // For security and version matching reason, only consider @@ -11051,34 +11028,34 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Attempt to delete null packageName."); return false; } - PackageParser.Package p; + PackageParser.Package pkg; boolean dataOnly = false; final int appId; synchronized (mPackages) { - p = mPackages.get(packageName); - if (p == null) { + pkg = mPackages.get(packageName); + if (pkg == null) { dataOnly = true; PackageSetting ps = mSettings.mPackages.get(packageName); if ((ps == null) || (ps.pkg == null)) { Slog.w(TAG, "Package named '" + packageName + "' doesn't exist."); return false; } - p = ps.pkg; + pkg = ps.pkg; } if (!dataOnly) { // need to check this only for fully installed applications - if (p == null) { + if (pkg == null) { Slog.w(TAG, "Package named '" + packageName + "' doesn't exist."); return false; } - final ApplicationInfo applicationInfo = p.applicationInfo; + final ApplicationInfo applicationInfo = pkg.applicationInfo; if (applicationInfo == null) { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } } - if (p != null && p.applicationInfo != null) { - appId = p.applicationInfo.uid; + if (pkg != null && pkg.applicationInfo != null) { + appId = pkg.applicationInfo.uid; } else { appId = -1; } @@ -11090,6 +11067,19 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } removeKeystoreDataIfNeeded(userId, appId); + + // Create a native library symlink only if we have native libraries + // and if the native libraries are 32 bit libraries. We do not provide + // this symlink for 64 bit libraries. + if (pkg != null && pkg.applicationInfo.primaryCpuAbi != null && + !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) { + final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir; + if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) { + Slog.w(TAG, "Failed linking native library dir"); + return false; + } + } + return true; } @@ -12914,6 +12904,17 @@ public class PackageManagerService extends IPackageManager.Stub { Bundle extras) throws RemoteException { Slog.d(TAG, "Install result for move: " + PackageManager.installStatusToString(returnCode, msg)); + + // We usually have a new package now after the install, but if + // we failed we need to clear the pending flag on the original + // package object. + synchronized (mPackages) { + final PackageParser.Package pkg = mPackages.get(packageName); + if (pkg != null) { + pkg.mOperationPending = false; + } + } + final int status = PackageManager.installStatusToPublicStatus(returnCode); switch (status) { case PackageInstaller.STATUS_SUCCESS: diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index ad87993..8ded7ca 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1576,6 +1576,7 @@ public class UserManagerService extends IUserManager.Stub { String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE); String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE); if (multiple != null) { + values.clear(); int count = Integer.parseInt(multiple); while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index f47a07c..5f97a00 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -821,7 +821,7 @@ public final class PowerManagerService extends com.android.server.SystemService + " [" + wakeLock.mTag + "], flags=0x" + Integer.toHexString(flags)); } - if ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0) { + if ((flags & PowerManager.WAIT_FOR_DISTANT_PROXIMITY) != 0) { mRequestWaitForNegativeProximity = true; } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 27023ad..d22912c 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.OperationApplicationException; import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -49,9 +50,11 @@ import android.media.tv.ITvInputServiceCallback; import android.media.tv.ITvInputSession; import android.media.tv.ITvInputSessionCallback; import android.media.tv.TvContentRating; +import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvContract; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; +import android.media.tv.TvInputManager; import android.media.tv.TvInputService; import android.media.tv.TvStreamConfig; import android.media.tv.TvTrackInfo; @@ -116,7 +119,7 @@ public final class TvInputManagerService extends SystemService { mContext = context; mContentResolver = context.getContentResolver(); - mWatchLogHandler = new WatchLogHandler(IoThread.get().getLooper()); + mWatchLogHandler = new WatchLogHandler(mContentResolver, IoThread.get().getLooper()); mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener()); @@ -137,6 +140,7 @@ public final class TvInputManagerService extends SystemService { } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { synchronized (mLock) { buildTvInputListLocked(mCurrentUserId); + buildTvContentRatingSystemListLocked(mCurrentUserId); } } mTvInputHardwareManager.onBootPhase(phase); @@ -149,6 +153,7 @@ public final class TvInputManagerService extends SystemService { if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()"); synchronized (mLock) { buildTvInputListLocked(mCurrentUserId); + buildTvContentRatingSystemListLocked(mCurrentUserId); } } @@ -243,7 +248,7 @@ public final class TvInputManagerService extends SystemService { serviceState = new ServiceState(component, userId); userState.serviceStateMap.put(component, serviceState); } else { - inputList.addAll(serviceState.mInputList); + inputList.addAll(serviceState.inputList); } } else { try { @@ -253,9 +258,6 @@ public final class TvInputManagerService extends SystemService { continue; } } - - // Reconnect the service if existing input is updated. - updateServiceConnectionLocked(component, userId); userState.packageSet.add(si.packageName); } @@ -268,7 +270,7 @@ public final class TvInputManagerService extends SystemService { if (state == null) { state = new TvInputState(); } - state.mInfo = info; + state.info = info; inputMap.put(info.getId(), state); } @@ -280,7 +282,7 @@ public final class TvInputManagerService extends SystemService { for (String inputId : userState.inputMap.keySet()) { if (!inputMap.containsKey(inputId)) { - TvInputInfo info = userState.inputMap.get(inputId).mInfo; + TvInputInfo info = userState.inputMap.get(inputId).info; ServiceState serviceState = userState.serviceStateMap.get(info.getComponent()); if (serviceState != null) { abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId); @@ -291,15 +293,32 @@ public final class TvInputManagerService extends SystemService { userState.inputMap.clear(); userState.inputMap = inputMap; + } + + private void buildTvContentRatingSystemListLocked(int userId) { + UserState userState = getUserStateLocked(userId); + userState.contentRatingSystemList.clear(); + + final PackageManager pm = mContext.getPackageManager(); + Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS); + for (ResolveInfo resolveInfo : + pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) { + ActivityInfo receiver = resolveInfo.activityInfo; + Bundle metaData = receiver.metaData; + if (metaData == null) { + continue; + } - userState.ratingSystemXmlUriSet.clear(); - for (TvInputState state : userState.inputMap.values()) { - Uri ratingSystemXmlUri = state.mInfo.getRatingSystemXmlUri(); - if (ratingSystemXmlUri != null) { - // TODO: need to check the validation of xml format and the duplication of rating - // systems. - userState.ratingSystemXmlUriSet.add(state.mInfo.getRatingSystemXmlUri()); + int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS); + if (xmlResId == 0) { + Slog.w(TAG, "Missing meta-data '" + + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver " + + receiver.packageName + "/" + receiver.name); + continue; } + userState.contentRatingSystemList.add( + TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId, + receiver.applicationInfo)); } } @@ -318,6 +337,7 @@ public final class TvInputManagerService extends SystemService { } mUserStates.put(userId, userState); buildTvInputListLocked(userId); + buildTvContentRatingSystemListLocked(userId); } } @@ -329,9 +349,9 @@ public final class TvInputManagerService extends SystemService { } // Release created sessions. for (SessionState state : userState.sessionStateMap.values()) { - if (state.mSession != null) { + if (state.session != null) { try { - state.mSession.release(); + state.session.release(); } catch (RemoteException e) { Slog.e(TAG, "error in release", e); } @@ -341,21 +361,21 @@ public final class TvInputManagerService extends SystemService { // Unregister all callbacks and unbind all services. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (serviceState.mCallback != null) { + if (serviceState.callback != null) { try { - serviceState.mService.unregisterCallback(serviceState.mCallback); + serviceState.service.unregisterCallback(serviceState.callback); } catch (RemoteException e) { Slog.e(TAG, "error in unregisterCallback", e); } } - mContext.unbindService(serviceState.mConnection); + mContext.unbindService(serviceState.connection); } userState.serviceStateMap.clear(); // Clear everything else. userState.inputMap.clear(); userState.packageSet.clear(); - userState.ratingSystemXmlUriSet.clear(); + userState.contentRatingSystemList.clear(); userState.clientStateMap.clear(); userState.callbackSet.clear(); userState.mainSessionToken = null; @@ -389,7 +409,7 @@ public final class TvInputManagerService extends SystemService { throw new IllegalArgumentException("Session state not found for token " + sessionToken); } // Only the application that requested this session or the system can access it. - if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) { + if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) { throw new SecurityException("Illegal access to the session with token " + sessionToken + " from uid " + callingUid); } @@ -401,10 +421,10 @@ public final class TvInputManagerService extends SystemService { } private ITvInputSession getSessionLocked(SessionState sessionState) { - ITvInputSession session = sessionState.mSession; + ITvInputSession session = sessionState.session; if (session == null) { throw new IllegalStateException("Session not yet created for token " - + sessionState.mSessionToken); + + sessionState.sessionToken); } return session; } @@ -416,8 +436,8 @@ public final class TvInputManagerService extends SystemService { } private static boolean shouldMaintainConnection(ServiceState serviceState) { - return !serviceState.mSessionTokens.isEmpty() || serviceState.mIsHardware; - // TODO: Find a way to maintain connection only when necessary. + return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware; + // TODO: Find a way to maintain connection to hardware TV input service only when necessary. } private void updateServiceConnectionLocked(ComponentName component, int userId) { @@ -426,18 +446,18 @@ public final class TvInputManagerService extends SystemService { if (serviceState == null) { return; } - if (serviceState.mReconnecting) { - if (!serviceState.mSessionTokens.isEmpty()) { + if (serviceState.reconnecting) { + if (!serviceState.sessionTokens.isEmpty()) { // wait until all the sessions are removed. return; } - serviceState.mReconnecting = false; + serviceState.reconnecting = false; } boolean maintainConnection = shouldMaintainConnection(serviceState); - if (serviceState.mService == null && maintainConnection && userId == mCurrentUserId) { + if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) { // This means that the service is not yet connected but its state indicates that we // have pending requests. Then, connect the service. - if (serviceState.mBound) { + if (serviceState.bound) { // We have already bound to the service so we don't try to bind again until after we // unbind later on. return; @@ -447,18 +467,15 @@ public final class TvInputManagerService extends SystemService { } Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component); - // Binding service may fail if the service is updating. - // In that case, the connection will be revived in buildTvInputListLocked called by - // onSomePackagesChanged. - serviceState.mBound = mContext.bindServiceAsUser( - i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId)); - } else if (serviceState.mService != null && !maintainConnection) { + serviceState.bound = mContext.bindServiceAsUser( + i, serviceState.connection, Context.BIND_AUTO_CREATE, new UserHandle(userId)); + } else if (serviceState.service != null && !maintainConnection) { // This means that the service is already connected but its state indicates that we have // nothing to do with it. Then, disconnect the service. if (DEBUG) { Slog.d(TAG, "unbindService(service=" + component + ")"); } - mContext.unbindService(serviceState.mConnection); + mContext.unbindService(serviceState.connection); userState.serviceStateMap.remove(component); } } @@ -467,16 +484,20 @@ public final class TvInputManagerService extends SystemService { String inputId, int userId) { // Let clients know the create session requests are failed. UserState userState = getUserStateLocked(userId); - for (IBinder sessionToken : serviceState.mSessionTokens) { + List<SessionState> sessionsToAbort = new ArrayList<>(); + for (IBinder sessionToken : serviceState.sessionTokens) { SessionState sessionState = userState.sessionStateMap.get(sessionToken); - if (sessionState.mSession == null && (inputId == null - || sessionState.mInfo.getId().equals(inputId))) { - removeSessionStateLocked(sessionToken, sessionState.mUserId); - sendSessionTokenToClientLocked(sessionState.mClient, - sessionState.mInfo.getId(), null, null, sessionState.mSeq); + if (sessionState.session == null && (inputId == null + || sessionState.info.getId().equals(inputId))) { + sessionsToAbort.add(sessionState); } } - updateServiceConnectionLocked(serviceState.mComponent, userId); + for (SessionState sessionState : sessionsToAbort) { + removeSessionStateLocked(sessionState.sessionToken, sessionState.userId); + sendSessionTokenToClientLocked(sessionState.client, + sessionState.info.getId(), null, null, sessionState.seq); + } + updateServiceConnectionLocked(serviceState.component, userId); } private ClientState createClientStateLocked(IBinder clientToken, int userId) { @@ -491,221 +512,26 @@ public final class TvInputManagerService extends SystemService { return clientState; } - private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken, - final int userId) { - final UserState userState = getUserStateLocked(userId); - final SessionState sessionState = userState.sessionStateMap.get(sessionToken); + private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken, + int userId) { + UserState userState = getUserStateLocked(userId); + SessionState sessionState = userState.sessionStateMap.get(sessionToken); if (DEBUG) { - Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInfo.getId() + ")"); + Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.info.getId() + ")"); } - - final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString()); + InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString()); // Set up a callback to send the session token. - ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() { - @Override - public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) { - if (DEBUG) { - Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInfo.getId() + ")"); - } - synchronized (mLock) { - sessionState.mSession = session; - sessionState.mHardwareSessionToken = harewareSessionToken; - if (session == null) { - removeSessionStateLocked(sessionToken, userId); - sendSessionTokenToClientLocked(sessionState.mClient, - sessionState.mInfo.getId(), null, null, sessionState.mSeq); - } else { - try { - session.asBinder().linkToDeath(sessionState, 0); - } catch (RemoteException e) { - Slog.e(TAG, "session process has already died", e); - } - - IBinder clientToken = sessionState.mClient.asBinder(); - ClientState clientState = userState.clientStateMap.get(clientToken); - if (clientState == null) { - clientState = createClientStateLocked(clientToken, userId); - } - clientState.mSessionTokens.add(sessionState.mSessionToken); - - sendSessionTokenToClientLocked(sessionState.mClient, - sessionState.mInfo.getId(), sessionToken, channels[0], - sessionState.mSeq); - } - channels[0].dispose(); - } - } - - @Override - public void onChannelRetuned(Uri channelUri) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onChannelRetuned(" + channelUri + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - // TODO: Consider adding this channel change in the watch log. When we do - // that, how we can protect the watch log from malicious tv inputs should - // be addressed. e.g. add a field which represents where the channel change - // originated from. - sessionState.mClient.onChannelRetuned(channelUri, sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onChannelRetuned", e); - } - } - } - - @Override - public void onTracksChanged(List<TvTrackInfo> tracks) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onTracksChanged(" + tracks + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onTracksChanged(tracks, sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onTracksChanged", e); - } - } - } - - @Override - public void onTrackSelected(int type, String trackId) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onTrackSelected(type, trackId, sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onTrackSelected", e); - } - } - } - - @Override - public void onVideoAvailable() { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onVideoAvailable()"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onVideoAvailable(sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onVideoAvailable", e); - } - } - } - - @Override - public void onVideoUnavailable(int reason) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onVideoUnavailable(" + reason + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onVideoUnavailable(reason, sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onVideoUnavailable", e); - } - } - } - - @Override - public void onContentAllowed() { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onContentAllowed()"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onContentAllowed(sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onContentAllowed", e); - } - } - } - - @Override - public void onContentBlocked(String rating) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onContentBlocked()"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onContentBlocked(rating, sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onContentBlocked", e); - } - } - } - - @Override - public void onLayoutSurface(int left, int top, int right, int bottom) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top - + ", right=" + right + ", bottom=" + bottom + ",)"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onLayoutSurface(left, top, right, bottom, - sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onLayoutSurface", e); - } - } - } - - @Override - public void onSessionEvent(String eventType, Bundle eventArgs) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onSessionEvent(eventType, eventArgs, - sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onSessionEvent", e); - } - } - } - }; + ITvInputSessionCallback callback = new SessionCallback(sessionState, channels); // Create a session. When failed, send a null token immediately. try { - service.createSession(channels[1], callback, sessionState.mInfo.getId()); + service.createSession(channels[1], callback, sessionState.info.getId()); } catch (RemoteException e) { Slog.e(TAG, "error in createSession", e); removeSessionStateLocked(sessionToken, userId); - sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInfo.getId(), null, - null, sessionState.mSeq); + sendSessionTokenToClientLocked(sessionState.client, sessionState.info.getId(), null, + null, sessionState.seq); } channels[1].dispose(); } @@ -721,17 +547,17 @@ public final class TvInputManagerService extends SystemService { private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) { SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId); - if (sessionState.mSession != null) { + if (sessionState.session != null) { UserState userState = getUserStateLocked(userId); if (sessionToken == userState.mainSessionToken) { setMainLocked(sessionToken, false, callingUid, userId); } try { - sessionState.mSession.release(); + sessionState.session.release(); } catch (RemoteException e) { Slog.e(TAG, "session process has already died", e); } - sessionState.mSession = null; + sessionState.session = null; } removeSessionStateLocked(sessionToken, userId); } @@ -754,26 +580,22 @@ public final class TvInputManagerService extends SystemService { // Also remove the session token from the session token list of the current client and // service. - ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder()); + ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder()); if (clientState != null) { - clientState.mSessionTokens.remove(sessionToken); + clientState.sessionTokens.remove(sessionToken); if (clientState.isEmpty()) { - userState.clientStateMap.remove(sessionState.mClient.asBinder()); - if (userState.clientStateMap.isEmpty()) { - // No longer need to keep the callbacks since there is no client. - userState.callbackSet.clear(); - } + userState.clientStateMap.remove(sessionState.client.asBinder()); } } - TvInputInfo info = sessionState.mInfo; + TvInputInfo info = sessionState.info; if (info != null) { ServiceState serviceState = userState.serviceStateMap.get(info.getComponent()); if (serviceState != null) { - serviceState.mSessionTokens.remove(sessionToken); + serviceState.sessionTokens.remove(sessionToken); } } - updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId); + updateServiceConnectionLocked(sessionState.info.getComponent(), userId); // Log the end of watch. SomeArgs args = SomeArgs.obtain(); @@ -784,13 +606,12 @@ public final class TvInputManagerService extends SystemService { private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) { SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId); - if (sessionState.mHardwareSessionToken != null) { - sessionState = getSessionStateLocked(sessionState.mHardwareSessionToken, + if (sessionState.hardwareSessionToken != null) { + sessionState = getSessionStateLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID, userId); } - ServiceState serviceState = getServiceStateLocked(sessionState.mInfo.getComponent(), - userId); - if (!serviceState.mIsHardware) { + ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId); + if (!serviceState.isHardware) { return; } ITvInputSession session = getSessionLocked(sessionState); @@ -853,10 +674,10 @@ public final class TvInputManagerService extends SystemService { private void setStateLocked(String inputId, int state, int userId) { UserState userState = getUserStateLocked(userId); TvInputState inputState = userState.inputMap.get(inputId); - ServiceState serviceState = userState.serviceStateMap.get(inputState.mInfo.getComponent()); - int oldState = inputState.mState; - inputState.mState = state; - if (serviceState != null && serviceState.mService == null + ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent()); + int oldState = inputState.state; + inputState.state = state; + if (serviceState != null && serviceState.service == null && shouldMaintainConnection(serviceState)) { // We don't notify state change while reconnecting. It should remain disconnected. return; @@ -877,7 +698,7 @@ public final class TvInputManagerService extends SystemService { UserState userState = getUserStateLocked(resolvedUserId); List<TvInputInfo> inputList = new ArrayList<TvInputInfo>(); for (TvInputState state : userState.inputMap.values()) { - inputList.add(state.mInfo); + inputList.add(state.info); } return inputList; } @@ -895,7 +716,7 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { UserState userState = getUserStateLocked(resolvedUserId); TvInputState state = userState.inputMap.get(inputId); - return state == null ? null : state.mInfo; + return state == null ? null : state.info; } } finally { Binder.restoreCallingIdentity(identity); @@ -903,16 +724,14 @@ public final class TvInputManagerService extends SystemService { } @Override - public List<Uri> getTvContentRatingSystemXmls(int userId) { + public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) { final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), - Binder.getCallingUid(), userId, "getTvContentRatingSystemXmls"); + Binder.getCallingUid(), userId, "getTvContentRatingSystemList"); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { UserState userState = getUserStateLocked(resolvedUserId); - List<Uri> ratingSystemXmlUriList = new ArrayList<Uri>(); - ratingSystemXmlUriList.addAll(userState.ratingSystemXmlUriSet); - return ratingSystemXmlUriList; + return userState.contentRatingSystemList; } } finally { Binder.restoreCallingIdentity(identity); @@ -943,8 +762,8 @@ public final class TvInputManagerService extends SystemService { Slog.e(TAG, "client process has already died", e); } for (TvInputState state : userState.inputMap.values()) { - notifyInputStateChangedLocked(userState, state.mInfo.getId(), - state.mState, callback); + notifyInputStateChangedLocked(userState, state.info.getId(), state.state, + callback); } } } finally { @@ -1093,14 +912,14 @@ public final class TvInputManagerService extends SystemService { sendSessionTokenToClientLocked(client, inputId, null, null, seq); return; } - TvInputInfo info = inputState.mInfo; + TvInputInfo info = inputState.info; ServiceState serviceState = userState.serviceStateMap.get(info.getComponent()); if (serviceState == null) { serviceState = new ServiceState(info.getComponent(), resolvedUserId); userState.serviceStateMap.put(info.getComponent(), serviceState); } // Send a null token immediately while reconnecting. - if (serviceState.mReconnecting == true) { + if (serviceState.reconnecting == true) { sendSessionTokenToClientLocked(client, inputId, null, null, seq); return; } @@ -1114,10 +933,10 @@ public final class TvInputManagerService extends SystemService { userState.sessionStateMap.put(sessionToken, sessionState); // Also, add them to the session state map of the current service. - serviceState.mSessionTokens.add(sessionToken); + serviceState.sessionTokens.add(sessionToken); - if (serviceState.mService != null) { - createSessionInternalLocked(serviceState.mService, sessionToken, + if (serviceState.service != null) { + createSessionInternalLocked(serviceState.service, sessionToken, resolvedUserId); } else { updateServiceConnectionLocked(info.getComponent(), resolvedUserId); @@ -1192,10 +1011,10 @@ public final class TvInputManagerService extends SystemService { try { SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, resolvedUserId); - if (sessionState.mHardwareSessionToken == null) { + if (sessionState.hardwareSessionToken == null) { getSessionLocked(sessionState).setSurface(surface); } else { - getSessionLocked(sessionState.mHardwareSessionToken, + getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID, resolvedUserId).setSurface(surface); } } catch (RemoteException e) { @@ -1223,9 +1042,10 @@ public final class TvInputManagerService extends SystemService { try { SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, resolvedUserId); - getSessionLocked(sessionState).dispatchSurfaceChanged(format, width, height); - if (sessionState.mHardwareSessionToken != null) { - getSessionLocked(sessionState.mHardwareSessionToken, Process.SYSTEM_UID, + getSessionLocked(sessionState).dispatchSurfaceChanged(format, width, + height); + if (sessionState.hardwareSessionToken != null) { + getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID, resolvedUserId).dispatchSurfaceChanged(format, width, height); } } catch (RemoteException e) { @@ -1251,10 +1071,10 @@ public final class TvInputManagerService extends SystemService { SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, resolvedUserId); getSessionLocked(sessionState).setVolume(volume); - if (sessionState.mHardwareSessionToken != null) { + if (sessionState.hardwareSessionToken != null) { // Here, we let the hardware session know only whether volume is on or // off to prevent that the volume is controlled in the both side. - getSessionLocked(sessionState.mHardwareSessionToken, + getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f) ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF); } @@ -1288,7 +1108,7 @@ public final class TvInputManagerService extends SystemService { // Log the start of watch. SomeArgs args = SomeArgs.obtain(); - args.arg1 = sessionState.mInfo.getComponent().getPackageName(); + args.arg1 = sessionState.info.getComponent().getPackageName(); args.arg2 = System.currentTimeMillis(); args.arg3 = ContentUris.parseId(channelUri); args.arg4 = params; @@ -1548,10 +1368,10 @@ public final class TvInputManagerService extends SystemService { return false; } for (SessionState sessionState : userState.sessionStateMap.values()) { - if (sessionState.mInfo.getId().equals(inputId) - && sessionState.mHardwareSessionToken != null) { + if (sessionState.info.getId().equals(inputId) + && sessionState.hardwareSessionToken != null) { hardwareInputId = userState.sessionStateMap.get( - sessionState.mHardwareSessionToken).mInfo.getId(); + sessionState.hardwareSessionToken).info.getId(); break; } } @@ -1580,8 +1400,8 @@ public final class TvInputManagerService extends SystemService { SessionState[] sessionStates = userState.sessionStateMap.values().toArray( new SessionState[0]); // Check if there is a wrapper input. - if (sessionStates[0].mHardwareSessionToken != null - || sessionStates[1].mHardwareSessionToken != null) { + if (sessionStates[0].hardwareSessionToken != null + || sessionStates[1].hardwareSessionToken != null) { return true; } } @@ -1641,15 +1461,15 @@ public final class TvInputManagerService extends SystemService { pw.increaseIndent(); - pw.println("mSessionTokens:"); + pw.println("sessionTokens:"); pw.increaseIndent(); - for (IBinder token : client.mSessionTokens) { + for (IBinder token : client.sessionTokens) { pw.println("" + token); } pw.decreaseIndent(); - pw.println("mClientTokens: " + client.mClientToken); - pw.println("mUserId: " + client.mUserId); + pw.println("clientTokens: " + client.clientToken); + pw.println("userId: " + client.userId); pw.decreaseIndent(); } @@ -1664,17 +1484,17 @@ public final class TvInputManagerService extends SystemService { pw.increaseIndent(); - pw.println("mSessionTokens:"); + pw.println("sessionTokens:"); pw.increaseIndent(); - for (IBinder token : service.mSessionTokens) { + for (IBinder token : service.sessionTokens) { pw.println("" + token); } pw.decreaseIndent(); - pw.println("mService: " + service.mService); - pw.println("mCallback: " + service.mCallback); - pw.println("mBound: " + service.mBound); - pw.println("mReconnecting: " + service.mReconnecting); + pw.println("service: " + service.service); + pw.println("callback: " + service.callback); + pw.println("bound: " + service.bound); + pw.println("reconnecting: " + service.reconnecting); pw.decreaseIndent(); } @@ -1688,15 +1508,15 @@ public final class TvInputManagerService extends SystemService { pw.println(entry.getKey() + ": " + session); pw.increaseIndent(); - pw.println("mInfo: " + session.mInfo); - pw.println("mClient: " + session.mClient); - pw.println("mSeq: " + session.mSeq); - pw.println("mCallingUid: " + session.mCallingUid); - pw.println("mUserId: " + session.mUserId); - pw.println("mSessionToken: " + session.mSessionToken); - pw.println("mSession: " + session.mSession); - pw.println("mLogUri: " + session.mLogUri); - pw.println("mHardwareSessionToken: " + session.mHardwareSessionToken); + pw.println("info: " + session.info); + pw.println("client: " + session.client); + pw.println("seq: " + session.seq); + pw.println("callingUid: " + session.callingUid); + pw.println("userId: " + session.userId); + pw.println("sessionToken: " + session.sessionToken); + pw.println("session: " + session.session); + pw.println("logUri: " + session.logUri); + pw.println("hardwareSessionToken: " + session.hardwareSessionToken); pw.decreaseIndent(); } pw.decreaseIndent(); @@ -1715,19 +1535,6 @@ public final class TvInputManagerService extends SystemService { } } - private static final class TvInputState { - // A TvInputInfo object which represents the TV input. - private TvInputInfo mInfo; - - // The state of TV input. Connected by default. - private int mState = INPUT_STATE_CONNECTED; - - @Override - public String toString() { - return "mInfo: " + mInfo + "; mState: " + mState; - } - } - private static final class UserState { // A mapping from the TV input id to its TvInputState. private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>(); @@ -1735,8 +1542,9 @@ public final class TvInputManagerService extends SystemService { // A set of all TV input packages. private final Set<String> packageSet = new HashSet<String>(); - // A set of all TV content rating system xml uris. - private final Set<Uri> ratingSystemXmlUriSet = new HashSet<Uri>(); + // A list of all TV content rating systems defined. + private final List<TvContentRatingSystemInfo> + contentRatingSystemList = new ArrayList<TvContentRatingSystemInfo>(); // A mapping from the token of a client to its state. private final Map<IBinder, ClientState> clientStateMap = @@ -1767,104 +1575,117 @@ public final class TvInputManagerService extends SystemService { } private final class ClientState implements IBinder.DeathRecipient { - private final List<IBinder> mSessionTokens = new ArrayList<IBinder>(); + private final List<IBinder> sessionTokens = new ArrayList<IBinder>(); - private IBinder mClientToken; - private final int mUserId; + private IBinder clientToken; + private final int userId; ClientState(IBinder clientToken, int userId) { - mClientToken = clientToken; - mUserId = userId; + this.clientToken = clientToken; + this.userId = userId; } public boolean isEmpty() { - return mSessionTokens.isEmpty(); + return sessionTokens.isEmpty(); } @Override public void binderDied() { synchronized (mLock) { - UserState userState = getUserStateLocked(mUserId); + UserState userState = getUserStateLocked(userId); // DO NOT remove the client state of clientStateMap in this method. It will be // removed in releaseSessionLocked(). - ClientState clientState = userState.clientStateMap.get(mClientToken); + ClientState clientState = userState.clientStateMap.get(clientToken); if (clientState != null) { - while (clientState.mSessionTokens.size() > 0) { + while (clientState.sessionTokens.size() > 0) { releaseSessionLocked( - clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId); + clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId); } } - mClientToken = null; + clientToken = null; } } } private final class ServiceState { - private final List<IBinder> mSessionTokens = new ArrayList<IBinder>(); - private final ServiceConnection mConnection; - private final ComponentName mComponent; - private final boolean mIsHardware; - private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>(); + private final List<IBinder> sessionTokens = new ArrayList<IBinder>(); + private final ServiceConnection connection; + private final ComponentName component; + private final boolean isHardware; + private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>(); - private ITvInputService mService; - private ServiceCallback mCallback; - private boolean mBound; - private boolean mReconnecting; + private ITvInputService service; + private ServiceCallback callback; + private boolean bound; + private boolean reconnecting; private ServiceState(ComponentName component, int userId) { - mComponent = component; - mConnection = new InputServiceConnection(component, userId); - mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mComponent); + this.component = component; + this.connection = new InputServiceConnection(component, userId); + this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component); + } + } + + private static final class TvInputState { + // A TvInputInfo object which represents the TV input. + private TvInputInfo info; + + // The state of TV input. Connected by default. + private int state = INPUT_STATE_CONNECTED; + + @Override + public String toString() { + return "info: " + info + "; state: " + state; } } private final class SessionState implements IBinder.DeathRecipient { - private final TvInputInfo mInfo; - private final ITvInputClient mClient; - private final int mSeq; - private final int mCallingUid; - private final int mUserId; - private final IBinder mSessionToken; - private ITvInputSession mSession; - private Uri mLogUri; + private final TvInputInfo info; + private final ITvInputClient client; + private final int seq; + private final int callingUid; + private final int userId; + private final IBinder sessionToken; + private ITvInputSession session; + private Uri logUri; // Not null if this session represents an external device connected to a hardware TV input. - private IBinder mHardwareSessionToken; + private IBinder hardwareSessionToken; private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq, int callingUid, int userId) { - mSessionToken = sessionToken; - mInfo = info; - mClient = client; - mSeq = seq; - mCallingUid = callingUid; - mUserId = userId; + this.sessionToken = sessionToken; + this.info = info; + this.client = client; + this.seq = seq; + this.callingUid = callingUid; + this.userId = userId; } @Override public void binderDied() { synchronized (mLock) { - mSession = null; - if (mClient != null) { + session = null; + if (client != null) { try { - mClient.onSessionReleased(mSeq); + client.onSessionReleased(seq); } catch(RemoteException e) { Slog.e(TAG, "error in onSessionReleased", e); } } // If there are any other sessions based on this session, they should be released. - UserState userState = getUserStateLocked(mUserId); + UserState userState = getUserStateLocked(userId); for (SessionState sessionState : userState.sessionStateMap.values()) { - if (mSessionToken == sessionState.mHardwareSessionToken) { - releaseSessionLocked(sessionState.mSessionToken, Process.SYSTEM_UID, - mUserId); + if (sessionToken == sessionState.hardwareSessionToken) { + releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID, + userId); try { - sessionState.mClient.onSessionReleased(sessionState.mSeq); + sessionState.client.onSessionReleased(sessionState.seq); } catch (RemoteException e) { Slog.e(TAG, "error in onSessionReleased", e); } } } - removeSessionStateLocked(mSessionToken, mUserId); + removeSessionStateLocked(sessionToken, userId); } } } @@ -1886,37 +1707,37 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { UserState userState = getUserStateLocked(mUserId); ServiceState serviceState = userState.serviceStateMap.get(mComponent); - serviceState.mService = ITvInputService.Stub.asInterface(service); + serviceState.service = ITvInputService.Stub.asInterface(service); // Register a callback, if we need to. - if (serviceState.mIsHardware && serviceState.mCallback == null) { - serviceState.mCallback = new ServiceCallback(mComponent, mUserId); + if (serviceState.isHardware && serviceState.callback == null) { + serviceState.callback = new ServiceCallback(mComponent, mUserId); try { - serviceState.mService.registerCallback(serviceState.mCallback); + serviceState.service.registerCallback(serviceState.callback); } catch (RemoteException e) { Slog.e(TAG, "error in registerCallback", e); } } // And create sessions, if any. - for (IBinder sessionToken : serviceState.mSessionTokens) { - createSessionInternalLocked(serviceState.mService, sessionToken, mUserId); + for (IBinder sessionToken : serviceState.sessionTokens) { + createSessionInternalLocked(serviceState.service, sessionToken, mUserId); } for (TvInputState inputState : userState.inputMap.values()) { - if (inputState.mInfo.getComponent().equals(component) - && inputState.mState != INPUT_STATE_DISCONNECTED) { - notifyInputStateChangedLocked(userState, inputState.mInfo.getId(), - inputState.mState, null); + if (inputState.info.getComponent().equals(component) + && inputState.state != INPUT_STATE_DISCONNECTED) { + notifyInputStateChangedLocked(userState, inputState.info.getId(), + inputState.state, null); } } - if (serviceState.mIsHardware) { + if (serviceState.isHardware) { List<TvInputHardwareInfo> hardwareInfoList = mTvInputHardwareManager.getHardwareList(); for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) { try { - serviceState.mService.notifyHardwareAdded(hardwareInfo); + serviceState.service.notifyHardwareAdded(hardwareInfo); } catch (RemoteException e) { Slog.e(TAG, "error in notifyHardwareAdded", e); } @@ -1926,7 +1747,7 @@ public final class TvInputManagerService extends SystemService { mTvInputHardwareManager.getHdmiDeviceList(); for (HdmiDeviceInfo deviceInfo : deviceInfoList) { try { - serviceState.mService.notifyHdmiDeviceAdded(deviceInfo); + serviceState.service.notifyHdmiDeviceAdded(deviceInfo); } catch (RemoteException e) { Slog.e(TAG, "error in notifyHdmiDeviceAdded", e); } @@ -1948,16 +1769,16 @@ public final class TvInputManagerService extends SystemService { UserState userState = getUserStateLocked(mUserId); ServiceState serviceState = userState.serviceStateMap.get(mComponent); if (serviceState != null) { - serviceState.mReconnecting = true; - serviceState.mBound = false; - serviceState.mService = null; - serviceState.mCallback = null; + serviceState.reconnecting = true; + serviceState.bound = false; + serviceState.service = null; + serviceState.callback = null; abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId); for (TvInputState inputState : userState.inputMap.values()) { - if (inputState.mInfo.getComponent().equals(component)) { - notifyInputStateChangedLocked(userState, inputState.mInfo.getId(), + if (inputState.info.getComponent().equals(component)) { + notifyInputStateChangedLocked(userState, inputState.info.getId(), INPUT_STATE_DISCONNECTED, null); } } @@ -1990,7 +1811,7 @@ public final class TvInputManagerService extends SystemService { private void addTvInputLocked(TvInputInfo inputInfo) { ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); - serviceState.mInputList.add(inputInfo); + serviceState.inputList.add(inputInfo); buildTvInputListLocked(mUserId); } @@ -2020,7 +1841,7 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); boolean removed = false; - for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator(); + for (Iterator<TvInputInfo> it = serviceState.inputList.iterator(); it.hasNext(); ) { if (it.next().getId().equals(inputId)) { it.remove(); @@ -2038,7 +1859,211 @@ public final class TvInputManagerService extends SystemService { } } - private final class WatchLogHandler extends Handler { + private final class SessionCallback extends ITvInputSessionCallback.Stub { + private final SessionState sessionState; + private final InputChannel[] mChannels; + + SessionCallback(SessionState sessionState, InputChannel[] channels) { + this.sessionState = sessionState; + mChannels = channels; + } + + @Override + public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) { + if (DEBUG) { + Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.info.getId() + ")"); + } + synchronized (mLock) { + sessionState.session = session; + sessionState.hardwareSessionToken = harewareSessionToken; + if (session == null) { + removeSessionStateLocked(sessionState.sessionToken, sessionState.userId); + sendSessionTokenToClientLocked(sessionState.client, + sessionState.info.getId(), null, null, sessionState.seq); + } else { + try { + session.asBinder().linkToDeath(sessionState, 0); + } catch (RemoteException e) { + Slog.e(TAG, "session process has already died", e); + } + + IBinder clientToken = sessionState.client.asBinder(); + UserState userState = getUserStateLocked(sessionState.userId); + ClientState clientState = userState.clientStateMap.get(clientToken); + if (clientState == null) { + clientState = createClientStateLocked(clientToken, sessionState.userId); + } + clientState.sessionTokens.add(sessionState.sessionToken); + + sendSessionTokenToClientLocked(sessionState.client, + sessionState.info.getId(), sessionState.sessionToken, mChannels[0], + sessionState.seq); + } + mChannels[0].dispose(); + } + } + + @Override + public void onChannelRetuned(Uri channelUri) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onChannelRetuned(" + channelUri + ")"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + // TODO: Consider adding this channel change in the watch log. When we do + // that, how we can protect the watch log from malicious tv inputs should + // be addressed. e.g. add a field which represents where the channel change + // originated from. + sessionState.client.onChannelRetuned(channelUri, sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onChannelRetuned", e); + } + } + } + + @Override + public void onTracksChanged(List<TvTrackInfo> tracks) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTracksChanged(" + tracks + ")"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + sessionState.client.onTracksChanged(tracks, sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTracksChanged", e); + } + } + } + + @Override + public void onTrackSelected(int type, String trackId) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + sessionState.client.onTrackSelected(type, trackId, sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTrackSelected", e); + } + } + } + + @Override + public void onVideoAvailable() { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onVideoAvailable()"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + sessionState.client.onVideoAvailable(sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onVideoAvailable", e); + } + } + } + + @Override + public void onVideoUnavailable(int reason) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onVideoUnavailable(" + reason + ")"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + sessionState.client.onVideoUnavailable(reason, sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onVideoUnavailable", e); + } + } + } + + @Override + public void onContentAllowed() { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onContentAllowed()"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + sessionState.client.onContentAllowed(sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onContentAllowed", e); + } + } + } + + @Override + public void onContentBlocked(String rating) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onContentBlocked()"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + sessionState.client.onContentBlocked(rating, sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onContentBlocked", e); + } + } + } + + @Override + public void onLayoutSurface(int left, int top, int right, int bottom) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + + ", right=" + right + ", bottom=" + bottom + ",)"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + sessionState.client.onLayoutSurface(left, top, right, bottom, sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onLayoutSurface", e); + } + } + } + + @Override + public void onSessionEvent(String eventType, Bundle eventArgs) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")"); + } + if (sessionState.session == null || sessionState.client == null) { + return; + } + try { + sessionState.client.onSessionEvent(eventType, eventArgs, + sessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onSessionEvent", e); + } + } + } + } + + private static final class WatchLogHandler extends Handler { // There are only two kinds of watch events that can happen on the system: // 1. The current TV input session is tuned to a new channel. // 2. The session is released for some reason. @@ -2050,8 +2075,11 @@ public final class TvInputManagerService extends SystemService { private static final int MSG_LOG_WATCH_START = 1; private static final int MSG_LOG_WATCH_END = 2; - public WatchLogHandler(Looper looper) { + private final ContentResolver mContentResolver; + + public WatchLogHandler(ContentResolver contentResolver, Looper looper) { super(looper); + mContentResolver = contentResolver; } @Override @@ -2137,7 +2165,7 @@ public final class TvInputManagerService extends SystemService { } } - final class HardwareListener implements TvInputHardwareManager.Listener { + private final class HardwareListener implements TvInputHardwareManager.Listener { @Override public void onStateChanged(String inputId, int state) { synchronized (mLock) { @@ -2151,9 +2179,9 @@ public final class TvInputManagerService extends SystemService { UserState userState = getUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.mIsHardware || serviceState.mService == null) continue; + if (!serviceState.isHardware || serviceState.service == null) continue; try { - serviceState.mService.notifyHardwareAdded(info); + serviceState.service.notifyHardwareAdded(info); } catch (RemoteException e) { Slog.e(TAG, "error in notifyHardwareAdded", e); } @@ -2167,9 +2195,9 @@ public final class TvInputManagerService extends SystemService { UserState userState = getUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.mIsHardware || serviceState.mService == null) continue; + if (!serviceState.isHardware || serviceState.service == null) continue; try { - serviceState.mService.notifyHardwareRemoved(info); + serviceState.service.notifyHardwareRemoved(info); } catch (RemoteException e) { Slog.e(TAG, "error in notifyHardwareRemoved", e); } @@ -2183,9 +2211,9 @@ public final class TvInputManagerService extends SystemService { UserState userState = getUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.mIsHardware || serviceState.mService == null) continue; + if (!serviceState.isHardware || serviceState.service == null) continue; try { - serviceState.mService.notifyHdmiDeviceAdded(deviceInfo); + serviceState.service.notifyHdmiDeviceAdded(deviceInfo); } catch (RemoteException e) { Slog.e(TAG, "error in notifyHdmiDeviceAdded", e); } @@ -2199,9 +2227,9 @@ public final class TvInputManagerService extends SystemService { UserState userState = getUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.mIsHardware || serviceState.mService == null) continue; + if (!serviceState.isHardware || serviceState.service == null) continue; try { - serviceState.mService.notifyHdmiDeviceRemoved(deviceInfo); + serviceState.service.notifyHdmiDeviceRemoved(deviceInfo); } catch (RemoteException e) { Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 09c4e20..9ceac41 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -41,11 +41,11 @@ class Task { void addAppToken(int addPos, AppWindowToken wtoken) { final int lastPos = mAppTokens.size(); - if (addPos > lastPos) { - // We lost an app token. Don't crash though. - Slog.e(TAG, "Task.addAppToken: Out of bounds attempt token=" + wtoken + " addPos=" - + addPos + " lastPos=" + lastPos); - addPos = lastPos; + for (int pos = 0; pos < lastPos && pos < addPos; ++pos) { + if (mAppTokens.get(pos).removed) { + // addPos assumes removed tokens are actually gone. + ++addPos; + } } mAppTokens.add(addPos, wtoken); mDeferRemoval = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e144bde..c70cb22 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2382,7 +2382,6 @@ public class WindowManagerService extends IWindowManager.Stub origId = Binder.clearCallingIdentity(); if (addToken) { - Slog.w("BadTokenDebug", "addWindow: Adding token=" + token + " attrs.token=" + attrs.token); mTokenMap.put(attrs.token, token); } win.attach(); @@ -2664,9 +2663,7 @@ public class WindowManagerService extends IWindowManager.Stub + token.windows.size()); if (token.windows.size() == 0) { if (!token.explicit) { - WindowToken wtoken = mTokenMap.remove(token.token); - Slog.w("BadTokenDebug", "removeWindowInnerLocked: Removing token=" + token + " removed=" + - wtoken + " Callers=" + Debug.getCallers(4)); + mTokenMap.remove(token.token); } else if (atoken != null) { atoken.firstWindowDrawn = false; } @@ -3452,7 +3449,6 @@ public class WindowManagerService extends IWindowManager.Stub return; } wtoken = new WindowToken(this, token, type, true); - Slog.w("BadTokenDebug", "addWindowToken: Adding token=" + token + " wtoken=" + wtoken); mTokenMap.put(token, wtoken); if (type == TYPE_WALLPAPER) { mWallpaperTokens.add(wtoken); @@ -3471,8 +3467,6 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { DisplayContent displayContent = null; WindowToken wtoken = mTokenMap.remove(token); - Slog.w("BadTokenDebug", "removeWindowToken: Removing token=" + token + " removed=" + wtoken - + " Callers=" + Debug.getCallers(3)); if (wtoken != null) { boolean delayed = false; if (!wtoken.hidden) { @@ -3589,7 +3583,6 @@ public class WindowManagerService extends IWindowManager.Stub task.addAppToken(addPos, atoken); } - Slog.w("BadTokenDebug", "addAppToken: Adding token=" + token.asBinder() + " atoken=" + atoken); mTokenMap.put(token.asBinder(), atoken); // Application tokens start out hidden. @@ -4689,7 +4682,6 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { WindowToken basewtoken = mTokenMap.remove(token); - Slog.w("BadTokenDebug", "removeAppToke: Removing token=" + token + " removed=" + basewtoken); if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken); delayed = setTokenVisibilityLocked(wtoken, null, false, diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 38433ae..4b7dd08 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -754,7 +754,11 @@ class WindowStateAnimator { final boolean isHwAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format; - if (!PixelFormat.formatHasAlpha(attrs.format)) { + if (!PixelFormat.formatHasAlpha(attrs.format) + && attrs.surfaceInsets.left == 0 + && attrs.surfaceInsets.top == 0 + && attrs.surfaceInsets.right == 0 + && attrs.surfaceInsets.bottom == 0) { flags |= SurfaceControl.OPAQUE; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 3bc226a..564a3df 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3740,6 +3740,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { // Check for permissions if (who == null) { @@ -3753,7 +3754,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { mUserManager.setUserEnabled(userId); Intent intent = new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED); - intent.putExtra(Intent.EXTRA_USER, new UserHandle(UserHandle.getCallingUserId())); + intent.putExtra(Intent.EXTRA_USER, new UserHandle(userHandle)); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); // TODO This should send to parent of profile (which is always owner at the moment). @@ -3940,6 +3941,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void addPersistentPreferredActivity(ComponentName who, IntentFilter filter, ComponentName activity) { + final int userHandle = UserHandle.getCallingUserId(); + synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -3949,7 +3952,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { IPackageManager pm = AppGlobals.getPackageManager(); long id = Binder.clearCallingIdentity(); try { - pm.addPersistentPreferredActivity(filter, activity, UserHandle.getCallingUserId()); + pm.addPersistentPreferredActivity(filter, activity, userHandle); } catch (RemoteException re) { // Shouldn't happen } finally { @@ -3960,6 +3963,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) { + final int userHandle = UserHandle.getCallingUserId(); + synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -3969,7 +3974,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { IPackageManager pm = AppGlobals.getPackageManager(); long id = Binder.clearCallingIdentity(); try { - pm.clearPackagePersistentPreferredActivities(packageName, UserHandle.getCallingUserId()); + pm.clearPackagePersistentPreferredActivities(packageName, userHandle); } catch (RemoteException re) { // Shouldn't happen } finally { @@ -4595,7 +4600,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setUserRestriction(ComponentName who, String key, boolean enabled) { final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId()); - synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -4606,13 +4610,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!isDeviceOwner && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) { throw new SecurityException("Profile owners cannot set user restriction " + key); } + boolean alreadyRestricted = mUserManager.hasUserRestriction(key, userHandle); + IAudioService iAudioService = null; + if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key) + || UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { + iAudioService = IAudioService.Stub.asInterface( + ServiceManager.getService(Context.AUDIO_SERVICE)); + } + + if (enabled && !alreadyRestricted) { + try { + if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { + iAudioService.setMicrophoneMute(true, who.getPackageName()); + } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { + iAudioService.setMasterMute(true, 0, who.getPackageName(), null); + } + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); + } + } long id = Binder.clearCallingIdentity(); try { - AudioManager audioManager = - (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - boolean alreadyRestricted = mUserManager.hasUserRestriction(key); - if (enabled && !alreadyRestricted) { if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) { Settings.Secure.putIntForUser(mContext.getContentResolver(), @@ -4643,25 +4662,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, 0, userHandle.getIdentifier()); - } else if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { - audioManager.setMicrophoneMute(true); - } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - audioManager.setMasterMute(true); } } - mUserManager.setUserRestriction(key, enabled, userHandle); - - if (!enabled && alreadyRestricted) { + } finally { + restoreCallingIdentity(id); + } + if (!enabled && alreadyRestricted) { + try { if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { - audioManager.setMicrophoneMute(false); + iAudioService.setMicrophoneMute(false, who.getPackageName()); } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - audioManager.setMasterMute(false); + iAudioService.setMasterMute(false, 0, who.getPackageName(), null); } + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); } - - } finally { - restoreCallingIdentity(id); } } } @@ -4890,7 +4906,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean getUninstallBlocked(ComponentName who, String packageName) { + public boolean isUninstallBlocked(ComponentName who, String packageName) { final int userId = UserHandle.getCallingUserId(); synchronized (this) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0f2aabc..f339dba 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -918,7 +918,7 @@ public final class SystemServer { mSystemServiceManager.startService(HdmiControlService.class); } - if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LIVE_TV)) { mSystemServiceManager.startService(TvInputManagerService.class); } diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java index a71f739..7c596c1 100644 --- a/telecomm/java/android/telecomm/Call.java +++ b/telecomm/java/android/telecomm/Call.java @@ -89,6 +89,7 @@ public final class Call { private final int mCallerDisplayNamePresentation; private final PhoneAccountHandle mAccountHandle; private final int mCallCapabilities; + private final int mCallProperties; private final int mDisconnectCauseCode; private final String mDisconnectCauseMessage; private final long mConnectTimeMillis; @@ -145,6 +146,14 @@ public final class Call { } /** + * @return A bitmask of the properties of the {@code Call}, as defined in + * {@link CallProperties}. + */ + public int getCallProperties() { + return mCallProperties; + } + + /** * @return For a {@link #STATE_DISCONNECTED} {@code Call}, the disconnect cause expressed * as a code chosen from among those declared in {@link DisconnectCause}. */ @@ -210,6 +219,7 @@ public final class Call { d.mCallerDisplayNamePresentation) && Objects.equals(mAccountHandle, d.mAccountHandle) && Objects.equals(mCallCapabilities, d.mCallCapabilities) && + Objects.equals(mCallProperties, d.mCallProperties) && Objects.equals(mDisconnectCauseCode, d.mDisconnectCauseCode) && Objects.equals(mDisconnectCauseMessage, d.mDisconnectCauseMessage) && Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) && @@ -230,6 +240,7 @@ public final class Call { Objects.hashCode(mCallerDisplayNamePresentation) + Objects.hashCode(mAccountHandle) + Objects.hashCode(mCallCapabilities) + + Objects.hashCode(mCallProperties) + Objects.hashCode(mDisconnectCauseCode) + Objects.hashCode(mDisconnectCauseMessage) + Objects.hashCode(mConnectTimeMillis) + @@ -247,6 +258,7 @@ public final class Call { int callerDisplayNamePresentation, PhoneAccountHandle accountHandle, int capabilities, + int properties, int disconnectCauseCode, String disconnectCauseMessage, long connectTimeMillis, @@ -260,6 +272,7 @@ public final class Call { mCallerDisplayNamePresentation = callerDisplayNamePresentation; mAccountHandle = accountHandle; mCallCapabilities = capabilities; + mCallProperties = properties; mDisconnectCauseCode = disconnectCauseCode; mDisconnectCauseMessage = disconnectCauseMessage; mConnectTimeMillis = connectTimeMillis; @@ -642,6 +655,7 @@ public final class Call { parcelableCall.getCallerDisplayNamePresentation(), parcelableCall.getAccountHandle(), parcelableCall.getCapabilities(), + parcelableCall.getProperties(), parcelableCall.getDisconnectCauseCode(), parcelableCall.getDisconnectCauseMsg(), parcelableCall.getConnectTimeMillis(), @@ -743,6 +757,16 @@ public final class Call { fireStartActivity(intent); } + /** {@hide} */ + final void internalSetDisconnected() { + if (mState != Call.STATE_DISCONNECTED) { + mState = Call.STATE_DISCONNECTED; + fireStateChanged(mState); + fireCallDestroyed(); + mPhone.internalRemoveCall(this); + } + } + private void fireStateChanged(int newState) { for (Listener listener : mListeners) { listener.onStateChanged(this, newState); diff --git a/telecomm/java/android/telecomm/CallProperties.java b/telecomm/java/android/telecomm/CallProperties.java new file mode 100644 index 0000000..90eb0cb --- /dev/null +++ b/telecomm/java/android/telecomm/CallProperties.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.telecomm; + +/** + * Defines properties of a phone call which may be affected by changes to the call. + * @hide + */ +public class CallProperties { + /** Call is currently in a conference call. */ + public static final int CONFERENCE = 0x00000001; +} diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java index a062632..cbcee75 100644 --- a/telecomm/java/android/telecomm/InCallService.java +++ b/telecomm/java/android/telecomm/InCallService.java @@ -163,6 +163,16 @@ public abstract class InCallService extends Service { return new InCallServiceBinder(); } + @Override + public boolean onUnbind(Intent intent) { + Phone oldPhone = mPhone; + mPhone = null; + + oldPhone.destroy(); + onPhoneDestroyed(oldPhone); + return false; + } + /** * Obtain the {@code Phone} associated with this {@code InCallService}. * diff --git a/telecomm/java/android/telecomm/ParcelableCall.java b/telecomm/java/android/telecomm/ParcelableCall.java index 8098b94..a2aa192 100644 --- a/telecomm/java/android/telecomm/ParcelableCall.java +++ b/telecomm/java/android/telecomm/ParcelableCall.java @@ -40,6 +40,7 @@ public final class ParcelableCall implements Parcelable { private final String mDisconnectCauseMsg; private final List<String> mCannedSmsResponses; private final int mCapabilities; + private final int mProperties; private final long mConnectTimeMillis; private final Uri mHandle; private final int mHandlePresentation; @@ -63,6 +64,7 @@ public final class ParcelableCall implements Parcelable { String disconnectCauseMsg, List<String> cannedSmsResponses, int capabilities, + int properties, long connectTimeMillis, Uri handle, int handlePresentation, @@ -83,6 +85,7 @@ public final class ParcelableCall implements Parcelable { mDisconnectCauseMsg = disconnectCauseMsg; mCannedSmsResponses = cannedSmsResponses; mCapabilities = capabilities; + mProperties = properties; mConnectTimeMillis = connectTimeMillis; mHandle = handle; mHandlePresentation = handlePresentation; @@ -137,6 +140,9 @@ public final class ParcelableCall implements Parcelable { return mCapabilities; } + /** Bitmask of properties of the call. */ + public int getProperties() { return mProperties; } + /** The time that the call switched to the active state. */ public long getConnectTimeMillis() { return mConnectTimeMillis; @@ -246,6 +252,7 @@ public final class ParcelableCall implements Parcelable { List<String> cannedSmsResponses = new ArrayList<>(); source.readList(cannedSmsResponses, classLoader); int capabilities = source.readInt(); + int properties = source.readInt(); long connectTimeMillis = source.readLong(); Uri handle = source.readParcelable(classLoader); int handlePresentation = source.readInt(); @@ -264,10 +271,10 @@ public final class ParcelableCall implements Parcelable { source.readList(conferenceableCallIds, classLoader); Bundle extras = source.readParcelable(classLoader); return new ParcelableCall(id, state, disconnectCauseCode, disconnectCauseMsg, - cannedSmsResponses, capabilities, connectTimeMillis, handle, handlePresentation, - callerDisplayName, callerDisplayNamePresentation, gatewayInfo, - accountHandle, videoCallProvider, parentCallId, childCallIds, statusHints, - videoState, conferenceableCallIds, extras); + cannedSmsResponses, capabilities, properties, connectTimeMillis, handle, + handlePresentation, callerDisplayName, callerDisplayNamePresentation, + gatewayInfo, accountHandle, videoCallProvider, parentCallId, childCallIds, + statusHints, videoState, conferenceableCallIds, extras); } @Override @@ -291,6 +298,7 @@ public final class ParcelableCall implements Parcelable { destination.writeString(mDisconnectCauseMsg); destination.writeList(mCannedSmsResponses); destination.writeInt(mCapabilities); + destination.writeInt(mProperties); destination.writeLong(mConnectTimeMillis); destination.writeParcelable(mHandle, 0); destination.writeInt(mHandlePresentation); diff --git a/telecomm/java/android/telecomm/Phone.java b/telecomm/java/android/telecomm/Phone.java index e125342..d90d954 100644 --- a/telecomm/java/android/telecomm/Phone.java +++ b/telecomm/java/android/telecomm/Phone.java @@ -20,7 +20,6 @@ import android.annotation.SystemApi; import android.app.PendingIntent; import android.util.ArrayMap; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -83,7 +82,7 @@ public final class Phone { // A List allows us to keep the Calls in a stable iteration order so that casually developed // user interface components do not incur any spurious jank - private final List<Call> mCalls = new ArrayList<>(); + private final List<Call> mCalls = new CopyOnWriteArrayList<>(); // An unmodifiable view of the above List can be safely shared with subclass implementations private final List<Call> mUnmodifiableCalls = Collections.unmodifiableList(mCalls); @@ -160,6 +159,18 @@ public final class Phone { } /** + * Called to destroy the phone and cleanup any lingering calls. + * @hide + */ + final void destroy() { + for (Call call : mCalls) { + if (call.getState() != Call.STATE_DISCONNECTED) { + call.internalSetDisconnected(); + } + } + } + + /** * Adds a listener to this {@code Phone}. * * @param listener A {@code Listener} object. diff --git a/telecomm/java/android/telecomm/PhoneAccount.java b/telecomm/java/android/telecomm/PhoneAccount.java index 411f48c..97a7b9d 100644 --- a/telecomm/java/android/telecomm/PhoneAccount.java +++ b/telecomm/java/android/telecomm/PhoneAccount.java @@ -93,7 +93,7 @@ public class PhoneAccount implements Parcelable { private CharSequence mLabel; private CharSequence mShortDescription; - private Builder() {} + public Builder() {} public Builder withAccountHandle(PhoneAccountHandle value) { this.mAccountHandle = value; diff --git a/telecomm/java/android/telecomm/PhoneCapabilities.java b/telecomm/java/android/telecomm/PhoneCapabilities.java index 45168d5..0c6a1ef 100644 --- a/telecomm/java/android/telecomm/PhoneCapabilities.java +++ b/telecomm/java/android/telecomm/PhoneCapabilities.java @@ -19,7 +19,6 @@ package android.telecomm; /** * Defines capabilities a phone call can support, such as conference calling and video telephony. * Also defines properties of a phone call, such as whether it is using VoLTE technology. - */ public final class PhoneCapabilities { /** Call can currently be put on hold or unheld. */ diff --git a/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl index 42c77d7..0ab7564 100644 --- a/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl +++ b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl @@ -20,6 +20,8 @@ import android.content.ComponentName; /** * Simple response callback object. + * + * {@hide} */ oneway interface RemoteServiceCallback { void onError(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 71b796a..cdee3de 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3105,13 +3105,13 @@ public class TelephonyManager { /** @hide */ @SystemApi - public List<String> getCarrierPackageNamesForBroadcastIntent(Intent intent) { + public List<String> getCarrierPackageNamesForIntent(Intent intent) { try { - return getITelephony().getCarrierPackageNamesForBroadcastIntent(intent); + return getITelephony().getCarrierPackageNamesForIntent(intent); } catch (RemoteException ex) { - Rlog.e(TAG, "getCarrierPackageNamesForBroadcastIntent RemoteException", ex); + Rlog.e(TAG, "getCarrierPackageNamesForIntent RemoteException", ex); } catch (NullPointerException ex) { - Rlog.e(TAG, "getCarrierPackageNamesForBroadcastIntent NPE", ex); + Rlog.e(TAG, "getCarrierPackageNamesForIntent NPE", ex); } return null; } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index b1c3c4a..5c3dcdb 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -710,12 +710,12 @@ interface ITelephony { * Returns the package name of the carrier apps that should handle the input intent. * * @param packageManager PackageManager for getting receivers. - * @param intent Intent that will be broadcast. + * @param intent Intent that will be sent. * @return list of carrier app package names that can handle the intent. * Returns null if there is an error and an empty list if there * are no matching packages. */ - List<String> getCarrierPackageNamesForBroadcastIntent(in Intent intent); + List<String> getCarrierPackageNamesForIntent(in Intent intent); /** * Set whether Android should display a simplified Mobile Network Settings UI. diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index ea0db56..e03b9c8 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -366,15 +366,11 @@ public class ActivityTestMain extends Activity { if (recent.id >= 0) { // Stack on top. intent.putExtra(DocActivity.LABEL, "Stacked"); - task.startActivity(ActivityTestMain.this, intent, null); } else { // Start root activity. - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT - | Intent.FLAG_ACTIVITY_MULTIPLE_TASK - | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS); intent.putExtra(DocActivity.LABEL, "New Root"); - task.startActivity(ActivityTestMain.this, intent, null); } + task.startActivity(ActivityTestMain.this, intent, null); } return true; } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java index a81e063..afd6a8d 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java @@ -16,7 +16,6 @@ package com.android.test.hwui; -import android.animation.TimeInterpolator; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; @@ -28,11 +27,6 @@ import android.os.Trace; import android.view.HardwareCanvas; import android.view.RenderNodeAnimator; import android.view.View; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.OvershootInterpolator; -import android.webkit.WebChromeClient; -import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.ProgressBar; diff --git a/tests/MusicBrowserDemo/src/com/example/android/musicbrowserdemo/AppListFragment.java b/tests/MusicBrowserDemo/src/com/example/android/musicbrowserdemo/AppListFragment.java index 526ea5d..50633db 100644 --- a/tests/MusicBrowserDemo/src/com/example/android/musicbrowserdemo/AppListFragment.java +++ b/tests/MusicBrowserDemo/src/com/example/android/musicbrowserdemo/AppListFragment.java @@ -21,7 +21,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.media.browse.MediaBrowserService; +import android.service.media.MediaBrowserService; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTransaction; diff --git a/tests/MusicBrowserDemo/src/com/example/android/musicbrowserdemo/BrowserListFragment.java b/tests/MusicBrowserDemo/src/com/example/android/musicbrowserdemo/BrowserListFragment.java index 2fc77dc..64602d52 100644 --- a/tests/MusicBrowserDemo/src/com/example/android/musicbrowserdemo/BrowserListFragment.java +++ b/tests/MusicBrowserDemo/src/com/example/android/musicbrowserdemo/BrowserListFragment.java @@ -22,8 +22,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.media.browse.MediaBrowser; -import android.media.browse.MediaBrowserItem; -import android.media.browse.MediaBrowserService; +import android.service.media.MediaBrowserService; import android.os.Bundle; import android.net.Uri; import android.support.v4.app.FragmentActivity; @@ -57,9 +56,9 @@ public class BrowserListFragment extends ListFragment { private MediaBrowser mBrowser; private static class Item { - final MediaBrowserItem media; + final MediaBrowser.MediaItem media; - Item(MediaBrowserItem m) { + Item(MediaBrowser.MediaItem m) { this.media = m; } } @@ -103,13 +102,13 @@ public class BrowserListFragment extends ListFragment { final Item item = mItems.get(position); Log.i("BrowserListFragment", "Item clicked: " + position + " -- " - + mAdapter.getItem(position).media.getUri()); + + mAdapter.getItem(position).media.getDescription().getIconUri()); final BrowserListFragment fragment = new BrowserListFragment(); final Bundle args = new Bundle(); args.putParcelable(BrowserListFragment.ARG_COMPONENT, mComponent); - args.putParcelable(BrowserListFragment.ARG_URI, item.media.getUri()); + args.putParcelable(BrowserListFragment.ARG_URI, item.media.getDescription().getIconUri()); fragment.setArguments(args); getFragmentManager().beginTransaction() @@ -130,7 +129,8 @@ public class BrowserListFragment extends ListFragment { } mBrowser.subscribe(mUri, new MediaBrowser.SubscriptionCallback() { @Override - public void onChildrenLoaded(Uri parentUri, List<MediaBrowserItem> children) { + public void onChildrenLoaded(Uri parentUri, + List<MediaBrowser.MediaItem> children) { Log.d(TAG, "onChildrenLoaded parentUri=" + parentUri + " children= " + children); mItems.clear(); @@ -197,7 +197,7 @@ public class BrowserListFragment extends ListFragment { final TextView tv = (TextView)convertView; final Item item = mItems.get(position); - tv.setText(item.media.getTitle()); + tv.setText(item.media.getDescription().getTitle()); return convertView; } diff --git a/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java b/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java index 937f1e6..845db6c 100644 --- a/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java +++ b/tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java @@ -25,13 +25,14 @@ import android.content.res.Resources.NotFoundException; import android.database.MatrixCursor; import android.graphics.Bitmap; import android.media.AudioManager; +import android.media.MediaDescription; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; -import android.media.browse.MediaBrowserItem; -import android.media.browse.MediaBrowserService; -import android.media.browse.MediaBrowserService.BrowserRoot; +import android.media.browse.MediaBrowser; +import android.service.media.MediaBrowserService; +import android.service.media.MediaBrowserService.BrowserRoot; import android.media.session.MediaSession; import android.net.Uri; import android.net.wifi.WifiManager; @@ -122,17 +123,19 @@ public class BrowserService extends MediaBrowserService { @Override public void onLoadChildren(final Uri parentUri, - final Result<List<MediaBrowserItem>> result) { + final Result<List<MediaBrowser.MediaItem>> result) { new Handler().postDelayed(new Runnable() { public void run() { - final ArrayList<MediaBrowserItem> list = new ArrayList(); + final ArrayList<MediaBrowser.MediaItem> list = new ArrayList(); for (int i=0; i<10; i++) { - list.add(new MediaBrowserItem.Builder( - Uri.withAppendedPath(BASE_URI, Integer.toString(i)), - MediaBrowserItem.FLAG_BROWSABLE, "Title " + i) - .setSummary("Summary " + i) - .build()); + MediaDescription.Builder bob = new MediaDescription.Builder(); + bob.setTitle("Title " + i); + bob.setSubtitle("Summary " + i); + bob.setMediaId(Uri.withAppendedPath(BASE_URI, + Integer.toString(i)).toString()); + list.add(new MediaBrowser.MediaItem(MediaBrowser.MediaItem.FLAG_BROWSABLE, + bob.build())); } result.sendResult(list); @@ -141,11 +144,6 @@ public class BrowserService extends MediaBrowserService { result.detach(); } - @Override - public void onLoadIcon(Uri uri, int width, int height, Result<Bitmap> result) { - result.sendResult(null); - } - /* @Override public void query(final Query query, final IMetadataResultHandler metadataResultHandler, diff --git a/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java b/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java index a5bcda5..d1172ac 100644 --- a/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java +++ b/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java @@ -12,6 +12,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; +import android.media.MediaDescription; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; @@ -185,10 +186,10 @@ public class NotificationHelper extends BroadcastReceiver { text = "Empty metadata!"; art = null; } else { - MediaMetadata.Description description = mMetadata.getDescription(); + MediaDescription description = mMetadata.getDescription(); title = description.getTitle(); text = description.getSubtitle(); - art = description.getIcon(); + art = description.getIconBitmap(); } String playPauseLabel = ""; diff --git a/tests/StatusBar/src/com/android/statusbartest/PowerTest.java b/tests/StatusBar/src/com/android/statusbartest/PowerTest.java index 0cab10d..2ec620b 100644 --- a/tests/StatusBar/src/com/android/statusbartest/PowerTest.java +++ b/tests/StatusBar/src/com/android/statusbartest/PowerTest.java @@ -77,9 +77,9 @@ public class PowerTest extends TestActivity mProx.release(); } }, - new Test("Disable proximity (WAIT_FOR_PROXIMITY_NEGATIVE)") { + new Test("Disable proximity (WAIT_FOR_DISTANT_PROXIMITY") { public void run() { - mProx.release(PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE); + mProx.release(PowerManager.WAIT_FOR_DISTANT_PROXIMITY); } }, new Test("Enable proximity, wait 5 seconds then disable") { @@ -93,13 +93,13 @@ public class PowerTest extends TestActivity }, 5000); } }, - new Test("Enable proximity, wait 5 seconds then disable (WAIT_FOR_PROXIMITY_NEGATIVE)") { + new Test("Enable proximity, wait 5 seconds then disable (WAIT_FOR_DISTANT_PROXIMITY)") { public void run() { mProx.acquire(); mHandler.postDelayed(new Runnable() { @Override public void run() { - mProx.release(PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE); + mProx.release(PowerManager.WAIT_FOR_DISTANT_PROXIMITY); } }, 5000); } diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java index b6591bd..c08c1a3 100644 --- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java +++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.format.DateUtils; -import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -36,6 +35,7 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Map; public class UsageStatsActivity extends ListActivity { private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14; @@ -84,7 +84,7 @@ public class UsageStatsActivity extends ListActivity { private void updateAdapter() { long now = System.currentTimeMillis(); long beginTime = now - USAGE_STATS_PERIOD; - ArrayMap<String, UsageStats> stats = mUsageStatsManager.queryAndAggregateUsageStats( + Map<String, UsageStats> stats = mUsageStatsManager.queryAndAggregateUsageStats( beginTime, now); mAdapter.update(stats); } @@ -92,17 +92,13 @@ public class UsageStatsActivity extends ListActivity { private class Adapter extends BaseAdapter { private ArrayList<UsageStats> mStats = new ArrayList<>(); - public void update(ArrayMap<String, UsageStats> stats) { + public void update(Map<String, UsageStats> stats) { mStats.clear(); if (stats == null) { return; } - final int packageCount = stats.size(); - for (int i = 0; i < packageCount; i++) { - mStats.add(stats.valueAt(i)); - } - + mStats.addAll(stats.values()); Collections.sort(mStats, mComparator); notifyDataSetChanged(); } diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index e1a579c..1f01461 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -92,8 +92,7 @@ public class MainInteractionService extends VoiceInteractionService { break; case AlwaysOnHotwordDetector.STATE_KEYPHRASE_UNENROLLED: Log.i(TAG, "STATE_KEYPHRASE_UNENROLLED"); - Intent enroll = mHotwordDetector.getManageIntent( - AlwaysOnHotwordDetector.MANAGE_ACTION_ENROLL); + Intent enroll = mHotwordDetector.createIntentToEnroll(); Log.i(TAG, "Need to enroll with " + enroll); break; case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED: diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index b44e2d1..117fc24 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1594,6 +1594,11 @@ const ResTable& AaptAssets::getIncludedResources() const return mIncludedAssets.getResources(false); } +AssetManager& AaptAssets::getAssetManager() +{ + return mIncludedAssets; +} + void AaptAssets::print(const String8& prefix) const { String8 innerPrefix(prefix); diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 0c2576a..3fc9f81 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -561,6 +561,7 @@ public: status_t buildIncludedResources(Bundle* bundle); status_t addIncludedResources(const sp<AaptFile>& file); const ResTable& getIncludedResources() const; + AssetManager& getAssetManager(); void print(const String8& prefix) const; diff --git a/tools/aapt/AaptXml.cpp b/tools/aapt/AaptXml.cpp new file mode 100644 index 0000000..708e405 --- /dev/null +++ b/tools/aapt/AaptXml.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ResourceTypes.h> +#include <utils/String8.h> + +#include "AaptXml.h" + +using namespace android; + +namespace AaptXml { + +static String8 getStringAttributeAtIndex(const ResXMLTree& tree, ssize_t attrIndex, + String8* outError) { + Res_value value; + if (tree.getAttributeValue(attrIndex, &value) < 0) { + if (outError != NULL) { + *outError = "could not find attribute at index"; + } + return String8(); + } + + if (value.dataType != Res_value::TYPE_STRING) { + if (outError != NULL) { + *outError = "attribute is not a string value"; + } + return String8(); + } + + size_t len; + const uint16_t* str = tree.getAttributeStringValue(attrIndex, &len); + return str ? String8(str, len) : String8(); +} + +static int32_t getIntegerAttributeAtIndex(const ResXMLTree& tree, ssize_t attrIndex, + int32_t defValue, String8* outError) { + Res_value value; + if (tree.getAttributeValue(attrIndex, &value) < 0) { + if (outError != NULL) { + *outError = "could not find attribute at index"; + } + return defValue; + } + + if (value.dataType < Res_value::TYPE_FIRST_INT + || value.dataType > Res_value::TYPE_LAST_INT) { + if (outError != NULL) { + *outError = "attribute is not an integer value"; + } + return defValue; + } + return value.data; +} + + +ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes) { + size_t attrCount = tree.getAttributeCount(); + for (size_t i = 0; i < attrCount; i++) { + if (tree.getAttributeNameResID(i) == attrRes) { + return (ssize_t)i; + } + } + return -1; +} + +String8 getAttribute(const ResXMLTree& tree, const char* ns, + const char* attr, String8* outError) { + ssize_t idx = tree.indexOfAttribute(ns, attr); + if (idx < 0) { + return String8(); + } + return getStringAttributeAtIndex(tree, idx, outError); +} + +String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return String8(); + } + return getStringAttributeAtIndex(tree, idx, outError); +} + +String8 getResolvedAttribute(const ResTable& resTable, const ResXMLTree& tree, + uint32_t attrRes, String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return String8(); + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType == Res_value::TYPE_STRING) { + size_t len; + const uint16_t* str = tree.getAttributeStringValue(idx, &len); + return str ? String8(str, len) : String8(); + } + resTable.resolveReference(&value, 0); + if (value.dataType != Res_value::TYPE_STRING) { + if (outError != NULL) { + *outError = "attribute is not a string value"; + } + return String8(); + } + } + size_t len; + const Res_value* value2 = &value; + const char16_t* str = resTable.valueToString(value2, 0, NULL, &len); + return str ? String8(str, len) : String8(); +} + +int32_t getIntegerAttribute(const ResXMLTree& tree, const char* ns, + const char* attr, int32_t defValue, String8* outError) { + ssize_t idx = tree.indexOfAttribute(ns, attr); + if (idx < 0) { + return defValue; + } + return getIntegerAttributeAtIndex(tree, idx, defValue, outError); +} + +int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes, int32_t defValue, + String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return defValue; + } + return getIntegerAttributeAtIndex(tree, idx, defValue, outError); +} + +int32_t getResolvedIntegerAttribute(const ResTable& resTable, const ResXMLTree& tree, + uint32_t attrRes, int32_t defValue, String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return defValue; + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType == Res_value::TYPE_REFERENCE) { + resTable.resolveReference(&value, 0); + } + if (value.dataType < Res_value::TYPE_FIRST_INT + || value.dataType > Res_value::TYPE_LAST_INT) { + if (outError != NULL) { + *outError = "attribute is not an integer value"; + } + return defValue; + } + } + return value.data; +} + +void getResolvedResourceAttribute(const ResTable& resTable, const ResXMLTree& tree, + uint32_t attrRes, Res_value* outValue, String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + if (outError != NULL) { + *outError = "attribute could not be found"; + } + return; + } + if (tree.getAttributeValue(idx, outValue) != NO_ERROR) { + if (outValue->dataType == Res_value::TYPE_REFERENCE) { + resTable.resolveReference(outValue, 0); + } + // The attribute was found and was resolved if need be. + return; + } + if (outError != NULL) { + *outError = "error getting resolved resource attribute"; + } +} + +} // namespace AaptXml diff --git a/tools/aapt/AaptXml.h b/tools/aapt/AaptXml.h new file mode 100644 index 0000000..16977f3 --- /dev/null +++ b/tools/aapt/AaptXml.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AAPT_XML_H +#define __AAPT_XML_H + +#include <androidfw/ResourceTypes.h> +#include <utils/String8.h> + +/** + * Utility methods for dealing with ResXMLTree. + */ +namespace AaptXml { + +/** + * Returns the index of the attribute, or < 0 if it was not found. + */ +ssize_t indexOfAttribute(const android::ResXMLTree& tree, uint32_t attrRes); + +/** + * Returns the string value for the specified attribute. + * The string must be present in the ResXMLTree's string pool (inline in the XML). + */ +android::String8 getAttribute(const android::ResXMLTree& tree, const char* ns, + const char* attr, android::String8* outError = NULL); + +/** + * Returns the string value for the specified attribute, or an empty string + * if the attribute does not exist. + * The string must be present in the ResXMLTree's string pool (inline in the XML). + */ +android::String8 getAttribute(const android::ResXMLTree& tree, uint32_t attrRes, + android::String8* outError = NULL); + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer must be declared inline in the XML. + */ +int32_t getIntegerAttribute(const android::ResXMLTree& tree, const char* ns, + const char* attr, int32_t defValue = -1, android::String8* outError = NULL); + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer must be declared inline in the XML. + */ +inline int32_t getIntegerAttribute(const android::ResXMLTree& tree, const char* ns, + const char* attr, android::String8* outError) { + return getIntegerAttribute(tree, ns, attr, -1, outError); +} + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer must be declared inline in the XML. + */ +int32_t getIntegerAttribute(const android::ResXMLTree& tree, uint32_t attrRes, + int32_t defValue = -1, android::String8* outError = NULL); + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer must be declared inline in the XML. + */ +inline int32_t getIntegerAttribute(const android::ResXMLTree& tree, uint32_t attrRes, + android::String8* outError) { + return getIntegerAttribute(tree, attrRes, -1, outError); +} + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer may be a resource in the supplied ResTable. + */ +int32_t getResolvedIntegerAttribute(const android::ResTable& resTable, + const android::ResXMLTree& tree, uint32_t attrRes, int32_t defValue = -1, + android::String8* outError = NULL); + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer may be a resource in the supplied ResTable. + */ +inline int32_t getResolvedIntegerAttribute(const android::ResTable& resTable, + const android::ResXMLTree& tree, uint32_t attrRes, + android::String8* outError) { + return getResolvedIntegerAttribute(resTable, tree, attrRes, -1, outError); +} + +/** + * Returns the string value for the specified attribute, or an empty string + * if the attribute does not exist. + * The string may be a resource in the supplied ResTable. + */ +android::String8 getResolvedAttribute(const android::ResTable& resTable, + const android::ResXMLTree& tree, uint32_t attrRes, + android::String8* outError = NULL); + +/** + * Returns the resource for the specified attribute in the outValue parameter. + * The resource may be a resource in the supplied ResTable. + */ +void getResolvedResourceAttribute(const android::ResTable& resTable, + const android::ResXMLTree& tree, uint32_t attrRes, android::Res_value* outValue, + android::String8* outError = NULL); + +} // namespace AaptXml + +#endif // __AAPT_XML_H diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index 4ce5045..2cbabe1 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -28,6 +28,7 @@ aaptSources := \ AaptAssets.cpp \ AaptConfig.cpp \ AaptUtil.cpp \ + AaptXml.cpp \ ApkBuilder.cpp \ Command.cpp \ CrunchCache.cpp \ diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index af49461..9bed899 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -130,6 +130,10 @@ public: void setErrorOnFailedInsert(bool val) { mErrorOnFailedInsert = val; } bool getErrorOnMissingConfigEntry() { return mErrorOnMissingConfigEntry; } void setErrorOnMissingConfigEntry(bool val) { mErrorOnMissingConfigEntry = val; } + const android::String8& getPlatformBuildVersionCode() { return mPlatformVersionCode; } + void setPlatformBuildVersionCode(const android::String8& code) { mPlatformVersionCode = code; } + const android::String8& getPlatformBuildVersionName() { return mPlatformVersionName; } + void setPlatformBuildVersionName(const android::String8& name) { mPlatformVersionName = name; } bool getUTF16StringsOption() { return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO); @@ -323,6 +327,8 @@ private: const char* mSingleCrunchInputFile; const char* mSingleCrunchOutputFile; bool mBuildSharedLibrary; + android::String8 mPlatformVersionCode; + android::String8 mPlatformVersionName; /* file specification */ int mArgc; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index a0f0a08..27e60f3 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -3,6 +3,7 @@ // // Android Asset Packaging Tool main entry point. // +#include "AaptXml.h" #include "ApkBuilder.h" #include "Bundle.h" #include "Images.h" @@ -241,162 +242,17 @@ bail: return result; } -static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes) -{ - size_t N = tree.getAttributeCount(); - for (size_t i=0; i<N; i++) { - if (tree.getAttributeNameResID(i) == attrRes) { - return (ssize_t)i; - } - } - return -1; -} - -String8 getAttribute(const ResXMLTree& tree, const char* ns, - const char* attr, String8* outError) -{ - ssize_t idx = tree.indexOfAttribute(ns, attr); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) { - *outError = "attribute is not a string value"; - } - return String8(); - } - } - size_t len; - const uint16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); -} - -static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) { - *outError = "attribute is not a string value"; - } - return String8(); - } - } - size_t len; - const uint16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); -} - -static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes, - String8* outError, int32_t defValue = -1) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return defValue; - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType < Res_value::TYPE_FIRST_INT - || value.dataType > Res_value::TYPE_LAST_INT) { - if (outError != NULL) { - *outError = "attribute is not an integer value"; - } - return defValue; - } - } - return value.data; -} - -static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree, - uint32_t attrRes, String8* outError, int32_t defValue = -1) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return defValue; - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType == Res_value::TYPE_REFERENCE) { - resTable->resolveReference(&value, 0); - } - if (value.dataType < Res_value::TYPE_FIRST_INT - || value.dataType > Res_value::TYPE_LAST_INT) { - if (outError != NULL) { - *outError = "attribute is not an integer value"; - } - return defValue; - } - } - return value.data; -} - -static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree, - uint32_t attrRes, String8* outError) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType == Res_value::TYPE_STRING) { - size_t len; - const uint16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); - } - resTable->resolveReference(&value, 0); - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) { - *outError = "attribute is not a string value"; - } - return String8(); - } - } - size_t len; - const Res_value* value2 = &value; - const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len); - return str ? String8(str, len) : String8(); -} - -static void getResolvedResourceAttribute(Res_value* value, const ResTable* resTable, - const ResXMLTree& tree, uint32_t attrRes, String8* outError) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - if (outError != NULL) { - *outError = "attribute could not be found"; - } - return; - } - if (tree.getAttributeValue(idx, value) != NO_ERROR) { - if (value->dataType == Res_value::TYPE_REFERENCE) { - resTable->resolveReference(value, 0); - } - // The attribute was found and was resolved if need be. - return; - } - if (outError != NULL) { - *outError = "error getting resolved resource attribute"; - } -} - -static void printResolvedResourceAttribute(const ResTable* resTable, const ResXMLTree& tree, +static void printResolvedResourceAttribute(const ResTable& resTable, const ResXMLTree& tree, uint32_t attrRes, String8 attrLabel, String8* outError) { Res_value value; - getResolvedResourceAttribute(&value, resTable, tree, attrRes, outError); + AaptXml::getResolvedResourceAttribute(resTable, tree, attrRes, &value, outError); if (*outError != "") { *outError = "error print resolved resource attribute"; return; } if (value.dataType == Res_value::TYPE_STRING) { - String8 result = getResolvedAttribute(resTable, tree, attrRes, outError); + String8 result = AaptXml::getResolvedAttribute(resTable, tree, attrRes, outError); printf("%s='%s'", attrLabel.string(), ResTable::normalizeForOutput(result.string()).string()); } else if (Res_value::TYPE_FIRST_INT <= value.dataType && @@ -488,10 +344,10 @@ static void printCompatibleScreens(ResXMLTree& tree, String8* outError) { } String8 tag(ctag16); if (tag == "screen") { - int32_t screenSize = getIntegerAttribute(tree, - SCREEN_SIZE_ATTR, NULL, -1); - int32_t screenDensity = getIntegerAttribute(tree, - SCREEN_DENSITY_ATTR, NULL, -1); + int32_t screenSize = AaptXml::getIntegerAttribute(tree, + SCREEN_SIZE_ATTR); + int32_t screenDensity = AaptXml::getIntegerAttribute(tree, + SCREEN_DENSITY_ATTR); if (screenSize > 0 && screenDensity > 0) { if (!first) { printf(","); @@ -577,7 +433,7 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool } } else if (depth == 2 && withinApduService) { if (tag == "aid-group") { - String8 category = getAttribute(tree, CATEGORY_ATTR, &error); + String8 category = AaptXml::getAttribute(tree, CATEGORY_ATTR, &error); if (error != "") { if (outError != NULL) *outError = error; return Vector<String8>(); @@ -876,11 +732,11 @@ int doDump(Bundle* bundle) fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); goto bail; } - String8 pkg = getAttribute(tree, NULL, "package", NULL); + String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string()); } else if (depth == 2 && tag == "permission") { String8 error; - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR: %s\n", error.string()); goto bail; @@ -889,14 +745,14 @@ int doDump(Bundle* bundle) ResTable::normalizeForOutput(name.string()).string()); } else if (depth == 2 && tag == "uses-permission") { String8 error; - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR: %s\n", error.string()); goto bail; } printUsesPermission(name, - getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0, - getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1)); + AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); } } } else if (strcmp("badging", option) == 0) { @@ -1151,12 +1007,14 @@ int doDump(Bundle* bundle) fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); goto bail; } - pkg = getAttribute(tree, NULL, "package", NULL); + pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); printf("package: name='%s' ", ResTable::normalizeForOutput(pkg.string()).string()); - int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error); + int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR, + &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", + error.string()); goto bail; } if (versionCode > 0) { @@ -1164,23 +1022,29 @@ int doDump(Bundle* bundle) } else { printf("versionCode='' "); } - String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error); + String8 versionName = AaptXml::getResolvedAttribute(res, tree, + VERSION_NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", + error.string()); goto bail; } printf("versionName='%s'", ResTable::normalizeForOutput(versionName.string()).string()); - String8 splitName = getAttribute(tree, NULL, "split", NULL); + String8 splitName = AaptXml::getAttribute(tree, NULL, "split"); if (!splitName.isEmpty()) { printf(" split='%s'", ResTable::normalizeForOutput( splitName.string()).string()); } + + String8 platformVersionName = AaptXml::getAttribute(tree, NULL, + "platformBuildVersionName"); + printf(" platformBuildVersionName='%s'", platformVersionName.string()); printf("\n"); - int32_t installLocation = getResolvedIntegerAttribute(&res, tree, - INSTALL_LOCATION_ATTR, &error, -1); + int32_t installLocation = AaptXml::getResolvedIntegerAttribute(res, tree, + INSTALL_LOCATION_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:installLocation' attribute: %s\n", error.string()); @@ -1215,7 +1079,8 @@ int doDump(Bundle* bundle) for (size_t i=0; i<NL; i++) { const char* localeStr = locales[i].string(); assets.setLocale(localeStr != NULL ? localeStr : ""); - String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + String8 llabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, + &error); if (llabel != "") { if (localeStr == NULL || strlen(localeStr) == 0) { label = llabel; @@ -1236,7 +1101,8 @@ int doDump(Bundle* bundle) for (size_t i=0; i<ND; i++) { tmpConfig.density = densities[i]; assets.setConfiguration(tmpConfig); - String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, + &error); if (icon != "") { printf("application-icon-%d:'%s'\n", densities[i], ResTable::normalizeForOutput(icon.string()).string()); @@ -1244,14 +1110,17 @@ int doDump(Bundle* bundle) } assets.setConfiguration(config); - String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", + error.string()); goto bail; } - int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0); + int32_t testOnly = AaptXml::getIntegerAttribute(tree, TEST_ONLY_ATTR, 0, + &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", + error.string()); goto bail; } printf("application: label='%s' ", @@ -1261,9 +1130,11 @@ int doDump(Bundle* bundle) printf("testOnly='%d'\n", testOnly); } - int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0); + int32_t debuggable = AaptXml::getResolvedIntegerAttribute(res, tree, + DEBUGGABLE_ATTR, 0, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", + error.string()); goto bail; } if (debuggable != 0) { @@ -1284,10 +1155,11 @@ int doDump(Bundle* bundle) } } } else if (tag == "uses-sdk") { - int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error); + int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error); if (error != "") { error = ""; - String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error); + String8 name = AaptXml::getResolvedAttribute(res, tree, + MIN_SDK_VERSION_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n", error.string()); @@ -1300,14 +1172,15 @@ int doDump(Bundle* bundle) targetSdk = code; printf("sdkVersion:'%d'\n", code); } - code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1); + code = AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR); if (code != -1) { printf("maxSdkVersion:'%d'\n", code); } - code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error); + code = AaptXml::getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error); if (error != "") { error = ""; - String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error); + String8 name = AaptXml::getResolvedAttribute(res, tree, + TARGET_SDK_VERSION_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n", error.string()); @@ -1323,16 +1196,16 @@ int doDump(Bundle* bundle) printf("targetSdkVersion:'%d'\n", code); } } else if (tag == "uses-configuration") { - int32_t reqTouchScreen = getIntegerAttribute(tree, - REQ_TOUCH_SCREEN_ATTR, NULL, 0); - int32_t reqKeyboardType = getIntegerAttribute(tree, - REQ_KEYBOARD_TYPE_ATTR, NULL, 0); - int32_t reqHardKeyboard = getIntegerAttribute(tree, - REQ_HARD_KEYBOARD_ATTR, NULL, 0); - int32_t reqNavigation = getIntegerAttribute(tree, - REQ_NAVIGATION_ATTR, NULL, 0); - int32_t reqFiveWayNav = getIntegerAttribute(tree, - REQ_FIVE_WAY_NAV_ATTR, NULL, 0); + int32_t reqTouchScreen = AaptXml::getIntegerAttribute(tree, + REQ_TOUCH_SCREEN_ATTR, 0); + int32_t reqKeyboardType = AaptXml::getIntegerAttribute(tree, + REQ_KEYBOARD_TYPE_ATTR, 0); + int32_t reqHardKeyboard = AaptXml::getIntegerAttribute(tree, + REQ_HARD_KEYBOARD_ATTR, 0); + int32_t reqNavigation = AaptXml::getIntegerAttribute(tree, + REQ_NAVIGATION_ATTR, 0); + int32_t reqFiveWayNav = AaptXml::getIntegerAttribute(tree, + REQ_FIVE_WAY_NAV_ATTR, 0); printf("uses-configuration:"); if (reqTouchScreen != 0) { printf(" reqTouchScreen='%d'", reqTouchScreen); @@ -1353,26 +1226,26 @@ int doDump(Bundle* bundle) } else if (tag == "supports-input") { withinSupportsInput = true; } else if (tag == "supports-screens") { - smallScreen = getIntegerAttribute(tree, - SMALL_SCREEN_ATTR, NULL, 1); - normalScreen = getIntegerAttribute(tree, - NORMAL_SCREEN_ATTR, NULL, 1); - largeScreen = getIntegerAttribute(tree, - LARGE_SCREEN_ATTR, NULL, 1); - xlargeScreen = getIntegerAttribute(tree, - XLARGE_SCREEN_ATTR, NULL, 1); - anyDensity = getIntegerAttribute(tree, - ANY_DENSITY_ATTR, NULL, 1); - requiresSmallestWidthDp = getIntegerAttribute(tree, - REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0); - compatibleWidthLimitDp = getIntegerAttribute(tree, - COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0); - largestWidthLimitDp = getIntegerAttribute(tree, - LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0); + smallScreen = AaptXml::getIntegerAttribute(tree, + SMALL_SCREEN_ATTR, 1); + normalScreen = AaptXml::getIntegerAttribute(tree, + NORMAL_SCREEN_ATTR, 1); + largeScreen = AaptXml::getIntegerAttribute(tree, + LARGE_SCREEN_ATTR, 1); + xlargeScreen = AaptXml::getIntegerAttribute(tree, + XLARGE_SCREEN_ATTR, 1); + anyDensity = AaptXml::getIntegerAttribute(tree, + ANY_DENSITY_ATTR, 1); + requiresSmallestWidthDp = AaptXml::getIntegerAttribute(tree, + REQUIRES_SMALLEST_WIDTH_DP_ATTR, 0); + compatibleWidthLimitDp = AaptXml::getIntegerAttribute(tree, + COMPATIBLE_WIDTH_LIMIT_DP_ATTR, 0); + largestWidthLimitDp = AaptXml::getIntegerAttribute(tree, + LARGEST_WIDTH_LIMIT_DP_ATTR, 0); } else if (tag == "feature-group") { withinFeatureGroup = true; FeatureGroup group; - group.label = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + group.label = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:label' attribute:" " %s\n", error.string()); @@ -1381,17 +1254,17 @@ int doDump(Bundle* bundle) featureGroups.add(group); } else if (tag == "uses-feature") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - int req = getIntegerAttribute(tree, - REQUIRED_ATTR, NULL, 1); + int req = AaptXml::getIntegerAttribute(tree, + REQUIRED_ATTR, 1); commonFeatures.features.add(name, req); if (req) { addParentFeatures(&commonFeatures, name); } } else { - int vers = getIntegerAttribute(tree, + int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error); if (error == "") { if (vers > commonFeatures.openGLESVersion) { @@ -1400,7 +1273,7 @@ int doDump(Bundle* bundle) } } } else if (tag == "uses-permission") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { if (name == "android.permission.CAMERA") { addImpliedFeature(&impliedFeatures, "android.hardware.camera", @@ -1478,15 +1351,15 @@ int doDump(Bundle* bundle) } printUsesPermission(name, - getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1) == 0, - getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1)); + AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "uses-package") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { printf("uses-package:'%s'\n", ResTable::normalizeForOutput(name.string()).string()); @@ -1496,7 +1369,7 @@ int doDump(Bundle* bundle) goto bail; } } else if (tag == "original-package") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { printf("original-package:'%s'\n", ResTable::normalizeForOutput(name.string()).string()); @@ -1506,7 +1379,7 @@ int doDump(Bundle* bundle) goto bail; } } else if (tag == "supports-gl-texture") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { printf("supports-gl-texture:'%s'\n", ResTable::normalizeForOutput(name.string()).string()); @@ -1524,9 +1397,9 @@ int doDump(Bundle* bundle) } depth--; } else if (tag == "package-verifier") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error); + String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR, &error); if (publicKey != "" && error == "") { printf("package-verifier: name='%s' publicKey='%s'\n", ResTable::normalizeForOutput(name.string()).string(), @@ -1553,35 +1426,38 @@ int doDump(Bundle* bundle) if (withinApplication) { if(tag == "activity") { withinActivity = true; - activityName = getAttribute(tree, NAME_ATTR, &error); + activityName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } - activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + activityLabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, + &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", error.string()); goto bail; } - activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + activityIcon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, + &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); goto bail; } - activityBanner = getResolvedAttribute(&res, tree, BANNER_ATTR, &error); + activityBanner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, + &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n", error.string()); goto bail; } - int32_t orien = getResolvedIntegerAttribute(&res, tree, + int32_t orien = AaptXml::getResolvedIntegerAttribute(res, tree, SCREEN_ORIENTATION_ATTR, &error); if (error == "") { if (orien == 0 || orien == 6 || orien == 8) { @@ -1595,21 +1471,21 @@ int doDump(Bundle* bundle) } } } else if (tag == "uses-library") { - String8 libraryName = getAttribute(tree, NAME_ATTR, &error); + String8 libraryName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute for uses-library" " %s\n", error.string()); goto bail; } - int req = getIntegerAttribute(tree, - REQUIRED_ATTR, NULL, 1); + int req = AaptXml::getIntegerAttribute(tree, + REQUIRED_ATTR, 1); printf("uses-library%s:'%s'\n", req ? "" : "-not-required", ResTable::normalizeForOutput( libraryName.string()).string()); } else if (tag == "receiver") { withinReceiver = true; - receiverName = getAttribute(tree, NAME_ATTR, &error); + receiverName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, @@ -1618,7 +1494,8 @@ int doDump(Bundle* bundle) goto bail; } - String8 permission = getAttribute(tree, PERMISSION_ATTR, &error); + String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR, + &error); if (error == "") { if (permission == "android.permission.BIND_DEVICE_ADMIN") { hasBindDeviceAdminPermission = true; @@ -1629,7 +1506,7 @@ int doDump(Bundle* bundle) } } else if (tag == "service") { withinService = true; - serviceName = getAttribute(tree, NAME_ATTR, &error); + serviceName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute for " @@ -1637,7 +1514,8 @@ int doDump(Bundle* bundle) goto bail; } - String8 permission = getAttribute(tree, PERMISSION_ATTR, &error); + String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR, + &error); if (error == "") { if (permission == "android.permission.BIND_INPUT_METHOD") { hasBindInputMethodPermission = true; @@ -1659,22 +1537,24 @@ int doDump(Bundle* bundle) } else if (tag == "provider") { withinProvider = true; - bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error); + bool exported = AaptXml::getResolvedIntegerAttribute(res, tree, + EXPORTED_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:" " %s\n", error.string()); goto bail; } - bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree, - GRANT_URI_PERMISSIONS_ATTR, &error); + bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute( + res, tree, GRANT_URI_PERMISSIONS_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:" " %s\n", error.string()); goto bail; } - String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error); + String8 permission = AaptXml::getResolvedAttribute(res, tree, + PERMISSION_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:" " %s\n", error.string()); @@ -1685,7 +1565,8 @@ int doDump(Bundle* bundle) permission == "android.permission.MANAGE_DOCUMENTS"; } else if (bundle->getIncludeMetaData() && tag == "meta-data") { - String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error); + String8 metaDataName = AaptXml::getResolvedAttribute(res, tree, + NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute for " "meta-data:%s\n", error.string()); @@ -1693,12 +1574,12 @@ int doDump(Bundle* bundle) } printf("meta-data: name='%s' ", ResTable::normalizeForOutput(metaDataName.string()).string()); - printResolvedResourceAttribute(&res, tree, VALUE_ATTR, String8("value"), + printResolvedResourceAttribute(res, tree, VALUE_ATTR, String8("value"), &error); if (error != "") { // Try looking for a RESOURCE_ATTR error = ""; - printResolvedResourceAttribute(&res, tree, RESOURCE_ATTR, + printResolvedResourceAttribute(res, tree, RESOURCE_ATTR, String8("resource"), &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:value' or " @@ -1709,7 +1590,7 @@ int doDump(Bundle* bundle) } printf("\n"); } else if (withinSupportsInput && tag == "input-type") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { supportedInput.add(name); } else { @@ -1721,12 +1602,13 @@ int doDump(Bundle* bundle) } else if (withinFeatureGroup && tag == "uses-feature") { FeatureGroup& top = featureGroups.editTop(); - String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error); + String8 name = AaptXml::getResolvedAttribute(res, tree, NAME_ATTR, &error); if (name != "" && error == "") { top.features.add(name, true); addParentFeatures(&top, name); } else { - int vers = getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error); + int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR, + &error); if (error == "") { if (vers > top.openGLESVersion) { top.openGLESVersion = vers; @@ -1754,7 +1636,7 @@ int doDump(Bundle* bundle) actCameraSecure = false; catLauncher = false; } else if (withinService && tag == "meta-data") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute for" " meta-data tag in service '%s': %s\n", serviceName.string(), error.string()); @@ -1768,7 +1650,8 @@ int doDump(Bundle* bundle) offHost = false; } - String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error); + String8 xmlPath = AaptXml::getResolvedAttribute(res, tree, + RESOURCE_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:resource' attribute for" " meta-data tag in service '%s': %s\n", serviceName.string(), error.string()); @@ -1797,7 +1680,7 @@ int doDump(Bundle* bundle) } else if ((depth == 5) && withinIntentFilter) { String8 action; if (tag == "action") { - action = getAttribute(tree, NAME_ATTR, &error); + action = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); @@ -1849,7 +1732,7 @@ int doDump(Bundle* bundle) } if (tag == "category") { - String8 category = getAttribute(tree, NAME_ATTR, &error); + String8 category = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'name' attribute: %s\n", error.string()); diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h index dd40b20..f24a023 100644 --- a/tools/aapt/Main.h +++ b/tools/aapt/Main.h @@ -60,9 +60,6 @@ extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets); int dumpResources(Bundle* bundle); -String8 getAttribute(const ResXMLTree& tree, const char* ns, - const char* attr, String8* outError); - status_t writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets, FILE* fp, bool includeRaw); #endif // __MAIN_H diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 7979a1d..5deeca2 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -4,6 +4,7 @@ // Build resource files from raw assets. // #include "AaptAssets.h" +#include "AaptXml.h" #include "CacheUpdater.h" #include "CrunchCache.h" #include "FileFinder.h" @@ -805,6 +806,20 @@ status_t massageManifest(Bundle* bundle, sp<XMLNode> root) } } + if (bundle->getPlatformBuildVersionCode() != "") { + if (!addTagAttribute(root, "", "platformBuildVersionCode", + bundle->getPlatformBuildVersionCode(), errorOnFailedInsert, true)) { + return UNKNOWN_ERROR; + } + } + + if (bundle->getPlatformBuildVersionName() != "") { + if (!addTagAttribute(root, "", "platformBuildVersionName", + bundle->getPlatformBuildVersionName(), errorOnFailedInsert, true)) { + return UNKNOWN_ERROR; + } + } + if (bundle->getDebugMode()) { sp<XMLNode> application = root->getChildElement(String16(), String16("application")); if (application != NULL) { @@ -881,6 +896,106 @@ status_t massageManifest(Bundle* bundle, sp<XMLNode> root) return NO_ERROR; } +static int32_t getPlatformAssetCookie(const AssetManager& assets) { + // Find the system package (0x01). AAPT always generates attributes + // with the type 0x01, so we're looking for the first attribute + // resource in the system package. + const ResTable& table = assets.getResources(true); + Res_value val; + ssize_t idx = table.getResource(0x01010000, &val, true); + if (idx != NO_ERROR) { + // Try as a bag. + const ResTable::bag_entry* entry; + ssize_t cnt = table.lockBag(0x01010000, &entry); + if (cnt >= 0) { + idx = entry->stringBlock; + } + table.unlockBag(entry); + } + + if (idx < 0) { + return 0; + } + return table.getTableCookie(idx); +} + +enum { + VERSION_CODE_ATTR = 0x0101021b, + VERSION_NAME_ATTR = 0x0101021c, +}; + +static ssize_t extractPlatformBuildVersion(ResXMLTree& tree, Bundle* bundle) { + size_t len; + ResXMLTree::event_code_t code; + while ((code = tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code != ResXMLTree::START_TAG) { + continue; + } + + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n"); + return UNKNOWN_ERROR; + } + + String8 tag(ctag16, len); + if (tag != "manifest") { + continue; + } + + String8 error; + int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: failed to get platform version code\n"); + return UNKNOWN_ERROR; + } + + if (versionCode >= 0 && bundle->getPlatformBuildVersionCode() == "") { + bundle->setPlatformBuildVersionCode(String8::format("%d", versionCode)); + } + + String8 versionName = AaptXml::getAttribute(tree, VERSION_NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: failed to get platform version name\n"); + return UNKNOWN_ERROR; + } + + if (versionName != "" && bundle->getPlatformBuildVersionName() == "") { + bundle->setPlatformBuildVersionName(versionName); + } + return NO_ERROR; + } + + fprintf(stderr, "ERROR: no <manifest> tag found in platform AndroidManifest.xml\n"); + return UNKNOWN_ERROR; +} + +static ssize_t extractPlatformBuildVersion(AssetManager& assets, Bundle* bundle) { + int32_t cookie = getPlatformAssetCookie(assets); + if (cookie == 0) { + fprintf(stderr, "ERROR: Platform package not found\n"); + return UNKNOWN_ERROR; + } + + ResXMLTree tree; + Asset* asset = assets.openNonAsset(cookie, "AndroidManifest.xml", Asset::ACCESS_STREAMING); + if (asset == NULL) { + fprintf(stderr, "ERROR: Platform AndroidManifest.xml not found\n"); + return UNKNOWN_ERROR; + } + + ssize_t result = NO_ERROR; + if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) { + fprintf(stderr, "ERROR: Platform AndroidManifest.xml is corrupt\n"); + result = UNKNOWN_ERROR; + } else { + result = extractPlatformBuildVersion(tree, bundle); + } + + delete asset; + return result; +} + #define ASSIGN_IT(n) \ do { \ ssize_t index = resources->indexOfKey(String8(#n)); \ @@ -1356,6 +1471,17 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil return UNKNOWN_ERROR; } + // If we're not overriding the platform build versions, + // extract them from the platform APK. + if (packageType != ResourceTable::System && + (bundle->getPlatformBuildVersionCode() == "" || + bundle->getPlatformBuildVersionName() == "")) { + err = extractPlatformBuildVersion(assets->getAssetManager(), bundle); + if (err != NO_ERROR) { + return UNKNOWN_ERROR; + } + } + const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0)); String8 manifestPath(manifestFile->getPrintableSource()); @@ -2636,13 +2762,14 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); return -1; } - pkg = getAttribute(tree, NULL, "package", NULL); + pkg = AaptXml::getAttribute(tree, NULL, "package"); } else if (depth == 2) { if (tag == "application") { inApplication = true; keepTag = true; - String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android", + String8 agent = AaptXml::getAttribute(tree, + "http://schemas.android.com/apk/res/android", "backupAgent", &error); if (agent.length() > 0) { addProguardKeepRule(keep, agent, pkg.string(), @@ -2658,8 +2785,8 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass } } if (keepTag) { - String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android", - "name", &error); + String8 name = AaptXml::getAttribute(tree, + "http://schemas.android.com/apk/res/android", "name", &error); if (error != "") { fprintf(stderr, "ERROR: %s\n", error.string()); return -1; diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py new file mode 100644 index 0000000..fce4323 --- /dev/null +++ b/tools/apilint/apilint.py @@ -0,0 +1,540 @@ +#!/usr/bin/env python + +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Enforces common Android public API design patterns. It ignores lint messages from +a previous API level, if provided. + +Usage: apilint.py current.txt +Usage: apilint.py current.txt previous.txt +""" + +import re, sys + + +BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) + +def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False): + # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes + codes = [] + if reset: codes.append("0") + else: + if not fg is None: codes.append("3%d" % (fg)) + if not bg is None: + if not bright: codes.append("4%d" % (bg)) + else: codes.append("10%d" % (bg)) + if bold: codes.append("1") + elif dim: codes.append("2") + else: codes.append("22") + return "\033[%sm" % (";".join(codes)) + + +class Field(): + def __init__(self, clazz, raw): + self.clazz = clazz + self.raw = raw.strip(" {;") + + raw = raw.split() + self.split = list(raw) + + for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]: + while r in raw: raw.remove(r) + + self.typ = raw[0] + self.name = raw[1].strip(";") + if len(raw) >= 4 and raw[2] == "=": + self.value = raw[3].strip(';"') + else: + self.value = None + + def __repr__(self): + return self.raw + + +class Method(): + def __init__(self, clazz, raw): + self.clazz = clazz + self.raw = raw.strip(" {;") + + raw = re.split("[\s(),;]+", raw) + for r in ["", ";"]: + while r in raw: raw.remove(r) + self.split = list(raw) + + for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract"]: + while r in raw: raw.remove(r) + + self.typ = raw[0] + self.name = raw[1] + self.args = [] + for r in raw[2:]: + if r == "throws": break + self.args.append(r) + + def __repr__(self): + return self.raw + + +class Class(): + def __init__(self, pkg, raw): + self.pkg = pkg + self.raw = raw.strip(" {;") + self.ctors = [] + self.fields = [] + self.methods = [] + + raw = raw.split() + self.split = list(raw) + if "class" in raw: + self.fullname = raw[raw.index("class")+1] + elif "interface" in raw: + self.fullname = raw[raw.index("interface")+1] + + if "." in self.fullname: + self.name = self.fullname[self.fullname.rindex(".")+1:] + else: + self.name = self.fullname + + def __repr__(self): + return self.raw + + +class Package(): + def __init__(self, raw): + self.raw = raw.strip(" {;") + + raw = raw.split() + self.name = raw[raw.index("package")+1] + + def __repr__(self): + return self.raw + + +def parse_api(fn): + api = [] + pkg = None + clazz = None + + with open(fn) as f: + for raw in f.readlines(): + raw = raw.rstrip() + + if raw.startswith("package"): + pkg = Package(raw) + elif raw.startswith(" ") and raw.endswith("{"): + clazz = Class(pkg, raw) + api.append(clazz) + elif raw.startswith(" ctor"): + clazz.ctors.append(Method(clazz, raw)) + elif raw.startswith(" method"): + clazz.methods.append(Method(clazz, raw)) + elif raw.startswith(" field"): + clazz.fields.append(Field(clazz, raw)) + + return api + + +failures = [] + +def _fail(clazz, detail, msg): + """Records an API failure to be processed later.""" + global failures + + res = msg + if detail is not None: + res += "\n in " + repr(detail) + res += "\n in " + repr(clazz) + res += "\n in " + repr(clazz.pkg) + failures.append(res) + +def warn(clazz, detail, msg): + _fail(clazz, detail, "%sWarning:%s %s" % (format(fg=YELLOW, bg=BLACK), format(reset=True), msg)) + +def error(clazz, detail, msg): + _fail(clazz, detail, "%sError:%s %s" % (format(fg=RED, bg=BLACK), format(reset=True), msg)) + + +def verify_constants(clazz): + """All static final constants must be FOO_NAME style.""" + if re.match("R\.[a-z]+", clazz.fullname): return + + for f in clazz.fields: + if "static" in f.split and "final" in f.split: + if re.match("[A-Z0-9_]+", f.name) is None: + error(clazz, f, "Constant field names should be FOO_NAME") + + +def verify_enums(clazz): + """Enums are bad, mmkay?""" + if "extends java.lang.Enum" in clazz.raw: + error(clazz, None, "Enums are not allowed") + + +def verify_class_names(clazz): + """Try catching malformed class names like myMtp or MTPUser.""" + if re.search("[A-Z]{2,}", clazz.name) is not None: + warn(clazz, None, "Class name style should be Mtp not MTP") + if re.match("[^A-Z]", clazz.name): + error(clazz, None, "Class must start with uppercase char") + + +def verify_method_names(clazz): + """Try catching malformed method names, like Foo() or getMTU().""" + if clazz.pkg.name == "android.opengl": return + + for m in clazz.methods: + if re.search("[A-Z]{2,}", m.name) is not None: + warn(clazz, m, "Method name style should be getMtu() instead of getMTU()") + if re.match("[^a-z]", m.name): + error(clazz, None, "Method name must start with lowercase char") + + +def verify_callbacks(clazz): + """Verify Callback classes. + All callback classes must be abstract. + All methods must follow onFoo() naming style.""" + + if clazz.name.endswith("Callbacks"): + error(clazz, None, "Class must be named exactly Callback") + if clazz.name.endswith("Observer"): + warn(clazz, None, "Class should be named Callback") + + if clazz.name.endswith("Callback"): + if "interface" in clazz.split: + error(clazz, None, "Callback must be abstract class") + + for m in clazz.methods: + if not re.match("on[A-Z][a-z]*", m.name): + error(clazz, m, "Callback method names must be onFoo style") + + +def verify_listeners(clazz): + """Verify Listener classes. + All Listener classes must be interface. + All methods must follow onFoo() naming style. + If only a single method, it must match class name: + interface OnFooListener { void onFoo() }""" + + if clazz.name.endswith("Listener"): + if " abstract class " in clazz.raw: + error(clazz, None, "Listener should be interface") + + for m in clazz.methods: + if not re.match("on[A-Z][a-z]*", m.name): + error(clazz, m, "Listener method names must be onFoo style") + + if len(clazz.methods) == 1 and clazz.name.startswith("On"): + m = clazz.methods[0] + if (m.name + "Listener").lower() != clazz.name.lower(): + error(clazz, m, "Single method name should match class name") + + +def verify_actions(clazz): + """Verify intent actions. + All action names must be named ACTION_FOO. + All action values must be scoped by package and match name: + package android.foo { + String ACTION_BAR = "android.foo.action.BAR"; + }""" + for f in clazz.fields: + if f.value is None: continue + if f.name.startswith("EXTRA_"): continue + + if "static" in f.split and "final" in f.split and f.typ == "java.lang.String": + if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower(): + if not f.name.startswith("ACTION_"): + error(clazz, f, "Intent action must be ACTION_FOO") + else: + if clazz.name == "Intent": + prefix = "android.intent.action" + elif clazz.name == "Settings": + prefix = "android.settings" + else: + prefix = clazz.pkg.name + ".action" + expected = prefix + "." + f.name[7:] + if f.value != expected: + error(clazz, f, "Inconsistent action value") + + +def verify_extras(clazz): + """Verify intent extras. + All extra names must be named EXTRA_FOO. + All extra values must be scoped by package and match name: + package android.foo { + String EXTRA_BAR = "android.foo.extra.BAR"; + }""" + for f in clazz.fields: + if f.value is None: continue + if f.name.startswith("ACTION_"): continue + + if "static" in f.split and "final" in f.split and f.typ == "java.lang.String": + if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower(): + if not f.name.startswith("EXTRA_"): + error(clazz, f, "Intent extra must be EXTRA_FOO") + else: + if clazz.name == "Intent": + prefix = "android.intent.extra" + else: + prefix = clazz.pkg.name + ".extra" + expected = prefix + "." + f.name[6:] + if f.value != expected: + error(clazz, f, "Inconsistent extra value") + + +def verify_equals(clazz): + """Verify that equals() and hashCode() must be overridden together.""" + methods = [ m.name for m in clazz.methods ] + eq = "equals" in methods + hc = "hashCode" in methods + if eq != hc: + error(clazz, None, "Must override both equals and hashCode") + + +def verify_parcelable(clazz): + """Verify that Parcelable objects aren't hiding required bits.""" + if "implements android.os.Parcelable" in clazz.raw: + creator = [ i for i in clazz.fields if i.name == "CREATOR" ] + write = [ i for i in clazz.methods if i.name == "writeToParcel" ] + describe = [ i for i in clazz.methods if i.name == "describeContents" ] + + if len(creator) == 0 or len(write) == 0 or len(describe) == 0: + error(clazz, None, "Parcelable requires CREATOR, writeToParcel, and describeContents") + + +def verify_protected(clazz): + """Verify that no protected methods are allowed.""" + for m in clazz.methods: + if "protected" in m.split: + error(clazz, m, "Protected method") + for f in clazz.fields: + if "protected" in f.split: + error(clazz, f, "Protected field") + + +def verify_fields(clazz): + """Verify that all exposed fields are final. + Exposed fields must follow myName style. + Catch internal mFoo objects being exposed.""" + for f in clazz.fields: + if not "final" in f.split: + error(clazz, f, "Bare fields must be final; consider adding accessors") + + if not "static" in f.split: + if not re.match("[a-z]([a-zA-Z]+)?", f.name): + error(clazz, f, "Non-static fields must be myName") + + if re.match("[m][A-Z]", f.name): + error(clazz, f, "Don't expose your internal objects") + + +def verify_register(clazz): + """Verify parity of registration methods. + Callback objects use register/unregister methods. + Listener objects use add/remove methods.""" + methods = [ m.name for m in clazz.methods ] + for m in clazz.methods: + if "Callback" in m.raw: + if m.name.startswith("register"): + other = "unregister" + m.name[8:] + if other not in methods: + error(clazz, m, "Missing unregister") + if m.name.startswith("unregister"): + other = "register" + m.name[10:] + if other not in methods: + error(clazz, m, "Missing register") + + if m.name.startswith("add") or m.name.startswith("remove"): + error(clazz, m, "Callback should be register/unregister") + + if "Listener" in m.raw: + if m.name.startswith("add"): + other = "remove" + m.name[3:] + if other not in methods: + error(clazz, m, "Missing remove") + if m.name.startswith("remove") and not m.name.startswith("removeAll"): + other = "add" + m.name[6:] + if other not in methods: + error(clazz, m, "Missing add") + + if m.name.startswith("register") or m.name.startswith("unregister"): + error(clazz, m, "Listener should be add/remove") + + +def verify_sync(clazz): + """Verify synchronized methods aren't exposed.""" + for m in clazz.methods: + if "synchronized" in m.split: + error(clazz, m, "Lock exposed") + + +def verify_intent_builder(clazz): + """Verify that Intent builders are createFooIntent() style.""" + if clazz.name == "Intent": return + + for m in clazz.methods: + if m.typ == "android.content.Intent": + if m.name.startswith("create") and m.name.endswith("Intent"): + pass + else: + warn(clazz, m, "Should be createFooIntent()") + + +def verify_helper_classes(clazz): + """Verify that helper classes are named consistently with what they extend.""" + if "extends android.app.Service" in clazz.raw: + if not clazz.name.endswith("Service"): + error(clazz, None, "Inconsistent class name") + if "extends android.content.ContentProvider" in clazz.raw: + if not clazz.name.endswith("Provider"): + error(clazz, None, "Inconsistent class name") + if "extends android.content.BroadcastReceiver" in clazz.raw: + if not clazz.name.endswith("Receiver"): + error(clazz, None, "Inconsistent class name") + + +def verify_builder(clazz): + """Verify builder classes. + Methods should return the builder to enable chaining.""" + if " extends " in clazz.raw: return + if not clazz.name.endswith("Builder"): return + + if clazz.name != "Builder": + warn(clazz, None, "Should be standalone Builder class") + + has_build = False + for m in clazz.methods: + if m.name == "build": + has_build = True + continue + + if m.name.startswith("get"): continue + if m.name.startswith("clear"): continue + + if not m.typ.endswith(clazz.fullname): + warn(clazz, m, "Should return the builder") + + if not has_build: + warn(clazz, None, "Missing build() method") + + +def verify_aidl(clazz): + """Catch people exposing raw AIDL.""" + if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw: + error(clazz, None, "Exposing raw AIDL interface") + + +def verify_internal(clazz): + """Catch people exposing internal classes.""" + if clazz.pkg.name.startswith("com.android"): + error(clazz, None, "Exposing internal class") + + +def verify_layering(clazz): + """Catch package layering violations. + For example, something in android.os depending on android.app.""" + ranking = [ + ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"], + "android.app", + "android.widget", + "android.view", + "android.animation", + "android.provider", + "android.content", + "android.database", + "android.graphics", + "android.text", + "android.os", + "android.util" + ] + + def rank(p): + for i in range(len(ranking)): + if isinstance(ranking[i], list): + for j in ranking[i]: + if p.startswith(j): return i + else: + if p.startswith(ranking[i]): return i + + cr = rank(clazz.pkg.name) + if cr is None: return + + for f in clazz.fields: + ir = rank(f.typ) + if ir and ir < cr: + warn(clazz, f, "Field type violates package layering") + + for m in clazz.methods: + ir = rank(m.typ) + if ir and ir < cr: + warn(clazz, m, "Method return type violates package layering") + for arg in m.args: + ir = rank(arg) + if ir and ir < cr: + warn(clazz, m, "Method argument type violates package layering") + + +def verify_all(api): + global failures + + failures = [] + for clazz in api: + if clazz.pkg.name.startswith("java"): continue + if clazz.pkg.name.startswith("junit"): continue + if clazz.pkg.name.startswith("org.apache"): continue + if clazz.pkg.name.startswith("org.xml"): continue + if clazz.pkg.name.startswith("org.json"): continue + if clazz.pkg.name.startswith("org.w3c"): continue + + verify_constants(clazz) + verify_enums(clazz) + verify_class_names(clazz) + verify_method_names(clazz) + verify_callbacks(clazz) + verify_listeners(clazz) + verify_actions(clazz) + verify_extras(clazz) + verify_equals(clazz) + verify_parcelable(clazz) + verify_protected(clazz) + verify_fields(clazz) + verify_register(clazz) + verify_sync(clazz) + verify_intent_builder(clazz) + verify_helper_classes(clazz) + verify_builder(clazz) + verify_aidl(clazz) + verify_internal(clazz) + verify_layering(clazz) + + return failures + + +cur = parse_api(sys.argv[1]) +cur_fail = verify_all(cur) + +if len(sys.argv) > 2: + prev = parse_api(sys.argv[2]) + prev_fail = verify_all(prev) + + # ignore errors from previous API level + for p in prev_fail: + if p in cur_fail: + cur_fail.remove(p) + + +for f in cur_fail: + print f + print |
