Merge "Not enforcing a timeout anymore for talkback users" into lmp-dev
diff --git a/Android.mk b/Android.mk
index 3edaefc..35bb66c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -325,8 +325,6 @@
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 @@
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 6fbb5ce..009b534 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -905,6 +905,8 @@
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
@@ -1757,6 +1759,7 @@
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
@@ -1765,6 +1768,7 @@
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
@@ -2025,6 +2029,12 @@
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
@@ -2063,13 +2073,6 @@
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
@@ -2628,6 +2631,7 @@
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
@@ -3512,7 +3516,7 @@
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);
@@ -3607,8 +3611,7 @@
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();
@@ -5747,7 +5750,7 @@
}
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
@@ -7148,6 +7151,7 @@
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";
@@ -7285,8 +7289,8 @@
method public void registerComponentCallbacks(android.content.ComponentCallbacks);
method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter);
method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler);
- method public abstract void removeStickyBroadcast(android.content.Intent);
- method public abstract void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
+ method public abstract deprecated void removeStickyBroadcast(android.content.Intent);
+ method public abstract deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
method public abstract void revokeUriPermission(android.net.Uri, int);
method public abstract void sendBroadcast(android.content.Intent);
method public abstract void sendBroadcast(android.content.Intent, java.lang.String);
@@ -7295,10 +7299,10 @@
method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String);
method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public abstract void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public abstract void sendStickyBroadcast(android.content.Intent);
- method public abstract void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
- method public abstract void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
- method public abstract void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+ method public abstract deprecated void sendStickyBroadcast(android.content.Intent);
+ method public abstract deprecated void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
+ method public abstract deprecated void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+ method public abstract deprecated void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
method public abstract void setTheme(int);
method public abstract deprecated void setWallpaper(android.graphics.Bitmap) throws java.io.IOException;
method public abstract deprecated void setWallpaper(java.io.InputStream) throws java.io.IOException;
@@ -8578,9 +8582,6 @@
field public java.lang.String targetPackage;
}
- public class KeySet {
- }
-
public class LabeledIntent extends android.content.Intent {
ctor public LabeledIntent(android.content.Intent, java.lang.String, int, int);
ctor public LabeledIntent(android.content.Intent, java.lang.String, java.lang.CharSequence, int);
@@ -8816,7 +8817,6 @@
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public abstract android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public abstract java.lang.String getNameForUid(int);
@@ -8836,15 +8836,12 @@
method public abstract android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public abstract android.content.pm.KeySet getSigningKeySet(java.lang.String);
method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public abstract java.lang.String[] getSystemSharedLibraryNames();
method public abstract java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract boolean hasSystemFeature(java.lang.String);
method public abstract boolean isSafeMode();
- method public abstract boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
- method public abstract boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
method public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
method public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
@@ -14526,7 +14523,6 @@
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 {
@@ -14658,7 +14654,6 @@
}
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();
@@ -14690,14 +14685,22 @@
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);
@@ -14755,39 +14758,6 @@
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
@@ -14902,12 +14872,34 @@
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
@@ -14924,6 +14916,31 @@
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[]);
@@ -15026,11 +15043,13 @@
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);
@@ -15052,7 +15071,6 @@
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";
@@ -15068,7 +15086,6 @@
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";
@@ -15102,7 +15119,7 @@
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);
@@ -15130,6 +15147,7 @@
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";
@@ -15150,14 +15168,6 @@
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();
@@ -15708,6 +15718,7 @@
}
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();
@@ -16243,7 +16254,6 @@
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);
}
@@ -16255,27 +16265,12 @@
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);
@@ -16284,37 +16279,10 @@
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);
}
}
@@ -16409,7 +16377,7 @@
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();
@@ -16426,7 +16394,7 @@
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);
@@ -16446,16 +16414,16 @@
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();
}
@@ -16476,7 +16444,7 @@
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
@@ -16491,34 +16459,27 @@
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);
@@ -16539,6 +16500,7 @@
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();
@@ -16550,15 +16512,15 @@
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
@@ -16572,6 +16534,7 @@
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
}
@@ -16582,7 +16545,7 @@
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);
@@ -17231,10 +17194,6 @@
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();
@@ -27098,6 +27057,33 @@
}
+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 {
@@ -27108,9 +27094,11 @@
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);
@@ -27118,13 +27106,12 @@
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";
}
@@ -27202,16 +27189,14 @@
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
@@ -27220,7 +27205,8 @@
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();
@@ -28280,17 +28266,6 @@
field public final int supportedRouteMask;
}
- public final class CameraCapabilities implements android.os.Parcelable {
- ctor public CameraCapabilities(boolean, float, int, int);
- method public int describeContents();
- method public int getHeight();
- method public float getMaxZoom();
- method public int getWidth();
- method public boolean isZoomSupported();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator CREATOR;
- }
-
public abstract class Conference {
ctor public Conference(android.telecomm.PhoneAccountHandle);
method public boolean addConnection(android.telecomm.Connection);
@@ -28330,7 +28305,7 @@
method public final android.telecomm.StatusHints getStatusHints();
method public final boolean isRequestingRingback();
method public void onAbort();
- method public void onAnswer(int);
+ method public void onAnswer();
method public void onConferenceWith(android.telecomm.Connection);
method public void onDisconnect();
method public void onHold();
@@ -28371,7 +28346,7 @@
}
public final class ConnectionRequest implements android.os.Parcelable {
- ctor public ConnectionRequest(android.telecomm.PhoneAccountHandle, android.net.Uri, int, android.os.Bundle, int);
+ ctor public ConnectionRequest(android.telecomm.PhoneAccountHandle, android.net.Uri, int, android.os.Bundle);
method public int describeContents();
method public android.telecomm.PhoneAccountHandle getAccountHandle();
method public android.os.Bundle getExtras();
@@ -28422,11 +28397,11 @@
method public void writeToParcel(android.os.Parcel, int);
field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
- field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
field public static final android.os.Parcelable.Creator CREATOR;
}
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);
@@ -28449,18 +28424,14 @@
public final class PhoneCapabilities {
method public static java.lang.String toString(int);
field public static final int ADD_CALL = 16; // 0x10
- field public static final int ALL = 3327; // 0xcff
+ field public static final int ALL = 255; // 0xff
field public static final int GENERIC_CONFERENCE = 128; // 0x80
field public static final int HOLD = 1; // 0x1
field public static final int MERGE_CALLS = 4; // 0x4
field public static final int MUTE = 64; // 0x40
field public static final int RESPOND_VIA_TEXT = 32; // 0x20
- field public static final int SUPPORTS_VT_LOCAL = 256; // 0x100
- field public static final int SUPPORTS_VT_REMOTE = 512; // 0x200
field public static final int SUPPORT_HOLD = 2; // 0x2
field public static final int SWAP_CALLS = 8; // 0x8
- field public static final int VoLTE = 1024; // 0x400
- field public static final int VoWIFI = 2048; // 0x800
}
public class PropertyPresentation {
@@ -28498,7 +28469,7 @@
public final class RemoteConnection {
method public void abort();
method public void addListener(android.telecomm.RemoteConnection.Listener);
- method public void answer(int);
+ method public void answer();
method public void disconnect();
method public boolean getAudioModeIsVoip();
method public int getCallCapabilities();
@@ -29811,7 +29782,6 @@
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public java.lang.String getNameForUid(int);
@@ -29830,15 +29800,12 @@
method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.KeySet getSigningKeySet(java.lang.String);
method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public java.lang.String[] getSystemSharedLibraryNames();
method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public boolean hasSystemFeature(java.lang.String);
method public boolean isSafeMode();
- method public boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
- method public boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
@@ -32239,17 +32206,23 @@
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 {
@@ -39590,22 +39563,6 @@
}
-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/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 74ccbc2..6e77e132 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -161,17 +161,17 @@
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/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index d9b40b1..da34094 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -866,6 +866,7 @@
private void runInstall() {
int installFlags = PackageManager.INSTALL_ALL_USERS;
+ int userId = UserHandle.USER_ALL;
String installerPackageName = null;
String opt;
@@ -909,6 +910,13 @@
}
} else if (opt.equals("--abi")) {
abi = checkAbiArgument(nextOptionData());
+ } else if (opt.equals("--user")) {
+ userId = Integer.parseInt(nextOptionData());
+ if (userId == UserHandle.USER_ALL) {
+ installFlags |= PackageManager.INSTALL_ALL_USERS;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_ALL_USERS;
+ }
} else {
System.err.println("Error: Unknown option: " + opt);
return;
@@ -953,8 +961,8 @@
VerificationParams verificationParams = new VerificationParams(verificationURI,
originatingURI, referrerURI, VerificationParams.NO_UID, null);
- mPm.installPackage(apkFilePath, obs.getBinder(), installFlags, installerPackageName,
- verificationParams, abi);
+ mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags, installerPackageName,
+ verificationParams, abi, userId);
synchronized (obs) {
while (!obs.finished) {
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index e57be83..f4e4671 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -370,14 +370,23 @@
+ " propertyXName or propertyYName is needed for PathData");
} else {
Path path = PathParser.createPathFromPathData(pathData);
- Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, !getFloats);
+ PathKeyframes keyframeSet = KeyframeSet.ofPath(path);
+ Keyframes xKeyframes;
+ Keyframes yKeyframes;
+ if (getFloats) {
+ xKeyframes = keyframeSet.createXFloatKeyframes();
+ yKeyframes = keyframeSet.createYFloatKeyframes();
+ } else {
+ xKeyframes = keyframeSet.createXIntKeyframes();
+ yKeyframes = keyframeSet.createYIntKeyframes();
+ }
PropertyValuesHolder x = null;
PropertyValuesHolder y = null;
if (propertyXName != null) {
- x = PropertyValuesHolder.ofKeyframe(propertyXName, keyframes[0]);
+ x = PropertyValuesHolder.ofKeyframes(propertyXName, xKeyframes);
}
if (propertyYName != null) {
- y = PropertyValuesHolder.ofKeyframe(propertyYName, keyframes[1]);
+ y = PropertyValuesHolder.ofKeyframes(propertyYName, yKeyframes);
}
if (x == null) {
oa.setValues(y);
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index 2d87e13..12e5862 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -30,7 +30,7 @@
* TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the
* Object equivalents of these primitive types.</p>
*/
-class FloatKeyframeSet extends KeyframeSet {
+class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes {
private float firstValue;
private float lastValue;
private float deltaValue;
@@ -58,10 +58,11 @@
}
@Override
- void invalidateCache() {
+ public void invalidateCache() {
firstTime = true;
}
+ @Override
public float getFloatValue(float fraction) {
if (mNumKeyframes == 2) {
if (firstTime) {
@@ -135,5 +136,9 @@
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
+ @Override
+ public Class getType() {
+ return Float.class;
+ }
}
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index ce47e2b..7a5b0ec 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -30,7 +30,7 @@
* TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the
* Object equivalents of these primitive types.</p>
*/
-class IntKeyframeSet extends KeyframeSet {
+class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes {
private int firstValue;
private int lastValue;
private int deltaValue;
@@ -58,10 +58,11 @@
}
@Override
- void invalidateCache() {
+ public void invalidateCache() {
firstTime = true;
}
+ @Override
public int getIntValue(float fraction) {
if (mNumKeyframes == 2) {
if (firstTime) {
@@ -134,5 +135,9 @@
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
}
+ @Override
+ public Class getType() {
+ return Integer.class;
+ }
}
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index a3db3a1..fc9bbb1 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -21,6 +21,7 @@
import android.animation.Keyframe.IntKeyframe;
import android.animation.Keyframe.FloatKeyframe;
import android.animation.Keyframe.ObjectKeyframe;
+import android.graphics.Path;
import android.util.Log;
/**
@@ -28,7 +29,7 @@
* values between those keyframes for a given animation. The class internal to the animation
* package because it is an implementation detail of how Keyframes are stored and used.
*/
-class KeyframeSet {
+class KeyframeSet implements Keyframes {
int mNumKeyframes;
@@ -52,7 +53,12 @@
* If subclass has variables that it calculates based on the Keyframes, it should reset them
* when this method is called because Keyframe contents might have changed.
*/
- void invalidateCache() {
+ @Override
+ public void invalidateCache() {
+ }
+
+ public ArrayList<Keyframe> getKeyframes() {
+ return mKeyframes;
}
public static KeyframeSet ofInt(int... values) {
@@ -144,6 +150,10 @@
return new KeyframeSet(keyframes);
}
+ public static PathKeyframes ofPath(Path path) {
+ return new PathKeyframes(path);
+ }
+
/**
* Sets the TypeEvaluator to be used when calculating animated values. This object
* is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet,
@@ -157,6 +167,11 @@
}
@Override
+ public Class getType() {
+ return mFirstKeyframe.getType();
+ }
+
+ @Override
public KeyframeSet clone() {
ArrayList<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
diff --git a/core/java/android/animation/Keyframes.java b/core/java/android/animation/Keyframes.java
new file mode 100644
index 0000000..6611c6c
--- /dev/null
+++ b/core/java/android/animation/Keyframes.java
@@ -0,0 +1,94 @@
+/*
+ * 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.animation;
+
+import java.util.ArrayList;
+
+/**
+ * This interface abstracts a collection of Keyframe objects and is called by
+ * ValueAnimator to calculate values between those keyframes for a given animation.
+ */
+interface Keyframes extends Cloneable {
+
+ /**
+ * Sets the TypeEvaluator to be used when calculating animated values. This object
+ * is required only for Keyframes that are not either IntKeyframes or FloatKeyframes,
+ * both of which assume their own evaluator to speed up calculations with those primitive
+ * types.
+ *
+ * @param evaluator The TypeEvaluator to be used to calculate animated values.
+ */
+ void setEvaluator(TypeEvaluator evaluator);
+
+ /**
+ * @return The value type contained by the contained Keyframes.
+ */
+ Class getType();
+
+ /**
+ * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
+ * animation's interpolator) and the evaluator used to calculate in-between values. This
+ * function maps the input fraction to the appropriate keyframe interval and a fraction
+ * between them and returns the interpolated value. Note that the input fraction may fall
+ * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
+ * spring interpolation that might send the fraction past 1.0). We handle this situation by
+ * just using the two keyframes at the appropriate end when the value is outside those bounds.
+ *
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ Object getValue(float fraction);
+
+ /**
+ * If subclass has variables that it calculates based on the Keyframes, it should reset them
+ * when this method is called because Keyframe contents might have changed.
+ */
+ void invalidateCache();
+
+ /**
+ * @return A list of all Keyframes contained by this. This may return null if this is
+ * not made up of Keyframes.
+ */
+ ArrayList<Keyframe> getKeyframes();
+
+ Keyframes clone();
+
+ /**
+ * A specialization of Keyframes that has integer primitive value calculation.
+ */
+ public interface IntKeyframes extends Keyframes {
+
+ /**
+ * Works like {@link #getValue(float)}, but returning a primitive.
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ int getIntValue(float fraction);
+ }
+
+ /**
+ * A specialization of Keyframes that has float primitive value calculation.
+ */
+ public interface FloatKeyframes extends Keyframes {
+
+ /**
+ * Works like {@link #getValue(float)}, but returning a primitive.
+ * @param fraction The elapsed fraction of the animation
+ * @return The animated value.
+ */
+ float getFloatValue(float fraction);
+ }
+}
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 188408d..5790682 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -899,11 +899,15 @@
PropertyValuesHolder[] oldValues = valueAnim.getValues();
for (int i = 0; i < oldValues.length; ++i) {
PropertyValuesHolder pvh = oldValues[i];
- KeyframeSet keyframeSet = pvh.mKeyframeSet;
- if (keyframeSet.mFirstKeyframe == null ||
- keyframeSet.mLastKeyframe == null ||
- !keyframeSet.mFirstKeyframe.getValue().equals(
- keyframeSet.mLastKeyframe.getValue())) {
+ if (pvh.mKeyframes instanceof KeyframeSet) {
+ KeyframeSet keyframeSet = (KeyframeSet) pvh.mKeyframes;
+ if (keyframeSet.mFirstKeyframe == null ||
+ keyframeSet.mLastKeyframe == null ||
+ !keyframeSet.mFirstKeyframe.getValue().equals(
+ keyframeSet.mLastKeyframe.getValue())) {
+ valuesDiffer = true;
+ }
+ } else if (!pvh.mKeyframes.getValue(0).equals(pvh.mKeyframes.getValue(1))) {
valuesDiffer = true;
}
}
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index a4ac73f..500634c 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -239,9 +239,11 @@
*/
public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName,
Path path) {
- Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, true);
- PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xPropertyName, keyframes[0]);
- PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yPropertyName, keyframes[1]);
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName,
+ keyframes.createXIntKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName,
+ keyframes.createYIntKeyframes());
return ofPropertyValuesHolder(target, x, y);
}
@@ -278,9 +280,11 @@
*/
public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty,
Property<T, Integer> yProperty, Path path) {
- Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, true);
- PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xProperty, keyframes[0]);
- PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yProperty, keyframes[1]);
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xProperty,
+ keyframes.createXIntKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yProperty,
+ keyframes.createYIntKeyframes());
return ofPropertyValuesHolder(target, x, y);
}
@@ -429,9 +433,11 @@
*/
public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
Path path) {
- Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, false);
- PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xPropertyName, keyframes[0]);
- PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yPropertyName, keyframes[1]);
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName,
+ keyframes.createXFloatKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName,
+ keyframes.createYFloatKeyframes());
return ofPropertyValuesHolder(target, x, y);
}
@@ -469,9 +475,11 @@
*/
public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty,
Property<T, Float> yProperty, Path path) {
- Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, false);
- PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xProperty, keyframes[0]);
- PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yProperty, keyframes[1]);
+ PathKeyframes keyframes = KeyframeSet.ofPath(path);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xProperty,
+ keyframes.createXFloatKeyframes());
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yProperty,
+ keyframes.createYFloatKeyframes());
return ofPropertyValuesHolder(target, x, y);
}
@@ -652,6 +660,10 @@
* uses a type other than <code>PointF</code>, <code>converter</code> can be used to change
* from <code>PointF</code> to the type associated with the <code>Property</code>.
*
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
* @param target The object whose property is to be animated.
* @param property The property being animated. Should not be null.
* @param converter Converts a PointF to the type associated with the setter. May be
@@ -809,10 +821,9 @@
Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
for (int i = 0; i < mValues.length; ++i) {
PropertyValuesHolder pvh = mValues[i];
- ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;
Log.d(LOG_TAG, " Values[" + i + "]: " +
- pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +
- keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());
+ pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
+ pvh.mKeyframes.getValue(1));
}
}
super.start();
diff --git a/core/java/android/animation/PathKeyframes.java b/core/java/android/animation/PathKeyframes.java
new file mode 100644
index 0000000..70eed90
--- /dev/null
+++ b/core/java/android/animation/PathKeyframes.java
@@ -0,0 +1,254 @@
+/*
+ * 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.animation;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.MathUtils;
+
+import java.util.ArrayList;
+
+/**
+ * PathKeyframes relies on approximating the Path as a series of line segments.
+ * The line segments are recursively divided until there is less than 1/2 pixel error
+ * between the lines and the curve. Each point of the line segment is converted
+ * to a Keyframe and a linear interpolation between Keyframes creates a good approximation
+ * of the curve.
+ * <p>
+ * PathKeyframes is optimized to reduce the number of objects created when there are
+ * many keyframes for a curve.
+ * </p>
+ * <p>
+ * Typically, the returned type is a PointF, but the individual components can be extracted
+ * as either an IntKeyframes or FloatKeyframes.
+ * </p>
+ */
+class PathKeyframes implements Keyframes {
+ private static final int FRACTION_OFFSET = 0;
+ private static final int X_OFFSET = 1;
+ private static final int Y_OFFSET = 2;
+ private static final int NUM_COMPONENTS = 3;
+ private static final ArrayList<Keyframe> EMPTY_KEYFRAMES = new ArrayList<Keyframe>();
+
+ private PointF mTempPointF = new PointF();
+ private float[] mKeyframeData;
+
+ public PathKeyframes(Path path) {
+ this(path, 0.5f);
+ }
+
+ public PathKeyframes(Path path, float error) {
+ if (path == null || path.isEmpty()) {
+ throw new IllegalArgumentException("The path must not be null or empty");
+ }
+ mKeyframeData = path.approximate(error);
+ }
+
+ @Override
+ public ArrayList<Keyframe> getKeyframes() {
+ return EMPTY_KEYFRAMES;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ fraction = MathUtils.constrain(fraction, 0, 1);
+
+ int numPoints = mKeyframeData.length / 3;
+
+ if (fraction == 0) {
+ return pointForIndex(0);
+ } else if (fraction == 1) {
+ return pointForIndex(numPoints - 1);
+ } else {
+ // Binary search for the correct section
+ int low = 0;
+ int high = numPoints - 1;
+
+ while (low <= high) {
+ int mid = (low + high) / 2;
+ float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET];
+
+ if (fraction < midFraction) {
+ high = mid - 1;
+ } else if (fraction > midFraction) {
+ low = mid + 1;
+ } else {
+ return pointForIndex(mid);
+ }
+ }
+
+ // now high is below the fraction and low is above the fraction
+ int startBase = (high * NUM_COMPONENTS);
+ int endBase = (low * NUM_COMPONENTS);
+
+ float startFraction = mKeyframeData[startBase + FRACTION_OFFSET];
+ float endFraction = mKeyframeData[endBase + FRACTION_OFFSET];
+
+ float intervalFraction = (fraction - startFraction)/(endFraction - startFraction);
+
+ float startX = mKeyframeData[startBase + X_OFFSET];
+ float endX = mKeyframeData[endBase + X_OFFSET];
+ float startY = mKeyframeData[startBase + Y_OFFSET];
+ float endY = mKeyframeData[endBase + Y_OFFSET];
+
+ float x = interpolate(intervalFraction, startX, endX);
+ float y = interpolate(intervalFraction, startY, endY);
+
+ mTempPointF.set(x, y);
+ return mTempPointF;
+ }
+ }
+
+ @Override
+ public void invalidateCache() {
+ }
+
+ @Override
+ public void setEvaluator(TypeEvaluator evaluator) {
+ }
+
+ @Override
+ public Class getType() {
+ return PointF.class;
+ }
+
+ @Override
+ public Keyframes clone() {
+ Keyframes clone = null;
+ try {
+ clone = (Keyframes) super.clone();
+ } catch (CloneNotSupportedException e) {}
+ return clone;
+ }
+
+ private PointF pointForIndex(int index) {
+ int base = (index * NUM_COMPONENTS);
+ int xOffset = base + X_OFFSET;
+ int yOffset = base + Y_OFFSET;
+ mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]);
+ return mTempPointF;
+ }
+
+ private static float interpolate(float fraction, float startValue, float endValue) {
+ float diff = endValue - startValue;
+ return startValue + (diff * fraction);
+ }
+
+ /**
+ * Returns a FloatKeyframes for the X component of the Path.
+ * @return a FloatKeyframes for the X component of the Path.
+ */
+ public FloatKeyframes createXFloatKeyframes() {
+ return new FloatKeyframesBase() {
+ @Override
+ public float getFloatValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return pointF.x;
+ }
+ };
+ }
+
+ /**
+ * Returns a FloatKeyframes for the Y component of the Path.
+ * @return a FloatKeyframes for the Y component of the Path.
+ */
+ public FloatKeyframes createYFloatKeyframes() {
+ return new FloatKeyframesBase() {
+ @Override
+ public float getFloatValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return pointF.y;
+ }
+ };
+ }
+
+ /**
+ * Returns an IntKeyframes for the X component of the Path.
+ * @return an IntKeyframes for the X component of the Path.
+ */
+ public IntKeyframes createXIntKeyframes() {
+ return new IntKeyframesBase() {
+ @Override
+ public int getIntValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return Math.round(pointF.x);
+ }
+ };
+ }
+
+ /**
+ * Returns an IntKeyframeSet for the Y component of the Path.
+ * @return an IntKeyframeSet for the Y component of the Path.
+ */
+ public IntKeyframes createYIntKeyframes() {
+ return new IntKeyframesBase() {
+ @Override
+ public int getIntValue(float fraction) {
+ PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
+ return Math.round(pointF.y);
+ }
+ };
+ }
+
+ private abstract static class SimpleKeyframes implements Keyframes {
+ @Override
+ public void setEvaluator(TypeEvaluator evaluator) {
+ }
+
+ @Override
+ public void invalidateCache() {
+ }
+
+ @Override
+ public ArrayList<Keyframe> getKeyframes() {
+ return EMPTY_KEYFRAMES;
+ }
+
+ @Override
+ public Keyframes clone() {
+ Keyframes clone = null;
+ try {
+ clone = (Keyframes) super.clone();
+ } catch (CloneNotSupportedException e) {}
+ return clone;
+ }
+ }
+
+ private abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
+ @Override
+ public Class getType() {
+ return Integer.class;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getIntValue(fraction);
+ }
+ }
+
+ private abstract static class FloatKeyframesBase extends SimpleKeyframes
+ implements FloatKeyframes {
+ @Override
+ public Class getType() {
+ return Float.class;
+ }
+
+ @Override
+ public Object getValue(float fraction) {
+ return getFloatValue(fraction);
+ }
+ }
+}
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index 73b83ef..d372933 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -18,7 +18,6 @@
import android.graphics.Path;
import android.graphics.PointF;
-import android.util.FloatMath;
import android.util.FloatProperty;
import android.util.IntProperty;
import android.util.Log;
@@ -26,6 +25,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -75,7 +75,7 @@
/**
* The set of keyframes (time/value pairs) that define this animation.
*/
- KeyframeSet mKeyframeSet = null;
+ Keyframes mKeyframes = null;
// type evaluators for the primitive types handled by this implementation
@@ -219,11 +219,9 @@
* @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
*/
public static PropertyValuesHolder ofMultiInt(String propertyName, Path path) {
- Keyframe[] keyframes = createKeyframes(path);
- KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(keyframes);
- TypeEvaluator<PointF> evaluator = new PointFEvaluator(new PointF());
+ Keyframes keyframes = KeyframeSet.ofPath(path);
PointFToIntArray converter = new PointFToIntArray();
- return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ return new MultiIntValuesHolder(propertyName, converter, null, keyframes);
}
/**
@@ -339,11 +337,9 @@
* @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
*/
public static PropertyValuesHolder ofMultiFloat(String propertyName, Path path) {
- Keyframe[] keyframes = createKeyframes(path);
- KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(keyframes);
- TypeEvaluator<PointF> evaluator = new PointFEvaluator(new PointF());
+ Keyframes keyframes = KeyframeSet.ofPath(path);
PointFToFloatArray converter = new PointFToFloatArray();
- return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ return new MultiFloatValuesHolder(propertyName, converter, null, keyframes);
}
/**
@@ -415,6 +411,10 @@
* <code>TypeConverter</code> to convert from <code>PointF</code> to the target
* type.
*
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
* @param propertyName The name of the property being animated.
* @param converter Converts a PointF to the type associated with the setter. May be
* null if conversion is unnecessary.
@@ -423,9 +423,9 @@
*/
public static PropertyValuesHolder ofObject(String propertyName,
TypeConverter<PointF, ?> converter, Path path) {
- Keyframe[] keyframes = createKeyframes(path);
- PropertyValuesHolder pvh = ofKeyframe(propertyName, keyframes);
- pvh.setEvaluator(new PointFEvaluator(new PointF()));
+ PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+ pvh.mKeyframes = KeyframeSet.ofPath(path);
+ pvh.mValueType = PointF.class;
pvh.setConverter(converter);
return pvh;
}
@@ -484,6 +484,10 @@
* <code>TypeConverter</code> to convert from <code>PointF</code> to the target
* type.
*
+ * <p>The PointF passed to <code>converter</code> or <code>property</code>, if
+ * <code>converter</code> is <code>null</code>, is reused on each animation frame and should
+ * not be stored by the setter or TypeConverter.</p>
+ *
* @param property The property being animated. Should not be null.
* @param converter Converts a PointF to the type associated with the setter. May be
* null if conversion is unnecessary.
@@ -492,9 +496,9 @@
*/
public static <V> PropertyValuesHolder ofObject(Property<?, V> property,
TypeConverter<PointF, V> converter, Path path) {
- Keyframe[] keyframes = createKeyframes(path);
- PropertyValuesHolder pvh = ofKeyframe(property, keyframes);
- pvh.setEvaluator(new PointFEvaluator(new PointF()));
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.mKeyframes = KeyframeSet.ofPath(path);
+ pvh.mValueType = PointF.class;
pvh.setConverter(converter);
return pvh;
}
@@ -520,17 +524,7 @@
*/
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
- if (keyframeSet instanceof IntKeyframeSet) {
- return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet);
- } else if (keyframeSet instanceof FloatKeyframeSet) {
- return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet);
- }
- else {
- PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
- pvh.mKeyframeSet = keyframeSet;
- pvh.mValueType = ((Keyframe)values[0]).getType();
- return pvh;
- }
+ return ofKeyframes(propertyName, keyframeSet);
}
/**
@@ -551,15 +545,32 @@
*/
public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
- if (keyframeSet instanceof IntKeyframeSet) {
- return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet);
- } else if (keyframeSet instanceof FloatKeyframeSet) {
- return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet);
+ return ofKeyframes(property, keyframeSet);
+ }
+
+ static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) {
+ if (keyframes instanceof Keyframes.IntKeyframes) {
+ return new IntPropertyValuesHolder(propertyName, (Keyframes.IntKeyframes) keyframes);
+ } else if (keyframes instanceof Keyframes.FloatKeyframes) {
+ return new FloatPropertyValuesHolder(propertyName,
+ (Keyframes.FloatKeyframes) keyframes);
+ } else {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+ pvh.mKeyframes = keyframes;
+ pvh.mValueType = keyframes.getType();
+ return pvh;
}
- else {
+ }
+
+ static PropertyValuesHolder ofKeyframes(Property property, Keyframes keyframes) {
+ if (keyframes instanceof Keyframes.IntKeyframes) {
+ return new IntPropertyValuesHolder(property, (Keyframes.IntKeyframes) keyframes);
+ } else if (keyframes instanceof Keyframes.FloatKeyframes) {
+ return new FloatPropertyValuesHolder(property, (Keyframes.FloatKeyframes) keyframes);
+ } else {
PropertyValuesHolder pvh = new PropertyValuesHolder(property);
- pvh.mKeyframeSet = keyframeSet;
- pvh.mValueType = ((Keyframe)values[0]).getType();
+ pvh.mKeyframes = keyframes;
+ pvh.mValueType = keyframes.getType();
return pvh;
}
}
@@ -579,7 +590,7 @@
*/
public void setIntValues(int... values) {
mValueType = int.class;
- mKeyframeSet = KeyframeSet.ofInt(values);
+ mKeyframes = KeyframeSet.ofInt(values);
}
/**
@@ -597,7 +608,7 @@
*/
public void setFloatValues(float... values) {
mValueType = float.class;
- mKeyframeSet = KeyframeSet.ofFloat(values);
+ mKeyframes = KeyframeSet.ofFloat(values);
}
/**
@@ -612,7 +623,7 @@
for (int i = 0; i < numKeyframes; ++i) {
keyframes[i] = (Keyframe)values[i];
}
- mKeyframeSet = new KeyframeSet(keyframes);
+ mKeyframes = new KeyframeSet(keyframes);
}
/**
@@ -630,9 +641,9 @@
*/
public void setObjectValues(Object... values) {
mValueType = values[0].getClass();
- mKeyframeSet = KeyframeSet.ofObject(values);
+ mKeyframes = KeyframeSet.ofObject(values);
if (mEvaluator != null) {
- mKeyframeSet.setEvaluator(mEvaluator);
+ mKeyframes.setEvaluator(mEvaluator);
}
}
@@ -775,12 +786,15 @@
* @param target The object on which the setter (and possibly getter) exist.
*/
void setupSetterAndGetter(Object target) {
- mKeyframeSet.invalidateCache();
+ mKeyframes.invalidateCache();
if (mProperty != null) {
// check to make sure that mProperty is on the class of target
try {
Object testValue = null;
- for (Keyframe kf : mKeyframeSet.mKeyframes) {
+ ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ int keyframeCount = keyframes == null ? 0 : keyframes.size();
+ for (int i = 0; i < keyframeCount; i++) {
+ Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (testValue == null) {
testValue = convertBack(mProperty.get(target));
@@ -800,7 +814,10 @@
if (mSetter == null) {
setupSetter(targetClass);
}
- for (Keyframe kf : mKeyframeSet.mKeyframes) {
+ ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ int keyframeCount = keyframes == null ? 0 : keyframes.size();
+ for (int i = 0; i < keyframeCount; i++) {
+ Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (mGetter == null) {
setupGetter(targetClass);
@@ -873,7 +890,10 @@
* @param target The object which holds the start values that should be set.
*/
void setupStartValue(Object target) {
- setupValue(target, mKeyframeSet.mKeyframes.get(0));
+ ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ if (!keyframes.isEmpty()) {
+ setupValue(target, keyframes.get(0));
+ }
}
/**
@@ -885,7 +905,10 @@
* @param target The object which holds the start values that should be set.
*/
void setupEndValue(Object target) {
- setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1));
+ ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ if (!keyframes.isEmpty()) {
+ setupValue(target, keyframes.get(keyframes.size() - 1));
+ }
}
@Override
@@ -894,7 +917,7 @@
PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
newPVH.mPropertyName = mPropertyName;
newPVH.mProperty = mProperty;
- newPVH.mKeyframeSet = mKeyframeSet.clone();
+ newPVH.mKeyframes = mKeyframes.clone();
newPVH.mEvaluator = mEvaluator;
return newPVH;
} catch (CloneNotSupportedException e) {
@@ -941,7 +964,7 @@
if (mEvaluator != null) {
// KeyframeSet knows how to evaluate the common types - only give it a custom
// evaluator if one has been set on this class
- mKeyframeSet.setEvaluator(mEvaluator);
+ mKeyframes.setEvaluator(mEvaluator);
}
}
@@ -957,7 +980,7 @@
*/
public void setEvaluator(TypeEvaluator evaluator) {
mEvaluator = evaluator;
- mKeyframeSet.setEvaluator(evaluator);
+ mKeyframes.setEvaluator(evaluator);
}
/**
@@ -967,7 +990,7 @@
* @param fraction The elapsed, interpolated fraction of the animation.
*/
void calculateValue(float fraction) {
- Object value = mKeyframeSet.getValue(fraction);
+ Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
@@ -1025,7 +1048,7 @@
@Override
public String toString() {
- return mPropertyName + ": " + mKeyframeSet.toString();
+ return mPropertyName + ": " + mKeyframes.toString();
}
/**
@@ -1059,21 +1082,21 @@
long mJniSetter;
private IntProperty mIntProperty;
- IntKeyframeSet mIntKeyframeSet;
+ Keyframes.IntKeyframes mIntKeyframes;
int mIntAnimatedValue;
- public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) {
+ public IntPropertyValuesHolder(String propertyName, Keyframes.IntKeyframes keyframes) {
super(propertyName);
mValueType = int.class;
- mKeyframeSet = keyframeSet;
- mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
+ mKeyframes = keyframes;
+ mIntKeyframes = keyframes;
}
- public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) {
+ public IntPropertyValuesHolder(Property property, Keyframes.IntKeyframes keyframes) {
super(property);
mValueType = int.class;
- mKeyframeSet = keyframeSet;
- mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
+ mKeyframes = keyframes;
+ mIntKeyframes = keyframes;
if (property instanceof IntProperty) {
mIntProperty = (IntProperty) mProperty;
}
@@ -1095,12 +1118,12 @@
@Override
public void setIntValues(int... values) {
super.setIntValues(values);
- mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
+ mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
}
@Override
void calculateValue(float fraction) {
- mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction);
+ mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
}
@Override
@@ -1111,7 +1134,7 @@
@Override
public IntPropertyValuesHolder clone() {
IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
- newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet;
+ newPVH.mIntKeyframes = (Keyframes.IntKeyframes) newPVH.mKeyframes;
return newPVH;
}
@@ -1196,21 +1219,21 @@
long mJniSetter;
private FloatProperty mFloatProperty;
- FloatKeyframeSet mFloatKeyframeSet;
+ Keyframes.FloatKeyframes mFloatKeyframes;
float mFloatAnimatedValue;
- public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) {
+ public FloatPropertyValuesHolder(String propertyName, Keyframes.FloatKeyframes keyframes) {
super(propertyName);
mValueType = float.class;
- mKeyframeSet = keyframeSet;
- mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
+ mKeyframes = keyframes;
+ mFloatKeyframes = keyframes;
}
- public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) {
+ public FloatPropertyValuesHolder(Property property, Keyframes.FloatKeyframes keyframes) {
super(property);
mValueType = float.class;
- mKeyframeSet = keyframeSet;
- mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
+ mKeyframes = keyframes;
+ mFloatKeyframes = keyframes;
if (property instanceof FloatProperty) {
mFloatProperty = (FloatProperty) mProperty;
}
@@ -1232,12 +1255,12 @@
@Override
public void setFloatValues(float... values) {
super.setFloatValues(values);
- mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
+ mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
@Override
void calculateValue(float fraction) {
- mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction);
+ mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
@Override
@@ -1248,7 +1271,7 @@
@Override
public FloatPropertyValuesHolder clone() {
FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
- newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet;
+ newPVH.mFloatKeyframes = (Keyframes.FloatKeyframes) newPVH.mKeyframes;
return newPVH;
}
@@ -1340,10 +1363,10 @@
}
public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
- TypeEvaluator evaluator, KeyframeSet keyframeSet) {
+ TypeEvaluator evaluator, Keyframes keyframes) {
super(propertyName);
setConverter(converter);
- mKeyframeSet = keyframeSet;
+ mKeyframes = keyframes;
setEvaluator(evaluator);
}
@@ -1443,10 +1466,10 @@
}
public MultiIntValuesHolder(String propertyName, TypeConverter converter,
- TypeEvaluator evaluator, KeyframeSet keyframeSet) {
+ TypeEvaluator evaluator, Keyframes keyframes) {
super(propertyName);
setConverter(converter);
- mKeyframeSet = keyframeSet;
+ mKeyframes = keyframes;
setEvaluator(evaluator);
}
@@ -1532,77 +1555,6 @@
}
}
- /* Path interpolation relies on approximating the Path as a series of line segments.
- The line segments are recursively divided until there is less than 1/2 pixel error
- between the lines and the curve. Each point of the line segment is converted
- to a Keyframe and a linear interpolation between Keyframes creates a good approximation
- of the curve.
-
- The fraction for each Keyframe is the length along the Path to the point, divided by
- the total Path length. Two points may have the same fraction in the case of a move
- command causing a disjoint Path.
-
- The value for each Keyframe is either the point as a PointF or one of the x or y
- coordinates as an int or float. In the latter case, two Keyframes are generated for
- each point that have the same fraction. */
-
- /**
- * Returns separate Keyframes arrays for the x and y coordinates along a Path. If
- * isInt is true, the Keyframes will be IntKeyframes, otherwise they will be FloatKeyframes.
- * The element at index 0 are the x coordinate Keyframes and element at index 1 are the
- * y coordinate Keyframes. The returned values can be linearly interpolated and get less
- * than 1/2 pixel error.
- */
- static Keyframe[][] createKeyframes(Path path, boolean isInt) {
- if (path == null || path.isEmpty()) {
- throw new IllegalArgumentException("The path must not be null or empty");
- }
- float[] pointComponents = path.approximate(0.5f);
-
- int numPoints = pointComponents.length / 3;
-
- Keyframe[][] keyframes = new Keyframe[2][];
- keyframes[0] = new Keyframe[numPoints];
- keyframes[1] = new Keyframe[numPoints];
- int componentIndex = 0;
- for (int i = 0; i < numPoints; i++) {
- float fraction = pointComponents[componentIndex++];
- float x = pointComponents[componentIndex++];
- float y = pointComponents[componentIndex++];
- if (isInt) {
- keyframes[0][i] = Keyframe.ofInt(fraction, Math.round(x));
- keyframes[1][i] = Keyframe.ofInt(fraction, Math.round(y));
- } else {
- keyframes[0][i] = Keyframe.ofFloat(fraction, x);
- keyframes[1][i] = Keyframe.ofFloat(fraction, y);
- }
- }
- return keyframes;
- }
-
- /**
- * Returns PointF Keyframes for a Path. The resulting points can be linearly interpolated
- * with less than 1/2 pixel in error.
- */
- private static Keyframe[] createKeyframes(Path path) {
- if (path == null || path.isEmpty()) {
- throw new IllegalArgumentException("The path must not be null or empty");
- }
- float[] pointComponents = path.approximate(0.5f);
-
- int numPoints = pointComponents.length / 3;
-
- Keyframe[] keyframes = new Keyframe[numPoints];
- int componentIndex = 0;
- for (int i = 0; i < numPoints; i++) {
- float fraction = pointComponents[componentIndex++];
- float x = pointComponents[componentIndex++];
- float y = pointComponents[componentIndex++];
- keyframes[i] = Keyframe.ofObject(fraction, new PointF(x, y));
- }
- return keyframes;
- }
-
/**
* Convert from PointF to float[] for multi-float setters along a Path.
*/
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 @@
* 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 @@
* @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 @@
/**
* 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 @@
* @see #requestVisibleBehind(boolean)
* @see #onBackgroundVisibleBehindChanged(boolean)
*/
- public void onVisibleBehindCancelled() {
+ public void onVisibleBehindCanceled() {
mCalled = true;
}
@@ -5521,7 +5521,7 @@
* {@link #requestVisibleBehind(boolean)}, false otherwise.
*
* @see #requestVisibleBehind(boolean)
- * @see #onVisibleBehindCancelled()
+ * @see #onVisibleBehindCanceled()
* @see #onBackgroundVisibleBehindChanged(boolean)
* @hide
*/
@@ -5544,7 +5544,7 @@
* @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.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 @@
}
/**
- * 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 @@
* 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 @@
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 @@
}
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 @@
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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 9342ae5..6843827 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1494,57 +1494,52 @@
return false;
}
+ /** @hide */
@Override
public KeySet getKeySetByAlias(String packageName, String alias) {
Preconditions.checkNotNull(packageName);
Preconditions.checkNotNull(alias);
- IBinder keySetToken;
+ KeySet ks;
try {
- keySetToken = mPM.getKeySetByAlias(packageName, alias);
+ ks = mPM.getKeySetByAlias(packageName, alias);
} catch (RemoteException e) {
return null;
}
- if (keySetToken == null) {
- return null;
- }
- return new KeySet(keySetToken);
+ return ks;
}
+ /** @hide */
@Override
public KeySet getSigningKeySet(String packageName) {
Preconditions.checkNotNull(packageName);
- IBinder keySetToken;
+ KeySet ks;
try {
- keySetToken = mPM.getSigningKeySet(packageName);
+ ks = mPM.getSigningKeySet(packageName);
} catch (RemoteException e) {
return null;
}
- if (keySetToken == null) {
- return null;
- }
- return new KeySet(keySetToken);
+ return ks;
}
-
+ /** @hide */
@Override
public boolean isSignedBy(String packageName, KeySet ks) {
Preconditions.checkNotNull(packageName);
Preconditions.checkNotNull(ks);
- IBinder keySetToken = ks.getToken();
try {
- return mPM.isPackageSignedByKeySet(packageName, keySetToken);
+ return mPM.isPackageSignedByKeySet(packageName, ks);
} catch (RemoteException e) {
return false;
}
}
+ /** @hide */
@Override
public boolean isSignedByExactly(String packageName, KeySet ks) {
Preconditions.checkNotNull(packageName);
Preconditions.checkNotNull(ks);
- IBinder keySetToken = ks.getToken();
try {
- return mPM.isPackageSignedByKeySetExactly(packageName, keySetToken);
+ return mPM.isPackageSignedByKeySetExactly(packageName, ks);
} catch (RemoteException e) {
return false;
}
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 @@
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..47d3fd6 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -194,10 +194,12 @@
@Override
public boolean onPreDraw() {
getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
- Bundle state = captureSharedElementState();
- setSharedElementMatrices();
- moveSharedElementsToOverlay();
- mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
+ if (mResultReceiver != null) {
+ Bundle state = captureSharedElementState();
+ setSharedElementMatrices();
+ moveSharedElementsToOverlay();
+ mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
+ }
return true;
}
});
@@ -327,7 +329,7 @@
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 @@
});
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..dbd180f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.net.Uri;
+import android.os.Bundle;
import android.service.notification.Condition;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
@@ -58,11 +59,15 @@
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();
+ boolean matchesCallFilter(in Bundle extras);
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
* 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 @@
if (mRebuildNotification == null) {
throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode.");
}
+ mHasThreeLines = hasThreeLines();
Bundle extras = mRebuildNotification.extras;
@@ -3124,6 +3148,7 @@
}
extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW);
+ mHasThreeLines = false;
return mRebuildNotification;
}
@@ -3238,6 +3263,7 @@
*/
public Notification build() {
mOriginatingUserId = mContext.getUserId();
+ mHasThreeLines = hasThreeLines();
Notification n = buildUnstyled();
@@ -3259,6 +3285,7 @@
mStyle.addExtras(n.extras);
}
+ mHasThreeLines = false;
return n;
}
@@ -3388,7 +3415,7 @@
*/
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 @@
applyTopPadding(contentView);
+ mBuilder.shrinkLine3Text(contentView);
+
mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
return contentView;
@@ -3800,6 +3829,8 @@
applyTopPadding(contentView);
+ mBuilder.shrinkLine3Text(contentView);
+
mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
return contentView;
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fc047de..7dc1ad64 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -20,6 +20,7 @@
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -251,5 +252,17 @@
}
}
+ /**
+ * @hide
+ */
+ public boolean matchesCallFilter(Bundle extras) {
+ INotificationManager service = getService();
+ try {
+ return service.matchesCallFilter(extras);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
private Context mContext;
}
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 @@
*/
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 @@
}
/**
- * 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 @@
}
/**
- * 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 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 @@
* @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 @@
*
* @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.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 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/Context.java b/core/java/android/content/Context.java
index b7d7c25..f979a0c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1486,8 +1486,6 @@
* @see #sendBroadcast(Intent)
* @see #sendBroadcast(Intent, String)
* @see #sendOrderedBroadcast(Intent, String)
- * @see #sendStickyBroadcast(Intent)
- * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
* @see android.content.BroadcastReceiver
* @see #registerReceiver
* @see android.app.Activity#RESULT_OK
@@ -1584,7 +1582,7 @@
@Nullable Bundle initialExtras);
/**
- * Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
+ * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
* Intent you are sending stays around after the broadcast is complete,
* so that others can quickly retrieve that data through the return
* value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In
@@ -1595,6 +1593,12 @@
* permission in order to use this API. If you do not hold that
* permission, {@link SecurityException} will be thrown.
*
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
* @param intent The Intent to broadcast; all receivers matching this
* Intent will receive the broadcast, and the Intent will be held to
* be re-broadcast to future receivers.
@@ -1602,10 +1606,11 @@
* @see #sendBroadcast(Intent)
* @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
*/
+ @Deprecated
public abstract void sendStickyBroadcast(Intent intent);
/**
- * Version of {@link #sendStickyBroadcast} that allows you to
+ * <p>Version of {@link #sendStickyBroadcast} that allows you to
* receive data back from the broadcast. This is accomplished by
* supplying your own BroadcastReceiver when calling, which will be
* treated as a final receiver at the end of the broadcast -- its
@@ -1622,6 +1627,12 @@
*
* <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
*
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
* @param intent The Intent to broadcast; all receivers matching this
* Intent will receive the broadcast.
* @param resultReceiver Your own BroadcastReceiver to treat as the final
@@ -1644,31 +1655,45 @@
* @see #registerReceiver
* @see android.app.Activity#RESULT_OK
*/
+ @Deprecated
public abstract void sendStickyOrderedBroadcast(Intent intent,
BroadcastReceiver resultReceiver,
@Nullable Handler scheduler, int initialCode, @Nullable String initialData,
@Nullable Bundle initialExtras);
/**
- * Remove the data previously sent with {@link #sendStickyBroadcast},
+ * <p>Remove the data previously sent with {@link #sendStickyBroadcast},
* so that it is as if the sticky broadcast had never happened.
*
* <p>You must hold the {@link android.Manifest.permission#BROADCAST_STICKY}
* permission in order to use this API. If you do not hold that
* permission, {@link SecurityException} will be thrown.
*
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
* @param intent The Intent that was previously broadcast.
*
* @see #sendStickyBroadcast
*/
+ @Deprecated
public abstract void removeStickyBroadcast(Intent intent);
/**
- * Version of {@link #sendStickyBroadcast(Intent)} that allows you to specify the
+ * <p>Version of {@link #sendStickyBroadcast(Intent)} that allows you to specify the
* user the broadcast will be sent to. This is not available to applications
* that are not pre-installed on the system image. Using it requires holding
* the INTERACT_ACROSS_USERS permission.
*
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
* @param intent The Intent to broadcast; all receivers matching this
* Intent will receive the broadcast, and the Intent will be held to
* be re-broadcast to future receivers.
@@ -1676,10 +1701,11 @@
*
* @see #sendBroadcast(Intent)
*/
+ @Deprecated
public abstract void sendStickyBroadcastAsUser(Intent intent, UserHandle user);
/**
- * Version of
+ * <p>Version of
* {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)}
* that allows you to specify the
* user the broadcast will be sent to. This is not available to applications
@@ -1688,6 +1714,12 @@
*
* <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
*
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
* @param intent The Intent to broadcast; all receivers matching this
* Intent will receive the broadcast.
* @param user UserHandle to send the intent to.
@@ -1705,13 +1737,14 @@
*
* @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
*/
+ @Deprecated
public abstract void sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
@Nullable Handler scheduler, int initialCode, @Nullable String initialData,
@Nullable Bundle initialExtras);
/**
- * Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the
+ * <p>Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the
* user the broadcast will be sent to. This is not available to applications
* that are not pre-installed on the system image. Using it requires holding
* the INTERACT_ACROSS_USERS permission.
@@ -1720,11 +1753,18 @@
* permission in order to use this API. If you do not hold that
* permission, {@link SecurityException} will be thrown.
*
+ * @deprecated Sticky broadcasts should not be used. They provide no security (anyone
+ * can access them), no protection (anyone can modify them), and many other problems.
+ * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em>
+ * has changed, with another mechanism for apps to retrieve the current value whenever
+ * desired.
+ *
* @param intent The Intent that was previously broadcast.
* @param user UserHandle to remove the sticky broadcast from.
*
* @see #sendStickyBroadcastAsUser
*/
+ @Deprecated
public abstract void removeStickyBroadcastAsUser(Intent intent, UserHandle user);
/**
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 44478d4..3e1f60a 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -32,6 +32,7 @@
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
import android.content.pm.PackageInfo;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageCleanItem;
@@ -198,6 +199,14 @@
in VerificationParams verificationParams,
in String packageAbiOverride);
+ void installPackageAsUser(in String originPath,
+ in IPackageInstallObserver2 observer,
+ int flags,
+ in String installerPackageName,
+ in VerificationParams verificationParams,
+ in String packageAbiOverride,
+ int userId);
+
void finishPackageInstall(int token);
void setInstallerPackageName(in String targetPackage, in String installerPackageName);
@@ -446,8 +455,8 @@
boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId);
boolean getBlockUninstallForUser(String packageName, int userId);
- IBinder getKeySetByAlias(String packageName, String alias);
- IBinder getSigningKeySet(String packageName);
- boolean isPackageSignedByKeySet(String packageName, IBinder ks);
- boolean isPackageSignedByKeySetExactly(String packageName, IBinder ks);
+ KeySet getKeySetByAlias(String packageName, String alias);
+ KeySet getSigningKeySet(String packageName);
+ boolean isPackageSignedByKeySet(String packageName, in KeySet ks);
+ boolean isPackageSignedByKeySetExactly(String packageName, in KeySet ks);
}
diff --git a/core/java/android/content/pm/KeySet.aidl b/core/java/android/content/pm/KeySet.aidl
new file mode 100644
index 0000000..493d288
--- /dev/null
+++ b/core/java/android/content/pm/KeySet.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.content.pm;
+
+parcelable KeySet;
\ No newline at end of file
diff --git a/core/java/android/content/pm/KeySet.java b/core/java/android/content/pm/KeySet.java
index fcdaa18..643db7e 100644
--- a/core/java/android/content/pm/KeySet.java
+++ b/core/java/android/content/pm/KeySet.java
@@ -17,13 +17,16 @@
package android.content.pm;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
/**
* Represents a {@code KeySet} that has been declared in the AndroidManifest.xml
* file for the application. A {@code KeySet} can be used explicitly to
* represent a trust relationship with other applications on the device.
+ * @hide
*/
-public class KeySet {
+public class KeySet implements Parcelable {
private IBinder token;
@@ -40,6 +43,7 @@
return token;
}
+ /** @hide */
@Override
public boolean equals(Object o) {
if (o instanceof KeySet) {
@@ -48,4 +52,58 @@
}
return false;
}
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return token.hashCode();
+ }
+
+ /**
+ * Implement Parcelable
+ * @hide
+ */
+ public static final Parcelable.Creator<KeySet> CREATOR
+ = new Parcelable.Creator<KeySet>() {
+
+ /**
+ * Create a KeySet from a Parcel
+ *
+ * @param in The parcel containing the KeySet
+ */
+ public KeySet createFromParcel(Parcel source) {
+ return readFromParcel(source);
+ }
+
+ /**
+ * Create an array of null KeySets
+ */
+ public KeySet[] newArray(int size) {
+ return new KeySet[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ private static KeySet readFromParcel(Parcel in) {
+ IBinder token = in.readStrongBinder();
+ return new KeySet(token);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(token);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
}
\ No newline at end of file
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 @@
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 fa2bb4d..1b15ff5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3698,10 +3698,13 @@
*
* @param alias The alias for a given {@link KeySet} as defined in the
* application's AndroidManifest.xml.
+ * @hide
*/
public abstract KeySet getKeySetByAlias(String packageName, String alias);
- /** Return the signing {@link KeySet} for this application. */
+ /** Return the signing {@link KeySet} for this application.
+ * @hide
+ */
public abstract KeySet getSigningKeySet(String packageName);
/**
@@ -3709,6 +3712,7 @@
* of the keys specified by the {@link KeySet} ks. This will return true if
* the package has been signed by additional keys (a superset) as well.
* Compare to {@link #isSignedByExactly(String packageName, KeySet ks)}.
+ * @hide
*/
public abstract boolean isSignedBy(String packageName, KeySet ks);
@@ -3716,6 +3720,7 @@
* Return whether the package denoted by packageName has been signed by all
* of, and only, the keys specified by the {@link KeySet} ks. Compare to
* {@link #isSignedBy(String packageName, KeySet ks)}.
+ * @hide
*/
public abstract boolean isSignedByExactly(String packageName, KeySet ks);
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 3c9b7b3..a57b361 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1575,6 +1575,13 @@
*/
public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17;
+ /**
+ * <p>Turn on custom high dynamic range (HDR) mode.</p>
+ * @see CaptureRequest#CONTROL_SCENE_MODE
+ * @hide
+ */
+ public static final int CONTROL_SCENE_MODE_HDR = 18;
+
//
// Enumeration values for CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
//
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 @@
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 @@
*/
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 TotalCaptureResult(CameraMetadataNative results, int sequenceId) {
super(results, sequenceId);
+
+ mPartialResults = new ArrayList<>();
}
/**
@@ -73,14 +86,13 @@
* <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/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 513d222..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 java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -721,6 +722,13 @@
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 @@
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 @@
/**
* 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 @@
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;
}
@@ -1237,12 +1296,6 @@
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) {
@@ -1250,6 +1303,9 @@
"holder is null, early return at frame "
+ frameNumber);
}
+
+ mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult);
+
return;
}
@@ -1259,6 +1315,8 @@
"camera is closed, early return at frame "
+ frameNumber);
}
+
+ mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult);
return;
}
@@ -1266,6 +1324,8 @@
Runnable resultDispatch = null;
+ CaptureResult finalResult;
+
// Either send a partial result or the final capture completed result
if (isPartialResult) {
final CaptureResult resultAsCapture =
@@ -1283,9 +1343,14 @@
}
}
};
+
+ 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() {
@@ -1299,10 +1364,15 @@
}
}
};
+
+ 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();
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.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 @@
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 @@
}
// 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 @@
// 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 @@
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 @@
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 @@
}
}
+ /** 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/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index ee0ca9c..a8d1018 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -882,6 +882,7 @@
Parameters.SCENE_MODE_PARTY,
Parameters.SCENE_MODE_CANDLELIGHT,
Parameters.SCENE_MODE_BARCODE,
+ Parameters.SCENE_MODE_HDR,
};
private final static int[] sSceneModes = {
@@ -901,6 +902,7 @@
CameraCharacteristics.CONTROL_SCENE_MODE_PARTY,
CameraCharacteristics.CONTROL_SCENE_MODE_CANDLELIGHT,
CameraCharacteristics.CONTROL_SCENE_MODE_BARCODE,
+ CameraCharacteristics.CONTROL_SCENE_MODE_HDR,
};
static int convertSceneModeFromLegacy(String mode) {
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index 85e7531b..2ec6126 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -166,7 +166,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
**/
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 @@
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 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/provider/CallLog.java b/core/java/android/provider/CallLog.java
index d9d4398..0202f91 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -350,6 +350,15 @@
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 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 @@
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 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 @@
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..33e0468 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -904,6 +904,15 @@
public static final String ACTION_APP_NOTIFICATION_SETTINGS
= "android.settings.APP_NOTIFICATION_SETTINGS";
+ /**
+ * Activity Action: Show notification redaction settings.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APP_NOTIFICATION_REDACTION
+ = "android.settings.ACTION_APP_NOTIFICATION_REDACTION";
+
/** @hide */ public static final String EXTRA_APP_UID = "app_uid";
/** @hide */ public static final String EXTRA_APP_PACKAGE = "app_package";
@@ -2113,6 +2122,13 @@
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
@@ -3707,6 +3723,14 @@
"lock_screen_allow_private_notifications";
/**
+ * Set by the system to track if the user needs to see the call to action for
+ * the lockscreen notification policy.
+ * @hide
+ */
+ public static final String SHOW_NOTE_ABOUT_NOTIFICATION_HIDING =
+ "show_note_about_notification_hiding";
+
+ /**
* The Logging ID (a unique 64-bit value) as a hex string.
* Used as a pseudonymous identifier for logging.
* @deprecated This identifier is poorly initialized and has
diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java
index f8bf45b..7e9aba0 100644
--- a/core/java/android/security/IKeystoreService.java
+++ b/core/java/android/security/IKeystoreService.java
@@ -478,6 +478,59 @@
}
return _result;
}
+
+ public int reset_uid(int uid) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeInt(uid);
+ mRemote.transact(Stub.TRANSACTION_reset_uid, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ public int sync_uid(int srcUid, int dstUid) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeInt(srcUid);
+ _data.writeInt(dstUid);
+ mRemote.transact(Stub.TRANSACTION_sync_uid, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ public int password_uid(String password, int uid) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(password);
+ _data.writeInt(uid);
+ mRemote.transact(Stub.TRANSACTION_password_uid, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
}
private static final String DESCRIPTOR = "android.security.keystore";
@@ -505,6 +558,9 @@
static final int TRANSACTION_duplicate = IBinder.FIRST_CALL_TRANSACTION + 20;
static final int TRANSACTION_is_hardware_backed = IBinder.FIRST_CALL_TRANSACTION + 21;
static final int TRANSACTION_clear_uid = IBinder.FIRST_CALL_TRANSACTION + 22;
+ static final int TRANSACTION_reset_uid = IBinder.FIRST_CALL_TRANSACTION + 23;
+ static final int TRANSACTION_sync_uid = IBinder.FIRST_CALL_TRANSACTION + 24;
+ static final int TRANSACTION_password_uid = IBinder.FIRST_CALL_TRANSACTION + 25;
/**
* Cast an IBinder object into an IKeystoreService interface, generating
@@ -597,4 +653,10 @@
public int is_hardware_backed(String string) throws RemoteException;
public int clear_uid(long uid) throws RemoteException;
+
+ public int reset_uid(int uid) throws RemoteException;
+
+ public int sync_uid(int sourceUid, int targetUid) throws RemoteException;
+
+ public int password_uid(String password, int uid) throws RemoteException;
}
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 @@
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 @@
private final String TAG = NotificationListenerService.class.getSimpleName()
+ "[" + getClass().getSimpleName() + "]";
- /** {@link #getCurrentListenerHints() Listener hints} constant - default state. */
- public static final int HINTS_NONE = 0;
+ /**
+ * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
+ * Normal interruption filter.
+ */
+ public static final int INTERRUPTION_FILTER_ALL = 1;
- /** Bitmask range for {@link #getCurrentListenerHints() Listener hints} host interruption level
- * constants. */
- public static final int HOST_INTERRUPTION_LEVEL_MASK = 0x3;
+ /**
+ * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
+ * Priority interruption filter.
+ */
+ public static final int INTERRUPTION_FILTER_PRIORITY = 2;
- /** {@link #getCurrentListenerHints() Listener hints} constant - Normal interruption level. */
- public static final int HINT_HOST_INTERRUPTION_LEVEL_ALL = 1;
-
- /** {@link #getCurrentListenerHints() Listener hints} constant - Priority interruption level. */
- public static final int HINT_HOST_INTERRUPTION_LEVEL_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 @@
// 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 @@
}
/**
+ * 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 @@
* 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 @@
* 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 @@
* 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 @@
}
/**
+ * 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 @@
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/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 @@
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 @@
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 @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 @@
/**
* 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 @@
* @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 @@
* 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 @@
/**
* 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,13 @@
}
/**
- * 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.
*
- * @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 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 +460,52 @@
* 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 createIntentToEnroll() {
+ if (DBG) Slog.d(TAG, "createIntentToEnroll");
synchronized (mLock) {
- return getManageIntentLocked(action);
+ 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.
+ *
+ * @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.
+ * @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 createIntentToUnEnroll() {
+ if (DBG) Slog.d(TAG, "createIntentToUnEnroll");
+ synchronized (mLock) {
+ 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 @@
"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 @@
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 @@
if (w <= width || isSpaceOrTab) {
fitWidth = w;
+ if (!isSpaceOrTab) {
+ fitWidthGraphing = w;
+ }
fit = j + 1;
if (fmTop < fitTop)
@@ -365,7 +370,7 @@
breakOpp[breakOppIndex] == j - paraStart + 1;
if (isLineBreak) {
- okWidth = w;
+ okWidth = fitWidthGraphing;
ok = j + 1;
if (fitTop < okTop)
@@ -426,6 +431,7 @@
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 @@
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 @@
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.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 @@
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 @@
this.dateTimeFormat = sDateTimeFormat;
this.timeOnlyFormat = sTimeOnlyFormat;
this.dateOnlyFormat = sDateOnlyFormat;
- this.locale = locale;
localeData = sLocaleData;
}
}
@@ -97,19 +92,21 @@
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 @@
* {@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 @@
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 @@
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 @@
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 @@
--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 @@
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 @@
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 @@
break;
default:
outputBuilder.append(str);
-
}
}
@@ -443,14 +431,14 @@
}
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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index ebb1a5c..eb17429 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -31,6 +31,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.util.IntProperty;
import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
@@ -185,25 +186,36 @@
}
if (numChanges > 0) {
if (!mResizeClip) {
- if (startLeft != endLeft) view.setLeft(startLeft);
- if (startTop != endTop) view.setTop(startTop);
- if (startRight != endRight) view.setRight(startRight);
- if (startBottom != endBottom) view.setBottom(startBottom);
- ObjectAnimator topLeftAnimator = null;
- if (startLeft != endLeft || startTop != endTop) {
- Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
- endLeft, endTop);
- topLeftAnimator = ObjectAnimator.ofInt(view, "left", "top", topLeftPath);
+ Animator anim;
+ if (startWidth == endWidth && startHeight == endHeight) {
+ view.offsetLeftAndRight(startLeft - view.getLeft());
+ view.offsetTopAndBottom(startTop - view.getTop());
+ Path positionPath = getPathMotion().getPath(0, 0, endLeft - startLeft,
+ endTop - startTop);
+ anim = ObjectAnimator.ofInt(view, new HorizontalOffsetProperty(),
+ new VerticalOffsetProperty(), positionPath);
+ } else {
+ if (startLeft != endLeft) view.setLeft(startLeft);
+ if (startTop != endTop) view.setTop(startTop);
+ if (startRight != endRight) view.setRight(startRight);
+ if (startBottom != endBottom) view.setBottom(startBottom);
+ ObjectAnimator topLeftAnimator = null;
+ if (startLeft != endLeft || startTop != endTop) {
+ Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
+ endLeft, endTop);
+ topLeftAnimator = ObjectAnimator
+ .ofInt(view, "left", "top", topLeftPath);
+ }
+ ObjectAnimator bottomRightAnimator = null;
+ if (startRight != endRight || startBottom != endBottom) {
+ Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
+ endRight, endBottom);
+ bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
+ bottomRightPath);
+ }
+ anim = TransitionUtils.mergeAnimators(topLeftAnimator,
+ bottomRightAnimator);
}
- ObjectAnimator bottomRightAnimator = null;
- if (startRight != endRight || startBottom != endBottom) {
- Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
- endRight, endBottom);
- bottomRightAnimator = ObjectAnimator.ofInt(view, "right", "bottom",
- bottomRightPath);
- }
- Animator anim = TransitionUtils.mergeAnimators(topLeftAnimator,
- bottomRightAnimator);
if (view.getParent() instanceof ViewGroup) {
final ViewGroup parent = (ViewGroup) view.getParent();
parent.suppressLayout(true);
@@ -341,4 +353,48 @@
}
return null;
}
+
+ private abstract static class OffsetProperty extends IntProperty<View> {
+ int mPreviousValue;
+
+ public OffsetProperty(String name) {
+ super(name);
+ }
+
+ @Override
+ public void setValue(View view, int value) {
+ int offset = value - mPreviousValue;
+ offsetBy(view, offset);
+ mPreviousValue = value;
+ }
+
+ @Override
+ public Integer get(View object) {
+ return null;
+ }
+
+ protected abstract void offsetBy(View view, int by);
+ }
+
+ private static class HorizontalOffsetProperty extends OffsetProperty {
+ public HorizontalOffsetProperty() {
+ super("offsetLeftAndRight");
+ }
+
+ @Override
+ protected void offsetBy(View view, int by) {
+ view.offsetLeftAndRight(by);
+ }
+ }
+
+ private static class VerticalOffsetProperty extends OffsetProperty {
+ public VerticalOffsetProperty() {
+ super("offsetTopAndBottom");
+ }
+
+ @Override
+ protected void offsetBy(View view, int by) {
+ view.offsetTopAndBottom(by);
+ }
+ }
}
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 @@
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 @@
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 @@
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 @@
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..88bb4393 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 @@
* 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 @@
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 @@
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/util/Spline.java b/core/java/android/util/Spline.java
index ed027eb..41a2e5d 100644
--- a/core/java/android/util/Spline.java
+++ b/core/java/android/util/Spline.java
@@ -20,15 +20,34 @@
* Performs spline interpolation given a set of control points.
* @hide
*/
-public final class Spline {
- private final float[] mX;
- private final float[] mY;
- private final float[] mM;
+public abstract class Spline {
- private Spline(float[] x, float[] y, float[] m) {
- mX = x;
- mY = y;
- mM = m;
+ /**
+ * Interpolates the value of Y = f(X) for given X.
+ * Clamps X to the domain of the spline.
+ *
+ * @param x The X value.
+ * @return The interpolated Y = f(X) value.
+ */
+ public abstract float interpolate(float x);
+
+ /**
+ * Creates an appropriate spline based on the properties of the control points.
+ *
+ * If the control points are monotonic then the resulting spline will preserve that and
+ * otherwise optimize for error bounds.
+ */
+ public static Spline createSpline(float[] x, float[] y) {
+ if (!isStrictlyIncreasing(x)) {
+ throw new IllegalArgumentException("The control points must all have strictly "
+ + "increasing X values.");
+ }
+
+ if (isMonotonic(y)) {
+ return createMonotoneCubicSpline(x, y);
+ } else {
+ return createLinearSpline(x, y);
+ }
}
/**
@@ -50,107 +69,229 @@
* @throws IllegalArgumentException if the control points are not monotonic.
*/
public static Spline createMonotoneCubicSpline(float[] x, float[] y) {
- if (x == null || y == null || x.length != y.length || x.length < 2) {
- throw new IllegalArgumentException("There must be at least two control "
- + "points and the arrays must be of equal length.");
- }
-
- final int n = x.length;
- float[] d = new float[n - 1]; // could optimize this out
- float[] m = new float[n];
-
- // Compute slopes of secant lines between successive points.
- for (int i = 0; i < n - 1; i++) {
- float h = x[i + 1] - x[i];
- if (h <= 0f) {
- throw new IllegalArgumentException("The control points must all "
- + "have strictly increasing X values.");
- }
- d[i] = (y[i + 1] - y[i]) / h;
- }
-
- // Initialize the tangents as the average of the secants.
- m[0] = d[0];
- for (int i = 1; i < n - 1; i++) {
- m[i] = (d[i - 1] + d[i]) * 0.5f;
- }
- m[n - 1] = d[n - 2];
-
- // Update the tangents to preserve monotonicity.
- for (int i = 0; i < n - 1; i++) {
- if (d[i] == 0f) { // successive Y values are equal
- m[i] = 0f;
- m[i + 1] = 0f;
- } else {
- float a = m[i] / d[i];
- float b = m[i + 1] / d[i];
- if (a < 0f || b < 0f) {
- throw new IllegalArgumentException("The control points must have "
- + "monotonic Y values.");
- }
- float h = FloatMath.hypot(a, b);
- if (h > 9f) {
- float t = 3f / h;
- m[i] = t * a * d[i];
- m[i + 1] = t * b * d[i];
- }
- }
- }
- return new Spline(x, y, m);
+ return new MonotoneCubicSpline(x, y);
}
/**
- * Interpolates the value of Y = f(X) for given X.
- * Clamps X to the domain of the spline.
+ * Creates a linear spline from a given set of control points.
*
- * @param x The X value.
- * @return The interpolated Y = f(X) value.
+ * Like a monotone cubic spline, the interpolated curve will be monotonic if the control points
+ * are monotonic.
+ *
+ * @param x The X component of the control points, strictly increasing.
+ * @param y The Y component of the control points.
+ * @return
+ *
+ * @throws IllegalArgumentException if the X or Y arrays are null, have
+ * different lengths or have fewer than 2 values.
+ * @throws IllegalArgumentException if the X components of the control points are not strictly
+ * increasing.
*/
- public float interpolate(float x) {
- // Handle the boundary cases.
- final int n = mX.length;
- if (Float.isNaN(x)) {
- return x;
- }
- if (x <= mX[0]) {
- return mY[0];
- }
- if (x >= mX[n - 1]) {
- return mY[n - 1];
- }
-
- // Find the index 'i' of the last point with smaller X.
- // We know this will be within the spline due to the boundary tests.
- int i = 0;
- while (x >= mX[i + 1]) {
- i += 1;
- if (x == mX[i]) {
- return mY[i];
- }
- }
-
- // Perform cubic Hermite spline interpolation.
- float h = mX[i + 1] - mX[i];
- float t = (x - mX[i]) / h;
- return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
- + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
+ public static Spline createLinearSpline(float[] x, float[] y) {
+ return new LinearSpline(x, y);
}
- // For debugging.
- @Override
- public String toString() {
- StringBuilder str = new StringBuilder();
- final int n = mX.length;
- str.append("[");
- for (int i = 0; i < n; i++) {
- if (i != 0) {
- str.append(", ");
- }
- str.append("(").append(mX[i]);
- str.append(", ").append(mY[i]);
- str.append(": ").append(mM[i]).append(")");
+ private static boolean isStrictlyIncreasing(float[] x) {
+ if (x == null || x.length < 2) {
+ throw new IllegalArgumentException("There must be at least two control points.");
}
- str.append("]");
- return str.toString();
+ float prev = x[0];
+ for (int i = 1; i < x.length; i++) {
+ float curr = x[i];
+ if (curr <= prev) {
+ return false;
+ }
+ prev = curr;
+ }
+ return true;
+ }
+
+ private static boolean isMonotonic(float[] x) {
+ if (x == null || x.length < 2) {
+ throw new IllegalArgumentException("There must be at least two control points.");
+ }
+ float prev = x[0];
+ for (int i = 1; i < x.length; i++) {
+ float curr = x[i];
+ if (curr < prev) {
+ return false;
+ }
+ prev = curr;
+ }
+ return true;
+ }
+
+ public static class MonotoneCubicSpline extends Spline {
+ private float[] mX;
+ private float[] mY;
+ private float[] mM;
+
+ public MonotoneCubicSpline(float[] x, float[] y) {
+ if (x == null || y == null || x.length != y.length || x.length < 2) {
+ throw new IllegalArgumentException("There must be at least two control "
+ + "points and the arrays must be of equal length.");
+ }
+
+ final int n = x.length;
+ float[] d = new float[n - 1]; // could optimize this out
+ float[] m = new float[n];
+
+ // Compute slopes of secant lines between successive points.
+ for (int i = 0; i < n - 1; i++) {
+ float h = x[i + 1] - x[i];
+ if (h <= 0f) {
+ throw new IllegalArgumentException("The control points must all "
+ + "have strictly increasing X values.");
+ }
+ d[i] = (y[i + 1] - y[i]) / h;
+ }
+
+ // Initialize the tangents as the average of the secants.
+ m[0] = d[0];
+ for (int i = 1; i < n - 1; i++) {
+ m[i] = (d[i - 1] + d[i]) * 0.5f;
+ }
+ m[n - 1] = d[n - 2];
+
+ // Update the tangents to preserve monotonicity.
+ for (int i = 0; i < n - 1; i++) {
+ if (d[i] == 0f) { // successive Y values are equal
+ m[i] = 0f;
+ m[i + 1] = 0f;
+ } else {
+ float a = m[i] / d[i];
+ float b = m[i + 1] / d[i];
+ if (a < 0f || b < 0f) {
+ throw new IllegalArgumentException("The control points must have "
+ + "monotonic Y values.");
+ }
+ float h = FloatMath.hypot(a, b);
+ if (h > 9f) {
+ float t = 3f / h;
+ m[i] = t * a * d[i];
+ m[i + 1] = t * b * d[i];
+ }
+ }
+ }
+
+ mX = x;
+ mY = y;
+ mM = m;
+ }
+
+ @Override
+ public float interpolate(float x) {
+ // Handle the boundary cases.
+ final int n = mX.length;
+ if (Float.isNaN(x)) {
+ return x;
+ }
+ if (x <= mX[0]) {
+ return mY[0];
+ }
+ if (x >= mX[n - 1]) {
+ return mY[n - 1];
+ }
+
+ // Find the index 'i' of the last point with smaller X.
+ // We know this will be within the spline due to the boundary tests.
+ int i = 0;
+ while (x >= mX[i + 1]) {
+ i += 1;
+ if (x == mX[i]) {
+ return mY[i];
+ }
+ }
+
+ // Perform cubic Hermite spline interpolation.
+ float h = mX[i + 1] - mX[i];
+ float t = (x - mX[i]) / h;
+ return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
+ + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
+ }
+
+ // For debugging.
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+ final int n = mX.length;
+ str.append("MonotoneCubicSpline{[");
+ for (int i = 0; i < n; i++) {
+ if (i != 0) {
+ str.append(", ");
+ }
+ str.append("(").append(mX[i]);
+ str.append(", ").append(mY[i]);
+ str.append(": ").append(mM[i]).append(")");
+ }
+ str.append("]}");
+ return str.toString();
+ }
+ }
+
+ public static class LinearSpline extends Spline {
+ private final float[] mX;
+ private final float[] mY;
+ private final float[] mM;
+
+ public LinearSpline(float[] x, float[] y) {
+ if (x == null || y == null || x.length != y.length || x.length < 2) {
+ throw new IllegalArgumentException("There must be at least two control "
+ + "points and the arrays must be of equal length.");
+ }
+ final int N = x.length;
+ mM = new float[N-1];
+ for (int i = 0; i < N-1; i++) {
+ mM[i] = (y[i+1] - y[i]) / (x[i+1] - x[i]);
+ }
+ mX = x;
+ mY = y;
+ }
+
+ @Override
+ public float interpolate(float x) {
+ // Handle the boundary cases.
+ final int n = mX.length;
+ if (Float.isNaN(x)) {
+ return x;
+ }
+ if (x <= mX[0]) {
+ return mY[0];
+ }
+ if (x >= mX[n - 1]) {
+ return mY[n - 1];
+ }
+
+ // Find the index 'i' of the last point with smaller X.
+ // We know this will be within the spline due to the boundary tests.
+ int i = 0;
+ while (x >= mX[i + 1]) {
+ i += 1;
+ if (x == mX[i]) {
+ return mY[i];
+ }
+ }
+ return mY[i] + mM[i] * (x - mX[i]);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+ final int n = mX.length;
+ str.append("LinearSpline{[");
+ for (int i = 0; i < n; i++) {
+ if (i != 0) {
+ str.append(", ");
+ }
+ str.append("(").append(mX[i]);
+ str.append(", ").append(mY[i]);
+ if (i < n-1) {
+ str.append(": ").append(mM[i]);
+ }
+ str.append(")");
+ }
+ str.append("]}");
+ return str.toString();
+ }
}
}
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 413ea04..fa4a13a 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -87,8 +87,11 @@
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;
@@ -142,7 +145,7 @@
}
private void checkMutable() {
- if (mStarted) {
+ if (mState != STATE_PREPARE) {
throw new IllegalStateException("Animator has already started, cannot change it now!");
}
}
@@ -170,11 +173,11 @@
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) {
@@ -186,6 +189,7 @@
}
private void doStart() {
+ mState = STATE_RUNNING;
nStart(mNativePtr.get(), this);
// Alpha is a special snowflake that has the canonical value stored
@@ -197,11 +201,7 @@
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
@@ -209,10 +209,21 @@
}
}
+ 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();
@@ -220,12 +231,17 @@
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());
}
}
@@ -299,12 +315,12 @@
@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
@@ -319,7 +335,11 @@
}
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();
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 764eea7..ca08ecc 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -277,7 +277,11 @@
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;
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 @@
}
}
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/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 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/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 @@
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 @@
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.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 @@
* @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 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 @@
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 @@
// 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 @@
* <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 @@
* <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 @@
* <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 @@
* <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 <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 @@
}
}
- 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/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.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.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 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 @@
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 @@
*
* <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 @@
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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
// 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 @@
* 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 @@
*
* @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 @@
*
* @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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
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 @@
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 @@
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.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.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -100,7 +100,7 @@
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 @@
} 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 @@
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 @@
}
}
+ 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 @@
}
}
+ 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 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 @@
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 @@
// 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 @@
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 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..ba236f39 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -191,6 +191,20 @@
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/res/res/color/btn_default_material_dark.xml b/core/res/res/color/btn_default_material_dark.xml
index 7c904cd..9be1417 100644
--- a/core/res/res/color/btn_default_material_dark.xml
+++ b/core/res/res/color/btn_default_material_dark.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="@dimen/disabled_alpha_material"
- android:color="@color/button_material_dark"/>
+ android:alpha="@dimen/disabled_alpha_material_dark"
+ android:color="@color/button_material_dark"/>
<item android:color="@color/button_material_dark"/>
</selector>
diff --git a/core/res/res/color/btn_default_material_light.xml b/core/res/res/color/btn_default_material_light.xml
index 738f9ad..af5afe6 100644
--- a/core/res/res/color/btn_default_material_light.xml
+++ b/core/res/res/color/btn_default_material_light.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="@dimen/disabled_alpha_material"
- android:color="@color/button_material_light"/>
+ android:alpha="@dimen/disabled_alpha_material_light"
+ android:color="@color/button_material_light"/>
<item android:color="@color/button_material_light"/>
</selector>
diff --git a/core/res/res/color/primary_text_disable_only_material_dark.xml b/core/res/res/color/primary_text_disable_only_material_dark.xml
index cdae790..a6296c96 100644
--- a/core/res/res/color/primary_text_disable_only_material_dark.xml
+++ b/core/res/res/color/primary_text_disable_only_material_dark.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="@dimen/disabled_alpha_material"
- android:color="@color/bright_foreground_material_dark"/>
+ android:alpha="@dimen/disabled_alpha_material_dark"
+ android:color="@color/bright_foreground_material_dark"/>
<item android:color="@color/bright_foreground_material_dark"/>
</selector>
diff --git a/core/res/res/color/primary_text_disable_only_material_light.xml b/core/res/res/color/primary_text_disable_only_material_light.xml
index 0bf14d0..b781844 100644
--- a/core/res/res/color/primary_text_disable_only_material_light.xml
+++ b/core/res/res/color/primary_text_disable_only_material_light.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="@dimen/disabled_alpha_material"
- android:color="@color/bright_foreground_material_light"/>
+ android:alpha="@dimen/disabled_alpha_material_light"
+ android:color="@color/bright_foreground_material_light"/>
<item android:color="@color/bright_foreground_material_light"/>
</selector>
diff --git a/core/res/res/color/primary_text_material_dark.xml b/core/res/res/color/primary_text_material_dark.xml
index 6ad837b..690f0ec 100644
--- a/core/res/res/color/primary_text_material_dark.xml
+++ b/core/res/res/color/primary_text_material_dark.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="@dimen/disabled_alpha_material"
- android:color="@color/primary_text_default_material_dark"/>
+ android:alpha="@dimen/disabled_alpha_material_dark"
+ android:color="@color/primary_text_default_material_dark"/>
<item android:color="@color/primary_text_default_material_dark"/>
</selector>
diff --git a/core/res/res/color/primary_text_material_light.xml b/core/res/res/color/primary_text_material_light.xml
index 4c19e12..634ad94 100644
--- a/core/res/res/color/primary_text_material_light.xml
+++ b/core/res/res/color/primary_text_material_light.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="@dimen/disabled_alpha_material"
- android:color="@color/primary_text_default_material_light"/>
+ android:alpha="@dimen/disabled_alpha_material_light"
+ android:color="@color/primary_text_default_material_light"/>
<item android:color="@color/primary_text_default_material_light"/>
</selector>
diff --git a/packages/DocumentsUI/res/drawable/grid_protect_background.xml b/core/res/res/color/secondary_text_material_dark.xml
similarity index 68%
copy from packages/DocumentsUI/res/drawable/grid_protect_background.xml
copy to core/res/res/color/secondary_text_material_dark.xml
index 2e7aadd..5bddf96 100644
--- a/packages/DocumentsUI/res/drawable/grid_protect_background.xml
+++ b/core/res/res/color/secondary_text_material_dark.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2008 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.
@@ -15,10 +15,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false">
- <color android:color="#88000000" />
- </item>
- <item>
- <color android:color="#88252525" />
- </item>
+ <item android:state_enabled="false"
+ android:alpha="@dimen/disabled_alpha_material_dark"
+ android:color="@color/secondary_text_default_material_dark"/>
+ <item android:color="@color/secondary_text_default_material_dark"/>
</selector>
diff --git a/packages/DocumentsUI/res/drawable/grid_protect_background.xml b/core/res/res/color/secondary_text_material_light.xml
similarity index 68%
copy from packages/DocumentsUI/res/drawable/grid_protect_background.xml
copy to core/res/res/color/secondary_text_material_light.xml
index 2e7aadd..3c354cb 100644
--- a/packages/DocumentsUI/res/drawable/grid_protect_background.xml
+++ b/core/res/res/color/secondary_text_material_light.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2008 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.
@@ -15,10 +15,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false">
- <color android:color="#88000000" />
- </item>
- <item>
- <color android:color="#88252525" />
- </item>
+ <item android:state_enabled="false"
+ android:alpha="@dimen/disabled_alpha_material_light"
+ android:color="@color/secondary_text_default_material_light"/>
+ <item android:color="@color/secondary_text_default_material_light"/>
</selector>
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 7a1079c..6b64337 100644
--- a/core/res/res/drawable/progress_horizontal_material.xml
+++ b/core/res/res/drawable/progress_horizontal_material.xml
@@ -18,13 +18,13 @@
<item android:id="@id/background">
<nine-patch android:src="@drawable/progress_mtrl_alpha"
android:tint="?attr/colorControlNormal"
- android:alpha="0.5" />
+ android:alpha="?attr/disabledAlpha" />
</item>
<item android:id="@id/secondaryProgress">
<scale android:scaleWidth="100%">
<nine-patch android:src="@drawable/progress_mtrl_alpha"
android:tint="?attr/colorControlActivated"
- android:alpha="0.5" />
+ android:alpha="?attr/disabledAlpha" />
</scale>
</item>
<item android:id="@id/progress">
diff --git a/core/res/res/layout/action_menu_item_layout.xml b/core/res/res/layout/action_menu_item_layout.xml
index 04d1f7b..6961f8d 100644
--- a/core/res/res/layout/action_menu_item_layout.xml
+++ b/core/res/res/layout/action_menu_item_layout.xml
@@ -25,5 +25,4 @@
android:paddingStart="8dip"
android:paddingEnd="8dip"
android:textAppearance="?attr/actionMenuTextAppearance"
- android:textColor="?attr/actionMenuTextColor"
- style="?android:attr/actionButtonStyle" />
+ style="?attr/actionButtonStyle" />
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..ef916ed1 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/attrs.xml b/core/res/res/values/attrs.xml
index f825c29..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">
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index c9292eb..d8e14a0 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -58,19 +58,14 @@
<!-- Text & foreground colors -->
<eat-comment />
- <!-- Black 87% -->
<color name="primary_text_default_material_light">#de000000</color>
- <!-- Black 54% -->
- <color name="secondary_text_material_light">#8a000000</color>
- <!-- Black 54% (TODO: same as secondary?) -->
- <color name="tertiary_text_material_light">#8a000000</color>
+ <color name="secondary_text_default_material_light">#8a000000</color>
- <!-- White 87% -->
- <color name="primary_text_default_material_dark">#deffffff</color>
- <!-- White 38% -->
- <color name="secondary_text_material_dark">#61ffffff</color>
- <!-- White 38% (TODO: same as secondary?) -->
- <color name="tertiary_text_material_dark">#61ffffff</color>
+ <color name="primary_text_default_material_dark">#ffffffff</color>
+ <color name="secondary_text_default_material_dark">#b3ffffff</color>
+
+ <item name="disabled_alpha_material_light" format="float" type="dimen">0.26</item>
+ <item name="disabled_alpha_material_dark" format="float" type="dimen">0.30</item>
<!-- Primary & accent colors -->
<eat-comment />
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..5f7f0ed 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 -->
@@ -71,8 +77,5 @@
<!-- Default rounded corner for controls -->
<dimen name="control_corner_material">2dp</dimen>
- <!-- Default alpha value for disabled elements. -->
- <item name="disabled_alpha_material" format="float" type="dimen">0.26</item>
-
- <dimen name="alert_dialog_padding_material">12dp</dimen>
+ <dimen name="alert_dialog_padding_material">18dp</dimen>
</resources>
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 eca6c72..5e76a87 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2280,12 +2280,16 @@
<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,13 +2531,13 @@
<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.Underlined" />
<public type="style" name="Widget.Material.Light.Spinner.Underlined" />
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 3ee5552..e783cd6 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -306,7 +306,7 @@
</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" parent="TextAppearance.Material.Menu">
<item name="textColor">?attr/textColorPrimaryDisableOnly</item>
@@ -410,37 +410,35 @@
<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 @@
<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>
@@ -829,6 +828,7 @@
<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 @@
<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 @@
<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>
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..2296a12 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -46,7 +46,7 @@
<item name="colorForegroundInverse">@color/bright_foreground_material_light</item>
<item name="colorBackground">@color/background_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
- <item name="disabledAlpha">@dimen/disabled_alpha_material</item>
+ <item name="disabledAlpha">@dimen/disabled_alpha_material_dark</item>
<item name="backgroundDimAmount">0.6</item>
<!-- Text styles -->
@@ -58,8 +58,8 @@
<item name="textColorPrimaryDisableOnly">@color/primary_text_disable_only_material_dark</item>
<item name="textColorSecondary">@color/secondary_text_material_dark</item>
<item name="textColorSecondaryInverse">@color/secondary_text_material_light</item>
- <item name="textColorTertiary">@color/tertiary_text_material_dark</item>
- <item name="textColorTertiaryInverse">@color/tertiary_text_material_light</item>
+ <item name="textColorTertiary">@color/secondary_text_material_dark</item>
+ <item name="textColorTertiaryInverse">@color/secondary_text_material_light</item>
<item name="textColorHint">@color/hint_foreground_material_dark</item>
<item name="textColorHintInverse">@color/hint_foreground_material_light</item>
<item name="textColorHighlight">@color/highlighted_text_material_dark</item>
@@ -387,7 +387,7 @@
<item name="colorForegroundInverse">@color/bright_foreground_material_dark</item>
<item name="colorBackground">@color/background_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
- <item name="disabledAlpha">@dimen/disabled_alpha_material</item>
+ <item name="disabledAlpha">@dimen/disabled_alpha_material_light</item>
<item name="backgroundDimAmount">0.6</item>
<!-- Text styles -->
@@ -398,8 +398,8 @@
<item name="textColorPrimaryInverse">@color/primary_text_material_dark</item>
<item name="textColorSecondary">@color/secondary_text_material_light</item>
<item name="textColorSecondaryInverse">@color/secondary_text_material_dark</item>
- <item name="textColorTertiary">@color/tertiary_text_material_light</item>
- <item name="textColorTertiaryInverse">@color/tertiary_text_material_dark</item>
+ <item name="textColorTertiary">@color/secondary_text_material_light</item>
+ <item name="textColorTertiaryInverse">@color/secondary_text_material_dark</item>
<item name="textColorPrimaryDisableOnly">@color/primary_text_disable_only_material_light</item>
<item name="textColorPrimaryInverseDisableOnly">@color/primary_text_disable_only_material_dark</item>
<item name="textColorHint">@color/hint_foreground_material_light</item>
@@ -750,8 +750,8 @@
<item name="textColorPrimaryInverse">@color/primary_text_material_dark</item>
<item name="textColorSecondary">@color/secondary_text_material_light</item>
<item name="textColorSecondaryInverse">@color/secondary_text_material_dark</item>
- <item name="textColorTertiary">@color/tertiary_text_material_light</item>
- <item name="textColorTertiaryInverse">@color/tertiary_text_material_dark</item>
+ <item name="textColorTertiary">@color/secondary_text_material_light</item>
+ <item name="textColorTertiaryInverse">@color/secondary_text_material_dark</item>
<item name="textColorPrimaryDisableOnly">@color/primary_text_disable_only_material_light</item>
<item name="textColorPrimaryInverseDisableOnly">@color/primary_text_disable_only_material_dark</item>
<item name="textColorHint">@color/hint_foreground_material_light</item>
@@ -790,8 +790,8 @@
<item name="textColorPrimaryDisableOnly">@color/primary_text_disable_only_material_dark</item>
<item name="textColorSecondary">@color/secondary_text_material_dark</item>
<item name="textColorSecondaryInverse">@color/secondary_text_material_light</item>
- <item name="textColorTertiary">@color/tertiary_text_material_dark</item>
- <item name="textColorTertiaryInverse">@color/tertiary_text_material_light</item>
+ <item name="textColorTertiary">@color/secondary_text_material_dark</item>
+ <item name="textColorTertiaryInverse">@color/secondary_text_material_light</item>
<item name="textColorHint">@color/hint_foreground_material_dark</item>
<item name="textColorHintInverse">@color/hint_foreground_material_light</item>
<item name="textColorHighlight">@color/highlighted_text_material_dark</item>
@@ -1257,4 +1257,6 @@
<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/docs/html/guide/topics/admin/device-admin.jd b/docs/html/guide/topics/admin/device-admin.jd
index ee6b814..bed4b4d 100644
--- a/docs/html/guide/topics/admin/device-admin.jd
+++ b/docs/html/guide/topics/admin/device-admin.jd
@@ -28,12 +28,6 @@
<li>{@link android.app.admin.DevicePolicyManager}</li>
<li>{@link android.app.admin.DeviceAdminInfo}</li>
</ol>
- <h2>Related samples</h2>
- <ol>
- <li><a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html">
-DeviceAdminSample</a></li>
-</ol>
</div>
</div>
@@ -232,18 +226,12 @@
<h2 id="sample">Sample Application</h2>
-<p>The examples used in this document are based on the <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html">
-Device Administration API
-sample</a>, which is included in the SDK samples. For information on downloading and
-installing the SDK samples, see <a
-href="{@docRoot}resources/samples/get.html">
-Getting the Samples</a>. Here is the <a
-href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html">
-complete code</a> for
-the sample. </p>
-<p>The
-sample application offers a demo of device admin features. It presents users
+<p>The examples used in this document are based on the Device Administration API
+sample, which is included in the SDK samples (available through the
+Android SDK Manager) and located on your system as
+<code><sdk_root>/ApiDemos/app/src/main/java/com/example/android/apis/app/DeviceAdminSample.java</code>.</p>
+
+<p>The sample application offers a demo of device admin features. It presents users
with a user interface that lets them enable the device admin application. Once
they've enabled the application, they can use the buttons in the user interface
to do the following:</p>
@@ -676,7 +664,8 @@
<p>You can also programmatically tell the device to lock immediately:</p>
<pre>
DevicePolicyManager mDPM;
-mDPM.lockNow();</pre>
+mDPM.lockNow();
+</pre>
@@ -692,12 +681,12 @@
<pre>
DevicePolicyManager mDPM;
mDPM.wipeData(0);</pre>
-<p>The {@link android.app.admin.DevicePolicyManager#wipeData wipeData()} method takes as its parameter a bit mask of
-additional options. Currently the value must be 0. </p>
+<p>The {@link android.app.admin.DevicePolicyManager#wipeData wipeData()} method takes as its
+ parameter a bit mask of additional options. Currently the value must be 0. </p>
<h4>Disable camera</h4>
<p>Beginning with Android 4.0, you can disable the camera. Note that this doesn't have to be a permanent disabling. The camera can be enabled/disabled dynamically based on context, time, and so on. </p>
-<p>You control whether the camera is disabled by using the
+<p>You control whether the camera is disabled by using the
{@link android.app.admin.DevicePolicyManager#setCameraDisabled(android.content.ComponentName, boolean) setCameraDisabled()} method. For example, this snippet sets the camera to be enabled or disabled based on a checkbox setting:</p>
<pre>private CheckBoxPreference mDisableCameraCheckbox;
@@ -708,8 +697,8 @@
</pre>
-<h4 id=storage">Storage encryption</h4>
-<p>Beginning with Android 3.0, you can use the
+<h4 id="storage">Storage encryption</h4>
+<p>Beginning with Android 3.0, you can use the
{@link android.app.admin.DevicePolicyManager#setStorageEncryption(android.content.ComponentName,boolean) setStorageEncryption()}
method to set a policy requiring encryption of the storage area, where supported.</p>
@@ -722,5 +711,5 @@
mDPM.setStorageEncryption(mDeviceAdminSample, true);
</pre>
<p>
-See the <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html"> Device Administration API sample</a> for a complete
-example of how to enable storage encryption.</p>
+See the Device Administration API sample for a complete example of how to enable storage encryption.
+</p>
\ No newline at end of file
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 063ac09..cd919a6 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -105,6 +105,9 @@
/** Whether we have an explicit maximum radius. */
private boolean mHasMaxRadius;
+ /** Whether we were canceled externally and should avoid self-removal. */
+ private boolean mCanceled;
+
/**
* Creates a new ripple.
*/
@@ -295,6 +298,8 @@
* Starts the enter animation.
*/
public void enter() {
+ cancel();
+
final int radiusDuration = (int)
(1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
@@ -332,7 +337,8 @@
* Starts the exit animation.
*/
public void exit() {
- cancelSoftwareAnimations();
+ cancel();
+
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final float remaining;
if (mAnimRadius != null && mAnimRadius.isRunning()) {
@@ -399,9 +405,15 @@
invalidateSelf();
}
+ /**
+ * Jump all animations to their end state. The caller is responsible for
+ * removing the ripple from the list of animating ripples.
+ */
public void jump() {
+ mCanceled = true;
endSoftwareAnimations();
endHardwareAnimations();
+ mCanceled = false;
}
private void endSoftwareAnimations() {
@@ -436,6 +448,8 @@
mPendingAnimations.clear();
removeSelf();
}
+
+ mHardwareAnimating = false;
}
private Paint getTempPaint() {
@@ -479,11 +493,14 @@
}
/**
- * Cancel all animations.
+ * Cancels all animations. The caller is responsible for removing
+ * the ripple from the list of animating ripples.
*/
public void cancel() {
+ mCanceled = true;
cancelSoftwareAnimations();
cancelHardwareAnimations(true);
+ mCanceled = false;
}
private void cancelSoftwareAnimations() {
@@ -517,13 +534,16 @@
if (cancelPending && !mPendingAnimations.isEmpty()) {
mPendingAnimations.clear();
- removeSelf();
}
+
+ mHardwareAnimating = false;
}
private void removeSelf() {
// The owner will invalidate itself.
- mOwner.removeRipple(this);
+ if (!mCanceled) {
+ mOwner.removeRipple(this);
+ }
}
private void invalidateSelf() {
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 49862bc..40a5e18 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -42,8 +42,9 @@
private static final float GLOBAL_SPEED = 1.0f;
private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
- private static final float WAVE_OUTER_OPACITY_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
- private static final float WAVE_OUTER_OPACITY_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
+ private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
+ private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
+ private static final float WAVE_OUTER_OPACITY_ENTER_VELOCITY = 10.0f * GLOBAL_SPEED;
private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
@@ -106,6 +107,9 @@
/** Whether we have an explicit maximum radius. */
private boolean mHasMaxRadius;
+ /** Whether we were canceled externally and should avoid self-removal. */
+ private boolean mCanceled;
+
/**
* Creates a new ripple.
*/
@@ -283,9 +287,11 @@
* Starts the enter animation.
*/
public void enter() {
+ cancel();
+
final int radiusDuration = (int)
(1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
- final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY_MIN);
+ final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_ENTER_VELOCITY);
final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
cX.setAutoCancel(true);
@@ -320,7 +326,7 @@
* Starts the exit animation.
*/
public void exit() {
- cancelSoftwareAnimations();
+ cancel();
// Scale the outer max opacity and opacity velocity based
// on the size of the outer radius.
@@ -328,8 +334,8 @@
final float outerSizeInfluence = MathUtils.constrain(
(mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
/ (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
- final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_VELOCITY_MIN,
- WAVE_OUTER_OPACITY_VELOCITY_MAX, outerSizeInfluence);
+ final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN,
+ WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX, outerSizeInfluence);
// Determine at what time the inner and outer opacity intersect.
// inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
@@ -403,9 +409,15 @@
invalidateSelf();
}
+ /**
+ * Jump all animations to their end state. The caller is responsible for
+ * removing the ripple from the list of animating ripples.
+ */
public void jump() {
+ mCanceled = true;
endSoftwareAnimations();
endHardwareAnimations();
+ mCanceled = false;
}
private void endSoftwareAnimations() {
@@ -436,6 +448,8 @@
mPendingAnimations.clear();
removeSelf();
}
+
+ mHardwareAnimating = false;
}
private Paint getTempPaint() {
@@ -508,11 +522,14 @@
}
/**
- * Cancel all animations.
+ * Cancel all animations. The caller is responsible for removing
+ * the ripple from the list of animating ripples.
*/
public void cancel() {
+ mCanceled = true;
cancelSoftwareAnimations();
cancelHardwareAnimations(true);
+ mCanceled = false;
}
private void cancelSoftwareAnimations() {
@@ -543,13 +560,16 @@
if (cancelPending && !mPendingAnimations.isEmpty()) {
mPendingAnimations.clear();
- removeSelf();
}
+
+ mHardwareAnimating = false;
}
private void removeSelf() {
// The owner will invalidate itself.
- mOwner.removeBackground(this);
+ if (!mCanceled) {
+ mOwner.removeBackground(this);
+ }
}
private void invalidateSelf() {
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 7402bdb..b90fd81 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -140,8 +140,8 @@
* Lazily-created array of actively animating ripples. Inactive ripples are
* pruned during draw(). The locations of these will not change.
*/
- private Ripple[] mAnimatingRipples;
- private int mAnimatingRipplesCount = 0;
+ private Ripple[] mExitingRipples;
+ private int mExitingRipplesCount = 0;
/** Paint used to control appearance of ripples. */
private Paint mRipplePaint;
@@ -156,12 +156,6 @@
private boolean mOverrideBounds;
/**
- * Whether hotspots are being cleared. Used to prevent re-entry by
- * animation finish listeners.
- */
- private boolean mClearingHotspots;
-
- /**
* Constructor used for drawable inflation.
*/
RippleDrawable() {
@@ -209,19 +203,21 @@
mBackground.jump();
}
- mClearingHotspots = true;
- final int count = mAnimatingRipplesCount;
- final Ripple[] ripples = mAnimatingRipples;
+ cancelExitingRipples();
+ invalidateSelf();
+ }
+
+ private void cancelExitingRipples() {
+ final int count = mExitingRipplesCount;
+ final Ripple[] ripples = mExitingRipples;
for (int i = 0; i < count; i++) {
- ripples[i].jump();
+ ripples[i].cancel();
}
+
if (ripples != null) {
Arrays.fill(ripples, 0, count, null);
}
- mAnimatingRipplesCount = 0;
- mClearingHotspots = false;
-
- invalidateSelf();
+ mExitingRipplesCount = 0;
}
@Override
@@ -287,9 +283,9 @@
if (mRippleActive != active) {
mRippleActive = active;
if (active) {
- activateRipple();
+ tryRippleEnter();
} else {
- removeRipple();
+ tryRippleExit();
}
}
}
@@ -298,9 +294,9 @@
if (mBackgroundActive != active) {
mBackgroundActive = active;
if (active) {
- activateBackground();
+ tryBackgroundEnter();
} else {
- removeBackground();
+ tryBackgroundExit();
}
}
}
@@ -327,11 +323,11 @@
// If we just became visible, ensure the background and ripple
// visibilities are consistent with their internal states.
if (mRippleActive) {
- activateRipple();
+ tryRippleEnter();
}
if (mBackgroundActive) {
- activateBackground();
+ tryBackgroundEnter();
}
}
@@ -491,7 +487,7 @@
/**
* Creates an active hotspot at the specified location.
*/
- private void activateBackground() {
+ private void tryBackgroundEnter() {
if (mBackground == null) {
final float x;
final float y;
@@ -511,7 +507,7 @@
mBackground.enter();
}
- private void removeBackground() {
+ private void tryBackgroundExit() {
if (mBackground != null) {
// Don't null out the background, we need it to draw!
mBackground.exit();
@@ -519,10 +515,11 @@
}
/**
- * Creates an active hotspot at the specified location.
+ * Attempts to start an enter animation for the active hotspot. Fails if
+ * there are too many animating ripples.
*/
- private void activateRipple() {
- if (mAnimatingRipplesCount >= MAX_RIPPLES) {
+ private void tryRippleEnter() {
+ if (mExitingRipplesCount >= MAX_RIPPLES) {
// This should never happen unless the user is tapping like a maniac
// or there is a bug that's preventing ripples from being removed.
return;
@@ -545,29 +542,27 @@
final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
mRipple.setup(mState.mMaxRadius, color, mDensity);
mRipple.enter();
-
- if (mAnimatingRipples == null) {
- mAnimatingRipples = new Ripple[MAX_RIPPLES];
- }
- 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() {
+ /**
+ * Attempts to start an exit animation for the active hotspot. Fails if
+ * there is no active hotspot.
+ */
+ private void tryRippleExit() {
if (mRipple != null) {
+ if (mExitingRipples == null) {
+ mExitingRipples = new Ripple[MAX_RIPPLES];
+ }
+ mExitingRipples[mExitingRipplesCount++] = mRipple;
mRipple.exit();
mRipple = null;
}
}
+ /**
+ * Cancels and removes the active ripple, all exiting ripples, and the
+ * background. Nothing will be drawn after this method is called.
+ */
private void clearHotspots() {
if (mRipple != null) {
mRipple.cancel();
@@ -579,18 +574,7 @@
mBackground = null;
}
- mClearingHotspots = true;
- final int count = mAnimatingRipplesCount;
- final Ripple[] ripples = mAnimatingRipples;
- for (int i = 0; i < count; i++) {
- ripples[i].cancel();
- }
- if (ripples != null) {
- Arrays.fill(ripples, 0, count, null);
- }
- mAnimatingRipplesCount = 0;
- mClearingHotspots = false;
-
+ cancelExitingRipples();
invalidateSelf();
}
@@ -612,8 +596,8 @@
* Notifies all the animating ripples that the hotspot bounds have changed.
*/
private void onHotspotBoundsChanged() {
- final int count = mAnimatingRipplesCount;
- final Ripple[] ripples = mAnimatingRipples;
+ final int count = mExitingRipplesCount;
+ final Ripple[] ripples = mExitingRipples;
for (int i = 0; i < count; i++) {
ripples[i].onHotspotBoundsChanged();
}
@@ -683,22 +667,21 @@
}
/**
- * Removes a ripple from the animating ripple list.
+ * Removes a ripple from the exiting ripple list.
*
* @param ripple the ripple to remove
*/
void removeRipple(Ripple ripple) {
- if (!mClearingHotspots) {
- // Ripple ripple ripple ripple. Ripple ripple.
- final Ripple[] ripples = mAnimatingRipples;
- final int count = mAnimatingRipplesCount;
- final int index = getRippleIndex(ripple);
- if (index >= 0) {
- System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1));
- ripples[count - 1] = null;
- mAnimatingRipplesCount--;
- invalidateSelf();
- }
+ // Ripple ripple ripple ripple. Ripple ripple.
+ final Ripple[] ripples = mExitingRipples;
+ final int count = mExitingRipplesCount;
+ final int index = getRippleIndex(ripple);
+ if (index >= 0) {
+ System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1));
+ ripples[count - 1] = null;
+ mExitingRipplesCount--;
+
+ invalidateSelf();
}
}
@@ -710,8 +693,8 @@
}
private int getRippleIndex(Ripple ripple) {
- final Ripple[] ripples = mAnimatingRipples;
- final int count = mAnimatingRipplesCount;
+ final Ripple[] ripples = mExitingRipples;
+ final int count = mExitingRipplesCount;
for (int i = 0; i < count; i++) {
if (ripples[i] == ripple) {
return i;
@@ -727,7 +710,7 @@
// We don't need a layer if we don't expect to draw any ripples, we have
// an explicit mask, or if the non-mask content is all opaque.
boolean needsLayer = false;
- if ((mAnimatingRipplesCount > 0 || mBackground != null) && mMask == null) {
+ if ((mExitingRipplesCount > 0 || mBackground != null) && mMask == null) {
for (int i = 0; i < count; i++) {
if (array[i].mId != R.id.mask
&& array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
@@ -831,10 +814,17 @@
int restoreTranslate = -1;
// Draw ripples and update the animating ripples array.
- final int count = mAnimatingRipplesCount;
- final Ripple[] ripples = mAnimatingRipples;
- for (int i = 0; i < count; i++) {
- final Ripple ripple = ripples[i];
+ final int count = mExitingRipplesCount;
+ final Ripple[] ripples = mExitingRipples;
+ for (int i = 0; i <= count; i++) {
+ final Ripple ripple;
+ if (i < count) {
+ ripple = ripples[i];
+ } else if (mRipple != null) {
+ ripple = mRipple;
+ } else {
+ continue;
+ }
// If we're masking the ripple layer, make sure we have a layer
// first. This will merge SRC_OVER (directly) onto the canvas.
@@ -898,8 +888,9 @@
final int cX = (int) mHotspotBounds.exactCenterX();
final int cY = (int) mHotspotBounds.exactCenterY();
final Rect rippleBounds = mTempRect;
- final Ripple[] activeRipples = mAnimatingRipples;
- final int N = mAnimatingRipplesCount;
+
+ final Ripple[] activeRipples = mExitingRipples;
+ final int N = mExitingRipplesCount;
for (int i = 0; i < N; i++) {
activeRipples[i].getBounds(rippleBounds);
rippleBounds.offset(cX, cY);
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 @@
};
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/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 6ac49ee..0db8c77 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -331,6 +331,36 @@
}
}
+ public boolean resetUid(int uid) {
+ try {
+ mError = mBinder.reset_uid(uid);
+ return mError == NO_ERROR;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return false;
+ }
+ }
+
+ public boolean syncUid(int sourceUid, int targetUid) {
+ try {
+ mError = mBinder.sync_uid(sourceUid, targetUid);
+ return mError == NO_ERROR;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return false;
+ }
+ }
+
+ public boolean passwordUid(String password, int uid) {
+ try {
+ mError = mBinder.password_uid(password, uid);
+ return mError == NO_ERROR;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return false;
+ }
+ }
+
public int getLastError() {
return mError;
}
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 @@
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 @@
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/Android.mk b/libs/androidfw/tests/Android.mk
index a10c387..5808d20 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -26,6 +26,7 @@
Idmap_test.cpp \
ResTable_test.cpp \
Split_test.cpp \
+ Theme_test.cpp \
TypeWrappers_test.cpp \
ZipUtils_test.cpp
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 @@
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 @@
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 @@
};
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 @@
<< "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, 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 @@
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, 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 @@
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, 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, 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 @@
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, 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 @@
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, 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 @@
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 @@
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 @@
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/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
new file mode 100644
index 0000000..4d07130
--- /dev/null
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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 <utils/String16.h>
+#include "TestHelpers.h"
+#include "data/system/R.h"
+#include "data/app/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+#include "data/system/system_arsc.h"
+#include "data/app/app_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+/**
+ * TODO(adamlesinski): Enable when fixed.
+ */
+TEST(ThemeTest, DISABLED_shouldCopyThemeFromDifferentResTable) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(system_arsc, system_arsc_len));
+ ASSERT_EQ(NO_ERROR, table.add(app_arsc, app_arsc_len));
+
+ ResTable::Theme theme1(table);
+ ASSERT_EQ(NO_ERROR, theme1.applyStyle(app::R::style::Theme_One));
+ Res_value val;
+ ASSERT_GE(theme1.getAttribute(android::R::attr::background, &val), 0);
+ ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
+ ASSERT_EQ(uint32_t(0xffff0000), val.data);
+ ASSERT_GE(theme1.getAttribute(app::R::attr::number, &val), 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(1), val.data);
+
+ ResTable table2;
+ ASSERT_EQ(NO_ERROR, table2.add(system_arsc, system_arsc_len));
+ ASSERT_EQ(NO_ERROR, table2.add(app_arsc, app_arsc_len));
+
+ ResTable::Theme theme2(table2);
+ ASSERT_EQ(NO_ERROR, theme2.setTo(theme1));
+ ASSERT_GE(theme2.getAttribute(android::R::attr::background, &val), 0);
+ ASSERT_EQ(Res_value::TYPE_INT_COLOR_RGB8, val.dataType);
+ ASSERT_EQ(uint32_t(0xffff0000), val.data);
+ ASSERT_GE(theme2.getAttribute(app::R::attr::number, &val), 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(1), val.data);
+}
+
+}
diff --git a/packages/DocumentsUI/res/drawable/grid_protect_background.xml b/libs/androidfw/tests/data/app/AndroidManifest.xml
similarity index 72%
rename from packages/DocumentsUI/res/drawable/grid_protect_background.xml
rename to libs/androidfw/tests/data/app/AndroidManifest.xml
index 2e7aadd..bfa3a39 100644
--- a/packages/DocumentsUI/res/drawable/grid_protect_background.xml
+++ b/libs/androidfw/tests/data/app/AndroidManifest.xml
@@ -14,11 +14,6 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false">
- <color android:color="#88000000" />
- </item>
- <item>
- <color android:color="#88252525" />
- </item>
-</selector>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.app">
+</manifest>
diff --git a/libs/androidfw/tests/data/app/R.h b/libs/androidfw/tests/data/app/R.h
new file mode 100644
index 0000000..780a116
--- /dev/null
+++ b/libs/androidfw/tests/data/app/R.h
@@ -0,0 +1,22 @@
+#ifndef __APP_R_H
+#define __APP_R_H
+
+namespace app {
+namespace R {
+
+namespace attr {
+ enum {
+ number = 0x7f010000, // default
+ };
+}
+
+namespace style {
+ enum {
+ Theme_One = 0x7f020000, // default
+ };
+}
+
+} // namespace R
+} // namespace app
+
+#endif // __APP_R_H
diff --git a/libs/androidfw/tests/data/app/app_arsc.h b/libs/androidfw/tests/data/app/app_arsc.h
new file mode 100644
index 0000000..d5d9a3b
--- /dev/null
+++ b/libs/androidfw/tests/data/app/app_arsc.h
@@ -0,0 +1,62 @@
+unsigned char app_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x9c, 0x02, 0x00, 0x00,
+ 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6e, 0x00,
+ 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
+ 0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x64, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00
+};
+unsigned int app_arsc_len = 708;
diff --git a/libs/androidfw/tests/data/app/build b/libs/androidfw/tests/data/app/build
new file mode 100755
index 0000000..89c4641
--- /dev/null
+++ b/libs/androidfw/tests/data/app/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -v -I ../system/bundle.apk -M AndroidManifest.xml -S res -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc app.arsc && \
+xxd -i app.arsc > app_arsc.h
diff --git a/libs/androidfw/tests/data/app/res/values/values.xml b/libs/androidfw/tests/data/app/res/values/values.xml
new file mode 100644
index 0000000..b0ead38
--- /dev/null
+++ b/libs/androidfw/tests/data/app/res/values/values.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <attr name="number" format="integer"/>
+ <style name="Theme.One" parent="@android:style/Theme.One">
+ <item name="number">1</item>
+ </style>
+</resources>
diff --git a/packages/DocumentsUI/res/drawable/grid_protect_background.xml b/libs/androidfw/tests/data/system/AndroidManifest.xml
similarity index 72%
copy from packages/DocumentsUI/res/drawable/grid_protect_background.xml
copy to libs/androidfw/tests/data/system/AndroidManifest.xml
index 2e7aadd..af105ee 100644
--- a/packages/DocumentsUI/res/drawable/grid_protect_background.xml
+++ b/libs/androidfw/tests/data/system/AndroidManifest.xml
@@ -14,11 +14,6 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false">
- <color android:color="#88000000" />
- </item>
- <item>
- <color android:color="#88252525" />
- </item>
-</selector>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+</manifest>
diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h
new file mode 100644
index 0000000..7a9d3db
--- /dev/null
+++ b/libs/androidfw/tests/data/system/R.h
@@ -0,0 +1,23 @@
+#ifndef __ANDROID_R_H
+#define __ANDROID_R_H
+
+namespace android {
+namespace R {
+
+namespace attr {
+ enum {
+ background = 0x01010000, // default
+ foreground = 0x01010001, // default
+ };
+}
+
+namespace style {
+ enum {
+ Theme_One = 0x01020000, // default
+ };
+}
+
+} // namespace R
+} // namespace android
+
+#endif // __ANDROID_R_H
diff --git a/libs/androidfw/tests/data/system/build b/libs/androidfw/tests/data/system/build
new file mode 100755
index 0000000..2a3ac0b
--- /dev/null
+++ b/libs/androidfw/tests/data/system/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -x -M AndroidManifest.xml -S res -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc system.arsc && \
+xxd -i system.arsc > system_arsc.h
diff --git a/libs/androidfw/tests/data/system/res/values/themes.xml b/libs/androidfw/tests/data/system/res/values/themes.xml
new file mode 100644
index 0000000..b29848e
--- /dev/null
+++ b/libs/androidfw/tests/data/system/res/values/themes.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <public name="background" type="attr" id="0x01010000"/>
+ <public name="foreground" type="attr" id="0x01010001"/>
+ <public name="Theme.One" type="style" id="0x01020000"/>
+
+ <attr name="background" format="color|reference"/>
+ <attr name="foreground" format="color|reference"/>
+ <style name="Theme.One" parent="">
+ <item name="android:background">#ff0000</item>
+ <item name="android:foreground">#000000</item>
+ </style>
+</resources>
diff --git a/libs/androidfw/tests/data/system/system_arsc.h b/libs/androidfw/tests/data/system/system_arsc.h
new file mode 100644
index 0000000..215ecae
--- /dev/null
+++ b/libs/androidfw/tests/data/system/system_arsc.h
@@ -0,0 +1,69 @@
+unsigned char system_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x18, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf0, 0x02, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
+ 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x67, 0x00,
+ 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x67, 0x00,
+ 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
+ 0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
+ 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x44, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0xff, 0xff,
+ 0x01, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xff
+};
+unsigned int system_arsc_len = 792;
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index 9cc83ed..7834ef8 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -16,6 +16,43 @@
#define LOG_TAG "OpenGLRenderer"
+/**
+ * Extra vertices for the corner for smoother corner.
+ * Only for outer vertices.
+ * Note that we use such extra memory to avoid an extra loop.
+ */
+// For half circle, we could add EXTRA_VERTEX_PER_PI vertices.
+// Set to 1 if we don't want to have any.
+#define EXTRA_CORNER_VERTEX_PER_PI 12
+
+// For the whole polygon, the sum of all the deltas b/t normals is 2 * M_PI,
+// therefore, the maximum number of extra vertices will be twice bigger.
+#define MAX_EXTRA_CORNER_VERTEX_NUMBER (2 * EXTRA_CORNER_VERTEX_PER_PI)
+
+// For each RADIANS_DIVISOR, we would allocate one more vertex b/t the normals.
+#define CORNER_RADIANS_DIVISOR (M_PI / EXTRA_CORNER_VERTEX_PER_PI)
+
+/**
+ * Extra vertices for the Edge for interpolation artifacts.
+ * Same value for both inner and outer vertices.
+ */
+#define EXTRA_EDGE_VERTEX_PER_PI 50
+
+#define MAX_EXTRA_EDGE_VERTEX_NUMBER (2 * EXTRA_EDGE_VERTEX_PER_PI)
+
+#define EDGE_RADIANS_DIVISOR (M_PI / EXTRA_EDGE_VERTEX_PER_PI)
+
+/**
+ * Other constants:
+ */
+// For the edge of the penumbra, the opacity is 0.
+#define OUTER_OPACITY (0.0f)
+
+// Once the alpha difference is greater than this threshold, we will allocate extra
+// edge vertices.
+// If this is set to negative value, then all the edge will be tessellated.
+#define ALPHA_THRESHOLD (0.1f / 255.0f)
+
#include <math.h>
#include <utils/Log.h>
#include <utils/Vector.h>
@@ -23,11 +60,97 @@
#include "AmbientShadow.h"
#include "ShadowTessellator.h"
#include "Vertex.h"
+#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
/**
+ * Local utility functions.
+ */
+inline Vector2 getNormalFromVertices(const Vector3* vertices, int current, int next) {
+ // Convert from Vector3 to Vector2 first.
+ Vector2 currentVertex = { vertices[current].x, vertices[current].y };
+ Vector2 nextVertex = { vertices[next].x, vertices[next].y };
+
+ return ShadowTessellator::calculateNormal(currentVertex, nextVertex);
+}
+
+// The input z value will be converted to be non-negative inside.
+// The output must be ranged from 0 to 1.
+inline float getAlphaFromFactoredZ(float factoredZ) {
+ return 1.0 / (1 + MathUtils::max(factoredZ, 0.0f));
+}
+
+inline float getTransformedAlphaFromAlpha(float alpha) {
+ return acosf(1.0f - 2.0f * alpha);
+}
+
+// The output is ranged from 0 to M_PI.
+inline float getTransformedAlphaFromFactoredZ(float factoredZ) {
+ return getTransformedAlphaFromAlpha(getAlphaFromFactoredZ(factoredZ));
+}
+
+inline int getExtraVertexNumber(const Vector2& vector1, const Vector2& vector2,
+ float divisor) {
+ // The formula is :
+ // extraNumber = floor(acos(dot(n1, n2)) / (M_PI / EXTRA_VERTEX_PER_PI))
+ // The value ranges for each step are:
+ // dot( ) --- [-1, 1]
+ // acos( ) --- [0, M_PI]
+ // floor(...) --- [0, EXTRA_VERTEX_PER_PI]
+ float dotProduct = vector1.dot(vector2);
+ // TODO: Use look up table for the dotProduct to extraVerticesNumber
+ // computation, if needed.
+ float angle = acosf(dotProduct);
+ return (int) floor(angle / divisor);
+}
+
+inline void checkOverflow(int used, int total, const char* bufferName) {
+ LOG_ALWAYS_FATAL_IF(used > total, "Error: %s overflow!!! used %d, total %d",
+ bufferName, used, total);
+}
+
+inline int getEdgeExtraAndUpdateSpike(Vector2* currentSpike,
+ const Vector3& secondVertex, const Vector3& centroid) {
+ Vector2 secondSpike = {secondVertex.x - centroid.x, secondVertex.y - centroid.y};
+ secondSpike.normalize();
+
+ int result = getExtraVertexNumber(secondSpike, *currentSpike, EDGE_RADIANS_DIVISOR);
+ *currentSpike = secondSpike;
+ return result;
+}
+
+// Given the caster's vertex count, compute all the buffers size depending on
+// whether or not the caster is opaque.
+inline void computeBufferSize(int* totalVertexCount, int* totalIndexCount,
+ int* totalUmbraCount, int casterVertexCount, bool isCasterOpaque) {
+ // Compute the size of the vertex buffer.
+ int outerVertexCount = casterVertexCount * 2 + MAX_EXTRA_CORNER_VERTEX_NUMBER +
+ MAX_EXTRA_EDGE_VERTEX_NUMBER;
+ int innerVertexCount = casterVertexCount + MAX_EXTRA_EDGE_VERTEX_NUMBER;
+ *totalVertexCount = outerVertexCount + innerVertexCount;
+
+ // Compute the size of the index buffer.
+ *totalIndexCount = 2 * outerVertexCount + 2;
+
+ // Compute the size of the umber buffer.
+ // For translucent object, keep track of the umbra(inner) vertex in order to draw
+ // inside. We only need to store the index information.
+ *totalUmbraCount = 0;
+ if (!isCasterOpaque) {
+ // Add the centroid if occluder is translucent.
+ *totalVertexCount++;
+ *totalIndexCount += 2 * innerVertexCount + 1;
+ *totalUmbraCount = innerVertexCount;
+ }
+}
+
+inline bool needsExtraForEdge(float firstAlpha, float secondAlpha) {
+ return abs(firstAlpha - secondAlpha) > ALPHA_THRESHOLD;
+}
+
+/**
* Calculate the shadows as a triangle strips while alpha value as the
* shadow values.
*
@@ -43,290 +166,198 @@
*
* @param shadowVertexBuffer Return an floating point array of (x, y, a)
* triangle strips mode.
+ *
+ * An simple illustration:
+ * For now let's mark the outer vertex as Pi, the inner as Vi, the centroid as C.
+ *
+ * First project the occluder to the Z=0 surface.
+ * Then we got all the inner vertices. And we compute the normal for each edge.
+ * According to the normal, we generate outer vertices. E.g: We generate P1 / P4
+ * as extra corner vertices to make the corner looks round and smoother.
+ *
+ * Due to the fact that the alpha is not linear interpolated along the inner
+ * edge, when the alpha is different, we may add extra vertices such as P2.1, P2.2,
+ * V0.1, V0.2 to avoid the visual artifacts.
+ *
+ * (P3)
+ * (P2) (P2.1) (P2.2) | ' (P4)
+ * (P1)' | | | | '
+ * ' | | | | '
+ * (P0) ------------------------------------------------(P5)
+ * | (V0) (V0.1) (V0.2) |(V1)
+ * | |
+ * | |
+ * | (C) |
+ * | |
+ * | |
+ * | |
+ * | |
+ * (V3)-----------------------------------(V2)
*/
void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
- const Vector3* vertices, int vertexCount, const Vector3& centroid3d,
+ const Vector3* casterVertices, int casterVertexCount, const Vector3& centroid3d,
float heightFactor, float geomFactor, VertexBuffer& shadowVertexBuffer) {
- const int rays = SHADOW_RAY_COUNT;
- // Validate the inputs.
- if (vertexCount < 3 || heightFactor <= 0 || rays <= 0
- || geomFactor <= 0) {
-#if DEBUG_SHADOW
- ALOGW("Invalid input for createAmbientShadow(), early return!");
-#endif
- return;
- }
+ shadowVertexBuffer.setMode(VertexBuffer::kIndices);
- Vector<Vector2> dir; // TODO: use C++11 unique_ptr
- dir.setCapacity(rays);
- float rayDist[rays];
- float rayHeight[rays];
- calculateRayDirections(rays, vertices, vertexCount, centroid3d, dir.editArray());
+ // In order to computer the outer vertices in one loop, we need pre-compute
+ // the normal by the vertex (n - 1) to vertex 0, and the spike and alpha value
+ // for vertex 0.
+ Vector2 previousNormal = getNormalFromVertices(casterVertices,
+ casterVertexCount - 1 , 0);
+ Vector2 currentSpike = {casterVertices[0].x - centroid3d.x,
+ casterVertices[0].y - centroid3d.y};
+ currentSpike.normalize();
+ float currentAlpha = getAlphaFromFactoredZ(casterVertices[0].z * heightFactor);
- // Calculate the length and height of the points along the edge.
- //
- // The math here is:
- // Intersect each ray (starting from the centroid) with the polygon.
- for (int i = 0; i < rays; i++) {
- int edgeIndex;
- float edgeFraction;
- float rayDistance;
- calculateIntersection(vertices, vertexCount, centroid3d, dir[i], edgeIndex,
- edgeFraction, rayDistance);
- rayDist[i] = rayDistance;
- if (edgeIndex < 0 || edgeIndex >= vertexCount) {
-#if DEBUG_SHADOW
- ALOGW("Invalid edgeIndex!");
-#endif
- edgeIndex = 0;
- }
- float h1 = vertices[edgeIndex].z;
- float h2 = vertices[((edgeIndex + 1) % vertexCount)].z;
- rayHeight[i] = h1 + edgeFraction * (h2 - h1);
- }
-
- // The output buffer length basically is roughly rays * layers, but since we
- // need triangle strips, so we need to duplicate vertices to accomplish that.
+ // Preparing all the output data.
+ int totalVertexCount, totalIndexCount, totalUmbraCount;
+ computeBufferSize(&totalVertexCount, &totalIndexCount, &totalUmbraCount,
+ casterVertexCount, isCasterOpaque);
AlphaVertex* shadowVertices =
- shadowVertexBuffer.alloc<AlphaVertex>(SHADOW_VERTEX_COUNT);
+ shadowVertexBuffer.alloc<AlphaVertex>(totalVertexCount);
+ int vertexBufferIndex = 0;
+ uint16_t* indexBuffer = shadowVertexBuffer.allocIndices<uint16_t>(totalIndexCount);
+ int indexBufferIndex = 0;
+ uint16_t umbraVertices[totalUmbraCount];
+ int umbraIndex = 0;
- // Calculate the vertex of the shadows.
- //
- // The math here is:
- // Along the edges of the polygon, for each intersection point P (generated above),
- // calculate the normal N, which should be perpendicular to the edge of the
- // polygon (represented by the neighbor intersection points) .
- // Shadow's vertices will be generated as : P + N * scale.
- const Vector2 centroid2d = {centroid3d.x, centroid3d.y};
- for (int rayIndex = 0; rayIndex < rays; rayIndex++) {
- Vector2 normal = {1.0f, 0.0f};
- calculateNormal(rays, rayIndex, dir.array(), rayDist, normal);
+ for (int i = 0; i < casterVertexCount; i++) {
+ // Corner: first figure out the extra vertices we need for the corner.
+ const Vector3& innerVertex = casterVertices[i];
+ Vector2 currentNormal = getNormalFromVertices(casterVertices, i,
+ (i + 1) % casterVertexCount);
- // The vertex should be start from rayDist[i] then scale the
- // normalizeNormal!
- Vector2 intersection = dir[rayIndex] * rayDist[rayIndex] +
- centroid2d;
+ int extraVerticesNumber = getExtraVertexNumber(currentNormal, previousNormal,
+ CORNER_RADIANS_DIVISOR);
- // outer ring of points, expanded based upon height of each ray intersection
- float expansionDist = rayHeight[rayIndex] * heightFactor *
- geomFactor;
- AlphaVertex::set(&shadowVertices[rayIndex],
- intersection.x + normal.x * expansionDist,
- intersection.y + normal.y * expansionDist,
- 0.0f);
-
- // inner ring of points
- float opacity = 1.0 / (1 + rayHeight[rayIndex] * heightFactor);
- // NOTE: Shadow alpha values are transformed when stored in alphavertices,
- // so that they can be consumed directly by gFS_Main_ApplyVertexAlphaShadowInterp
- float transformedOpacity = acos(1.0f - 2.0f * opacity);
- AlphaVertex::set(&shadowVertices[rays + rayIndex],
- intersection.x,
- intersection.y,
- transformedOpacity);
- }
-
- if (isCasterOpaque) {
- // skip inner ring, calc bounds over filled portion of buffer
- shadowVertexBuffer.computeBounds<AlphaVertex>(2 * rays);
- shadowVertexBuffer.setMode(VertexBuffer::kOnePolyRingShadow);
- } else {
- // If caster isn't opaque, we need to to fill the umbra by storing the umbra's
- // centroid in the innermost ring of vertices.
- float centroidAlpha = 1.0 / (1 + centroid3d.z * heightFactor);
- AlphaVertex centroidXYA;
- AlphaVertex::set(¢roidXYA, centroid2d.x, centroid2d.y, centroidAlpha);
- for (int rayIndex = 0; rayIndex < rays; rayIndex++) {
- shadowVertices[2 * rays + rayIndex] = centroidXYA;
- }
- // calc bounds over entire buffer
- shadowVertexBuffer.computeBounds<AlphaVertex>();
- shadowVertexBuffer.setMode(VertexBuffer::kTwoPolyRingShadow);
- }
-
+ float expansionDist = innerVertex.z * heightFactor * geomFactor;
+ const int cornerSlicesNumber = extraVerticesNumber + 1; // Minimal as 1.
#if DEBUG_SHADOW
- for (int i = 0; i < SHADOW_VERTEX_COUNT; i++) {
- ALOGD("ambient shadow value: i %d, (x:%f, y:%f, a:%f)", i, shadowVertices[i].x,
- shadowVertices[i].y, shadowVertices[i].alpha);
- }
+ ALOGD("cornerSlicesNumber is %d", cornerSlicesNumber);
#endif
-}
-/**
- * Generate an array of rays' direction vectors.
- * To make sure the vertices generated are clockwise, the directions are from PI
- * to -PI.
- *
- * @param rays The number of rays shooting out from the centroid.
- * @param vertices Vertices of the polygon.
- * @param vertexCount The number of vertices.
- * @param centroid3d The centroid of the polygon.
- * @param dir Return the array of ray vectors.
- */
-void AmbientShadow::calculateRayDirections(const int rays, const Vector3* vertices,
- const int vertexCount, const Vector3& centroid3d, Vector2* dir) {
- // If we don't have enough rays, then fall back to the uniform distribution.
- if (vertexCount * 2 > rays) {
- float deltaAngle = 2 * M_PI / rays;
- for (int i = 0; i < rays; i++) {
- dir[i].x = cosf(M_PI - deltaAngle * i);
- dir[i].y = sinf(M_PI - deltaAngle * i);
+ // Corner: fill the corner Vertex Buffer(VB) and Index Buffer(IB).
+ // We fill the inner vertex first, such that we can fill the index buffer
+ // inside the loop.
+ int currentInnerVertexIndex = vertexBufferIndex;
+ if (!isCasterOpaque) {
+ umbraVertices[umbraIndex++] = vertexBufferIndex;
}
- return;
- }
+ AlphaVertex::set(&shadowVertices[vertexBufferIndex++], casterVertices[i].x,
+ casterVertices[i].y,
+ getTransformedAlphaFromAlpha(currentAlpha));
- // If we have enough rays, then we assign each vertices a ray, and distribute
- // the rest uniformly.
- float rayThetas[rays];
+ const Vector3& innerStart = casterVertices[i];
- const int uniformRayCount = rays - vertexCount;
- const float deltaAngle = 2 * M_PI / uniformRayCount;
+ // outerStart is the first outer vertex for this inner vertex.
+ // outerLast is the last outer vertex for this inner vertex.
+ Vector2 outerStart = {0, 0};
+ Vector2 outerLast = {0, 0};
+ // This will create vertices from [0, cornerSlicesNumber] inclusively,
+ // which means minimally 2 vertices even without the extra ones.
+ for (int j = 0; j <= cornerSlicesNumber; j++) {
+ Vector2 averageNormal =
+ previousNormal * (cornerSlicesNumber - j) + currentNormal * j;
+ averageNormal /= cornerSlicesNumber;
+ averageNormal.normalize();
+ Vector2 outerVertex;
+ outerVertex.x = innerVertex.x + averageNormal.x * expansionDist;
+ outerVertex.y = innerVertex.y + averageNormal.y * expansionDist;
- // We have to generate all the vertices' theta anyway and we also need to
- // find the minimal, so let's precompute it first.
- // Since the incoming polygon is clockwise, we can find the dip to identify
- // the minimal theta.
- float polyThetas[vertexCount];
- int maxPolyThetaIndex = 0;
- for (int i = 0; i < vertexCount; i++) {
- polyThetas[i] = atan2(vertices[i].y - centroid3d.y,
- vertices[i].x - centroid3d.x);
- if (i > 0 && polyThetas[i] > polyThetas[i - 1]) {
- maxPolyThetaIndex = i;
- }
- }
+ indexBuffer[indexBufferIndex++] = vertexBufferIndex;
+ indexBuffer[indexBufferIndex++] = currentInnerVertexIndex;
+ AlphaVertex::set(&shadowVertices[vertexBufferIndex++], outerVertex.x,
+ outerVertex.y, OUTER_OPACITY);
- // Both poly's thetas and uniform thetas are in decrease order(clockwise)
- // from PI to -PI.
- int polyThetaIndex = maxPolyThetaIndex;
- float polyTheta = polyThetas[maxPolyThetaIndex];
- int uniformThetaIndex = 0;
- float uniformTheta = M_PI;
- for (int i = 0; i < rays; i++) {
- // Compare both thetas and pick the smaller one and move on.
- bool hasThetaCollision = abs(polyTheta - uniformTheta) < MINIMAL_DELTA_THETA;
- if (polyTheta > uniformTheta || hasThetaCollision) {
- if (hasThetaCollision) {
- // Shift the uniformTheta to middle way between current polyTheta
- // and next uniform theta. The next uniform theta can wrap around
- // to exactly PI safely here.
- // Note that neither polyTheta nor uniformTheta can be FLT_MAX
- // due to the hasThetaCollision is true.
- uniformTheta = (polyTheta + M_PI - deltaAngle * (uniformThetaIndex + 1)) / 2;
-#if DEBUG_SHADOW
- ALOGD("Shifted uniformTheta to %f", uniformTheta);
-#endif
- }
- rayThetas[i] = polyTheta;
- polyThetaIndex = (polyThetaIndex + 1) % vertexCount;
- if (polyThetaIndex != maxPolyThetaIndex) {
- polyTheta = polyThetas[polyThetaIndex];
- } else {
- // out of poly points.
- polyTheta = - FLT_MAX;
- }
- } else {
- rayThetas[i] = uniformTheta;
- uniformThetaIndex++;
- if (uniformThetaIndex < uniformRayCount) {
- uniformTheta = M_PI - deltaAngle * uniformThetaIndex;
- } else {
- // out of uniform points.
- uniformTheta = - FLT_MAX;
+ if (j == 0) {
+ outerStart = outerVertex;
+ } else if (j == cornerSlicesNumber) {
+ outerLast = outerVertex;
}
}
- }
+ previousNormal = currentNormal;
- for (int i = 0; i < rays; i++) {
+ // Edge: first figure out the extra vertices needed for the edge.
+ const Vector3& innerNext = casterVertices[(i + 1) % casterVertexCount];
+ float nextAlpha = getAlphaFromFactoredZ(innerNext.z * heightFactor);
+ if (needsExtraForEdge(currentAlpha, nextAlpha)) {
+ // TODO: See if we can / should cache this outer vertex across the loop.
+ Vector2 outerNext;
+ float expansionDist = innerNext.z * heightFactor * geomFactor;
+ outerNext.x = innerNext.x + currentNormal.x * expansionDist;
+ outerNext.y = innerNext.y + currentNormal.y * expansionDist;
+
+ // Compute the angle and see how many extra points we need.
+ int extraVerticesNumber = getEdgeExtraAndUpdateSpike(¤tSpike,
+ innerNext, centroid3d);
#if DEBUG_SHADOW
- ALOGD("No. %d : %f", i, rayThetas[i] * 180 / M_PI);
+ ALOGD("extraVerticesNumber %d for edge %d", extraVerticesNumber, i);
#endif
- // TODO: Fix the intersection precision problem and remvoe the delta added
- // here.
- dir[i].x = cosf(rayThetas[i] + MINIMAL_DELTA_THETA);
- dir[i].y = sinf(rayThetas[i] + MINIMAL_DELTA_THETA);
- }
-}
+ // Edge: fill the edge's VB and IB.
+ // This will create vertices pair from [1, extraVerticesNumber - 1].
+ // If there is no extra vertices created here, the edge will be drawn
+ // as just 2 triangles.
+ for (int k = 1; k < extraVerticesNumber; k++) {
+ int startWeight = extraVerticesNumber - k;
+ Vector2 currentOuter =
+ (outerLast * startWeight + outerNext * k) / extraVerticesNumber;
+ indexBuffer[indexBufferIndex++] = vertexBufferIndex;
+ AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentOuter.x,
+ currentOuter.y, OUTER_OPACITY);
-/**
- * Calculate the intersection of a ray hitting the polygon.
- *
- * @param vertices The shadow caster's polygon, which is represented in a
- * Vector3 array.
- * @param vertexCount The length of caster's polygon in terms of number of vertices.
- * @param start The starting point of the ray.
- * @param dir The direction vector of the ray.
- *
- * @param outEdgeIndex Return the index of the segment (or index of the starting
- * vertex) that ray intersect with.
- * @param outEdgeFraction Return the fraction offset from the segment starting
- * index.
- * @param outRayDist Return the ray distance from centroid to the intersection.
- */
-void AmbientShadow::calculateIntersection(const Vector3* vertices, int vertexCount,
- const Vector3& start, const Vector2& dir, int& outEdgeIndex,
- float& outEdgeFraction, float& outRayDist) {
- float startX = start.x;
- float startY = start.y;
- float dirX = dir.x;
- float dirY = dir.y;
- // Start the search from the last edge from poly[len-1] to poly[0].
- int p1 = vertexCount - 1;
-
- for (int p2 = 0; p2 < vertexCount; p2++) {
- float p1x = vertices[p1].x;
- float p1y = vertices[p1].y;
- float p2x = vertices[p2].x;
- float p2y = vertices[p2].y;
-
- // The math here is derived from:
- // f(t, v) = p1x * (1 - t) + p2x * t - (startX + dirX * v) = 0;
- // g(t, v) = p1y * (1 - t) + p2y * t - (startY + dirY * v) = 0;
- float div = (dirX * (p1y - p2y) + dirY * p2x - dirY * p1x);
- if (div != 0) {
- float t = (dirX * (p1y - startY) + dirY * startX - dirY * p1x) / (div);
- if (t > 0 && t <= 1) {
- float t2 = (p1x * (startY - p2y)
- + p2x * (p1y - startY)
- + startX * (p2y - p1y)) / div;
- if (t2 > 0) {
- outEdgeIndex = p1;
- outRayDist = t2;
- outEdgeFraction = t;
- return;
+ if (!isCasterOpaque) {
+ umbraVertices[umbraIndex++] = vertexBufferIndex;
}
+ Vector3 currentInner =
+ (innerStart * startWeight + innerNext * k) / extraVerticesNumber;
+ indexBuffer[indexBufferIndex++] = vertexBufferIndex;
+ AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentInner.x,
+ currentInner.y,
+ getTransformedAlphaFromFactoredZ(currentInner.z * heightFactor));
}
}
- p1 = p2;
+ currentAlpha = nextAlpha;
}
- return;
-};
-/**
- * Calculate the normal at the intersection point between a ray and the polygon.
- *
- * @param rays The total number of rays.
- * @param currentRayIndex The index of the ray which the normal is based on.
- * @param dir The array of the all the rays directions.
- * @param rayDist The pre-computed ray distances array.
- *
- * @param normal Return the normal.
- */
-void AmbientShadow::calculateNormal(int rays, int currentRayIndex,
- const Vector2* dir, const float* rayDist, Vector2& normal) {
- int preIndex = (currentRayIndex - 1 + rays) % rays;
- int postIndex = (currentRayIndex + 1) % rays;
- Vector2 p1 = dir[preIndex] * rayDist[preIndex];
- Vector2 p2 = dir[postIndex] * rayDist[postIndex];
+ indexBuffer[indexBufferIndex++] = 1;
+ indexBuffer[indexBufferIndex++] = 0;
- // Now the rays are going CW around the poly.
- Vector2 delta = p2 - p1;
- if (delta.length() != 0) {
- delta.normalize();
- // Calculate the normal , which is CCW 90 rotate to the delta.
- normal.x = - delta.y;
- normal.y = delta.x;
+ if (!isCasterOpaque) {
+ // Add the centroid as the last one in the vertex buffer.
+ float centroidOpacity =
+ getTransformedAlphaFromFactoredZ(centroid3d.z * heightFactor);
+ int centroidIndex = vertexBufferIndex;
+ AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid3d.x,
+ centroid3d.y, centroidOpacity);
+
+ for (int i = 0; i < umbraIndex; i++) {
+ // Note that umbraVertices[0] is always 0.
+ // So the start and the end of the umbra are using the "0".
+ // And penumbra ended with 0, so a degenerated triangle is formed b/t
+ // the umbra and penumbra.
+ indexBuffer[indexBufferIndex++] = umbraVertices[i];
+ indexBuffer[indexBufferIndex++] = centroidIndex;
+ }
+ indexBuffer[indexBufferIndex++] = 0;
}
+
+ // At the end, update the real index and vertex buffer size.
+ shadowVertexBuffer.updateVertexCount(vertexBufferIndex);
+ shadowVertexBuffer.updateIndexCount(indexBufferIndex);
+
+ checkOverflow(vertexBufferIndex, totalVertexCount, "Vertex Buffer");
+ checkOverflow(indexBufferIndex, totalIndexCount, "Index Buffer");
+ checkOverflow(umbraIndex, totalUmbraCount, "Umbra Buffer");
+
+#if DEBUG_SHADOW
+ for (int i = 0; i < vertexBufferIndex; i++) {
+ ALOGD("vertexBuffer i %d, (%f, %f %f)", i, shadowVertices[i].x, shadowVertices[i].y,
+ shadowVertices[i].alpha);
+ }
+ for (int i = 0; i < indexBufferIndex; i++) {
+ ALOGD("indexBuffer i %d, indexBuffer[i] %d", i, indexBuffer[i]);
+ }
+#endif
}
}; // namespace uirenderer
diff --git a/libs/hwui/AmbientShadow.h b/libs/hwui/AmbientShadow.h
index 68df246..9660dc0 100644
--- a/libs/hwui/AmbientShadow.h
+++ b/libs/hwui/AmbientShadow.h
@@ -28,27 +28,12 @@
/**
* AmbientShadow is used to calculate the ambient shadow value around a polygon.
- *
- * TODO: calculateIntersection() now is O(N*M), where N is the number of
- * polygon's vertics and M is the number of rays. In fact, by staring tracing
- * the vertex from the previous intersection, the algorithm can be O(N + M);
*/
class AmbientShadow {
public:
static void createAmbientShadow(bool isCasterOpaque, const Vector3* poly,
int polyLength, const Vector3& centroid3d, float heightFactor,
float geomFactor, VertexBuffer& shadowVertexBuffer);
-
-private:
- static void calculateRayDirections(const int rays, const Vector3* vertices,
- const int vertexCount, const Vector3& centroid3d, Vector2* dir);
-
- static void calculateIntersection(const Vector3* poly, int nbVertices,
- const Vector3& start, const Vector2& dir, int& outEdgeIndex,
- float& outEdgeFraction, float& outRayDist);
-
- static void calculateNormal(int rays, int currentRayIndex, const Vector2* dir,
- const float* rayDist, Vector2& normal);
}; // AmbientShadow
}; // namespace uirenderer
diff --git a/libs/hwui/AnimationContext.cpp b/libs/hwui/AnimationContext.cpp
index ec44de3..d7d9743 100644
--- a/libs/hwui/AnimationContext.cpp
+++ b/libs/hwui/AnimationContext.cpp
@@ -31,6 +31,14 @@
}
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) {
@@ -96,11 +104,18 @@
if (mRenderNode->animators().hasAnimators()) {
mContext.addAnimationHandle(this);
} else {
- mRenderNode->animators().setAnimationHandle(NULL);
- delete this;
+ 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;
diff --git a/libs/hwui/AnimationContext.h b/libs/hwui/AnimationContext.h
index e32c33d..900d953 100644
--- a/libs/hwui/AnimationContext.h
+++ b/libs/hwui/AnimationContext.h
@@ -46,8 +46,15 @@
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);
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 1c697d5..da65f38 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -95,6 +95,8 @@
// Oh boy, we're starting! Man the battle stations!
if (mPlayState == RUNNING) {
transitionToRunning(context);
+ } else if (mPlayState == FINISHED) {
+ callOnFinishedListener(context);
}
}
}
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index 3832d42..678b1ee 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -160,13 +160,15 @@
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();
}
- mAnimators.clear();
}
} /* namespace uirenderer */
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 @@
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/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index bbf0551..0f36c06 100755
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2417,6 +2417,10 @@
} else if (mode == VertexBuffer::kTwoPolyRingShadow) {
mCaches.bindShadowIndicesBuffer();
glDrawElements(GL_TRIANGLE_STRIP, TWO_POLY_RING_SHADOW_INDEX_COUNT, GL_UNSIGNED_SHORT, 0);
+ } else if (mode == VertexBuffer::kIndices) {
+ mCaches.unbindIndicesBuffer();
+ glDrawElements(GL_TRIANGLE_STRIP, vertexBuffer.getIndexCount(), GL_UNSIGNED_SHORT,
+ vertexBuffer.getIndices());
}
if (isAA) {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index a79875e..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 @@
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) {
@@ -653,41 +661,12 @@
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 @@
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 @@
renderer.restoreToCount(restoreTo);
drawIndex++;
}
+ renderer.restoreToCount(rootRestoreTo);
}
template <class T>
@@ -869,6 +854,8 @@
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 @@
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 @@
}
}
- // 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 27b05e2..d897997 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -208,16 +208,9 @@
inline void issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler);
template <class T>
- inline int issueOperationsOfNegZChildren(
- 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 issueOperationsOfPosZChildren(int shadowRestoreTo,
- 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 @@
// 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 @@
// 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 @@
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 @@
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 e78d8bd..ae6ea94 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -26,6 +26,10 @@
namespace android {
namespace uirenderer {
+namespace renderthread {
+class CanvasContext;
+}
+
class OpenGLRenderer;
class RenderState;
@@ -59,6 +63,7 @@
, renderState(renderState)
, renderer(NULL)
, errorHandler(NULL)
+ , canvasContext(NULL)
{}
explicit TreeInfo(TraversalMode mode, const TreeInfo& clone)
@@ -69,6 +74,7 @@
, renderState(clone.renderState)
, renderer(clone.renderer)
, errorHandler(clone.errorHandler)
+ , canvasContext(clone.canvasContext)
{}
const TraversalMode mode;
@@ -89,6 +95,8 @@
// 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/Vector.h b/libs/hwui/Vector.h
index 2a9f01c..d033ed9 100644
--- a/libs/hwui/Vector.h
+++ b/libs/hwui/Vector.h
@@ -111,6 +111,23 @@
float y;
float z;
+ Vector3 operator+(const Vector3& v) const {
+ return (Vector3){x + v.x, y + v.y, z + v.z};
+ }
+
+ Vector3 operator-(const Vector3& v) const {
+ return (Vector3){x - v.x, y - v.y, z - v.z};
+ }
+
+ Vector3 operator/(float s) const {
+ return (Vector3){x / s, y / s, z / s};
+ }
+
+ Vector3 operator*(float s) const {
+ return (Vector3){x * s, y * s, z * s};
+ }
+
+
void dump() {
ALOGD("Vector3[%.2f, %.2f, %.2f]", x, y, z);
}
diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h
index 3837f88..966fa4e 100644
--- a/libs/hwui/VertexBuffer.h
+++ b/libs/hwui/VertexBuffer.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_HWUI_VERTEX_BUFFER_H
#define ANDROID_HWUI_VERTEX_BUFFER_H
+#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
@@ -26,19 +27,27 @@
enum Mode {
kStandard = 0,
kOnePolyRingShadow = 1,
- kTwoPolyRingShadow = 2
+ kTwoPolyRingShadow = 2,
+ kIndices = 3
};
VertexBuffer()
: mBuffer(0)
+ , mIndices(0)
, mVertexCount(0)
+ , mIndexCount(0)
+ , mAllocatedVertexCount(0)
+ , mAllocatedIndexCount(0)
, mByteCount(0)
, mMode(kStandard)
+ , mReallocBuffer(0)
, mCleanupMethod(NULL)
+ , mCleanupIndexMethod(NULL)
{}
~VertexBuffer() {
if (mCleanupMethod) mCleanupMethod(mBuffer);
+ if (mCleanupIndexMethod) mCleanupIndexMethod(mIndices);
}
/**
@@ -59,6 +68,7 @@
mReallocBuffer = reallocBuffer + vertexCount;
return reallocBuffer;
}
+ mAllocatedVertexCount = vertexCount;
mVertexCount = vertexCount;
mByteCount = mVertexCount * sizeof(TYPE);
mReallocBuffer = mBuffer = (void*)new TYPE[vertexCount];
@@ -69,6 +79,17 @@
}
template <class TYPE>
+ TYPE* allocIndices(int indexCount) {
+ mAllocatedIndexCount = indexCount;
+ mIndexCount = indexCount;
+ mIndices = (void*)new TYPE[indexCount];
+
+ mCleanupIndexMethod = &(cleanup<TYPE>);
+
+ return (TYPE*)mIndices;
+ }
+
+ template <class TYPE>
void copyInto(const VertexBuffer& srcBuffer, float xOffset, float yOffset) {
int verticesToCopy = srcBuffer.getVertexCount();
@@ -103,9 +124,17 @@
}
const void* getBuffer() const { return mBuffer; }
+ const void* getIndices() const { return mIndices; }
const Rect& getBounds() const { return mBounds; }
unsigned int getVertexCount() const { return mVertexCount; }
unsigned int getSize() const { return mByteCount; }
+ unsigned int getIndexCount() const { return mIndexCount; }
+ void updateIndexCount(unsigned int newCount) {
+ mIndexCount = MathUtils::min(newCount, mAllocatedIndexCount);
+ }
+ void updateVertexCount(unsigned int newCount) {
+ newCount = MathUtils::min(newCount, mAllocatedVertexCount);
+ }
Mode getMode() const { return mMode; }
void setBounds(Rect bounds) { mBounds = bounds; }
@@ -127,14 +156,22 @@
}
Rect mBounds;
+
void* mBuffer;
+ void* mIndices;
+
unsigned int mVertexCount;
+ unsigned int mIndexCount;
+ unsigned int mAllocatedVertexCount;
+ unsigned int mAllocatedIndexCount;
unsigned int mByteCount;
+
Mode mMode;
void* mReallocBuffer; // used for multi-allocation
void (*mCleanupMethod)(void*);
+ void (*mCleanupIndexMethod)(void*);
};
}; // namespace uirenderer
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index d9fa0bc..491a295 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -16,6 +16,7 @@
#include "CanvasContext.h"
+#include <algorithm>
#include <private/hwui/DrawGlInfo.h>
#include <strings.h>
@@ -53,6 +54,7 @@
destroyCanvasAndSurface();
mRenderThread.removeFrameCallback(this);
delete mAnimationContext;
+ freePrefetechedLayers();
}
void CanvasContext::destroyCanvasAndSurface() {
@@ -142,10 +144,17 @@
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
// of fast-path based off when we last called eglSwapBuffers() as well as
@@ -249,6 +258,26 @@
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) {
@@ -270,6 +299,9 @@
node->setPropertyFieldsDirty(RenderNode::GENERIC);
mCanvas->flushLayerUpdates();
+
+ node->incStrong(0);
+ mPrefetechedLayers.insert(node);
}
bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
@@ -282,6 +314,7 @@
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 749da1b..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>
@@ -71,6 +73,7 @@
void buildLayer(RenderNode* node);
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
+ void markLayerInUse(RenderNode* node);
void destroyHardwareResources();
static void trimMemory(RenderThread& thread, int level);
@@ -99,6 +102,8 @@
void requireGlContext();
+ void freePrefetechedLayers();
+
RenderThread& mRenderThread;
EglManager& mEglManager;
sp<ANativeWindow> mNativeWindow;
@@ -114,6 +119,8 @@
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/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 @@
/**
* 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 bce4074..d002924 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -200,6 +200,7 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
@Override
public void handleMessage(Message msg) {
-
switch (msg.what) {
case MSG_SET_DEVICE_VOLUME:
@@ -3851,7 +3871,7 @@
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 @@
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;
}
}
}
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 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 @@
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 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..3e8ee93 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -557,12 +557,15 @@
int i = 0;
for (Map.Entry<String, Object> entry: formatMap.entrySet()) {
if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) {
- // TODO: Wire up as soon as AudioService is ready. Check entry.getValue() for
- // non-integral type.
- // long audioHwSync = audioService.getAudioHwSyncForSession(entry.getValue());
- long audioHwSync = 0;
+ int sessionId = 0;
+ try {
+ sessionId = (Integer)entry.getValue();
+ }
+ catch (Exception e) {
+ throw new IllegalArgumentException("Wrong Session ID Parameter!");
+ }
keys[i] = "audio-hw-sync";
- values[i] = audioHwSync;
+ values[i] = AudioSystem.getAudioHwSyncForSession(sessionId);
} else {
keys[i] = entry.getKey();
values[i] = entry.getValue();
@@ -665,7 +668,7 @@
* 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 +1732,7 @@
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 @@
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 @@
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 @@
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,1311 +338,54 @@
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;
}
- /**
- * Returns the encoding capabilities or {@code null} if this is not an encoder.
- */
- public final EncoderCapabilities getEncoderCapabilities() {
- return mEncoderCaps;
+ private boolean isAudio() {
+ return mAudioCaps != null;
}
- private EncoderCapabilities mEncoderCaps;
+
+ /**
+ * Returns the audio capabilities or {@code null} if this is not an audio codec.
+ */
+ public AudioCapabilities getAudioCapabilities() {
+ return mAudioCaps;
+ }
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;
- }
-
- /** 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;
- }
-
- /**
- * 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;
- }
-
- private Range<Integer> mQualityRange;
- private Range<Integer> mComplexityRange;
- private CodecCapabilities mParent;
-
- /* no public constructor */
- private EncoderCapabilities() { }
-
- /** @hide */
- public static EncoderCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
- EncoderCapabilities caps = new EncoderCapabilities();
- 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);
- }
-
- 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);
- }
- }
-
- private int mBitControl;
- private Integer mDefaultComplexity;
- private Integer mDefaultQuality;
- private String mQualityScale;
-
- private void parseFromInfo(MediaFormat info) {
- Map<String, Object> map = info.getMap();
-
- 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);
- }
- }
-
- try {
- mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
- } catch (NumberFormatException e) { }
-
- try {
- mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
- } catch (NumberFormatException e) { }
-
- mQualityScale = (String)map.get("quality-scale");
- }
-
- 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;
- }
-
- /** @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;
- }
- }
- }
-
- /** @hide */
- public boolean supportsFormat(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- final String mime = mParent.getMime();
-
- Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
- if (mode != null && !isBitrateModeSupported(mode)) {
- return false;
- }
-
- 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");
- }
- }
-
- // 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");
- }
- }
-
- Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
-
- return supports(complexity, quality, profile);
- }
- };
-
- /**
- * 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;
- }
-
- /** @hide */
- protected Range<Integer> mBitrateRange;
-
- /** @hide */
- protected CodecCapabilities mParent;
-
- /** @hide */
- protected BaseCapabilities() {
- }
-
- /** @hide */
- protected void init(MediaFormat info, CodecCapabilities parent) {
- mParent = parent;
- mBitrateRange = Range.create(0, Integer.MAX_VALUE);
- }
+ public EncoderCapabilities getEncoderCapabilities() {
+ return mEncoderCaps;
}
- /**
- * A class that supports querying the video capabilities of a codec.
- */
- 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;
- }
-
- /**
- * Returns the range of supported video heights.
- */
- public final Range<Integer> getSupportedHeights() {
- return mHeightRange;
- }
-
- /**
- * Returns the alignment requirement for video width.
- */
- public final int getWidthAlignment() {
- return mWidthAlignment;
- }
-
- /**
- * Returns the alignment requirement for video height.
- */
- public final int getHeightAlignment() {
- return mHeightAlignment;
- }
-
- /**
- * 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 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;
- }
-
- /**
- * 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);
- }
-
- // 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 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");
- }
- }
-
- /**
- * 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) {
- 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()));
- }
-
- /**
- * 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);
- }
-
- /**
- * 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);
- }
-
- private final 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) {
- double blocksPerSec = blockCount * rate;
- ok = mBlocksPerSecondRange.contains(
- Utils.longRangeFor(blocksPerSec));
- }
- }
- 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);
-
- // we ignore color-format for now as it is not reliably reported by codec
-
- return supports(width, height, rate);
- }
-
- /* 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 void init(MediaFormat info, CodecCapabilities parent) {
- super.init(info, parent);
- initWithPlatformLimits();
- applyLevelLimits();
- parseFromInfo(info);
- updateLimits();
- }
-
- /** @hide */
- public Size getBlockSize() {
- return new Size(mBlockWidth, mBlockHeight);
- }
-
- /** @hide */
- public Range<Integer> getBlockCountRange() {
- return mBlockCountRange;
- }
-
- /** @hide */
- public Range<Long> getBlocksPerSecondRange() {
- return mBlocksPerSecondRange;
- }
-
- /** @hide */
- public Range<Rational> getAspectRatioRange(boolean blocks) {
- return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
- }
-
- private void initWithPlatformLimits() {
- mWidthRange = SIZE_RANGE;
- mHeightRange = SIZE_RANGE;
- mFrameRateRange = FRAME_RATE_RANGE;
-
- mHorizontalBlockRange = SIZE_RANGE;
- mVerticalBlockRange = SIZE_RANGE;
-
- // full positive ranges are supported as these get calculated
- mBlockCountRange = POSITIVE_INTEGERS;
- mBlocksPerSecondRange = POSITIVE_LONGS;
-
- mBlockAspectRatioRange = POSITIVE_RATIONALS;
- mAspectRatioRange = POSITIVE_RATIONALS;
-
- // YUV 4:2:0 requires 2:2 alignment
- mWidthAlignment = 2;
- mHeightAlignment = 2;
- mBlockWidth = 2;
- mBlockHeight = 2;
- mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
- }
-
- 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);
- }
- }
-
- 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;
- }
- }
-
- 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);
- }
- if (frameRates != null) {
- mFrameRateRange = mFrameRateRange.intersect(frameRates);
- }
- }
- updateLimits();
- }
-
- private int checkPowerOfTwo(int value, String message) {
- if ((value & (value - 1)) != 0) {
- throw new IllegalArgumentException(message);
- }
- 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);
- }
- 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;
- }
-
- 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);
- }
-
- mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
- mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
-
- mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
- mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
- }
-
- 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()));
- }
-
- 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.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);
- }
-
- 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);
- }
- 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;
- }
-
- /* 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);
- }
-
- 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;
- }
- mBitrateRange = Range.create(1, maxBps);
- mParent.mError |= errors;
- }
- };
-
- VideoCapabilities mVideoCaps;
-
private boolean isVideo() {
return mVideoCaps != null;
}
@@ -1624,16 +393,28 @@
/**
* Returns the video capabilities or {@code null} if this is not a video codec.
*/
- public final VideoCapabilities getVideoCapabilities() {
+ public VideoCapabilities getVideoCapabilities() {
return mVideoCaps;
}
+ /** @hide */
+ public CodecCapabilities dup() {
+ return new CodecCapabilities(
+ // clone writable arrays
+ Arrays.copyOf(profileLevels, profileLevels.length),
+ Arrays.copyOf(colorFormats, colorFormats.length),
+ isEncoder(),
+ mFlagsVerified,
+ mDefaultFormat,
+ mCapabilitiesInfo);
+ }
+
/**
* 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 final CodecCapabilities CreateFromProfileLevel(
+ public static CodecCapabilities CreateFromProfileLevel(
String mime, int profile, int level) {
CodecProfileLevel pl = new CodecProfileLevel();
pl.profile = profile;
@@ -1699,282 +480,1503 @@
// TODO restrict features by mFlagsVerified once all codecs reliably verify them
}
}
+ }
+
+ /**
+ * 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 int[] mSampleRates;
+ private Range<Integer>[] mSampleRateRanges;
+ private int mMaxInputChannelCount;
+
+ private static final int MAX_INPUT_CHANNEL_COUNT = 30;
/**
- * A class that supports querying the audio capabilities of a codec.
+ * Returns the range of supported bitrates in bits/second.
*/
- 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);
- }
-
- /**
- * 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);
- }
-
- /**
- * 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 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) {
- super.init(info, parent);
- initWithPlatformLimits();
- applyLevelLimits();
- parseFromInfo(info);
- }
-
- private void initWithPlatformLimits() {
- mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
- // mBitrateRange = Range.create(1, 320000);
- mSampleRateRanges = new Range[] { Range.create(8000, 96000) };
- mSampleRates = null;
- }
-
- 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;
- }
-
- /**
- * Query whether the sample rate is supported by the codec.
- */
- public final boolean isSampleRateSupported(int sampleRate) {
- return supports(sampleRate, null);
- }
-
- /** 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();
- }
- }
-
- /** modifies rateRanges */
- private void limitSampleRates(Range<Integer>[] rateRanges) {
- sortDistinctRanges(rateRanges);
- mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
-
- // check if all values are discrete
- for (Range<Integer> range: mSampleRateRanges) {
- if (!range.getLower().equals(range.getUpper())) {
- mSampleRates = null;
- return;
- }
- }
- createDiscreteSampleRates();
- }
-
- 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;
- }
-
- // 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);
- }
- }
-
- private void parseFromInfo(MediaFormat info) {
- int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
- Range<Integer> bitRates = POSITIVE_INTEGERS;
-
- 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 */
- 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 */
- 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;
- }
- };
-
- AudioCapabilities mAudioCaps;
- private boolean isAudio() {
- return mAudioCaps != null;
+ public Range<Integer> getBitrateRange() {
+ return mBitrateRange;
}
/**
- * Returns the audio capabilities or {@code null} if this is not an audio codec.
+ * 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 AudioCapabilities getAudioCapabilities() {
- return mAudioCaps;
+ public int[] getSupportedSampleRates() {
+ return Arrays.copyOf(mSampleRates, mSampleRates.length);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /* 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 CodecCapabilities dup() {
- return new CodecCapabilities(
- // clone writable arrays
- Arrays.copyOf(profileLevels, profileLevels.length),
- Arrays.copyOf(colorFormats, colorFormats.length),
- isEncoder(),
- mFlagsVerified,
- mDefaultFormat,
- mCapabilitiesInfo);
+ public void init(MediaFormat info, CodecCapabilities parent) {
+ mParent = parent;
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ /**
+ * Query whether the sample rate is supported by the codec.
+ */
+ public boolean isSampleRateSupported(int sampleRate) {
+ return supports(sampleRate, null);
+ }
+
+ /** 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();
+ }
+ }
+
+ /** modifies rateRanges */
+ private void limitSampleRates(Range<Integer>[] rateRanges) {
+ sortDistinctRanges(rateRanges);
+ mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
+
+ // check if all values are discrete
+ for (Range<Integer> range: mSampleRateRanges) {
+ if (!range.getLower().equals(range.getUpper())) {
+ mSampleRates = null;
+ return;
+ }
+ }
+ createDiscreteSampleRates();
+ }
+
+ 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);
+ }
+ }
+
+ private void parseFromInfo(MediaFormat info) {
+ int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+ Range<Integer> bitRates = POSITIVE_INTEGERS;
+
+ 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 */
+ 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 */
+ 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;
+
+ /**
+ * Returns the range of supported bitrates in bits/second.
+ */
+ public Range<Integer> getBitrateRange() {
+ return mBitrateRange;
+ }
+
+ /**
+ * Returns the range of supported video widths.
+ */
+ public Range<Integer> getSupportedWidths() {
+ return mWidthRange;
+ }
+
+ /**
+ * Returns the range of supported video heights.
+ */
+ public Range<Integer> getSupportedHeights() {
+ return mHeightRange;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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 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;
+ }
+
+ /**
+ * 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");
+ }
+ 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 heights for a video width
+ * @param width the width of the video
+ */
+ public 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");
+ }
+ }
+
+ /**
+ * 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 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()));
+ }
+
+ /**
+ * 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) {
+ double blocksPerSec = blockCount * rate;
+ ok = mBlocksPerSecondRange.contains(
+ Utils.longRangeFor(blocksPerSec));
+ }
+ }
+ 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);
+
+ // we ignore color-format for now as it is not reliably reported by codec
+
+ return supports(width, height, rate);
+ }
+
+ /* 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 void init(MediaFormat info, CodecCapabilities parent) {
+ mParent = parent;
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ updateLimits();
+ }
+
+ /** @hide */
+ public Size getBlockSize() {
+ return new Size(mBlockWidth, mBlockHeight);
+ }
+
+ /** @hide */
+ public Range<Integer> getBlockCountRange() {
+ return mBlockCountRange;
+ }
+
+ /** @hide */
+ public Range<Long> getBlocksPerSecondRange() {
+ return mBlocksPerSecondRange;
+ }
+
+ /** @hide */
+ public Range<Rational> getAspectRatioRange(boolean blocks) {
+ return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
+ }
+
+ private void initWithPlatformLimits() {
+ mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+
+ mWidthRange = SIZE_RANGE;
+ mHeightRange = SIZE_RANGE;
+ mFrameRateRange = FRAME_RATE_RANGE;
+
+ mHorizontalBlockRange = SIZE_RANGE;
+ mVerticalBlockRange = SIZE_RANGE;
+
+ // full positive ranges are supported as these get calculated
+ mBlockCountRange = POSITIVE_INTEGERS;
+ mBlocksPerSecondRange = POSITIVE_LONGS;
+
+ mBlockAspectRatioRange = POSITIVE_RATIONALS;
+ mAspectRatioRange = POSITIVE_RATIONALS;
+
+ // YUV 4:2:0 requires 2:2 alignment
+ mWidthAlignment = 2;
+ mHeightAlignment = 2;
+ mBlockWidth = 2;
+ mBlockHeight = 2;
+ mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
+ }
+
+ 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);
+ }
+ }
+
+ 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;
+ }
+ }
+
+ 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);
+ }
+ if (frameRates != null) {
+ mFrameRateRange = mFrameRateRange.intersect(frameRates);
+ }
+ }
+ updateLimits();
+ }
+
+ 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;
+ }
+
+ 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);
+ }
+
+ mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
+ mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
+
+ mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
+ mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
+ }
+
+ 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()));
+ }
+
+ 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;
+ }
+ 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);
+ }
+
+ 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);
+ }
+ 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;
+ }
+
+ /* 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);
+ }
+
+ 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;
+ }
+ 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 supported range of quality values.
+ *
+ * @hide
+ */
+ public 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 Range<Integer> getComplexityRange() {
+ return mComplexityRange;
+ }
+
+ /** 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;
+ }
+
+ /**
+ * Query whether a bitrate mode is supported.
+ */
+ public boolean isBitrateModeSupported(int mode) {
+ for (Feature feat: bitrates) {
+ if (mode == feat.mValue) {
+ return (mBitControl & (1 << mode)) != 0;
+ }
+ }
+ return false;
+ }
+
+ private Range<Integer> mQualityRange;
+ private Range<Integer> mComplexityRange;
+ private CodecCapabilities mParent;
+
+ /* no public constructor */
+ private EncoderCapabilities() { }
+
+ /** @hide */
+ public static EncoderCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ EncoderCapabilities caps = new EncoderCapabilities();
+ 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);
+ }
+
+ 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 int mBitControl;
+ private Integer mDefaultComplexity;
+ private Integer mDefaultQuality;
+ private String mQualityScale;
+
+ private void parseFromInfo(MediaFormat info) {
+ Map<String, Object> map = info.getMap();
+
+ 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);
+ }
+ }
+
+ try {
+ mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
+ } catch (NumberFormatException e) { }
+
+ try {
+ mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
+ } catch (NumberFormatException e) { }
+
+ mQualityScale = (String)map.get("quality-scale");
+ }
+
+ 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;
+ }
+
+ /** @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;
+ }
+ }
+ }
+
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ final String mime = mParent.getMimeType();
+
+ Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
+ if (mode != null && !isBitrateModeSupported(mode)) {
+ return false;
+ }
+
+ 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");
+ }
+ }
+
+ // 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");
+ }
+ }
+
+ Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+ 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 @@
/**
* Count the number of available (regular) codecs.
*
+ * @deprecated Use {@link #getCodecInfos} instead.
+ *
* @see #REGULAR_CODECS
*/
public static final int getCodecCount() {
@@ -49,6 +51,8 @@
* 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 @@
/**
* 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 @@
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 @@
* 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
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 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 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 @@
= "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 @@
}
private final Bundle mBundle;
- private Description mDescription;
+ private MediaDescription mDescription;
private MediaMetadata(Bundle bundle) {
mBundle = new Bundle(bundle);
@@ -406,11 +431,13 @@
*
* @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 @@
}
}
- 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 @@
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 @@
}
/**
+ * 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/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.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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
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 @@
});
}
- 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,6 +518,126 @@
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 { }
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Private constructor.
+ */
+ 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];
+ }
+ };
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns the description of the media.
+ */
+ public @NonNull MediaDescription getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Returns the media id for this item.
+ */
+ public @NonNull String getMediaId() {
+ return mDescription.getMediaId();
+ }
+ }
+
/**
* Callbacks for connection related events.
@@ -632,7 +670,7 @@
* Called when the list of children is loaded or updated.
*/
public void onChildrenLoaded(@NonNull Uri parentUri,
- @NonNull List<MediaBrowserItem> children) {
+ @NonNull List<MediaItem> children) {
}
/**
@@ -647,87 +685,6 @@
}
/**
- * Callbacks for icon loading.
- */
- public static abstract class IconCallback {
- /**
- * Called when the icon is loaded.
- */
- public void onIconLoaded(@NonNull Uri uri, @NonNull Bitmap bitmap) {
- }
-
- /**
- * Called when the Uri doesn’t exist or the bitmap cannot be loaded.
- */
- public void onError(@NonNull Uri uri) {
- }
- }
-
- private static class IconRequest {
- final int mSeq;
- final Uri mUri;
- final int mWidth;
- final int mHeight;
- final List<IconCallback> mCallbacks;
-
- /**
- * 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.
- */
- 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>();
- }
-
- /**
- * Adds a callback to the icon request.
- * If the callback already exists, it will not be added again.
- */
- 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);
- }
- }
-
- /**
- * Checks if the icon request has the same uri, width, and height as the given values.
- */
- public boolean isSameRequest(@Nullable Uri uri, int width, int height) {
- return Objects.equals(mUri, uri) && mWidth == width && mHeight == height;
- }
-
- @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();
- }
-
- /**
- * Gets an unmodifiable view of the list of callbacks associated with the request.
- */
- public List<IconCallback> getCallbacks() {
- return Collections.unmodifiableList(mCallbacks);
- }
- }
-
- /**
* ServiceConnection to the other app.
*/
private class MediaServiceConnection implements ServiceConnection {
@@ -809,7 +766,7 @@
}
return true;
}
- };
+ }
/**
* Callbacks from the service.
@@ -852,14 +809,6 @@
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 @@
// 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 @@
// 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 @@
*
* @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 @@
* @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 @@
/**
* 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 @@
* 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 @@
@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 @@
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..ae8ce4b 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -25,6 +25,7 @@
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.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 @@
* @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 @@
*
* @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 @@
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) {
@@ -673,6 +683,7 @@
*
* @param mediaButtonIntent an intent containing the KeyEvent as an
* extra
+ * @return True if the event was handled, false otherwise.
*/
public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
if (mSession != null
@@ -685,36 +696,43 @@
case KeyEvent.KEYCODE_MEDIA_PLAY:
if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
onPlay();
+ return true;
}
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
onPause();
+ return true;
}
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
onSkipToNext();
+ return true;
}
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
onSkipToPrevious();
+ return true;
}
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
if ((validActions & PlaybackState.ACTION_STOP) != 0) {
onStop();
+ return true;
}
break;
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
onFastForward();
+ return true;
}
break;
case KeyEvent.KEYCODE_MEDIA_REWIND:
if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
onRewind();
+ return true;
}
break;
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
@@ -727,8 +745,10 @@
| PlaybackState.ACTION_PAUSE)) != 0;
if (isPlaying && canPause) {
onPause();
+ return true;
} else if (!isPlaying && canPlay) {
onPlay();
+ return true;
}
break;
}
@@ -744,9 +764,10 @@
}
/**
- * 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 +780,7 @@
* 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 +845,6 @@
*/
public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
}
-
- private void setSession(MediaSession session) {
- mSession = session;
- }
}
/**
@@ -872,10 +889,10 @@
}
@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 +1007,59 @@
}
/**
- * 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 +1067,24 @@
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 +1103,7 @@
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 +1149,14 @@
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 @@
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 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 @@
}
/**
+ * 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 @@
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 @@
/**
* 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/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 0bda2b3..93b40fb 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -126,7 +126,7 @@
* code snippet:
* </p>
* <pre>
- * String rating = TvContentRating.createRating(
+ * TvContentRating rating = TvContentRating.createRating(
* "com.android.tv",
* "US_TV",
* "US_TV_PG",
@@ -136,7 +136,7 @@
* <table>
* <tr>
* <th>Constant Value</th>
- * <th>Comment</th>
+ * <th>Description</th>
* </tr>
* <tr>
* <td>com.android.tv</td>
@@ -148,7 +148,7 @@
* <table>
* <tr>
* <th>Constant Value</th>
- * <th>Comment</th>
+ * <th>Description</th>
* </tr>
* <tr>
* <td>AM_TV_RS</td>
@@ -346,7 +346,7 @@
* <tr>
* <th>Rating System</th>
* <th>Constant Value</th>
- * <th>Comment</th>
+ * <th>Description</th>
* </tr>
* <tr>
* <td valign="top" rowspan="6">AM_TV_RS</td>
@@ -1419,7 +1419,7 @@
* <tr>
* <th>Rating System</th>
* <th>Constant Value</th>
- * <th>Comment</th>
+ * <th>Description</th>
* </tr>
* <tr>
* <td valign="top" rowspan="6">NL_TV</td>
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 @@
* <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/TvView.java b/media/java/android/media/tv/TvView.java
index 017645a..f4c761e 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -964,6 +964,9 @@
@Override
public void onContentBlocked(Session session, TvContentRating rating) {
+ if (this != mSessionCallback) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "onContentBlocked()");
}
@@ -974,6 +977,9 @@
@Override
public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
+ if (this != mSessionCallback) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
+ right + ", bottom=" + bottom + ",)");
diff --git a/media/java/android/media/browse/IMediaBrowserService.aidl b/media/java/android/service/media/IMediaBrowserService.aidl
similarity index 76%
rename from media/java/android/media/browse/IMediaBrowserService.aidl
rename to 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 @@
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
similarity index 91%
rename from media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl
rename to 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 @@
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
similarity index 81%
rename from media/java/android/media/browse/MediaBrowserService.java
rename to 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.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.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 @@
}
});
}
-
- @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 @@
* @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 @@
* 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 @@
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 @@
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 @@
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 @@
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.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 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
return true;
else
return false;
- }
+ }
public static boolean pause(String filePath) throws Exception {
Log.v(TAG, "pause - " + filePath);
@@ -206,7 +206,7 @@
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 @@
mp.pause();
mp.release();
}
-
+
static MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener =
new MediaPlayer.OnVideoSizeChangedListener() {
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
@@ -258,7 +258,7 @@
//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 @@
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
-
+
return videoHeight;
}
@@ -321,12 +321,12 @@
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 @@
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 @@
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 @@
//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 @@
if (currentPosition < duration/2)
return false;
else
- return true;
+ return true;
}
public static boolean mediaRecorderRecord(String filePath){
@@ -499,7 +499,7 @@
mRecorder.release();
}catch (Exception e){
Log.v(TAG, e.toString());
- }
+ }
//Verify the recorded file
MediaPlayer mp = new MediaPlayer();
@@ -540,7 +540,7 @@
}
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 @@
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 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 @@
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 @@
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 @@
// 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 @@
}
}.start();
}
-
+
/*
* Terminates the message looper thread.
*/
@@ -693,7 +693,7 @@
mLooper.quit();
mMediaPlayer.release();
}
-
+
static MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
synchronized (prepareDone) {
@@ -707,14 +707,14 @@
}
}
};
-
+
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 @@
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 @@
}
};
- // 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 @@
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/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/DocumentsUI/res/drawable/item_activated_overlay.xml b/packages/DocumentsUI/res/color/item_root_icon.xml
similarity index 73%
copy from packages/DocumentsUI/res/drawable/item_activated_overlay.xml
copy to packages/DocumentsUI/res/color/item_root_icon.xml
index 83e4d7e..1374e61 100644
--- a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml
+++ b/packages/DocumentsUI/res/color/item_root_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,11 +15,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
- <item android:state_focused="false" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
- <item android:drawable="@android:color/transparent" />
+ <item android:state_focused="true" android:state_activated="true" android:color="@*android:color/primary_text_default_material_dark" />
+ <item android:state_focused="false" android:state_activated="true" android:color="@*android:color/primary_text_default_material_dark" />
+ <item android:color="@*android:color/secondary_text_material_light" />
</selector>
diff --git a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml b/packages/DocumentsUI/res/color/item_root_primary_text.xml
similarity index 63%
copy from packages/DocumentsUI/res/drawable/item_activated_overlay.xml
copy to packages/DocumentsUI/res/color/item_root_primary_text.xml
index 83e4d7e..7e703fa 100644
--- a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml
+++ b/packages/DocumentsUI/res/color/item_root_primary_text.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,11 +15,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
- <item android:state_focused="false" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
- <item android:drawable="@android:color/transparent" />
+ <item android:state_focused="true" android:state_activated="true" android:color="@*android:color/primary_text_default_material_dark" />
+ <item android:state_focused="false" android:state_activated="true" android:color="@*android:color/primary_text_default_material_dark" />
+ <item android:state_enabled="false" android:alpha="@*android:dimen/disabled_alpha_material_light" android:color="@*android:color/primary_text_default_material_light" />
+ <item android:color="@*android:color/primary_text_default_material_light" />
</selector>
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_selection_check.png
new file mode 100644
index 0000000..f3007c2
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_grid_selection_check.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_selection_check.png
new file mode 100644
index 0000000..16f2ab9
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-mdpi/ic_grid_selection_check.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_selection_check.png
new file mode 100644
index 0000000..0885320
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_grid_selection_check.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_selection_check.png
new file mode 100644
index 0000000..083bbcc
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_grid_selection_check.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_grid_selection_check.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_grid_selection_check.png
new file mode 100644
index 0000000..74b1ca5
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_grid_selection_check.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml b/packages/DocumentsUI/res/drawable/item_doc_grid_overlay.xml
similarity index 79%
copy from packages/DocumentsUI/res/drawable/item_activated_overlay.xml
copy to packages/DocumentsUI/res/drawable/item_doc_grid_overlay.xml
index 83e4d7e..3fbd25e 100644
--- a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml
+++ b/packages/DocumentsUI/res/drawable/item_doc_grid_overlay.xml
@@ -15,11 +15,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
- <item android:state_focused="false" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
+ <item android:state_focused="true" android:state_activated="true" android:drawable="@color/item_doc_grid_overlay_activated" />
+ <item android:state_focused="false" android:state_activated="true" android:drawable="@color/item_doc_grid_overlay_activated" />
+ <item android:state_enabled="false" android:drawable="@color/item_doc_grid_overlay_disabled" />
<item android:drawable="@android:color/transparent" />
</selector>
diff --git a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml b/packages/DocumentsUI/res/drawable/item_doc_grid_overlay_icon.xml
similarity index 79%
copy from packages/DocumentsUI/res/drawable/item_activated_overlay.xml
copy to packages/DocumentsUI/res/drawable/item_doc_grid_overlay_icon.xml
index 83e4d7e..d40de1e 100644
--- a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml
+++ b/packages/DocumentsUI/res/drawable/item_doc_grid_overlay_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,12 +14,8 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
- <item android:state_focused="false" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
+<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true">
+ <item android:state_focused="true" android:state_activated="true" android:drawable="@drawable/ic_grid_selection_check" />
+ <item android:state_focused="false" android:state_activated="true" android:drawable="@drawable/ic_grid_selection_check" />
<item android:drawable="@android:color/transparent" />
</selector>
diff --git a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml b/packages/DocumentsUI/res/drawable/item_doc_list_background.xml
similarity index 84%
rename from packages/DocumentsUI/res/drawable/item_activated_overlay.xml
rename to packages/DocumentsUI/res/drawable/item_doc_list_background.xml
index 83e4d7e..b879542 100644
--- a/packages/DocumentsUI/res/drawable/item_activated_overlay.xml
+++ b/packages/DocumentsUI/res/drawable/item_doc_list_background.xml
@@ -15,11 +15,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
- <item android:state_focused="false" android:state_activated="true">
- <color android:color="@color/accent_color_overlay" />
- </item>
+ <item android:state_focused="true" android:state_activated="true" android:drawable="@color/item_doc_list_background_activated" />
+ <item android:state_focused="false" android:state_activated="true" android:drawable="@color/item_doc_list_background_activated" />
<item android:drawable="@android:color/transparent" />
</selector>
diff --git a/packages/DocumentsUI/res/drawable/item_activated.xml b/packages/DocumentsUI/res/drawable/item_root_background.xml
similarity index 100%
rename from packages/DocumentsUI/res/drawable/item_activated.xml
rename to packages/DocumentsUI/res/drawable/item_root_background.xml
diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
index 95af7e9..d124320 100644
--- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
@@ -17,7 +17,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/item_activated">
+ android:background="@drawable/item_doc_list_background">
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
index bdb3184..d62d050 100644
--- a/packages/DocumentsUI/res/layout/item_doc_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -17,9 +17,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/grid_item_height"
- android:orientation="vertical"
- android:background="@color/grid_item_background"
- android:foreground="@drawable/item_activated_overlay">
+ android:background="@color/item_doc_grid_background">
<ImageView
android:id="@+id/icon_thumb"
@@ -44,7 +42,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/grid_protect_background"
+ android:background="@color/item_doc_grid_protect_background"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="12dp"
@@ -68,7 +66,7 @@
android:ellipsize="middle"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Subhead"
- android:textColor="?android:attr/textColorPrimaryInverse" />
+ android:textColor="@*android:color/primary_text_default_material_dark" />
<ImageView
android:id="@android:id/icon1"
@@ -97,7 +95,7 @@
android:ellipsize="end"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Caption"
- android:textColor="?android:attr/textColorPrimaryInverse" />
+ android:textColor="@*android:color/primary_text_default_material_dark" />
<TextView
android:id="@+id/size"
@@ -109,7 +107,7 @@
android:ellipsize="end"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Caption"
- android:textColor="?android:attr/textColorPrimaryInverse" />
+ android:textColor="@*android:color/primary_text_default_material_dark" />
<ImageView
android:id="@android:id/icon2"
@@ -126,4 +124,19 @@
</LinearLayout>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@drawable/item_doc_grid_overlay"
+ android:contentDescription="@null"
+ android:duplicateParentState="true" />
+
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@drawable/item_doc_grid_overlay_icon"
+ android:scaleType="center"
+ android:contentDescription="@null"
+ android:duplicateParentState="true" />
+
</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index c5f1842..c576669 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -17,7 +17,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/item_activated">
+ android:background="@drawable/item_doc_list_background">
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
index 266b9b0..bd83923 100644
--- a/packages/DocumentsUI/res/layout/item_root.xml
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -23,19 +23,21 @@
android:gravity="center_vertical"
android:orientation="horizontal"
android:baselineAligned="false"
- android:background="@drawable/item_activated">
+ android:background="@drawable/item_root_background">
<FrameLayout
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
- android:layout_marginEnd="16dp">
+ android:layout_marginEnd="16dp"
+ android:duplicateParentState="true">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/root_icon_size"
android:layout_height="match_parent"
android:scaleType="centerInside"
- android:contentDescription="@null" />
+ android:contentDescription="@null"
+ android:duplicateParentState="true" />
</FrameLayout>
@@ -54,7 +56,7 @@
android:ellipsize="end"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Body1"
- android:textColor="?android:attr/textColorPrimary" />
+ android:textColor="@color/item_root_primary_text" />
<TextView
android:id="@android:id/summary"
@@ -64,7 +66,7 @@
android:ellipsize="end"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Body1"
- android:textColor="?android:attr/textColorSecondary" />
+ android:textColor="@color/item_root_primary_text" />
</LinearLayout>
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index 7442b09..2ceb968 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -18,8 +18,16 @@
<color name="material_grey_50">#fffafafa</color>
<color name="material_grey_300">#ffeeeeee</color>
- <!-- Half-alpha of material_teal_500 -->
- <color name="accent_color_overlay">#8800bcd4</color>
+ <color name="item_doc_grid_background">@color/material_grey_300</color>
- <color name="grid_item_background">@color/material_grey_300</color>
+ <color name="item_doc_grid_protect_background">#88000000</color>
+
+ <color name="item_doc_grid_overlay_activated">#88000000</color>
+ <color name="item_doc_grid_overlay_disabled">#88ffffff</color>
+
+ <color name="item_doc_list_overlay_disabled">#88ffffff</color>
+
+ <!-- 10% alpha of material_deep_teal_500 -->
+ <color name="item_doc_list_background_activated">#1a009688</color>
+
</resources>
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index 5cfe046..7693da3 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -28,6 +28,7 @@
<item name="android:colorAccent">@*android:color/material_deep_teal_500</item>
<item name="android:windowActionBar">false</item>
+ <item name="android:windowActionModeOverlay">true</item>
<item name="android:windowNoTitle">true</item>
<item name="*android:windowFixedWidthMajor">@null</item>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index caa07ab..39c2252 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -807,6 +807,9 @@
|| MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, docMimeType);
final boolean showThumbnail = supportsThumbnail && allowThumbnail && !mSvelteRecents;
+ final boolean enabled = isDocumentEnabled(docMimeType, docFlags);
+ final float iconAlpha = (state.derivedMode == MODE_LIST && !enabled) ? 0.5f : 1f;
+
boolean cacheHit = false;
if (showThumbnail) {
final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
@@ -817,7 +820,7 @@
} else {
iconThumb.setImageDrawable(null);
final ThumbnailAsyncTask task = new ThumbnailAsyncTask(
- uri, iconMime, iconThumb, mThumbSize);
+ uri, iconMime, iconThumb, mThumbSize, iconAlpha);
iconThumb.setTag(task);
ProviderExecutor.forAuthority(docAuthority).execute(task);
}
@@ -886,7 +889,7 @@
// hint to remind user they're a directory.
if (Document.MIME_TYPE_DIR.equals(docMimeType) && state.derivedMode == MODE_GRID
&& showThumbnail) {
- iconDrawable = IconUtils.applyTint(context, R.drawable.ic_doc_folder,
+ iconDrawable = IconUtils.applyTintAttr(context, R.drawable.ic_doc_folder,
android.R.attr.textColorPrimaryInverse);
}
@@ -940,20 +943,12 @@
line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE);
}
- final boolean enabled = isDocumentEnabled(docMimeType, docFlags);
- if (enabled) {
- setEnabledRecursive(convertView, true);
- iconMime.setAlpha(1f);
- iconThumb.setAlpha(1f);
- if (icon1 != null) icon1.setAlpha(1f);
- if (icon2 != null) icon2.setAlpha(1f);
- } else {
- setEnabledRecursive(convertView, false);
- iconMime.setAlpha(0.5f);
- iconThumb.setAlpha(0.5f);
- if (icon1 != null) icon1.setAlpha(0.5f);
- if (icon2 != null) icon2.setAlpha(0.5f);
- }
+ setEnabledRecursive(convertView, enabled);
+
+ iconMime.setAlpha(iconAlpha);
+ iconThumb.setAlpha(iconAlpha);
+ if (icon1 != null) icon1.setAlpha(iconAlpha);
+ if (icon2 != null) icon2.setAlpha(iconAlpha);
return convertView;
}
@@ -1000,14 +995,16 @@
private final ImageView mIconMime;
private final ImageView mIconThumb;
private final Point mThumbSize;
+ private final float mTargetAlpha;
private final CancellationSignal mSignal;
- public ThumbnailAsyncTask(
- Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize) {
+ public ThumbnailAsyncTask(Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize,
+ float targetAlpha) {
mUri = uri;
mIconMime = iconMime;
mIconThumb = iconThumb;
mThumbSize = thumbSize;
+ mTargetAlpha = targetAlpha;
mSignal = new CancellationSignal();
}
@@ -1051,11 +1048,10 @@
mIconThumb.setTag(null);
mIconThumb.setImageBitmap(result);
- final float targetAlpha = mIconMime.isEnabled() ? 1f : 0.5f;
- mIconMime.setAlpha(targetAlpha);
+ mIconMime.setAlpha(mTargetAlpha);
mIconMime.animate().alpha(0f).start();
mIconThumb.setAlpha(0f);
- mIconThumb.animate().alpha(targetAlpha).start();
+ mIconThumb.animate().alpha(mTargetAlpha).start();
}
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java b/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java
index b2e38fc..416aeb0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java
@@ -266,15 +266,16 @@
}
}
- public static Drawable applyTint(Context context, int drawableId, int tintAttrId) {
- final Resources res = context.getResources();
-
- final TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(tintAttrId, outValue, true);
-
+ public static Drawable applyTintColor(Context context, int drawableId, int tintColorId) {
final Drawable icon = context.getDrawable(drawableId);
icon.mutate();
- icon.setTintList(res.getColorStateList(outValue.resourceId));
+ icon.setTintList(context.getResources().getColorStateList(tintColorId));
return icon;
}
+
+ public static Drawable applyTintAttr(Context context, int drawableId, int tintAttrId) {
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(tintAttrId, outValue, true);
+ return applyTintColor(context, drawableId, outValue.resourceId);
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index a465ecd..f81690a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -58,7 +58,7 @@
* Cache of known storage backends and their roots.
*/
public class RootsCache {
- private static final boolean LOGD = true;
+ private static final boolean LOGD = false;
public static final Uri sNotificationUri = Uri.parse(
"content://com.android.documentsui.roots/");
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index b19e028..884cf31 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -235,7 +235,7 @@
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
final Context context = convertView.getContext();
- icon.setImageDrawable(root.loadIcon(context));
+ icon.setImageDrawable(root.loadDrawerIcon(context));
title.setText(root.title);
// Show available space if no summary
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index fcfe518..97d8ed0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -211,9 +211,17 @@
}
}
+ public Drawable loadDrawerIcon(Context context) {
+ if (derivedIcon != 0) {
+ return IconUtils.applyTintColor(context, derivedIcon, R.color.item_root_icon);
+ } else {
+ return IconUtils.loadPackageIcon(context, authority, icon);
+ }
+ }
+
public Drawable loadGridIcon(Context context) {
if (derivedIcon != 0) {
- return IconUtils.applyTint(context, derivedIcon,
+ return IconUtils.applyTintAttr(context, derivedIcon,
android.R.attr.textColorPrimaryInverse);
} else {
return IconUtils.loadPackageIcon(context, authority, icon);
@@ -222,7 +230,7 @@
public Drawable loadToolbarIcon(Context context) {
if (derivedIcon != 0) {
- return IconUtils.applyTint(context, derivedIcon,
+ return IconUtils.applyTintAttr(context, derivedIcon,
android.R.attr.colorControlNormal);
} else {
return IconUtils.loadPackageIcon(context, authority, icon);
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..6cf9754b5 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 @@
+ " 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 @@
}
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 @@
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 @@
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 @@
// 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 @@
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 @@
mDestinationSpinner.setEnabled(false);
}
mCopiesEditText.setEnabled(false);
+ mCopiesEditText.setFocusable(false);
mMediaSizeSpinner.setEnabled(false);
mColorModeSpinner.setEnabled(false);
mOrientationSpinner.setEnabled(false);
@@ -1089,6 +1090,7 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
mMoreOptionsButton.setAlpha(inverseAlpha);
mEmbeddedContentScrim.setBackgroundColor(computeScrimColor());
-
if (progress == 0) {
if (mOptionsStateChangeListener != null) {
mOptionsStateChangeListener.onOptionsOpened();
@@ -354,14 +365,15 @@
}
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/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 299e50c..a3bed4f 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -187,6 +187,9 @@
<!-- Default for Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1==on -->
<integer name="def_lock_screen_show_notifications">1</integer>
+ <!-- Default for Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS -->
+ <bool name="def_lock_screen_allow_private_notifications">true</bool>
+
<!-- Default for Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, 1==on -->
<integer name="def_heads_up_enabled">1</integer>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index edefb13..fd5e6fe 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -70,7 +70,7 @@
// database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
// is properly propagated through your change. Not doing so will result in a loss of user
// settings.
- private static final int DATABASE_VERSION = 108;
+ private static final int DATABASE_VERSION = 109;
private Context mContext;
private int mUserHandle;
@@ -1673,8 +1673,8 @@
try {
stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)"
+ " VALUES(?,?);");
- loadBooleanSetting(stmt, Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- R.bool.def_guest_user_enabled);
+ loadIntegerSetting(stmt, Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ R.integer.def_lock_screen_show_notifications);
if (mUserHandle == UserHandle.USER_OWNER) {
final int oldShow = getIntValueFromTable(db,
TABLE_GLOBAL, Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, -1);
@@ -1733,6 +1733,22 @@
upgradeVersion = 108;
}
+ if (upgradeVersion < 109) {
+ db.beginTransaction();
+ SQLiteStatement stmt = null;
+ try {
+ stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)"
+ + " VALUES(?,?);");
+ loadBooleanSetting(stmt, Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ R.bool.def_lock_screen_allow_private_notifications);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ if (stmt != null) stmt.close();
+ }
+ upgradeVersion = 109;
+ }
+
// *** Remember to update DATABASE_VERSION above!
if (upgradeVersion != currentVersion) {
@@ -2301,6 +2317,9 @@
loadIntegerSetting(stmt, Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
R.integer.def_lock_screen_show_notifications);
+ loadBooleanSetting(stmt, Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ R.bool.def_lock_screen_allow_private_notifications);
+
} finally {
if (stmt != null) stmt.close();
}
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 @@
}
}
- 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 @@
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 @@
// 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 @@
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/drawable/ic_android.xml b/packages/SystemUI/res/drawable/ic_android.xml
new file mode 100644
index 0000000..19ee9a7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_android.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M6.000000,18.000000c0.000000,0.600000 0.400000,1.000000 1.000000,1.000000l1.000000,0.000000l0.000000,3.500000C8.000000,23.299999 8.700000,24.000000 9.500000,24.000000c0.800000,0.000000 1.500000,-0.700000 1.500000,-1.500000L11.000000,19.000000l2.000000,0.000000l0.000000,3.500000c0.000000,0.800000 0.700000,1.500000 1.500000,1.500000c0.800000,0.000000 1.500000,-0.700000 1.500000,-1.500000L16.000000,19.000000l1.000000,0.000000c0.600000,0.000000 1.000000,-0.400000 1.000000,-1.000000L18.000000,8.000000L6.000000,8.000000L6.000000,18.000000zM3.500000,8.000000C2.700000,8.000000 2.000000,8.700000 2.000000,9.500000l0.000000,7.000000C2.000000,17.299999 2.700000,18.000000 3.500000,18.000000C4.300000,18.000000 5.000000,17.299999 5.000000,16.500000l0.000000,-7.000000C5.000000,8.700000 4.300000,8.000000 3.500000,8.000000zM20.500000,8.000000C19.700001,8.000000 19.000000,8.700000 19.000000,9.500000l0.000000,7.000000c0.000000,0.800000 0.700000,1.500000 1.500000,1.500000c0.800000,0.000000 1.500000,-0.700000 1.500000,-1.500000l0.000000,-7.000000C22.000000,8.700000 21.299999,8.000000 20.500000,8.000000zM15.500000,2.200000l1.300000,-1.300000c0.200000,-0.200000 0.200000,-0.500000 0.000000,-0.700000c-0.200000,-0.200000 -0.500000,-0.200000 -0.700000,0.000000l-1.500000,1.500000C13.900000,1.200000 13.000000,1.000000 12.000000,1.000000c-1.000000,0.000000 -1.900000,0.200000 -2.700000,0.600000L7.900000,0.100000C7.700000,0.000000 7.300000,0.000000 7.100000,0.100000C7.000000,0.300000 7.000000,0.700000 7.100000,0.900000l1.300000,1.300000C7.000000,3.300000 6.000000,5.000000 6.000000,7.000000l12.000000,0.000000C18.000000,5.000000 17.000000,3.200000 15.500000,2.200000zM10.000000,5.000000L9.000000,5.000000L9.000000,4.000000l1.000000,0.000000L10.000000,5.000000zM15.000000,5.000000l-1.000000,0.000000L14.000000,4.000000l1.000000,0.000000L15.000000,5.000000z"
+ android:fillColor="#ffffffff"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_close.xml b/packages/SystemUI/res/drawable/ic_close.xml
new file mode 100644
index 0000000..7d93d45
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_close.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
+ android:fillColor="#FF000000"/>
+</vector>
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/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 726a54f..0e18979 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -200,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/strings.xml b/packages/SystemUI/res/values/strings.xml
index 189c052..b488c56 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] -->
@@ -323,6 +314,15 @@
<!-- Content description of an item with full signal for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_signal_full">Signal full.</string>
+ <!-- Content description of an item that is turned on for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_desc_on">On.</string>
+ <!-- Content description of an item that is turned off for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_desc_off">Off.</string>
+ <!-- Content description of an item that is connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_desc_connected">Connected.</string>
+ <!-- Content description of an item that is connecting for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_desc_connecting">Connecting.</string>
+
<!-- Content description of the data connection type GPRS for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_data_connection_gprs">GPRS</string>
@@ -697,7 +697,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>
@@ -894,6 +894,18 @@
<!-- 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>
+ <!-- Title of notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=40] -->
+ <string name="hidden_notifications_title">Get notifications faster</string>
+
+ <!-- Body of notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=60] -->
+ <string name="hidden_notifications_text">See them before you unlock</string>
+
+ <!-- Cancel action for notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=10] -->
+ <string name="hidden_notifications_cancel">No thanks</string>
+
+ <!-- continue action for notification educating the user about enabling notifications on the lockscreen. [CHAR LIMIT=10] -->
+ <string name="hidden_notifications_setup">Set up</string>
+
<!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
<string name="muted_by">Muted by <xliff:g id="third_party">%1$s</xliff:g></string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d236c7e..efd4fb4 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -234,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/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 @@
@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/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index d328660..ec39d77 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -72,6 +72,7 @@
static RecentsComponent.Callbacks sRecentsComponentCallbacks;
Context mContext;
+ LayoutInflater mInflater;
SystemServicesProxy mSystemServicesProxy;
Handler mHandler;
boolean mBootCompleted;
@@ -98,32 +99,20 @@
public AlternateRecentsComponent(Context context) {
RecentsTaskLoader.initialize(context);
- Resources res = context.getResources();
+ 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() {
- // Initialize some static datastructures
- TaskStackViewLayoutAlgorithm.initializeCurve();
- reloadHeaderBarLayout();
- }
+ public void onStart() {}
public void onBootCompleted() {
+ // Initialize some static datastructures
+ TaskStackViewLayoutAlgorithm.initializeCurve();
+ // Load the header bar layout
+ reloadHeaderBarLayout();
mBootCompleted = true;
}
@@ -235,9 +224,19 @@
}
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 @@
} 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 @@
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),
@@ -419,10 +411,6 @@
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();
@@ -444,6 +432,8 @@
}
// 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/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index a93e244..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 @@
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.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;
@@ -232,6 +233,8 @@
/* 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;
@@ -335,11 +338,15 @@
infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
taskKey.userId);
}
- icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
- mApplicationIconCache.put(taskKey, icon);
- return icon;
+ if (infoHandle.info != null) {
+ icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
+ if (icon != null) {
+ mApplicationIconCache.put(taskKey, icon);
+ return icon;
+ }
+ }
}
- // If we are not preloading, return the default icon to show
+ // If we couldn't load any icon, return null
return null;
}
@@ -361,8 +368,13 @@
infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
taskKey.userId);
}
- label = ssp.getActivityLabel(infoHandle.info);
- mActivityLabelCache.put(taskKey, label);
+ 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;
}
@@ -401,8 +413,8 @@
List<Task> tasksToLoadOut) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
- HashMap<ComponentName, ActivityInfoHandle> activityInfoCache =
- new HashMap<ComponentName, ActivityInfoHandle>();
+ HashMap<Task.ComponentNameKey, ActivityInfoHandle> activityInfoCache =
+ new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
ArrayList<Task> tasksToAdd = new ArrayList<Task>();
TaskStack stack = new TaskStack();
@@ -410,19 +422,21 @@
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = tasks.get(i);
- // Get an existing activity info handle if possible
- ComponentName cn = t.baseIntent.getComponent();
- ActivityInfoHandle infoHandle = new ActivityInfoHandle();
- boolean hasCachedActivityInfo = false;
- if (activityInfoCache.containsKey(cn)) {
- infoHandle = activityInfoCache.get(cn);
- hasCachedActivityInfo = true;
- }
-
// Compose the task key
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
t.firstActiveTime, t.lastActiveTime);
+ // 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();
+ }
+
// Determine whether to preload this task
boolean preloadTask = false;
if (preloadTaskId > 0) {
@@ -440,7 +454,7 @@
// Update the activity info cache
if (!hasCachedActivityInfo && infoHandle.info != null) {
- activityInfoCache.put(cn, infoHandle);
+ activityInfoCache.put(cnKey, infoHandle);
}
// Add the task to the stack
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 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 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 @@
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/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 1ac3bc3..0c6e7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -67,6 +67,7 @@
ArrayList<TaskStack> mStacks;
View mSearchBar;
RecentsViewCallbacks mCb;
+ boolean mAlreadyLaunchingTask;
public RecentsView(Context context) {
super(context);
@@ -120,6 +121,9 @@
}
addView(stackView);
}
+
+ // Reset the launched state
+ mAlreadyLaunchingTask = false;
}
/** Removes all the task stack views from this recents view. */
@@ -381,6 +385,11 @@
if (mCb != null) {
mCb.onTaskViewClicked();
}
+ // Skip if we are already launching tasks
+ if (mAlreadyLaunchingTask) {
+ return;
+ }
+ mAlreadyLaunchingTask = true;
// Upfront the processing of the thumbnail
TaskViewTransform transform = new TaskViewTransform();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 35ed3e5..f04c8c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -22,6 +22,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.app.admin.DevicePolicyManager;
@@ -35,7 +36,11 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
@@ -78,6 +83,7 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
import com.android.internal.util.NotificationColorUtil;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SearchPanelView;
@@ -126,6 +132,12 @@
public static final int EXPANDED_LEAVE_ALONE = -10000;
public static final int EXPANDED_FULL_OPEN = -10001;
+ private static final int HIDDEN_NOTIFICATION_ID = 10000;
+ private static final String BANNER_ACTION_CANCEL =
+ "com.android.systemui.statusbar.banner_action_cancel";
+ private static final String BANNER_ACTION_SETUP =
+ "com.android.systemui.statusbar.banner_action_setup";
+
protected CommandQueue mCommandQueue;
protected IStatusBarService mBarService;
protected H mHandler = createHandler();
@@ -308,6 +320,20 @@
mUsersAllowingPrivateNotifications.clear();
updateLockscreenNotificationSetting();
updateNotifications();
+ } else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
+ NotificationManager noMan = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ noMan.cancel(HIDDEN_NOTIFICATION_ID);
+
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
+ if (BANNER_ACTION_SETUP.equals(action)) {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+ mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ );
+ }
}
}
};
@@ -490,12 +516,61 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_ADDED);
+ filter.addAction(BANNER_ACTION_CANCEL);
+ filter.addAction(BANNER_ACTION_SETUP);
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, filter);
updateCurrentProfilesCache();
}
+ protected void notifyUserAboutHiddenNotifications() {
+ if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 1)) {
+ Log.d(TAG, "user hasn't seen notification about hidden notifications");
+ final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
+ if (!lockPatternUtils.isSecure()) {
+ Log.d(TAG, "insecure lockscreen, skipping notification");
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
+ return;
+ }
+ Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+ // disable lockscreen notifications until user acts on the banner.
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+
+ final String packageName = mContext.getPackageName();
+ PendingIntent cancelIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(BANNER_ACTION_CANCEL).setPackage(packageName),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent setupIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(BANNER_ACTION_SETUP).setPackage(packageName),
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ final Resources res = mContext.getResources();
+ final int colorRes = com.android.internal.R.color.system_notification_accent_color;
+ Notification.Builder note = new Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_android)
+ .setContentTitle(mContext.getString(R.string.hidden_notifications_title))
+ .setContentText(mContext.getString(R.string.hidden_notifications_text))
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setOngoing(true)
+ .setColor(res.getColor(colorRes))
+ .setContentIntent(setupIntent)
+ .addAction(R.drawable.ic_close,
+ mContext.getString(R.string.hidden_notifications_cancel),
+ cancelIntent)
+ .addAction(R.drawable.ic_settings,
+ mContext.getString(R.string.hidden_notifications_setup),
+ setupIntent);
+
+ NotificationManager noMan =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ noMan.notify(HIDDEN_NOTIFICATION_ID, note.build());
+ }
+ }
+
public void userSwitched(int newUserId) {
// should be overridden
}
@@ -582,9 +657,11 @@
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);
@@ -1232,7 +1309,7 @@
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,
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 @@
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 @@
@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 @@
(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/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 62552b2..f9da30f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -380,21 +380,10 @@
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 9fd3d9c..1a0d2d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -582,6 +582,8 @@
putComponent(PhoneStatusBar.class, this);
setControllerUsers();
+
+ notifyUserAboutHiddenNotifications();
}
// ================================================================================
@@ -3612,7 +3614,8 @@
}
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/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 @@
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 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 @@
mAlwaysShowCdmaRssi ? mPhoneSignalIconId : mWimaxIconId,
mDataTypeIconId,
mContentDescriptionWimax,
- mContentDescriptionDataType);
+ mContentDescriptionDataType,
+ mDataTypeIconId == ROAMING_ICON);
} else {
// normal mobile data
cluster.setMobileDataIndicators(
@@ -380,7 +382,8 @@
mShowPhoneRSSIForData ? mPhoneSignalIconId : mDataSignalIconId,
mDataTypeIconId,
mContentDescriptionPhoneSignal,
- mContentDescriptionDataType);
+ mContentDescriptionDataType,
+ mDataTypeIconId == ROAMING_ICON);
}
cluster.setIsAirplaneMode(mAirplaneMode, mAirplaneIconId);
}
@@ -777,11 +780,11 @@
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 @@
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 @@
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 @@
iconId,
mDemoDataTypeIconId,
"Demo",
- "Demo");
+ "Demo",
+ mDemoDataTypeIconId == ROAMING_ICON);
}
refreshViews();
}
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.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 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 @@
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 @@
private TextView mZenSubheadExpanded;
private View mMoreSettings;
private LinearLayout mZenConditions;
- private TextView mAlarmWarning;
private Callback mCallback;
private ZenModeController mController;
@@ -103,21 +98,21 @@
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 @@
});
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 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+ checkForAttachedZenChange();
+ mAttachedZen = -1;
mSessionZen = -1;
mSessionExitCondition = null;
setExpanded(false);
@@ -184,6 +180,17 @@
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 @@
updateWidgets();
}
- private Uri getExitConditionId() {
- return getConditionId(mExitCondition);
- }
-
private static Uri getConditionId(Condition condition) {
return condition != null ? condition.id : null;
}
@@ -301,9 +304,7 @@
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 @@
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 @@
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 @@
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 @@
}
}
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 @@
}
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 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 @@
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 @@
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 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 @@
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
updateMinuteIndex();
+ updateNoneSelected();
}
private SharedPreferences prefs() {
@@ -693,12 +672,21 @@
}
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 @@
private final Context mContext;
+ private final Runnable mOnAccessibilityEnabledCallback;
private final UserManager mUserManager;
private final TextToSpeech mTts;
private final Ringtone mTone;
@@ -97,8 +98,9 @@
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 @@
/* 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 @@
// 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 a13da609..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 @@
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 @@
}
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 @@
view = new View(mContext);
view.setBackgroundColor(color);
view.setTransitionName(transitionName);
+ view.setId(id);
addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, height,
Gravity.START | verticalGravity));
}
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 @@
// 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/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 @@
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 @@
// 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 @@
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 @@
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 @@
}
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 @@
}
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
// 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 @@
}
}
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 @@
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 @@
// 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 @@
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 @@
}
}
- 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 @@
}
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
// 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
}
+ 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/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 122786f..c8718e3e 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -2606,15 +2606,15 @@
return true;
case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
final int sequenceNumber = msg.arg1;
- final IInputMethodClient client = (IInputMethodClient)msg.obj;
+ final ClientState clientState = (ClientState)msg.obj;
try {
- client.setUserActionNotificationSequenceNumber(sequenceNumber);
+ clientState.client.setUserActionNotificationSequenceNumber(sequenceNumber);
} catch (RemoteException e) {
Slog.w(TAG, "Got RemoteException sending "
+ "setUserActionNotificationSequenceNumber("
+ sequenceNumber + ") notification to pid "
- + ((ClientState)msg.obj).pid + " uid "
- + ((ClientState)msg.obj).uid);
+ + clientState.pid + " uid "
+ + clientState.uid);
}
return true;
}
@@ -3036,7 +3036,7 @@
if (mCurClient != null && mCurClient.client != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
- mCurUserActionNotificationSequenceNumber, mCurClient.client));
+ mCurUserActionNotificationSequenceNumber, mCurClient));
}
// Set Subtype here
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 86ce961..92f5170 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -16,9 +16,12 @@
package com.android.server;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -31,6 +34,7 @@
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.storage.IMountService;
import android.os.ServiceManager;
@@ -41,11 +45,14 @@
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
+import android.security.KeyChain;
+import android.security.KeyChain.KeyChainConnection;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.ILockSettingsObserver;
import com.android.internal.widget.LockPatternUtils;
@@ -99,8 +106,30 @@
mLockPatternUtils = new LockPatternUtils(context);
mFirstCallToVold = true;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_ADDED);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
}
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Update keystore settings for profiles which use the same password as their parent
+ if (Intent.ACTION_USER_STARTED.equals(intent.getAction())) {
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+ final UserInfo parentInfo = um.getProfileParent(userHandle);
+ if (parentInfo != null) {
+ final KeyStore ks = KeyStore.getInstance();
+ final int profileUid = UserHandle.getUid(userHandle, Process.SYSTEM_UID);
+ final int parentUid = UserHandle.getUid(parentInfo.id, Process.SYSTEM_UID);
+ ks.syncUid(parentUid, profileUid);
+ }
+ }
+ }
+ };
+
public void systemReady() {
migrateOldData();
}
@@ -275,6 +304,17 @@
}
}
+ private int getUserParentOrSelfId(int userId) {
+ if (userId != 0) {
+ final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+ final UserInfo pi = um.getProfileParent(userId);
+ if (pi != null) {
+ return pi.id;
+ }
+ }
+ return userId;
+ }
+
private String getLockPatternFilename(int userId) {
String dataSystemDirectory =
android.os.Environment.getDataDirectory().getAbsolutePath() +
@@ -283,6 +323,7 @@
// Leave it in the same place for user 0
return dataSystemDirectory + LOCK_PATTERN_FILE;
} else {
+ userId = getUserParentOrSelfId(userId);
return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
.getAbsolutePath();
}
@@ -296,7 +337,8 @@
// Leave it in the same place for user 0
return dataSystemDirectory + LOCK_PASSWORD_FILE;
} else {
- return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
+ userId = getUserParentOrSelfId(userId);
+ return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
.getAbsolutePath();
}
}
@@ -315,16 +357,27 @@
return new File(getLockPatternFilename(userId)).length() > 0;
}
- private void maybeUpdateKeystore(String password, int userId) {
- if (userId == UserHandle.USER_OWNER) {
- final KeyStore keyStore = KeyStore.getInstance();
- // Conditionally reset the keystore if empty. If non-empty, we are just
- // switching key guard type
- if (TextUtils.isEmpty(password) && keyStore.isEmpty()) {
- keyStore.reset();
+ private void maybeUpdateKeystore(String password, int userHandle) {
+ final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+ final KeyStore ks = KeyStore.getInstance();
+
+ final List<UserInfo> profiles = um.getProfiles(userHandle);
+ boolean shouldReset = TextUtils.isEmpty(password);
+
+ // For historical reasons, don't wipe a non-empty keystore if we have a single user with a
+ // single profile.
+ if (userHandle == UserHandle.USER_OWNER && profiles.size() == 1) {
+ if (!ks.isEmpty()) {
+ shouldReset = false;
+ }
+ }
+
+ for (UserInfo pi : profiles) {
+ final int profileUid = UserHandle.getUid(pi.id, Process.SYSTEM_UID);
+ if (shouldReset) {
+ ks.resetUid(profileUid);
} else {
- // Update the keystore password
- keyStore.password(password);
+ ks.passwordUid(password, profileUid);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6ca536c..c2c86ff 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 @@
*/
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 @@
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);
@@ -8952,6 +8948,14 @@
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;
@@ -8959,6 +8963,8 @@
ProviderInfo cpi = null;
synchronized(this) {
+ long startTime = SystemClock.elapsedRealtime();
+
ProcessRecord r = null;
if (caller != null) {
r = getRecordForAppLocked(caller);
@@ -8972,6 +8978,8 @@
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
@@ -8996,10 +9004,12 @@
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
@@ -9015,6 +9025,8 @@
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);
@@ -9024,7 +9036,9 @@
// 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");
}
}
@@ -9037,7 +9051,9 @@
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
@@ -9052,7 +9068,9 @@
"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.
@@ -9069,9 +9087,11 @@
boolean singleton;
if (!providerRunning) {
try {
+ checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");
cpi = AppGlobals.getPackageManager().
resolveContentProvider(name,
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
+ checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");
} catch (RemoteException ex) {
}
if (cpi == null) {
@@ -9088,12 +9108,15 @@
userId = UserHandle.USER_OWNER;
}
cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
+ checkTime(startTime, "getContentProviderImpl: got app info for user");
String msg;
+ checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
!= null) {
throw new SecurityException(msg);
}
+ checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");
if (!mProcessesReady && !mDidUpdate && !mWaitingUpdate
&& !cpi.processName.equals("system")) {
@@ -9115,15 +9138,19 @@
}
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
+ checkTime(startTime, "getContentProviderImpl: before getProviderByClass");
cpr = mProviderMap.getProviderByClass(comp, userId);
+ checkTime(startTime, "getContentProviderImpl: after getProviderByClass");
final boolean firstClass = cpr == null;
if (firstClass) {
try {
+ checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");
ApplicationInfo ai =
AppGlobals.getPackageManager().
getApplicationInfo(
cpi.applicationInfo.packageName,
STOCK_PM_FLAGS, userId);
+ checkTime(startTime, "getContentProviderImpl: after getApplicationInfo");
if (ai == null) {
Slog.w(TAG, "No package info for content provider "
+ cpi.name);
@@ -9136,6 +9163,8 @@
}
}
+ checkTime(startTime, "getContentProviderImpl: now have ContentProviderRecord");
+
if (r != null && cpr.canRunHere(r)) {
// If this is a multiprocess provider, then just return its
// info and allow the caller to instantiate it. Only do
@@ -9169,8 +9198,10 @@
try {
// Content provider is now in use, its package can't be stopped.
try {
+ checkTime(startTime, "getContentProviderImpl: before set stopped state");
AppGlobals.getPackageManager().setPackageStoppedState(
cpr.appInfo.packageName, false, userId);
+ checkTime(startTime, "getContentProviderImpl: after set stopped state");
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
@@ -9178,22 +9209,26 @@
}
// Use existing process if already started
+ checkTime(startTime, "getContentProviderImpl: looking for process record");
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null) {
if (DEBUG_PROVIDER) {
Slog.d(TAG, "Installing in existing process " + proc);
}
+ checkTime(startTime, "getContentProviderImpl: scheduling install");
proc.pubProviders.put(cpi.name, cpr);
try {
proc.thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
} else {
+ checkTime(startTime, "getContentProviderImpl: before start process");
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0, "content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false, false, false);
+ checkTime(startTime, "getContentProviderImpl: after start process");
if (proc == null) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
@@ -9209,6 +9244,8 @@
}
}
+ checkTime(startTime, "getContentProviderImpl: updating data structures");
+
// Make sure the provider is published (the same provider class
// may be published under multiple names).
if (firstClass) {
@@ -9221,6 +9258,7 @@
conn.waiting = true;
}
}
+ checkTime(startTime, "getContentProviderImpl: done!");
}
// Wait for the provider to be published...
@@ -10680,6 +10718,14 @@
}
}
+ /** 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;
@@ -10961,6 +11007,7 @@
}
retrieveSettings();
+ loadResourcesOnSystemReady();
synchronized (this) {
readGrantedUriPermissionsLocked();
@@ -15208,6 +15255,10 @@
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 @@
private ActivityRecord mLastScreenshotActivity = null;
private Bitmap mLastScreenshotBitmap = null;
- int mThumbnailWidth = -1;
- int mThumbnailHeight = -1;
-
int mCurrentUser;
final int mStackId;
@@ -355,10 +352,6 @@
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 @@
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..6545134 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 @@
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 @@
// 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 @@
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 @@
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 @@
return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
targetStack = inTask.stack;
- targetStack.moveToFront();
- mWindowManager.moveTaskToTop(targetStack.topTask().taskId);
+ targetStack.moveTaskToFrontLocked(inTask, r, options);
+ mWindowManager.moveTaskToTop(inTask.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/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index ff22764..a37249d 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -166,8 +166,16 @@
}
if (full) {
if (hasExternalProcessHandles()) {
- pw.print(prefix); pw.print("externals=");
- pw.println(externalProcessTokenToHandle.size());
+ pw.print(prefix); pw.print("externals:");
+ if (externalProcessTokenToHandle != null) {
+ pw.print(" w/token=");
+ pw.print(externalProcessTokenToHandle.size());
+ }
+ if (externalProcessNoHandleCount > 0) {
+ pw.print(" notoken=");
+ pw.print(externalProcessNoHandleCount);
+ }
+ pw.println();
}
} else {
if (connections.size() > 0 || externalProcessNoHandleCount > 0) {
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.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 @@
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/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 38077eb..8c342dd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1009,7 +1009,7 @@
y[i] = normalizeAbsoluteBrightness(brightness[i]);
}
- Spline spline = Spline.createMonotoneCubicSpline(x, y);
+ Spline spline = Spline.createSpline(x, y);
if (DEBUG) {
Slog.d(TAG, "Auto-brightness spline: " + spline);
for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
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.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 libcore.util.EmptyArray;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -446,7 +449,7 @@
allocated.add(address);
}
}
- mIoThreadLogger.debug("DevicePollingResult:" + allocated);
+ mIoThreadLogger.debug("[P]:Allocated Address=" + allocated);
if (callback != null) {
runOnServiceThread(new Runnable() {
@Override
@@ -548,7 +551,7 @@
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 @@
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 @@
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 @@
*/
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.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 @@
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 @@
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.SystemProperties;
import android.util.Slog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
/**
@@ -219,4 +220,10 @@
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.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 @@
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 e7b920a..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.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 libcore.util.EmptyArray;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -1395,6 +1398,28 @@
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
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 @@
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 @@
// 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 @@
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 @@
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 @@
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/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 379ec94..c3bc306 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -819,6 +819,7 @@
};
void dumpInternal(PrintWriter pw) {
+ final long now = SystemClock.elapsedRealtime();
synchronized (mJobs) {
pw.print("Started users: ");
for (int i=0; i<mStartedUsers.size(); i++) {
@@ -833,15 +834,14 @@
job.dump(pw, " ");
}
} else {
- pw.println();
- pw.println("No jobs scheduled.");
+ pw.println(" None.");
}
for (int i=0; i<mControllers.size(); i++) {
pw.println();
mControllers.get(i).dumpControllerState(pw);
}
pw.println();
- pw.println("Pending");
+ pw.println("Pending:");
for (int i=0; i<mPendingJobs.size(); i++) {
pw.println(mPendingJobs.get(i).hashCode());
}
@@ -852,10 +852,14 @@
if (jsc.isAvailable()) {
continue;
} else {
- pw.println(jsc.getRunningJob().hashCode() + " for: " +
- (SystemClock.elapsedRealtime()
- - jsc.getExecutionStartTimeElapsed())/1000 + "s " +
- "timeout: " + jsc.getTimeoutElapsed());
+ final long timeout = jsc.getTimeoutElapsed();
+ pw.print("Running for: ");
+ pw.print((now - jsc.getExecutionStartTimeElapsed())/1000);
+ pw.print("s timeout=");
+ pw.print(timeout);
+ pw.print(" fromnow=");
+ pw.println(timeout-now);
+ jsc.getRunningJob().dump(pw, " ");
}
}
pw.println();
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 6f5d3c2..f562721 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -251,6 +251,7 @@
// Dumpsys infrastructure
public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
pw.println(this.toString());
}
}
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.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 @@
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 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 @@
}
@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 @@
}
@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..d0f4054 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;
@@ -50,6 +52,7 @@
import android.media.IRingtonePlayer;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
@@ -57,7 +60,6 @@
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 +129,7 @@
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 +181,7 @@
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 +810,7 @@
@Override
void onZenModeChanged() {
synchronized(mNotificationList) {
- updateListenerHintsLocked();
+ updateInterruptionFilterLocked();
}
}
});
@@ -938,8 +942,7 @@
}
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 +957,13 @@
.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 +1293,23 @@
*/
@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 +1327,6 @@
} else {
mListenersDisablingEffects.remove(info);
}
- mZenModeHelper.requestFromListener(hints);
updateListenerHintsLocked();
updateEffectsSuppressorLocked();
}
@@ -1335,6 +1343,39 @@
}
@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();
@@ -1398,7 +1439,7 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump NotificationManager from from pid="
+ pw.println("Permission Denial: can't dump NotificationManager from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return;
@@ -1412,6 +1453,13 @@
enforceSystemOrSystemUI("INotificationManager.getEffectsSuppressor");
return mEffectsSuppressor;
}
+
+ @Override
+ public boolean matchesCallFilter(Bundle extras) {
+ enforceSystemOrSystemUI("INotificationManager.matchesCallFilter");
+ return mZenModeHelper.matchesCallFilter(extras,
+ mRankingHelper.findExtractor(ValidateNotificationPeople.class));
+ }
};
private String[] getActiveNotificationKeys(INotificationListener token) {
@@ -2058,12 +2106,26 @@
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 +2145,9 @@
case MESSAGE_LISTENER_HINTS_CHANGED:
handleListenerHintsChanged(msg.arg1);
break;
+ case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED:
+ handleListenerInterruptionFilterChanged(msg.arg1);
+ break;
}
}
@@ -2565,6 +2630,8 @@
public class NotificationListeners extends ManagedServices {
+ private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
+
public NotificationListeners() {
super(getContext(), mHandler, mNotificationList, mUserProfiles);
}
@@ -2605,6 +2672,20 @@
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 +2696,10 @@
* 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 +2721,20 @@
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 +2794,20 @@
}
}
+ 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 +2850,16 @@
}
}
+ 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/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 01188af..435177b 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -87,6 +87,17 @@
mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>();
}
+ public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
+ final int N = mSignalExtractors.length;
+ for (int i = 0; i < N; i++) {
+ final NotificationSignalExtractor extractor = mSignalExtractors[i];
+ if (extractorClass.equals(extractor.getClass())) {
+ return (T) extractor;
+ }
+ }
+ return null;
+ }
+
public void extractSignals(NotificationRecord r) {
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index bdc364c..aa47858 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -71,8 +71,17 @@
private LruCache<String, LookupResult> mPeopleCache;
private RankingReconsideration validatePeople(final NotificationRecord record) {
+ final String key = record.getKey();
+ final Bundle extras = record.getNotification().extras;
+ final float[] affinityOut = new float[1];
+ final RankingReconsideration rr = validatePeople(key, extras, affinityOut);
+ record.setContactAffinity(affinityOut[0]);
+ return rr;
+ }
+
+ private PeopleRankingReconsideration validatePeople(String key, Bundle extras,
+ float[] affinityOut) {
float affinity = NONE;
- Bundle extras = record.getNotification().extras;
if (extras == null) {
return null;
}
@@ -82,7 +91,7 @@
return null;
}
- if (INFO) Slog.i(TAG, "Validating: " + record.sbn.getKey());
+ if (INFO) Slog.i(TAG, "Validating: " + key);
final LinkedList<String> pendingLookups = new LinkedList<String>();
for (int personIdx = 0; personIdx < people.length && personIdx < MAX_PEOPLE; personIdx++) {
final String handle = people[personIdx];
@@ -102,51 +111,15 @@
}
// record the best available data, so far:
- record.setContactAffinity(affinity);
+ affinityOut[0] = affinity;
if (pendingLookups.isEmpty()) {
if (INFO) Slog.i(TAG, "final affinity: " + affinity);
return null;
}
- if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + record.sbn.getKey());
- return new RankingReconsideration(record.getKey()) {
- float mContactAffinity = NONE;
- @Override
- public void work() {
- if (INFO) Slog.i(TAG, "Executing: validation for: " + record.getKey());
- for (final String handle: pendingLookups) {
- LookupResult lookupResult = null;
- final Uri uri = Uri.parse(handle);
- if ("tel".equals(uri.getScheme())) {
- if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
- lookupResult = resolvePhoneContact(uri.getSchemeSpecificPart());
- } else if ("mailto".equals(uri.getScheme())) {
- if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
- lookupResult = resolveEmailContact(uri.getSchemeSpecificPart());
- } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
- if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
- lookupResult = searchContacts(uri);
- } else {
- lookupResult = new LookupResult(); // invalid person for the cache
- Slog.w(TAG, "unsupported URI " + handle);
- }
- if (lookupResult != null) {
- synchronized (mPeopleCache) {
- mPeopleCache.put(handle, lookupResult);
- }
- mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
- }
- }
- }
-
- @Override
- public void applyChangesLocked(NotificationRecord operand) {
- float affinityBound = operand.getContactAffinity();
- operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
- if (INFO) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
- }
- };
+ if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + key);
+ return new PeopleRankingReconsideration(key, pendingLookups);
}
// VisibleForTesting
@@ -269,6 +242,19 @@
// ignore: config has no relevant information yet.
}
+ public float getContactAffinity(Bundle extras) {
+ if (extras == null) return NONE;
+ final String key = Long.toString(System.nanoTime());
+ final float[] affinityOut = new float[1];
+ final PeopleRankingReconsideration prr = validatePeople(key, extras, affinityOut);
+ float affinity = affinityOut[0];
+ if (prr != null) {
+ prr.work();
+ affinity = Math.max(prr.getContactAffinity(), affinity);
+ }
+ return affinity;
+ }
+
private static class LookupResult {
private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr
public static final int INVALID_ID = -1;
@@ -328,5 +314,55 @@
return this;
}
}
+
+ private class PeopleRankingReconsideration extends RankingReconsideration {
+ private final LinkedList<String> mPendingLookups;
+
+ private float mContactAffinity = NONE;
+
+ private PeopleRankingReconsideration(String key, LinkedList<String> pendingLookups) {
+ super(key);
+ mPendingLookups = pendingLookups;
+ }
+
+ @Override
+ public void work() {
+ if (INFO) Slog.i(TAG, "Executing: validation for: " + mKey);
+ for (final String handle: mPendingLookups) {
+ LookupResult lookupResult = null;
+ final Uri uri = Uri.parse(handle);
+ if ("tel".equals(uri.getScheme())) {
+ if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
+ lookupResult = resolvePhoneContact(uri.getSchemeSpecificPart());
+ } else if ("mailto".equals(uri.getScheme())) {
+ if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
+ lookupResult = resolveEmailContact(uri.getSchemeSpecificPart());
+ } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+ if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
+ lookupResult = searchContacts(uri);
+ } else {
+ lookupResult = new LookupResult(); // invalid person for the cache
+ Slog.w(TAG, "unsupported URI " + handle);
+ }
+ if (lookupResult != null) {
+ synchronized (mPeopleCache) {
+ mPeopleCache.put(handle, lookupResult);
+ }
+ mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
+ }
+ }
+ }
+
+ @Override
+ public void applyChangesLocked(NotificationRecord operand) {
+ float affinityBound = operand.getContactAffinity();
+ operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
+ if (INFO) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
+ }
+
+ public float getContactAffinity() {
+ return mContactAffinity;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0b93690..fd35ede 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -34,6 +34,7 @@
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings.Global;
@@ -115,35 +116,35 @@
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");
}
@@ -189,7 +190,7 @@
}
private boolean shouldInterceptAudience(NotificationRecord record) {
- if (!audienceMatches(record)) {
+ if (!audienceMatches(record.getContactAffinity())) {
ZenLog.traceIntercepted(record, "!audienceMatches");
return true;
}
@@ -372,14 +373,27 @@
return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
}
- private boolean audienceMatches(NotificationRecord record) {
+ public boolean matchesCallFilter(Bundle extras, ValidateNotificationPeople validator) {
+ final int zen = mZenMode;
+ if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
+ if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ if (!mConfig.allowCalls) return false; // no calls get through
+ if (validator != null) {
+ final float contactAffinity = validator.getContactAffinity(extras);
+ return audienceMatches(contactAffinity);
+ }
+ }
+ return true;
+ }
+
+ private boolean audienceMatches(float contactAffinity) {
switch (mConfig.allowFrom) {
case ZenModeConfig.SOURCE_ANYONE:
return true;
case ZenModeConfig.SOURCE_CONTACT:
- return record.getContactAffinity() >= ValidateNotificationPeople.VALID_CONTACT;
+ return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
case ZenModeConfig.SOURCE_STAR:
- return record.getContactAffinity() >= ValidateNotificationPeople.STARRED_CONTACT;
+ return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
default:
Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
return true;
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 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 89878ef..5aa0294 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -104,6 +104,7 @@
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfo;
@@ -1497,29 +1498,6 @@
}
}
- 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
@@ -7721,8 +7699,21 @@
public void installPackage(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams,
String packageAbiOverride) {
+ installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams,
+ packageAbiOverride, UserHandle.getCallingUserId());
+ }
+
+ @Override
+ public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
+ int installFlags, String installerPackageName, VerificationParams verificationParams,
+ String packageAbiOverride, int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
null);
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "installPackage " + userId);
+ }
final File originFile = new File(originPath);
final int uid = Binder.getCallingUid();
@@ -7740,7 +7731,7 @@
if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
- user = new UserHandle(UserHandle.getUserId(uid));
+ user = new UserHandle(userId);
}
final int filteredInstallFlags;
@@ -12988,7 +12979,7 @@
}
/** Called by UserManagerService */
- void cleanUpUserLILPw(int userHandle) {
+ void cleanUpUserLILPw(UserManagerService userManager, int userHandle) {
mDirtyUsers.remove(userHandle);
mSettings.removeUserLPw(userHandle);
mPendingBroadcasts.remove(userHandle);
@@ -12999,6 +12990,50 @@
mInstaller.removeUserDataDirs(userHandle);
}
mUserNeedsBadging.delete(userHandle);
+ removeUnusedPackagesLILPw(userManager, userHandle);
+ }
+
+ /**
+ * We're removing userHandle and would like to remove any downloaded packages
+ * that are no longer in use by any other user.
+ * @param userHandle the user being removed
+ */
+ private void removeUnusedPackagesLILPw(UserManagerService userManager, final int userHandle) {
+ final boolean DEBUG_CLEAN_APKS = false;
+ int [] users = userManager.getUserIdsLPr();
+ Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
+ while (psit.hasNext()) {
+ PackageSetting ps = psit.next();
+ final String packageName = ps.pkg.packageName;
+ // Skip over if system app
+ if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ continue;
+ }
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, "Checking package " + packageName);
+ }
+ boolean keep = false;
+ for (int i = 0; i < users.length; i++) {
+ if (users[i] != userHandle && ps.getInstalled(users[i])) {
+ keep = true;
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, " Keeping package " + packageName + " for user "
+ + users[i]);
+ }
+ break;
+ }
+ }
+ if (!keep) {
+ if (DEBUG_CLEAN_APKS) {
+ Slog.i(TAG, " Removing package " + packageName);
+ }
+ mHandler.post(new Runnable() {
+ public void run() {
+ deletePackageX(packageName, userHandle, 0);
+ } //end run
+ });
+ }
+ }
}
/** Called by UserManagerService */
@@ -13098,7 +13133,7 @@
}
@Override
- public KeySetHandle getKeySetByAlias(String packageName, String alias) {
+ public KeySet getKeySetByAlias(String packageName, String alias) {
if (packageName == null || alias == null) {
return null;
}
@@ -13108,18 +13143,13 @@
Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- if (pkg.applicationInfo.uid != Binder.getCallingUid()
- && Process.SYSTEM_UID != Binder.getCallingUid()) {
- throw new SecurityException("May not access KeySets defined by"
- + " aliases in other applications.");
- }
KeySetManagerService ksms = mSettings.mKeySetManagerService;
- return ksms.getKeySetByAliasAndPackageNameLPr(packageName, alias);
+ return new KeySet(ksms.getKeySetByAliasAndPackageNameLPr(packageName, alias));
}
}
@Override
- public KeySetHandle getSigningKeySet(String packageName) {
+ public KeySet getSigningKeySet(String packageName) {
if (packageName == null) {
return null;
}
@@ -13134,12 +13164,12 @@
throw new SecurityException("May not access signing KeySet of other apps.");
}
KeySetManagerService ksms = mSettings.mKeySetManagerService;
- return ksms.getSigningKeySetByPackageNameLPr(packageName);
+ return new KeySet(ksms.getSigningKeySetByPackageNameLPr(packageName));
}
}
@Override
- public boolean isPackageSignedByKeySet(String packageName, IBinder ks) {
+ public boolean isPackageSignedByKeySet(String packageName, KeySet ks) {
if (packageName == null || ks == null) {
return false;
}
@@ -13149,16 +13179,17 @@
Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- if (ks instanceof KeySetHandle) {
+ IBinder ksh = ks.getToken();
+ if (ksh instanceof KeySetHandle) {
KeySetManagerService ksms = mSettings.mKeySetManagerService;
- return ksms.packageIsSignedByLPr(packageName, (KeySetHandle) ks);
+ return ksms.packageIsSignedByLPr(packageName, (KeySetHandle) ksh);
}
return false;
}
}
@Override
- public boolean isPackageSignedByKeySetExactly(String packageName, IBinder ks) {
+ public boolean isPackageSignedByKeySetExactly(String packageName, KeySet ks) {
if (packageName == null || ks == null) {
return false;
}
@@ -13168,9 +13199,10 @@
Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- if (ks instanceof KeySetHandle) {
+ IBinder ksh = ks.getToken();
+ if (ksh instanceof KeySetHandle) {
KeySetManagerService ksms = mSettings.mKeySetManagerService;
- return ksms.packageIsSignedByExactlyLPr(packageName, (KeySetHandle) ks);
+ return ksms.packageIsSignedByExactlyLPr(packageName, (KeySetHandle) ksh);
}
return false;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8ded7ca..2929939 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -293,6 +293,10 @@
private List<UserInfo> getProfilesLocked(int userId, boolean enabledOnly) {
UserInfo user = getUserInfoLocked(userId);
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+ if (user == null) {
+ // Probably a dying user
+ return users;
+ }
for (int i = 0; i < mUsers.size(); i++) {
UserInfo profile = mUsers.valueAt(i);
if (!isProfileOf(user, profile)) {
@@ -1280,7 +1284,7 @@
private void removeUserStateLocked(final int userHandle) {
// Cleanup package manager settings
- mPm.cleanUpUserLILPw(userHandle);
+ mPm.cleanUpUserLILPw(this, userHandle);
// Remove this user from the list
mUsers.remove(userHandle);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index a49d8ab..3380f71 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -119,7 +119,7 @@
mContext = context;
mContentResolver = context.getContentResolver();
- mWatchLogHandler = new WatchLogHandler(IoThread.get().getLooper());
+ mWatchLogHandler = new WatchLogHandler(mContentResolver, IoThread.get().getLooper());
mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
@@ -247,8 +247,9 @@
// let it add TvInputInfo objects to mInputList if there's any.
serviceState = new ServiceState(component, userId);
userState.serviceStateMap.put(component, serviceState);
+ updateServiceConnectionLocked(component, userId);
} else {
- inputList.addAll(serviceState.mInputList);
+ inputList.addAll(serviceState.inputList);
}
} else {
try {
@@ -258,9 +259,6 @@
continue;
}
}
-
- // Reconnect the service if existing input is updated.
- updateServiceConnectionLocked(component, userId);
userState.packageSet.add(si.packageName);
}
@@ -273,7 +271,7 @@
if (state == null) {
state = new TvInputState();
}
- state.mInfo = info;
+ state.info = info;
inputMap.put(info.getId(), state);
}
@@ -285,7 +283,7 @@
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);
@@ -352,9 +350,9 @@
}
// 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);
}
@@ -364,14 +362,14 @@
// 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();
@@ -412,7 +410,7 @@
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);
}
@@ -424,10 +422,10 @@
}
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;
}
@@ -439,8 +437,8 @@
}
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) {
@@ -449,18 +447,18 @@
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;
@@ -470,18 +468,15 @@
}
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);
}
}
@@ -491,19 +486,19 @@
// Let clients know the create session requests are failed.
UserState userState = getUserStateLocked(userId);
List<SessionState> sessionsToAbort = new ArrayList<>();
- for (IBinder sessionToken : serviceState.mSessionTokens) {
+ for (IBinder sessionToken : serviceState.sessionTokens) {
SessionState sessionState = userState.sessionStateMap.get(sessionToken);
- if (sessionState.mSession == null && (inputId == null
- || sessionState.mInfo.getId().equals(inputId))) {
+ if (sessionState.session == null && (inputId == null
+ || sessionState.info.getId().equals(inputId))) {
sessionsToAbort.add(sessionState);
}
}
for (SessionState sessionState : sessionsToAbort) {
- removeSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
- sendSessionTokenToClientLocked(sessionState.mClient,
- sessionState.mInfo.getId(), null, null, sessionState.mSeq);
+ removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
+ sendSessionTokenToClientLocked(sessionState.client,
+ sessionState.info.getId(), null, null, sessionState.seq);
}
- updateServiceConnectionLocked(serviceState.mComponent, userId);
+ updateServiceConnectionLocked(serviceState.component, userId);
}
private ClientState createClientStateLocked(IBinder clientToken, int userId) {
@@ -518,221 +513,26 @@
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();
}
@@ -748,17 +548,17 @@
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);
}
@@ -781,22 +581,22 @@
// 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());
+ 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();
@@ -807,13 +607,12 @@
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);
@@ -876,10 +675,10 @@
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;
@@ -900,7 +699,7 @@
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;
}
@@ -918,7 +717,7 @@
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);
@@ -964,8 +763,8 @@
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 {
@@ -1114,14 +913,14 @@
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;
}
@@ -1135,10 +934,10 @@
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);
@@ -1213,10 +1012,10 @@
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) {
@@ -1244,9 +1043,10 @@
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) {
@@ -1272,10 +1072,10 @@
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);
}
@@ -1309,7 +1109,7 @@
// 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;
@@ -1569,10 +1369,10 @@
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;
}
}
@@ -1601,8 +1401,8 @@
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;
}
}
@@ -1662,15 +1462,15 @@
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();
}
@@ -1685,17 +1485,17 @@
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();
}
@@ -1709,15 +1509,15 @@
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();
@@ -1736,19 +1536,6 @@
}
}
- 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>();
@@ -1789,104 +1576,117 @@
}
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);
}
}
}
@@ -1908,37 +1708,37 @@
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);
}
@@ -1948,7 +1748,7 @@
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);
}
@@ -1970,16 +1770,16 @@
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);
}
}
@@ -2012,7 +1812,7 @@
private void addTvInputLocked(TvInputInfo inputInfo) {
ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
- serviceState.mInputList.add(inputInfo);
+ serviceState.inputList.add(inputInfo);
buildTvInputListLocked(mUserId);
}
@@ -2042,7 +1842,7 @@
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();
@@ -2060,7 +1860,211 @@
}
}
- 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.
@@ -2072,8 +2076,11 @@
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
@@ -2159,7 +2166,7 @@
}
}
- final class HardwareListener implements TvInputHardwareManager.Listener {
+ private final class HardwareListener implements TvInputHardwareManager.Listener {
@Override
public void onStateChanged(String inputId, int state) {
synchronized (mLock) {
@@ -2173,9 +2180,9 @@
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);
}
@@ -2189,9 +2196,9 @@
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);
}
@@ -2205,9 +2212,9 @@
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);
}
@@ -2221,9 +2228,9 @@
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/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index ef74205..69c9144 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -114,6 +114,10 @@
transformation.clear();
transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
hasTransformation = true;
+
+ if (!mAppToken.appFullscreen) {
+ anim.setBackgroundColor(0);
+ }
}
public void setDummyAnimation() {
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 @@
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 ffadcf2..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 @@
if (!mHasFeature) {
return;
}
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
// Check for permissions
if (who == null) {
@@ -3753,7 +3754,7 @@
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 @@
@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 @@
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 @@
@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 @@
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 @@
@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 @@
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,26 +4662,23 @@
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) {
- if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
- audioManager.setMicrophoneMute(false);
- } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- audioManager.setMasterMute(false);
- }
- }
-
} finally {
restoreCallingIdentity(id);
}
+ if (!enabled && alreadyRestricted) {
+ try {
+ if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
+ iAudioService.setMicrophoneMute(false, who.getPackageName());
+ } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
+ iAudioService.setMasterMute(false, 0, who.getPackageName(), null);
+ }
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
+ }
+ }
}
}
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 @@
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 ecb0d4b..7c596c1 100644
--- a/telecomm/java/android/telecomm/Call.java
+++ b/telecomm/java/android/telecomm/Call.java
@@ -89,6 +89,7 @@
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 @@
}
/**
+ * @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 @@
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 @@
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 @@
int callerDisplayNamePresentation,
PhoneAccountHandle accountHandle,
int capabilities,
+ int properties,
int disconnectCauseCode,
String disconnectCauseMessage,
long connectTimeMillis,
@@ -260,6 +272,7 @@
mCallerDisplayNamePresentation = callerDisplayNamePresentation;
mAccountHandle = accountHandle;
mCallCapabilities = capabilities;
+ mCallProperties = properties;
mDisconnectCauseCode = disconnectCauseCode;
mDisconnectCauseMessage = disconnectCauseMessage;
mConnectTimeMillis = connectTimeMillis;
@@ -642,6 +655,7 @@
parcelableCall.getCallerDisplayNamePresentation(),
parcelableCall.getAccountHandle(),
parcelableCall.getCapabilities(),
+ parcelableCall.getProperties(),
parcelableCall.getDisconnectCauseCode(),
parcelableCall.getDisconnectCauseMsg(),
parcelableCall.getConnectTimeMillis(),
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/CameraCapabilities.java b/telecomm/java/android/telecomm/CameraCapabilities.java
index 7c7dde1..5f860db 100644
--- a/telecomm/java/android/telecomm/CameraCapabilities.java
+++ b/telecomm/java/android/telecomm/CameraCapabilities.java
@@ -21,6 +21,7 @@
/**
* Represents the camera capabilities important to a Video Telephony provider.
+ * @hide
*/
public final class CameraCapabilities implements Parcelable {
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index d8f487f..f349659 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -1012,11 +1012,20 @@
* a request to accept.
*
* @param videoState The video state in which to answer the call.
+ * @hide
*/
public void onAnswer(int videoState) {}
/**
* Notifies this Connection, which is in {@link #STATE_RINGING}, of
+ * a request to accept.
+ */
+ public void onAnswer() {
+ onAnswer(VideoProfile.VideoState.AUDIO_ONLY);
+ }
+
+ /**
+ * Notifies this Connection, which is in {@link #STATE_RINGING}, of
* a request to reject.
*/
public void onReject() {}
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index b991af1..39ae59a 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -40,7 +40,23 @@
* @param handlePresentation The {@link PropertyPresentation} which controls how the handle
* is shown.
* @param extras Application-specific extra data.
+ */
+ public ConnectionRequest(
+ PhoneAccountHandle accountHandle,
+ Uri handle,
+ int handlePresentation,
+ Bundle extras) {
+ this(accountHandle, handle, handlePresentation, extras, VideoProfile.VideoState.AUDIO_ONLY);
+ }
+
+ /**
+ * @param accountHandle The accountHandle which should be used to place the call.
+ * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect.
+ * @param handlePresentation The {@link PropertyPresentation} which controls how the handle
+ * is shown.
+ * @param extras Application-specific extra data.
* @param videoState Determines the video state for the connection.
+ * @hide
*/
public ConnectionRequest(
PhoneAccountHandle accountHandle,
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 4696815..19cf622 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -71,6 +71,7 @@
private static final int MSG_ON_POST_DIAL_CONTINUE = 14;
private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 15;
private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16;
+ private static final int MSG_ANSWER_VIDEO = 17;
private static Connection sNullConnection;
@@ -116,11 +117,17 @@
}
@Override
- public void answer(String callId, int videoState) {
+ /** @hide */
+ public void answerVideo(String callId, int videoState) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.argi1 = videoState;
- mHandler.obtainMessage(MSG_ANSWER, args).sendToTarget();
+ mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
+ }
+
+ @Override
+ public void answer(String callId) {
+ mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
}
@Override
@@ -234,12 +241,15 @@
case MSG_ABORT:
abort((String) msg.obj);
break;
- case MSG_ANSWER: {
+ case MSG_ANSWER:
+ answer((String) msg.obj);
+ break;
+ case MSG_ANSWER_VIDEO: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
int videoState = args.argi1;
- answer(callId, videoState);
+ answerVideo(callId, videoState);
} finally {
args.recycle();
}
@@ -545,11 +555,16 @@
findConnectionForAction(callId, "abort").onAbort();
}
- private void answer(String callId, int videoState) {
- Log.d(this, "answer %s", callId);
+ private void answerVideo(String callId, int videoState) {
+ Log.d(this, "answerVideo %s", callId);
findConnectionForAction(callId, "answer").onAnswer(videoState);
}
+ private void answer(String callId) {
+ Log.d(this, "answer %s", callId);
+ findConnectionForAction(callId, "answer").onAnswer();
+ }
+
private void reject(String callId) {
Log.d(this, "reject %s", callId);
findConnectionForAction(callId, "reject").onReject();
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 @@
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 @@
String disconnectCauseMsg,
List<String> cannedSmsResponses,
int capabilities,
+ int properties,
long connectTimeMillis,
Uri handle,
int handlePresentation,
@@ -83,6 +85,7 @@
mDisconnectCauseMsg = disconnectCauseMsg;
mCannedSmsResponses = cannedSmsResponses;
mCapabilities = capabilities;
+ mProperties = properties;
mConnectTimeMillis = connectTimeMillis;
mHandle = handle;
mHandlePresentation = handlePresentation;
@@ -137,6 +140,9 @@
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 @@
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 @@
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 @@
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/PhoneAccount.java b/telecomm/java/android/telecomm/PhoneAccount.java
index 411f48c..5b46409 100644
--- a/telecomm/java/android/telecomm/PhoneAccount.java
+++ b/telecomm/java/android/telecomm/PhoneAccount.java
@@ -73,6 +73,7 @@
* Flag indicating that this {@code PhoneAccount} is capable of placing video calls.
* <p>
* See {@link #getCapabilities}
+ * @hide
*/
public static final int CAPABILITY_VIDEO_CALLING = 0x8;
@@ -93,7 +94,7 @@
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..3d76608 100644
--- a/telecomm/java/android/telecomm/PhoneCapabilities.java
+++ b/telecomm/java/android/telecomm/PhoneCapabilities.java
@@ -19,7 +19,6 @@
/**
* 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. */
@@ -46,18 +45,32 @@
/** Call supports generic conference mode. */
public static final int GENERIC_CONFERENCE = 0x00000080;
- /** Local device supports video telephony. */
+ /**
+ * Local device supports video telephony.
+ * @hide
+ */
public static final int SUPPORTS_VT_LOCAL = 0x00000100;
- /** Remote device supports video telephony. */
+ /**
+ * Remote device supports video telephony.
+ * @hide
+ */
public static final int SUPPORTS_VT_REMOTE = 0x00000200;
+ /**
+ * Call is using voice over LTE.
+ * @hide
+ */
public static final int VoLTE = 0x00000400;
+ /**
+ * Call is using voice over WIFI.
+ * @hide
+ */
public static final int VoWIFI = 0x00000800;
public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL
- | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE | VoLTE | VoWIFI;
+ | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE;
public static String toString(int capabilities) {
StringBuilder builder = new StringBuilder();
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
index 8c86b15..b7e4b2a 100644
--- a/telecomm/java/android/telecomm/RemoteConnection.java
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -426,12 +426,25 @@
/**
* Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer.
+ */
+ public void answer() {
+ try {
+ if (mConnected) {
+ mConnectionService.answer(mConnectionId);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Instructs this {@link Connection#STATE_RINGING} {@code RemoteConnection} to answer.
* @param videoState The video state in which to answer the call.
+ * @hide
*/
public void answer(int videoState) {
try {
if (mConnected) {
- mConnectionService.answer(mConnectionId, videoState);
+ mConnectionService.answerVideo(mConnectionId, videoState);
}
} catch (RemoteException ignored) {
}
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index 868282f..2243288 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -285,6 +285,20 @@
}
/**
+ * Sets the default account for making outgoing phone calls.
+ * @hide
+ */
+ public void setDefaultOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
+ try {
+ if (isServiceConnected()) {
+ getTelecommService().setDefaultOutgoingPhoneAccount(accountHandle);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#setDefaultOutgoingPhoneAccount");
+ }
+ }
+
+ /**
* Return a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
* calls.
*
@@ -303,6 +317,55 @@
}
/**
+ * Returns the current SIM call manager. Apps must be prepared for this method to return
+ * {@code null}, indicating that there currently exists no user-chosen default
+ * {@code PhoneAccount}.
+ * @return The phone account handle of the current sim call manager.
+ * @hide
+ */
+ public PhoneAccountHandle getSimCallManager() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecommService().getSimCallManager();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#getSimCallManager");
+ }
+ return null;
+ }
+
+ /**
+ * Sets the SIM call manager to the specified phone account.
+ * @param accountHandle The phone account handle of the account to set as the sim call manager.
+ * @hide
+ */
+ public void setSimCallManager(PhoneAccountHandle accountHandle) {
+ try {
+ if (isServiceConnected()) {
+ getTelecommService().setSimCallManager(accountHandle);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#setSimCallManager");
+ }
+ }
+
+ /**
+ * Returns the list of registered SIM call managers.
+ * @return List of registered SIM call managers.
+ * @hide
+ */
+ public List<PhoneAccountHandle> getSimCallManagers() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecommService().getSimCallManagers();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#getSimCallManagers");
+ }
+ return new ArrayList<>();
+ }
+
+ /**
* Determine whether the device has more than one account registered and enabled.
*
* @return {@code true} if the device has more than one account registered and enabled and
@@ -614,4 +677,4 @@
}
return isConnected;
}
-}
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
index c412424..32b877d 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
@@ -43,7 +43,9 @@
void abort(String callId);
- void answer(String callId, int videoState);
+ void answerVideo(String callId, int videoState);
+
+ void answer(String callId);
void reject(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index 0ac5078..131307a 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -35,37 +35,57 @@
void showInCallScreen(boolean showDialpad);
/**
- * @see TelecommManager#getDefaultOutgoingPhoneAccount
+ * @see TelecommServiceImpl#getDefaultOutgoingPhoneAccount
*/
PhoneAccountHandle getDefaultOutgoingPhoneAccount();
/**
- * @see TelecommManager#getOutgoingPhoneAccounts
+ * @see TelecommServiceImpl#setDefaultOutgoingPhoneAccount
+ */
+ void setDefaultOutgoingPhoneAccount(in PhoneAccountHandle account);
+
+ /**
+ * @see TelecommServiceImpl#getOutgoingPhoneAccounts
*/
List<PhoneAccountHandle> getOutgoingPhoneAccounts();
/**
- * @see TelecommManager#getPhoneAccount
+ * @see TelecommServiceImpl#getPhoneAccount
*/
PhoneAccount getPhoneAccount(in PhoneAccountHandle account);
/**
- * @see TelecommManager#registerPhoneAccount
+ * @see TelecommServiceImpl#getSimCallManager
+ */
+ PhoneAccountHandle getSimCallManager();
+
+ /**
+ * @see TelecommServiceImpl#setSimCallManager
+ */
+ void setSimCallManager(in PhoneAccountHandle account);
+
+ /**
+ * @see TelecommServiceImpl#getSimCallManagers
+ */
+ List<PhoneAccountHandle> getSimCallManagers();
+
+ /**
+ * @see TelecommServiceImpl#registerPhoneAccount
*/
void registerPhoneAccount(in PhoneAccount metadata);
/**
- * @see TelecommManager#unregisterPhoneAccount
+ * @see TelecommServiceImpl#unregisterPhoneAccount
*/
void unregisterPhoneAccount(in PhoneAccountHandle account);
/**
- * @see TelecommManager#clearAccounts
+ * @see TelecommServiceImpl#clearAccounts
*/
void clearAccounts(String packageName);
/**
- * @see TelecommManager#getDefaultPhoneApp
+ * @see TelecommServiceImpl#getDefaultPhoneApp
*/
ComponentName getDefaultPhoneApp();
@@ -74,52 +94,52 @@
//
/**
- * @see TelecommManager#silenceRinger
+ * @see TelecommServiceImpl#silenceRinger
*/
void silenceRinger();
/**
- * @see TelecommManager#isInCall
+ * @see TelecommServiceImpl#isInCall
*/
boolean isInCall();
/**
- * @see TelecomManager#isRinging
+ * @see TelecommServiceImpl#isRinging
*/
boolean isRinging();
/**
- * @see TelecommManager#endCall
+ * @see TelecommServiceImpl#endCall
*/
boolean endCall();
/**
- * @see TelecommManager#acceptRingingCall
+ * @see TelecommServiceImpl#acceptRingingCall
*/
void acceptRingingCall();
/**
- * @see TelecommManager#cancelMissedCallsNotification
+ * @see TelecommServiceImpl#cancelMissedCallsNotification
*/
void cancelMissedCallsNotification();
/**
- * @see TelecommManager#handleMmi
+ * @see TelecommServiceImpl#handleMmi
*/
boolean handlePinMmi(String dialString);
/**
- * @see TelecomManager#isTtySupported
+ * @see TelecommServiceImpl#isTtySupported
*/
boolean isTtySupported();
/**
- * @see TelecomManager#getCurrentTtyMode
+ * @see TelecommServiceImpl#getCurrentTtyMode
*/
int getCurrentTtyMode();
/**
- * @see TelecommManager#addNewIncomingCall
+ * @see TelecommServiceImpl#addNewIncomingCall
*/
void addNewIncomingCall(in PhoneAccountHandle phoneAccount, in Bundle extras);
}
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 @@
/**
* 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 @@
/** @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 @@
* 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/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index c84f40e..cd7d178 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -617,21 +617,25 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
@Override
public KeySet getKeySetByAlias(String packageName, String alias) {
throw new UnsupportedOperationException();
}
+ /** @hide */
@Override
public KeySet getSigningKeySet(String packageName) {
throw new UnsupportedOperationException();
}
+ /** @hide */
@Override
public boolean isSignedBy(String packageName, KeySet ks) {
throw new UnsupportedOperationException();
}
+ /** @hide */
@Override
public boolean isSignedByExactly(String packageName, KeySet ks) {
throw new UnsupportedOperationException();
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 @@
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/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.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.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 @@
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 @@
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 @@
}
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 @@
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.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 @@
@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 @@
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.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 @@
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/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.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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
} 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 @@
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 @@
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 @@
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 @@
} 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 @@
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 @@
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 @@
}
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 @@
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 @@
}
}
} 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 @@
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 @@
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 @@
} 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 @@
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 @@
}
}
} 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 @@
}
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 @@
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 @@
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 @@
}
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 @@
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 @@
}
}
} 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 @@
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 @@
}
} 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 @@
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 @@
} 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 @@
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 @@
}
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 @@
}
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 @@
} 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 @@
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 @@
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 @@
} 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 @@
}
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..f24a023b 100644
--- a/tools/aapt/Main.h
+++ b/tools/aapt/Main.h
@@ -60,9 +60,6 @@
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 @@
}
}
+ 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 @@
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 @@
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 @@
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 @@
}
}
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..6bb28e1
--- /dev/null
+++ b/tools/apilint/apilint.py
@@ -0,0 +1,606 @@
+#!/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, collections
+
+
+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 filter_dupe(s):
+ return s.replace(" deprecated ", " ")
+
+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(filter_dupe(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.
+ All developer extendable methods should be named onFoo()."""
+ test_methods = False
+ if "extends android.app.Service" in clazz.raw:
+ test_methods = True
+ if not clazz.name.endswith("Service"):
+ error(clazz, None, "Inconsistent class name")
+ if "extends android.content.ContentProvider" in clazz.raw:
+ test_methods = True
+ if not clazz.name.endswith("Provider"):
+ error(clazz, None, "Inconsistent class name")
+ if "extends android.content.BroadcastReceiver" in clazz.raw:
+ test_methods = True
+ if not clazz.name.endswith("Receiver"):
+ error(clazz, None, "Inconsistent class name")
+ if "extends android.app.Activity" in clazz.raw:
+ test_methods = True
+ if not clazz.name.endswith("Activity"):
+ error(clazz, None, "Inconsistent class name")
+
+ if test_methods:
+ for m in clazz.methods:
+ if "final" in m.split: continue
+ if not re.match("on[A-Z]", m.name):
+ error(clazz, m, "Extendable methods should be onFoo() style, otherwise final")
+
+
+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 m.name.startswith("with"):
+ error(clazz, m, "Builder methods must be setFoo()")
+
+ if m.name.startswith("set"):
+ 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_boolean(clazz):
+ """Catches people returning boolean from getFoo() style methods.
+ Ignores when matching setFoo() is present."""
+ methods = [ m.name for m in clazz.methods ]
+ for m in clazz.methods:
+ if m.typ == "boolean" and m.name.startswith("get") and m.name != "get" and len(m.args) == 0:
+ setter = "set" + m.name[3:]
+ if setter not in methods:
+ error(clazz, m, "Methods returning boolean should be isFoo or hasFoo")
+
+
+def verify_collections(clazz):
+ """Verifies that collection types are interfaces."""
+ bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
+ "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
+ for m in clazz.methods:
+ filt = re.sub("<.+>", "", m.typ)
+ if filt in bad:
+ error(clazz, m, "Return type is concrete collection")
+ for arg in m.args:
+ filt = re.sub("<.+>", "", arg)
+ if filt in bad:
+ error(clazz, m, "Argument is concrete collection")
+
+
+def verify_flags(clazz):
+ """Verifies that flags are non-overlapping."""
+ known = collections.defaultdict(int)
+ for f in clazz.fields:
+ if "FLAG_" in f.name:
+ try:
+ val = int(f.value)
+ except:
+ continue
+
+ scope = f.name[0:f.name.index("FLAG_")]
+ if val & known[scope]:
+ warn(clazz, f, "Found overlapping flag")
+ known[scope] |= val
+
+
+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)
+ verify_boolean(clazz)
+ verify_collections(clazz)
+ verify_flags(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