Merge "Overhaul RenderNode's DisplayList management"
diff --git a/api/current.txt b/api/current.txt
index 76a2289..85747c1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20900,6 +20900,40 @@
method public default void onRoutingChanged(android.media.AudioRouting);
}
+ public final class BufferingParams implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getInitialBufferingMode();
+ method public int getInitialBufferingWatermarkKB();
+ method public int getInitialBufferingWatermarkMs();
+ method public int getRebufferingMode();
+ method public int getRebufferingWatermarkHighKB();
+ method public int getRebufferingWatermarkHighMs();
+ method public int getRebufferingWatermarkLowKB();
+ method public int getRebufferingWatermarkLowMs();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+ field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+ field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+ field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+ field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+ }
+
+ public static class BufferingParams.Builder {
+ ctor public BufferingParams.Builder();
+ ctor public BufferingParams.Builder(android.media.BufferingParams);
+ method public android.media.BufferingParams build();
+ method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingMode(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+ }
+
public class CamcorderProfile {
method public static android.media.CamcorderProfile get(int);
method public static android.media.CamcorderProfile get(int, int);
@@ -22072,7 +22106,9 @@
method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
method public void deselectTrack(int) throws java.lang.IllegalStateException;
method public int getAudioSessionId();
+ method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
+ method public android.media.BufferingParams getDefaultBufferingParams();
method public int getDuration();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22095,6 +22131,7 @@
method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public deprecated void setAudioStreamType(int);
method public void setAuxEffectSendLevel(float);
+ method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -33513,6 +33550,7 @@
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
@@ -33565,8 +33603,10 @@
field public static final java.lang.String AUTHORITY = "settings";
field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+ field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -46207,6 +46247,13 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
}
+ public final class AutoFillManager {
+ method public void updateAutoFillInput(android.view.View, int);
+ method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+ field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+ field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ }
+
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
method public static android.view.autofill.AutoFillType forList();
@@ -46266,7 +46313,7 @@
public static abstract class VirtualViewDelegate.Callback {
ctor public VirtualViewDelegate.Callback();
- method public void onFocusChanged(int, boolean);
+ method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
method public void onNodeRemoved(int...);
method public void onValueChanged(int);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index bda634f..6a37e82 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -22500,6 +22500,40 @@
method public default void onRoutingChanged(android.media.AudioRouting);
}
+ public final class BufferingParams implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getInitialBufferingMode();
+ method public int getInitialBufferingWatermarkKB();
+ method public int getInitialBufferingWatermarkMs();
+ method public int getRebufferingMode();
+ method public int getRebufferingWatermarkHighKB();
+ method public int getRebufferingWatermarkHighMs();
+ method public int getRebufferingWatermarkLowKB();
+ method public int getRebufferingWatermarkLowMs();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+ field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+ field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+ field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+ field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+ }
+
+ public static class BufferingParams.Builder {
+ ctor public BufferingParams.Builder();
+ ctor public BufferingParams.Builder(android.media.BufferingParams);
+ method public android.media.BufferingParams build();
+ method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingMode(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+ }
+
public class CamcorderProfile {
method public static android.media.CamcorderProfile get(int);
method public static android.media.CamcorderProfile get(int, int);
@@ -23672,7 +23706,9 @@
method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
method public void deselectTrack(int) throws java.lang.IllegalStateException;
method public int getAudioSessionId();
+ method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
+ method public android.media.BufferingParams getDefaultBufferingParams();
method public int getDuration();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -23695,6 +23731,7 @@
method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public deprecated void setAudioStreamType(int);
method public void setAuxEffectSendLevel(float);
+ method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -36504,6 +36541,7 @@
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_CONFIGURE_WIFI_SETTINGS = "android.settings.CONFIGURE_WIFI_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
@@ -36559,8 +36597,10 @@
field public static final java.lang.String AUTHORITY = "settings";
field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+ field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -49592,6 +49632,13 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
}
+ public final class AutoFillManager {
+ method public void updateAutoFillInput(android.view.View, int);
+ method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+ field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+ field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ }
+
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
method public static android.view.autofill.AutoFillType forList();
@@ -49651,7 +49698,7 @@
public static abstract class VirtualViewDelegate.Callback {
ctor public VirtualViewDelegate.Callback();
- method public void onFocusChanged(int, boolean);
+ method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
method public void onNodeRemoved(int...);
method public void onValueChanged(int);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index c6de2e4..d67c022 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -20990,6 +20990,40 @@
method public default void onRoutingChanged(android.media.AudioRouting);
}
+ public final class BufferingParams implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getInitialBufferingMode();
+ method public int getInitialBufferingWatermarkKB();
+ method public int getInitialBufferingWatermarkMs();
+ method public int getRebufferingMode();
+ method public int getRebufferingWatermarkHighKB();
+ method public int getRebufferingWatermarkHighMs();
+ method public int getRebufferingWatermarkLowKB();
+ method public int getRebufferingWatermarkLowMs();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+ field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+ field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+ field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+ field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+ }
+
+ public static class BufferingParams.Builder {
+ ctor public BufferingParams.Builder();
+ ctor public BufferingParams.Builder(android.media.BufferingParams);
+ method public android.media.BufferingParams build();
+ method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+ method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingMode(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+ method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+ }
+
public class CamcorderProfile {
method public static android.media.CamcorderProfile get(int);
method public static android.media.CamcorderProfile get(int, int);
@@ -22162,7 +22196,9 @@
method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
method public void deselectTrack(int) throws java.lang.IllegalStateException;
method public int getAudioSessionId();
+ method public android.media.BufferingParams getBufferingParams();
method public int getCurrentPosition();
+ method public android.media.BufferingParams getDefaultBufferingParams();
method public int getDuration();
method public android.media.PlaybackParams getPlaybackParams();
method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22185,6 +22221,7 @@
method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public deprecated void setAudioStreamType(int);
method public void setAuxEffectSendLevel(float);
+ method public void setBufferingParams(android.media.BufferingParams);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -33629,6 +33666,7 @@
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
@@ -33682,8 +33720,10 @@
field public static final java.lang.String AUTHORITY = "settings";
field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+ field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -46517,6 +46557,13 @@
field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
}
+ public final class AutoFillManager {
+ method public void updateAutoFillInput(android.view.View, int);
+ method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+ field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+ field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+ }
+
public final class AutoFillType implements android.os.Parcelable {
method public int describeContents();
method public static android.view.autofill.AutoFillType forList();
@@ -46576,7 +46623,7 @@
public static abstract class VirtualViewDelegate.Callback {
ctor public VirtualViewDelegate.Callback();
- method public void onFocusChanged(int, boolean);
+ method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
method public void onNodeRemoved(int...);
method public void onValueChanged(int);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3cb920a..3902bd6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -698,6 +698,13 @@
}
/**
+ * Returns true if the input stack is affected by drag resizing.
+ */
+ public static boolean isStackAffectedByDragResizing(int stackId) {
+ return isStaticStack(stackId) && stackId != PINNED_STACK_ID;
+ }
+
+ /**
* Returns true if the windows of tasks being moved to the target stack from the source
* stack should be replaced, meaning that window manager will keep the old window around
* until the new is ready.
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 848464c..7f0b6fb 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -58,11 +58,16 @@
void setTheme(String theme);
/**
- * Gets whith theme overlays to use within /vendor/overlay.
+ * Gets which theme overlays to use within /vendor/overlay.
*/
String getTheme();
/**
+ * Gets the themes available in /vendor/overlay.
+ */
+ String[] getAvailableThemes();
+
+ /**
* Tells if UI mode is locked or not.
*/
boolean isUiModeLocked();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 3270349..b4fe6b8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -46,6 +46,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.BidiFormatter;
import android.text.SpannableStringBuilder;
@@ -2427,6 +2428,9 @@
private static final int MAX_ACTION_BUTTONS = 3;
+ private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
+ SystemProperties.getBoolean("notifications.only_title", true);
+
private Context mContext;
private Notification mN;
private Bundle mUserExtras = new Bundle();
@@ -3760,7 +3764,7 @@
} else if (mActions.size() != 0) {
result = applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
- adaptNotificationHeaderForBigContentView(result);
+ makeHeaderExpanded(result);
return result;
}
@@ -3795,7 +3799,12 @@
}
}
- private void adaptNotificationHeaderForBigContentView(RemoteViews result) {
+ /**
+ * Adapt the Notification header if this view is used as an expanded view.
+ *
+ * @hide
+ */
+ public static void makeHeaderExpanded(RemoteViews result) {
if (result != null) {
result.setBoolean(R.id.notification_header, "setExpanded", true);
}
@@ -3855,7 +3864,57 @@
return publicView;
}
+ /**
+ * Construct a content view for the display when low - priority
+ *
+ * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
+ * a new subtext is created consisting of the content of the
+ * notification.
+ * @hide
+ */
+ public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
+ int color = mN.color;
+ mN.color = COLOR_DEFAULT;
+ CharSequence summary = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+ if (!useRegularSubtext || TextUtils.isEmpty(summary)) {
+ CharSequence newSummary = createSummaryText();
+ if (!TextUtils.isEmpty(newSummary)) {
+ mN.extras.putCharSequence(EXTRA_SUB_TEXT, newSummary);
+ }
+ }
+ RemoteViews header = makeNotificationHeader();
+ if (summary != null) {
+ mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary);
+ } else {
+ mN.extras.remove(EXTRA_SUB_TEXT);
+ }
+ mN.color = color;
+ return header;
+ }
+ private CharSequence createSummaryText() {
+ CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
+ if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
+ return titleText;
+ }
+ SpannableStringBuilder summary = new SpannableStringBuilder();
+ if (titleText == null) {
+ titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
+ }
+ BidiFormatter bidi = BidiFormatter.getInstance();
+ if (titleText != null) {
+ summary.append(bidi.unicodeWrap(titleText));
+ }
+ CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
+ if (titleText != null && contentText != null) {
+ summary.append(bidi.unicodeWrap(mContext.getText(
+ R.string.notification_header_divider_symbol_with_spaces)));
+ }
+ if (contentText != null) {
+ summary.append(bidi.unicodeWrap(contentText));
+ }
+ return summary;
+ }
private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
boolean oddAction, boolean ambient) {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 5d8909c..5a75a67 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -112,6 +112,7 @@
import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
+import android.service.autofill.IAutoFillManagerService;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
import android.telecom.TelecomManager;
@@ -126,6 +127,7 @@
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
+import android.view.autofill.AutoFillManager;
import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassificationManager;
import android.view.textservice.TextServicesManager;
@@ -804,6 +806,14 @@
IBinder b = ServiceManager.getServiceOrThrow(Context.FONT_SERVICE);
return new FontManager(IFontManager.Stub.asInterface(b));
}});
+ registerService(Context.AUTO_FILL_MANAGER_SERVICE, AutoFillManager.class,
+ new CachedServiceFetcher<AutoFillManager>() {
+ @Override
+ public AutoFillManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.AUTO_FILL_MANAGER_SERVICE);
+ IAutoFillManagerService service = IAutoFillManagerService.Stub.asInterface(b);
+ return new AutoFillManager(ctx, service);
+ }});
}
/**
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 2572a20..af41a7d 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -271,6 +271,21 @@
}
/**
+ * Gets the valid inputs to {@link #setTheme(String)}.
+ * @hide
+ */
+ public String[] getAvailableThemes() {
+ if (mService != null) {
+ try {
+ return mService.getAvailableThemes();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns the currently configured night mode.
* <p>
* May be one of:
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 7ed827c..3a87cb3 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -414,7 +414,11 @@
return (Boolean) value;
} catch (ClassCastException e) {
if (value instanceof CharSequence) {
- return Boolean.valueOf(value.toString());
+ // Note that we also check against 1 here because SQLite's internal representation
+ // for booleans is an integer with a value of 0 or 1. Without this check, boolean
+ // values obtained via DatabaseUtils#cursorRowToContentValues will always return
+ // false.
+ return Boolean.valueOf(value.toString()) || "1".equals(value);
} else if (value instanceof Number) {
return ((Number) value).intValue() != 0;
} else {
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 227066d..8cd3d7b 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -728,13 +728,10 @@
* @param values the {@link ContentValues} to put the row into.
*/
public static void cursorRowToContentValues(Cursor cursor, ContentValues values) {
- AbstractWindowedCursor awc =
- (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null;
-
String[] columns = cursor.getColumnNames();
int length = columns.length;
for (int i = 0; i < length; i++) {
- if (awc != null && awc.isBlob(i)) {
+ if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) {
values.put(columns[i], cursor.getBlob(i));
} else {
values.put(columns[i], cursor.getString(i));
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 2d5c4ce..6b7546f 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -358,7 +358,12 @@
throw new IllegalArgumentException("Exceeds maximum number of surfaces");
}
- if (!mConfiguredSize.equals(SurfaceUtils.getSurfaceSize(surface))) {
+ // TODO: b/34697112. This needs to be reverted once app fix is merged.
+ // Do not throw exception for below case:
+ // - OutputConfiguration(Size(0, 0), klass)
+ // - addSurface(surface)
+ if ((mConfiguredSize.getWidth() != 0 || mConfiguredSize.getHeight() != 0) &&
+ !mConfiguredSize.equals(SurfaceUtils.getSurfaceSize(surface))) {
throw new IllegalArgumentException("The size of added surface doesn't match");
}
if (mConfiguredDataspace != SurfaceUtils.getSurfaceDataspace(surface)) {
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 83d17ba..bd32314 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -805,7 +805,7 @@
if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
throw new FormatException("expected MB flag");
- } else if (mb && records.size() != 0 && !ignoreMbMe) {
+ } else if (mb && (records.size() != 0 || inChunk) && !ignoreMbMe) {
throw new FormatException("unexpected MB flag");
} else if (inChunk && il) {
throw new FormatException("unexpected IL flag in non-leading chunk");
@@ -839,6 +839,9 @@
if (cf && !inChunk) {
// first chunk
+ if (typeLength == 0) {
+ throw new FormatException("expected non-zero type length in first chunk");
+ }
chunks.clear();
chunkTnf = tnf;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9e2d4a7..fd8b2ca 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -30,6 +30,7 @@
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.Application;
+import android.app.NotificationChannel;
import android.app.SearchManager;
import android.app.WallpaperManager;
import android.content.ComponentName;
@@ -58,6 +59,7 @@
import android.os.LocaleList;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.speech.tts.TextToSpeech;
@@ -162,13 +164,13 @@
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
- * Input: {@link ConnectivityManager.EXTRA_TETHER_TYPE} should be included to specify which type
- * of tethering should be checked. {@link ConnectivityManager.EXTRA_PROVISION_CALLBACK} should
+ * Input: {@link ConnectivityManager#EXTRA_TETHER_TYPE} should be included to specify which type
+ * of tethering should be checked. {@link ConnectivityManager#EXTRA_PROVISION_CALLBACK} should
* contain a {@link ResultReceiver} which will be called back with a tether result code.
* <p>
* Output: The result of the provisioning check.
- * {@link ConnectivityManager.TETHER_ERROR_NO_ERROR} if successful,
- * {@link ConnectivityManager.TETHER_ERROR_PROVISION_FAILED} for failure.
+ * {@link ConnectivityManager#TETHER_ERROR_NO_ERROR} if successful,
+ * {@link ConnectivityManager#TETHER_ERROR_PROVISION_FAILED} for failure.
*
* @hide
*/
@@ -1274,6 +1276,7 @@
/**
* Activity Action: Show notification settings for a single app.
*
+ * Input: Optionally, {@link #EXTRA_CHANNEL_ID}, to highlight that channel.
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -1281,6 +1284,35 @@
= "android.settings.APP_NOTIFICATION_SETTINGS";
/**
+ * Activity Action: Show notification settings for a single {@link NotificationChannel}.
+ * <p>
+ * Must be called from an activity.
+ * <p>
+ * Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel to display.
+ * Input: {@link #EXTRA_CHANNEL_ID}, the id of the channel to display.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANNEL_NOTIFICATION_SETTINGS
+ = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
+
+ /**
+ * Activity Extra: The package owner of the notification channel settings to display.
+ * <p>
+ * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
+ */
+ public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
+
+ /**
+ * Activity Extra: The {@link NotificationChannel#getId()} of the notification channel settings
+ * to display.
+ * <p>
+ * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
+ */
+ public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
+
+ /**
* Activity Action: Show notification redaction settings.
*
* @hide
@@ -1290,7 +1322,6 @@
= "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";
/**
* Activity Action: Show a dialog with disabled by policy message.
@@ -8755,6 +8786,26 @@
BLUETOOTH_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_";
/**
+ * Activity manager specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "enforce_bg_check=true,max_cached_processes=24"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * enforce_bg_check (boolean)
+ * max_cached_processes (int)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * @hide
+ * @see com.android.server.am.ActivityManagerConstants
+ */
+ public static final String ACTIVITY_MANAGER_CONSTANTS = "activity_manager_constants";
+
+ /**
* Device Idle (Doze) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -8820,6 +8871,25 @@
public static final String APP_IDLE_CONSTANTS = "app_idle_constants";
/**
+ * Power manager specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "no_cached_wake_locks=1"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * no_cached_wake_locks (boolean)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * @hide
+ * @see com.android.server.power.PowerManagerConstants
+ */
+ public static final String POWER_MANAGER_CONSTANTS = "power_manager_constants";
+
+ /**
* Alarm manager specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -9368,7 +9438,7 @@
/**
* WFC mode on roaming network.
* <p>
- * Type: int - see {@link WFC_IMS_MODE} for values
+ * Type: int - see {@link #WFC_IMS_MODE} for values
*
* @hide
*/
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index 805d8e5..c7c8321 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -15,8 +15,6 @@
*/
package android.service.autofill;
-import static android.service.voice.VoiceInteractionSession.KEY_FLAGS;
-import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
@@ -30,13 +28,11 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
import android.util.Log;
import android.view.autofill.AutoFillId;
import android.view.autofill.FillResponse;
import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
// TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
@@ -49,8 +45,8 @@
*/
public abstract class AutoFillService extends Service {
- static final String TAG = "AutoFillService";
- static final boolean DEBUG = true; // TODO: set to false once stable
+ private static final String TAG = "AutoFillService";
+ static final boolean DEBUG = true; // TODO(b/33197203): set to false once stable
/**
* The {@link Intent} that must be declared as handled by the service.
diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java
index 5a9a9f6..925da8b 100644
--- a/core/java/android/service/autofill/FillCallback.java
+++ b/core/java/android/service/autofill/FillCallback.java
@@ -21,7 +21,6 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.autofill.FillResponse;
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index f8ae57b..ce42107 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -16,7 +16,9 @@
package android.service.autofill;
+import android.graphics.Rect;
import android.os.Bundle;
+import android.view.autofill.AutoFillId;
/**
* Mediator between apps being auto-filled and auto-fill service implementations.
@@ -24,5 +26,9 @@
* {@hide}
*/
oneway interface IAutoFillManagerService {
+
+ void showAutoFillInput(in AutoFillId id, in Rect boundaries);
+
+ // TODO(b/33197203): remove it and refactor onShellCommand
void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags);
}
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 627d74c..04f8c9f 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -21,7 +21,6 @@
import android.app.Activity;
import android.app.assist.AssistStructure.ViewNode;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.autofill.AutoFillId;
diff --git a/core/java/android/util/ByteStringUtils.java b/core/java/android/util/ByteStringUtils.java
index 7103e6d..333208d 100644
--- a/core/java/android/util/ByteStringUtils.java
+++ b/core/java/android/util/ByteStringUtils.java
@@ -33,7 +33,7 @@
* @param bytes Byte array to encode.
* @return Hex encoded string representation of bytes.
*/
- public static String toString(byte[] bytes) {
+ public static String toHexString(byte[] bytes) {
if (bytes == null || bytes.length == 0 || bytes.length % 2 != 0) {
return null;
}
@@ -55,7 +55,7 @@
* @param str Hex encoded string to decode.
* @return Decoded byte array representation of str.
*/
- public static byte[] toByteArray(String str) {
+ public static byte[] fromHexToByteArray(String str) {
if (str == null || str.length() == 0 || str.length() % 2 != 0) {
return null;
}
diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java
index e4c025d..be531ff 100644
--- a/core/java/android/util/KeyValueListParser.java
+++ b/core/java/android/util/KeyValueListParser.java
@@ -129,4 +129,22 @@
}
return def;
}
+
+ /**
+ * Get the value for key as a boolean.
+ * @param key The key to lookup.
+ * @param def The value to return if the key was not found.
+ * @return the string value associated with the key.
+ */
+ public boolean getBoolean(String key, boolean def) {
+ String value = mValues.get(key);
+ if (value != null) {
+ try {
+ return Boolean.parseBoolean(value);
+ } catch (NumberFormatException e) {
+ // fallthrough
+ }
+ }
+ return def;
+ }
}
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 3181979..0fe56f6 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -80,6 +80,6 @@
messageDigest.update(data);
- return ByteStringUtils.toString(messageDigest.digest());
+ return ByteStringUtils.toHexString(messageDigest.digest());
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 57ebd34..123e368 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1765,6 +1765,12 @@
*/
int mAccessibilityViewId = NO_ID;
+ /**
+ * The stable ID of this view for auto-fill purposes.
+ */
+ private int mAutoFillId = NO_ID;
+
+
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent;
@@ -6932,8 +6938,8 @@
if (forAutoFill) {
// The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
// reuse the accessibility id to save space.
- structure.setAutoFillId(getAccessibilityViewId());
-
+ mAutoFillId = getAccessibilityViewId();
+ structure.setAutoFillId(mAutoFillId);
structure.setAutoFillType(getAutoFillType());
}
@@ -7063,7 +7069,7 @@
}
/**
- * Describes the auto-fill type that should be used on callas to
+ * Describes the auto-fill type that should be used on calls to
* {@link #autoFill(AutoFillValue)} and
* {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
*
@@ -7560,6 +7566,20 @@
}
/**
+ * Gets the unique identifier of this view for auto-fill purposes.
+ *
+ * <p>It's only set after {@link #onProvideAutoFillStructure(ViewStructure, int)} is called.
+ *
+ * @return The view autofill id or {@link #NO_ID} if
+ * {@link #onProvideAutoFillStructure(ViewStructure, int)} was not called yet.
+ *
+ * @hide
+ */
+ public int getAutoFillViewId() {
+ return mAutoFillId;
+ }
+
+ /**
* Gets the unique identifier of the window in which this View reseides.
*
* @return The window accessibility id.
diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java
index b7b694d..e9c1c3b 100644
--- a/core/java/android/view/autofill/AutoFillId.java
+++ b/core/java/android/view/autofill/AutoFillId.java
@@ -43,6 +43,13 @@
}
/** @hide */
+ public AutoFillId(int parentId, int virtualChildId) {
+ mVirtual = true;
+ mViewId = parentId;
+ mVirtualId = virtualChildId;
+ }
+
+ /** @hide */
public int getViewId() {
return mViewId;
}
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
new file mode 100644
index 0000000..cd9842f
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.autofill;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.service.autofill.IAutoFillManagerService;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * App entry point to the AutoFill Framework.
+ */
+// TODO(b/33197203): improve this javadoc
+public final class AutoFillManager {
+
+ private static final String TAG = "AutoFillManager";
+ private static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable
+
+ /**
+ * Flag used to show the auto-fill UI affordance for a view.
+ */
+ public static final int FLAG_UPDATE_UI_SHOW = 1 << 0;
+
+ /**
+ * Flag used to hide the auto-fill UI affordance for a view.
+ */
+ public static final int FLAG_UPDATE_UI_HIDE = 1 << 1;
+
+ private final IAutoFillManagerService mService;
+
+ /**
+ * @hide
+ */
+ public AutoFillManager(@SuppressWarnings("unused") Context context,
+ IAutoFillManagerService service) {
+ mService = service;
+ }
+
+ /**
+ * Updates the auto-fill bar for a given {@link View}.
+ *
+ * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
+ * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
+ *
+ * @param view view to be updated.
+ * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
+ * {@link #FLAG_UPDATE_UI_HIDE}.
+ */
+ public void updateAutoFillInput(View view, int flags) {
+ if (DEBUG) {
+ Log.v(TAG, "updateAutoFillInput(" + view.getAutoFillViewId() + "): flags=" + flags);
+ }
+
+ updateAutoFillInput(view, false, View.NO_ID, null, flags);
+ }
+
+ /**
+ * Updates the auto-fill bar for a virtual child of a given {@link View}.
+ *
+ * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
+ * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
+ *
+ * @param parent parent view.
+ * @param childId id identifying the virtual child inside the parent view.
+ * @param boundaries boundaries of the child (inside the parent; could be {@code null} when
+ * flag is {@link #FLAG_UPDATE_UI_HIDE}.
+ * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
+ * {@link #FLAG_UPDATE_UI_HIDE}.
+ */
+ public void updateAutoFillInput(View parent, int childId, @Nullable Rect boundaries,
+ int flags) {
+ if (DEBUG) {
+ Log.v(TAG, "updateAutoFillInput(" + parent.getAutoFillViewId() + ", " + childId
+ + "): boundaries=" + boundaries + ", flags=" + flags);
+ }
+ updateAutoFillInput(parent, true, childId, boundaries, flags);
+ }
+
+ private void updateAutoFillInput(View view, boolean virtual, int childId, Rect boundaries,
+ int flags) {
+ if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
+ final int viewId = view.getAutoFillViewId();
+ final AutoFillId id = virtual
+ ? new AutoFillId(viewId, childId)
+ : new AutoFillId(viewId);
+ showAutoFillInput(id, boundaries);
+ return;
+ }
+ // TODO(b/33197203): handle FLAG_UPDATE_UI_HIDE
+ }
+
+ private void showAutoFillInput(AutoFillId id, Rect boundaries) {
+ final int autoFillViewId = id.getViewId();
+ /*
+ * TODO(b/33197203): currently SHOW_AUTO_FILL_BAR is only set once per activity (i.e, when
+ * the view does not have an auto-fill id), but it should be called again for views that
+ * were not part of the initial auto-fill dataset returned by the service. For example:
+ *
+ * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
+ * 2.User taps `first_name`.
+ * 3.Service returns a dataset with ids for `first_name` and `last_name`.
+ * 4.When user taps `first_name` (again) or `last_name`, flag should not have
+ * SHOW_AUTO_FILL_BAR set, but when user taps `address`, it should (since that field was
+ * not part of the initial dataset).
+ *
+ * Similarly, once the activity is auto-filled, the flag logic should be reset (so if the
+ * user taps the view again, a new auto-fill request is made)
+ */
+ if (autoFillViewId != View.NO_ID) {
+ return;
+ }
+
+ try {
+ mService.showAutoFillInput(id, boundaries);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/view/autofill/VirtualViewDelegate.java b/core/java/android/view/autofill/VirtualViewDelegate.java
index a19b4e5..278bf4f 100644
--- a/core/java/android/view/autofill/VirtualViewDelegate.java
+++ b/core/java/android/view/autofill/VirtualViewDelegate.java
@@ -15,6 +15,8 @@
*/
package android.view.autofill;
+import android.annotation.Nullable;
+import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.ViewStructure;
@@ -71,20 +73,18 @@
public abstract static class Callback {
/**
- * Sent when the focus inside the hierarchy changed.
+ * Sent when the auto-fill bar for a child must be updated.
*
- * <p>Typically callled twice - for the nodes that lost and gained focus.
- *
- * <p>This method should only be called when the change was not caused by the AutoFill
- * Framework itselft (i.e, through {@link VirtualViewDelegate#autoFill(int, AutoFillValue)},
- * but by external causes (for example, when the user changed the value through the view's
- * UI).
- *
- * @param virtualId id of the node whose focus changed.
- * @param hasFocus {@code true} when focus was gained, {@code false} when it was lost.
+ * See {@link AutoFillManager#updateAutoFillInput(View, int, android.graphics.Rect, int)}
+ * for more details.
*/
- public void onFocusChanged(int virtualId, boolean hasFocus) {
- if (DEBUG) Log.d(TAG, "onFocusChanged() for " + virtualId + ": " + hasFocus);
+ // TODO(b/33197203): do we really need it, or should the parent view just call
+ // AutoFillManager.updateAutoFillInput() directly?
+ public void onAutoFillInputUpdated(int virtualId, @Nullable Rect boundaries, int flags) {
+ if (DEBUG) {
+ Log.v(TAG, "onAutoFillInputUpdated(): virtualId=" + virtualId + ", boundaries="
+ + boundaries + ", flags=" + flags);
+ }
}
/**
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 72796cf..0657067 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -17,20 +17,34 @@
package android.view.textclassifier;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
+import android.icu.text.BreakIterator;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.SmartSelection;
+import android.text.Spannable;
import android.text.TextUtils;
+import android.text.method.WordIterator;
+import android.text.style.ClickableSpan;
+import android.text.util.Linkify;
import android.util.Log;
+import android.view.View;
import com.android.internal.util.Preconditions;
import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
/**
* Default implementation of the {@link TextClassifier} interface.
@@ -99,29 +113,7 @@
type = type.toLowerCase().trim();
// TODO: Added this log for debug only. Remove before release.
Log.d(LOG_TAG, String.format("Classification type: %s", type));
- final Intent intent;
- final String title;
- switch (type) {
- case TextClassifier.TYPE_EMAIL:
- intent = new Intent(Intent.ACTION_SENDTO);
- intent.setData(Uri.parse(String.format("mailto:%s", text)));
- title = mContext.getString(com.android.internal.R.string.email);
- return createClassificationResult(classified, type, intent, title);
- case TextClassifier.TYPE_PHONE:
- intent = new Intent(Intent.ACTION_DIAL);
- intent.setData(Uri.parse(String.format("tel:%s", text)));
- title = mContext.getString(com.android.internal.R.string.dial);
- return createClassificationResult(classified, type, intent, title);
- case TextClassifier.TYPE_ADDRESS:
- intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
- title = mContext.getString(com.android.internal.R.string.map);
- return createClassificationResult(classified, type, intent, title);
- default:
- // No classification type found. Return a no-op result.
- break;
- // TODO: Add other classification types.
- }
+ return createClassificationResult(type, classified);
}
}
} catch (Throwable t) {
@@ -132,9 +124,18 @@
return TextClassifier.NO_OP.getTextClassificationResult(text, startIndex, endIndex);
}
+
@Override
- public LinksInfo getLinks(@NonNull CharSequence text, int linkMask) {
- // TODO: Implement
+ public LinksInfo getLinks(CharSequence text, int linkMask) {
+ Preconditions.checkArgument(text != null);
+ try {
+ return LinksInfoFactory.create(
+ mContext, getSmartSelection(), text.toString(), linkMask);
+ } catch (Throwable t) {
+ // Avoid throwing from this method. Log the error.
+ Log.e(LOG_TAG, "Error getting links info.", t);
+ }
+ // Getting here means something went wrong, return a NO_OP result.
return TextClassifier.NO_OP.getLinks(text, linkMask);
}
@@ -145,19 +146,23 @@
return mSmartSelection;
}
- private TextClassificationResult createClassificationResult(
- CharSequence text, String type, Intent intent, String label) {
- TextClassificationResult.Builder builder = new TextClassificationResult.Builder()
+ private TextClassificationResult createClassificationResult(String type, CharSequence text) {
+ final Intent intent = IntentFactory.create(type, text.toString());
+ if (intent == null) {
+ return TextClassificationResult.EMPTY;
+ }
+
+ final TextClassificationResult.Builder builder = new TextClassificationResult.Builder()
.setText(text.toString())
.setEntityType(type, 1.0f /* confidence */)
.setIntent(intent)
.setOnClickListener(TextClassificationResult.createStartActivityOnClick(
mContext, intent))
- .setLabel(label);
- PackageManager pm = mContext.getPackageManager();
- ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
- // TODO: If the resolveInfo is the "chooser", do not set the package name and use a
- // default icon for this classification type.
+ .setLabel(IntentFactory.getLabel(mContext, type));
+ final PackageManager pm = mContext.getPackageManager();
+ final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+ // TODO: If the resolveInfo is the "chooser", do not set the package name and use a default
+ // icon for this classification type.
intent.setPackage(resolveInfo.activityInfo.packageName);
Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
if (icon == null) {
@@ -177,4 +182,198 @@
Preconditions.checkArgument(endIndex <= text.length());
Preconditions.checkArgument(endIndex >= startIndex);
}
+
+ /**
+ * Detects and creates links for specified text.
+ */
+ private static final class LinksInfoFactory {
+
+ private LinksInfoFactory() {}
+
+ public static LinksInfo create(
+ Context context, SmartSelection smartSelection, String text, int linkMask) {
+ final WordIterator wordIterator = new WordIterator();
+ wordIterator.setCharSequence(text, 0, text.length());
+ final List<SpanSpec> spans = new ArrayList<>();
+ int start = 0;
+ int end;
+ while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
+ final String token = text.substring(start, end);
+ if (TextUtils.isEmpty(token)) {
+ continue;
+ }
+
+ final int[] selection = smartSelection.suggest(text, start, end);
+ final int selectionStart = selection[0];
+ final int selectionEnd = selection[1];
+ if (selectionStart >= 0 && selectionEnd <= text.length()
+ && selectionStart <= selectionEnd) {
+ final String type =
+ smartSelection.classifyText(text, selectionStart, selectionEnd);
+ if (matches(type, linkMask)) {
+ final ClickableSpan span = createSpan(
+ context, type, text.substring(selectionStart, selectionEnd));
+ spans.add(new SpanSpec(selectionStart, selectionEnd, span));
+ }
+ }
+ start = end;
+ }
+ return new LinksInfoImpl(text, avoidOverlaps(spans, text));
+ }
+
+ /**
+ * Returns true if the classification type matches the specified linkMask.
+ */
+ private static boolean matches(String type, int linkMask) {
+ if ((linkMask & Linkify.PHONE_NUMBERS) != 0
+ && TextClassifier.TYPE_PHONE.equals(type)) {
+ return true;
+ }
+ if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
+ && TextClassifier.TYPE_EMAIL.equals(type)) {
+ return true;
+ }
+ if ((linkMask & Linkify.MAP_ADDRESSES) != 0
+ && TextClassifier.TYPE_ADDRESS.equals(type)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Trim the number of spans so that no two spans overlap.
+ *
+ * This algorithm first ensures that there is only one span per start index, then it
+ * makes sure that no two spans overlap.
+ */
+ private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
+ Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
+ // Group spans by start index. Take the longest span.
+ final Map<Integer, SpanSpec> reps = new LinkedHashMap<>(); // order matters.
+ final int size = spans.size();
+ for (int i = 0; i < size; i++) {
+ final SpanSpec span = spans.get(i);
+ final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
+ if (rep == null || rep.mEnd < span.mEnd) {
+ reps.put(span.mStart, span);
+ }
+ }
+ // Avoid span intersections. Take the longer span.
+ final LinkedList<SpanSpec> result = new LinkedList<>();
+ for (SpanSpec rep : reps.values()) {
+ if (result.isEmpty()) {
+ result.add(rep);
+ continue;
+ }
+
+ final SpanSpec last = result.getLast();
+ if (rep.mStart < last.mEnd) {
+ // Spans intersect. Use the one with characters.
+ if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
+ result.set(result.size() - 1, rep);
+ }
+ } else {
+ result.add(rep);
+ }
+ }
+ return result;
+ }
+
+ private static ClickableSpan createSpan(
+ final Context context, final String type, final String text) {
+ return new ClickableSpan() {
+ // TODO: Style this span.
+ @Override
+ public void onClick(View widget) {
+ context.startActivity(IntentFactory.create(type, text));
+ }
+ };
+ }
+
+ /**
+ * Implementation of LinksInfo that adds ClickableSpans to the specified text.
+ */
+ private static final class LinksInfoImpl implements LinksInfo {
+
+ private final CharSequence mOriginalText;
+ private final List<SpanSpec> mSpans;
+
+ LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
+ mOriginalText = originalText;
+ mSpans = spans;
+ }
+
+ @Override
+ public boolean apply(@NonNull CharSequence text) {
+ Preconditions.checkArgument(text != null);
+ if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
+ Spannable spannable = (Spannable) text;
+ final int size = mSpans.size();
+ for (int i = 0; i < size; i++) {
+ final SpanSpec span = mSpans.get(i);
+ spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Span plus its start and end index.
+ */
+ private static final class SpanSpec {
+
+ private final int mStart;
+ private final int mEnd;
+ private final ClickableSpan mSpan;
+
+ SpanSpec(int start, int end, ClickableSpan span) {
+ mStart = start;
+ mEnd = end;
+ mSpan = span;
+ }
+ }
+ }
+
+ /**
+ * Creates intents based on the classification type.
+ */
+ private static final class IntentFactory {
+
+ private IntentFactory() {}
+
+ @Nullable
+ public static Intent create(String type, String text) {
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ return new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("mailto:%s", text)));
+ case TextClassifier.TYPE_PHONE:
+ return new Intent(Intent.ACTION_DIAL)
+ .setData(Uri.parse(String.format("tel:%s", text)));
+ case TextClassifier.TYPE_ADDRESS:
+ return new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+ default:
+ return null;
+ // TODO: Add other classification types.
+ }
+ }
+
+ @Nullable
+ public static String getLabel(Context context, String type) {
+ switch (type) {
+ case TextClassifier.TYPE_EMAIL:
+ return context.getString(com.android.internal.R.string.email);
+ case TextClassifier.TYPE_PHONE:
+ return context.getString(com.android.internal.R.string.dial);
+ case TextClassifier.TYPE_ADDRESS:
+ return context.getString(com.android.internal.R.string.map);
+ default:
+ return null;
+ // TODO: Add other classification types.
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index faf0379..6706790 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -169,8 +169,6 @@
Log.w(VIEW_LOG_TAG, "EditText.autoFill(): no text on AutoFillValue");
return;
}
- // TODO(b/33197203): once auto-fill is triggered by the IME, we'll need a new setText()
- // or setAutoFillText() method on TextView to avoid re-triggering it.
setText(text);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a11ece6..072fe4a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -136,6 +136,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
+import android.view.autofill.AutoFillManager;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
@@ -264,6 +265,7 @@
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
static final String LOG_TAG = "TextView";
static final boolean DEBUG_EXTRACT = false;
+ static final boolean DEBUG_AUTOFILL = false;
// Enum for the "typeface" XML parameter.
// TODO: How can we get this from the XML instead of hardcoding it here?
@@ -9024,6 +9026,15 @@
Spannable sp = (Spannable) mText;
MetaKeyKeyListener.resetMetaState(sp);
}
+ } else {
+ final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
+ if (afm != null) {
+ if (DEBUG_AUTOFILL) {
+ Log.v(LOG_TAG, "onFocusChanged(): id=" + getAutoFillViewId() + ", focused= "
+ + focused);
+ }
+ afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_HIDE);
+ }
}
startStopMarquee(focused);
@@ -10602,6 +10613,14 @@
* @hide
*/
protected void viewClicked(InputMethodManager imm) {
+ final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
+ if (afm != null) {
+ if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAutoFillViewId());
+
+ // TODO(b/33197203): integrate with onFocus and/or move to view?
+ afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_SHOW);
+ }
+
if (imm != null) {
imm.viewClicked(this);
}
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 793d132..0c7f5a1 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -509,7 +509,7 @@
sp<MemoryDealer> memoryDealer;
sp<IMemory> memory;
size_t size;
- sound_model_handle_t handle;
+ sound_model_handle_t handle = 0;
jobject jUuid;
jstring jUuidString;
const char *nUuidString;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 070a2d9..5c65241 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -247,6 +247,11 @@
static void DropCapabilitiesBoundingSet(JNIEnv* env) {
for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
+ // Keep CAP_SYS_PTRACE in our bounding set so crash_dump can gain it.
+ if (i == CAP_SYS_PTRACE) {
+ continue;
+ }
+
int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
if (rc == -1) {
if (errno == EINVAL) {
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 1f71a18..0dfeb62 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -20,10 +20,11 @@
android:id="@+id/notification_header"
android:orientation="horizontal"
android:layout_width="wrap_content"
- android:layout_height="53dp"
+ android:layout_height="48dp"
android:clipChildren="false"
android:paddingTop="10dp"
- android:paddingBottom="16dp"
+ android:paddingBottom="11dp"
+ android:layout_marginBottom="5dp"
android:paddingStart="@dimen/notification_content_margin_start"
android:paddingEnd="16dp">
<com.android.internal.widget.CachingIconView
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index eece9fc..ac8c896 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -578,6 +578,9 @@
<!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
<string name="notification_header_divider_symbol" translatable="false">•</string>
+ <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
+ <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
+
<!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
<string name="notification_hidden_text">Contents hidden</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 58c925f..78f6b49 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2788,6 +2788,7 @@
<java-symbol type="drawable" name="lockscreen_notselected" />
<java-symbol type="drawable" name="lockscreen_selected" />
+ <java-symbol type="string" name="notification_header_divider_symbol_with_spaces" />
<java-symbol type="string" name="config_defaultCellBroadcastReceiverComponent" />
<java-symbol type="string" name="app_category_game" />
diff --git a/media/java/android/media/BufferingParams.aidl b/media/java/android/media/BufferingParams.aidl
new file mode 100644
index 0000000..d156d44
--- /dev/null
+++ b/media/java/android/media/BufferingParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+parcelable BufferingParams;
diff --git a/media/java/android/media/BufferingParams.java b/media/java/android/media/BufferingParams.java
new file mode 100644
index 0000000..fdcd6ba
--- /dev/null
+++ b/media/java/android/media/BufferingParams.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Structure for source buffering management params.
+ *
+ * Used by {@link MediaPlayer#getDefaultBufferingParams()},
+ * {@link MediaPlayer#getBufferingParams()} and
+ * {@link MediaPlayer#setBufferingParams(BufferingParams)}
+ * to control source buffering behavior.
+ *
+ * <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering
+ * (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer}
+ * is playing back source). {@link BufferingParams} includes mode and corresponding
+ * watermarks for each stage of source buffering. The watermarks could be either size
+ * based (in milliseconds), or time based (in kilobytes) or both, depending on the mode.
+ *
+ * <p>There are 4 buffering modes: {@link #BUFFERING_MODE_NONE},
+ * {@link #BUFFERING_MODE_TIME_ONLY}, {@link #BUFFERING_MODE_SIZE_ONLY} and
+ * {@link #BUFFERING_MODE_TIME_THEN_SIZE}.
+ * {@link MediaPlayer} source component has default buffering modes which can be queried
+ * by calling {@link MediaPlayer#getDefaultBufferingParams()}.
+ * Users should always use those default modes or their downsized version when trying to
+ * change buffering params. For example, {@link #BUFFERING_MODE_TIME_THEN_SIZE} can be
+ * downsized to {@link #BUFFERING_MODE_NONE}, {@link #BUFFERING_MODE_TIME_ONLY} or
+ * {@link #BUFFERING_MODE_SIZE_ONLY}. But {@link #BUFFERING_MODE_TIME_ONLY} can not be
+ * downsized to {@link #BUFFERING_MODE_SIZE_ONLY}.
+ * <ul>
+ * <li><strong>initial buffering stage:</strong> has one watermark which is used when
+ * {@link MediaPlayer} is being prepared. When cached data amount exceeds this watermark,
+ * {@link MediaPlayer} is prepared.</li>
+ * <li><strong>rebuffering stage:</strong> has two watermarks, low and high, which are
+ * used when {@link MediaPlayer} is playing back content.
+ * <ul>
+ * <li> When cached data amount exceeds high watermark, {@link MediaPlayer} will pause
+ * buffering. Buffering will resume when cache runs below some limit which could be low
+ * watermark or some intermediate value decided by the source component.</li>
+ * <li> When cached data amount runs below low watermark, {@link MediaPlayer} will paused
+ * playback. Playback will resume when cached data amount exceeds high watermark
+ * or reaches end of stream.</li>
+ * </ul>
+ * </ul>
+ * <p>Users should use {@link Builder} to change {@link BufferingParams}.
+ */
+public final class BufferingParams implements Parcelable {
+ /**
+ * This mode indicates that source buffering is not supported.
+ */
+ public static final int BUFFERING_MODE_NONE = 0;
+ /**
+ * This mode indicates that only time based source buffering is supported. This means
+ * the watermark(s) are time based.
+ */
+ public static final int BUFFERING_MODE_TIME_ONLY = 1;
+ /**
+ * This mode indicates that only size based source buffering is supported. This means
+ * the watermark(s) are size based.
+ */
+ public static final int BUFFERING_MODE_SIZE_ONLY = 2;
+ /**
+ * This mode indicates that both time and size based source buffering are supported,
+ * and time based calculation precedes size based. Size based calculation will be used
+ * only when time information is not available from the source.
+ */
+ public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ BUFFERING_MODE_NONE,
+ BUFFERING_MODE_TIME_ONLY,
+ BUFFERING_MODE_SIZE_ONLY,
+ BUFFERING_MODE_TIME_THEN_SIZE,
+ }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BufferingMode {}
+
+ private static final int BUFFERING_NO_WATERMARK = -1;
+
+ // params
+ private int mInitialBufferingMode = BUFFERING_MODE_NONE;
+ private int mRebufferingMode = BUFFERING_MODE_NONE;
+
+ private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
+ private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
+
+ private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+
+ private BufferingParams() {
+ }
+
+ /**
+ * Return the initial buffering mode used when {@link MediaPlayer} is being prepared.
+ * @return one of the values that can be set in {@link Builder#setInitialBufferingMode(int)}
+ */
+ public int getInitialBufferingMode() {
+ return mInitialBufferingMode;
+ }
+
+ /**
+ * Return the rebuffering mode used when {@link MediaPlayer} is playing back source.
+ * @return one of the values that can be set in {@link Builder#setRebufferingMode(int)}
+ */
+ public int getRebufferingMode() {
+ return mRebufferingMode;
+ }
+
+ /**
+ * Return the time based initial buffering watermark in milliseconds.
+ * It is meaningful only when initial buffering mode obatined from
+ * {@link #getInitialBufferingMode()} is time based.
+ * @return time based initial buffering watermark in milliseconds
+ */
+ public int getInitialBufferingWatermarkMs() {
+ return mInitialWatermarkMs;
+ }
+
+ /**
+ * Return the size based initial buffering watermark in kilobytes.
+ * It is meaningful only when initial buffering mode obatined from
+ * {@link #getInitialBufferingMode()} is size based.
+ * @return size based initial buffering watermark in kilobytes
+ */
+ public int getInitialBufferingWatermarkKB() {
+ return mInitialWatermarkKB;
+ }
+
+ /**
+ * Return the time based low watermark in milliseconds for rebuffering.
+ * It is meaningful only when rebuffering mode obatined from
+ * {@link #getRebufferingMode()} is time based.
+ * @return time based low watermark for rebuffering in milliseconds
+ */
+ public int getRebufferingWatermarkLowMs() {
+ return mRebufferingWatermarkLowMs;
+ }
+
+ /**
+ * Return the time based high watermark in milliseconds for rebuffering.
+ * It is meaningful only when rebuffering mode obatined from
+ * {@link #getRebufferingMode()} is time based.
+ * @return time based high watermark for rebuffering in milliseconds
+ */
+ public int getRebufferingWatermarkHighMs() {
+ return mRebufferingWatermarkHighMs;
+ }
+
+ /**
+ * Return the size based low watermark in kilobytes for rebuffering.
+ * It is meaningful only when rebuffering mode obatined from
+ * {@link #getRebufferingMode()} is size based.
+ * @return size based low watermark for rebuffering in kilobytes
+ */
+ public int getRebufferingWatermarkLowKB() {
+ return mRebufferingWatermarkLowKB;
+ }
+
+ /**
+ * Return the size based high watermark in kilobytes for rebuffering.
+ * It is meaningful only when rebuffering mode obatined from
+ * {@link #getRebufferingMode()} is size based.
+ * @return size based high watermark for rebuffering in kilobytes
+ */
+ public int getRebufferingWatermarkHighKB() {
+ return mRebufferingWatermarkHighKB;
+ }
+
+ /**
+ * Builder class for {@link BufferingParams} objects.
+ * <p> Here is an example where <code>Builder</code> is used to define the
+ * {@link BufferingParams} to be used by a {@link MediaPlayer} instance:
+ *
+ * <pre class="prettyprint">
+ * BufferingParams myParams = mediaplayer.getDefaultBufferingParams();
+ * myParams = new BufferingParams.Builder(myParams)
+ * .setInitialBufferingWatermarkMs(10000)
+ * .build();
+ * mediaplayer.setBufferingParams(myParams);
+ * </pre>
+ */
+ public static class Builder {
+ private int mInitialBufferingMode = BUFFERING_MODE_NONE;
+ private int mRebufferingMode = BUFFERING_MODE_NONE;
+
+ private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
+ private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
+
+ private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
+ private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+
+ /**
+ * Constructs a new Builder with the defaults.
+ * By default, both initial buffering mode and rebuffering mode are
+ * {@link BufferingParams#BUFFERING_MODE_NONE}, and all watermarks are -1.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructs a new Builder from a given {@link BufferingParams} instance
+ * @param bp the {@link BufferingParams} object whose data will be reused
+ * in the new Builder.
+ */
+ public Builder(BufferingParams bp) {
+ mInitialBufferingMode = bp.mInitialBufferingMode;
+ mRebufferingMode = bp.mRebufferingMode;
+
+ mInitialWatermarkMs = bp.mInitialWatermarkMs;
+ mInitialWatermarkKB = bp.mInitialWatermarkKB;
+
+ mRebufferingWatermarkLowMs = bp.mRebufferingWatermarkLowMs;
+ mRebufferingWatermarkHighMs = bp.mRebufferingWatermarkHighMs;
+ mRebufferingWatermarkLowKB = bp.mRebufferingWatermarkLowKB;
+ mRebufferingWatermarkHighKB = bp.mRebufferingWatermarkHighKB;
+ }
+
+ /**
+ * Combines all of the fields that have been set and return a new
+ * {@link BufferingParams} object. <code>IllegalStateException</code> will be
+ * thrown if there is conflict between fields.
+ * @return a new {@link BufferingParams} object
+ */
+ public BufferingParams build() {
+ if (isTimeBasedMode(mRebufferingMode)
+ && mRebufferingWatermarkLowMs > mRebufferingWatermarkHighMs) {
+ throw new IllegalStateException("Illegal watermark:"
+ + mRebufferingWatermarkLowMs + " : " + mRebufferingWatermarkHighMs);
+ }
+ if (isSizeBasedMode(mRebufferingMode)
+ && mRebufferingWatermarkLowKB > mRebufferingWatermarkHighKB) {
+ throw new IllegalStateException("Illegal watermark:"
+ + mRebufferingWatermarkLowKB + " : " + mRebufferingWatermarkHighKB);
+ }
+
+ BufferingParams bp = new BufferingParams();
+ bp.mInitialBufferingMode = mInitialBufferingMode;
+ bp.mRebufferingMode = mRebufferingMode;
+
+ bp.mInitialWatermarkMs = mInitialWatermarkMs;
+ bp.mInitialWatermarkKB = mInitialWatermarkKB;
+
+ bp.mRebufferingWatermarkLowMs = mRebufferingWatermarkLowMs;
+ bp.mRebufferingWatermarkHighMs = mRebufferingWatermarkHighMs;
+ bp.mRebufferingWatermarkLowKB = mRebufferingWatermarkLowKB;
+ bp.mRebufferingWatermarkHighKB = mRebufferingWatermarkHighKB;
+ return bp;
+ }
+
+ private boolean isTimeBasedMode(int mode) {
+ return (mode == BUFFERING_MODE_TIME_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
+ }
+
+ private boolean isSizeBasedMode(int mode) {
+ return (mode == BUFFERING_MODE_SIZE_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
+ }
+
+ /**
+ * Sets the initial buffering mode.
+ * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
+ * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
+ * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
+ * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * @return the same Builder instance.
+ */
+ public Builder setInitialBufferingMode(@BufferingMode int mode) {
+ switch (mode) {
+ case BUFFERING_MODE_NONE:
+ case BUFFERING_MODE_TIME_ONLY:
+ case BUFFERING_MODE_SIZE_ONLY:
+ case BUFFERING_MODE_TIME_THEN_SIZE:
+ mInitialBufferingMode = mode;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal buffering mode " + mode);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the rebuffering mode.
+ * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
+ * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
+ * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
+ * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingMode(@BufferingMode int mode) {
+ switch (mode) {
+ case BUFFERING_MODE_NONE:
+ case BUFFERING_MODE_TIME_ONLY:
+ case BUFFERING_MODE_SIZE_ONLY:
+ case BUFFERING_MODE_TIME_THEN_SIZE:
+ mRebufferingMode = mode;
+ break;
+ default:
+ throw new IllegalArgumentException("Illegal buffering mode " + mode);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the time based watermark in milliseconds for initial buffering.
+ * @param watermarkMs time based watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setInitialBufferingWatermarkMs(int watermarkMs) {
+ mInitialWatermarkMs = watermarkMs;
+ return this;
+ }
+
+ /**
+ * Sets the size based watermark in kilobytes for initial buffering.
+ * @param watermarkKB size based watermark in kilobytes
+ * @return the same Builder instance.
+ */
+ public Builder setInitialBufferingWatermarkKB(int watermarkKB) {
+ mInitialWatermarkKB = watermarkKB;
+ return this;
+ }
+
+ /**
+ * Sets the time based low watermark in milliseconds for rebuffering.
+ * @param watermarkMs time based low watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarkLowMs(int watermarkMs) {
+ mRebufferingWatermarkLowMs = watermarkMs;
+ return this;
+ }
+
+ /**
+ * Sets the time based high watermark in milliseconds for rebuffering.
+ * @param watermarkMs time based high watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarkHighMs(int watermarkMs) {
+ mRebufferingWatermarkHighMs = watermarkMs;
+ return this;
+ }
+
+ /**
+ * Sets the size based low watermark in milliseconds for rebuffering.
+ * @param watermarkKB size based low watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarkLowKB(int watermarkKB) {
+ mRebufferingWatermarkLowKB = watermarkKB;
+ return this;
+ }
+
+ /**
+ * Sets the size based high watermark in milliseconds for rebuffering.
+ * @param watermarkKB size based high watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarkHighKB(int watermarkKB) {
+ mRebufferingWatermarkHighKB = watermarkKB;
+ return this;
+ }
+
+ /**
+ * Sets the time based low and high watermarks in milliseconds for rebuffering.
+ * @param lowWatermarkMs time based low watermark in milliseconds
+ * @param highWatermarkMs time based high watermark in milliseconds
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarksMs(int lowWatermarkMs, int highWatermarkMs) {
+ mRebufferingWatermarkLowMs = lowWatermarkMs;
+ mRebufferingWatermarkHighMs = highWatermarkMs;
+ return this;
+ }
+
+ /**
+ * Sets the size based low and high watermarks in kilobytes for rebuffering.
+ * @param lowWatermarkKB size based low watermark in kilobytes
+ * @param highWatermarkKB size based high watermark in kilobytes
+ * @return the same Builder instance.
+ */
+ public Builder setRebufferingWatermarksKB(int lowWatermarkKB, int highWatermarkKB) {
+ mRebufferingWatermarkLowKB = lowWatermarkKB;
+ mRebufferingWatermarkHighKB = highWatermarkKB;
+ return this;
+ }
+ }
+
+ private BufferingParams(Parcel in) {
+ mInitialBufferingMode = in.readInt();
+ mRebufferingMode = in.readInt();
+
+ mInitialWatermarkMs = in.readInt();
+ mInitialWatermarkKB = in.readInt();
+
+ mRebufferingWatermarkLowMs = in.readInt();
+ mRebufferingWatermarkHighMs = in.readInt();
+ mRebufferingWatermarkLowKB = in.readInt();
+ mRebufferingWatermarkHighKB = in.readInt();
+ }
+
+ public static final Parcelable.Creator<BufferingParams> CREATOR =
+ new Parcelable.Creator<BufferingParams>() {
+ @Override
+ public BufferingParams createFromParcel(Parcel in) {
+ return new BufferingParams(in);
+ }
+
+ @Override
+ public BufferingParams[] newArray(int size) {
+ return new BufferingParams[size];
+ }
+ };
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mInitialBufferingMode);
+ dest.writeInt(mRebufferingMode);
+
+ dest.writeInt(mInitialWatermarkMs);
+ dest.writeInt(mInitialWatermarkKB);
+
+ dest.writeInt(mRebufferingWatermarkLowMs);
+ dest.writeInt(mRebufferingWatermarkHighMs);
+ dest.writeInt(mRebufferingWatermarkLowKB);
+ dest.writeInt(mRebufferingWatermarkHighKB);
+ }
+}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index e3a0f25..4023400 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -45,6 +45,7 @@
import android.widget.VideoView;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
+import android.media.BufferingParams;
import android.media.MediaFormat;
import android.media.MediaTimeProvider;
import android.media.PlaybackParams;
@@ -479,6 +480,11 @@
* <td>{} </p></td>
* <td>This method can be called in any state and calling it does not change
* the object state. </p></td></tr>
+ * <tr><td>setBufferingParams</p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}</p></td>
+ * <td>{Idle} </p></td>
+ * <td>This method does not change the object state.
+ * </p></td></tr>
* <tr><td>setPlaybackParams</p></td>
* <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td>
* <td>{Idle, Stopped} </p></td>
@@ -1390,6 +1396,45 @@
public native boolean isPlaying();
/**
+ * Gets the default buffering management params.
+ * Calling it only after {@code setDataSource} has been called.
+ * Each type of data source might have different set of default params.
+ *
+ * @return the default buffering management params supported by the source component.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized, or {@code setDataSource} has not been called.
+ */
+ @NonNull
+ public native BufferingParams getDefaultBufferingParams();
+
+ /**
+ * Gets the current buffering management params used by the source component.
+ * Calling it only after {@code setDataSource} has been called.
+ *
+ * @return the current buffering management params used by the source component.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized, or {@code setDataSource} has not been called.
+ */
+ @NonNull
+ public native BufferingParams getBufferingParams();
+
+ /**
+ * Sets buffering management params.
+ * The object sets its internal BufferingParams to the input, except that the input is
+ * invalid or not supported.
+ * Call it only after {@code setDataSource} has been called.
+ * Users should only use supported mode returned by {@link #getDefaultBufferingParams()}
+ * or its downsized version as described in {@link BufferingParams}.
+ *
+ * @param params the buffering management params.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released, or {@code setDataSource} has not been called.
+ * @throws IllegalArgumentException if params is invalid or not supported.
+ */
+ public native void setBufferingParams(@NonNull BufferingParams params);
+
+ /**
* Change playback speed of audio by resampling the audio.
* <p>
* Specifies resampling as audio mode for variable rate playback, i.e.,
diff --git a/media/jni/android_media_BufferingParams.h b/media/jni/android_media_BufferingParams.h
new file mode 100644
index 0000000..24c51f5
--- /dev/null
+++ b/media/jni/android_media_BufferingParams.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_BUFFERING_PARAMS_H_
+#define _ANDROID_MEDIA_BUFFERING_PARAMS_H_
+
+#include <media/BufferingSettings.h>
+
+namespace android {
+
+// This entire class is inline
+struct BufferingParams {
+ BufferingSettings settings;
+
+ struct fields_t {
+ jclass clazz;
+ jmethodID constructID;
+
+ jfieldID initial_buffering_mode;
+ jfieldID rebuffering_mode;
+ jfieldID initial_watermark_ms;
+ jfieldID initial_watermark_kb;
+ jfieldID rebuffering_watermark_low_ms;
+ jfieldID rebuffering_watermark_high_ms;
+ jfieldID rebuffering_watermark_low_kb;
+ jfieldID rebuffering_watermark_high_kb;
+
+ void init(JNIEnv *env) {
+ jclass lclazz = env->FindClass("android/media/BufferingParams");
+ if (lclazz == NULL) {
+ return;
+ }
+
+ clazz = (jclass)env->NewGlobalRef(lclazz);
+ if (clazz == NULL) {
+ return;
+ }
+
+ constructID = env->GetMethodID(clazz, "<init>", "()V");
+
+ initial_buffering_mode = env->GetFieldID(clazz, "mInitialBufferingMode", "I");
+ rebuffering_mode = env->GetFieldID(clazz, "mRebufferingMode", "I");
+ initial_watermark_ms = env->GetFieldID(clazz, "mInitialWatermarkMs", "I");
+ initial_watermark_kb = env->GetFieldID(clazz, "mInitialWatermarkKB", "I");
+ rebuffering_watermark_low_ms = env->GetFieldID(clazz, "mRebufferingWatermarkLowMs", "I");
+ rebuffering_watermark_high_ms = env->GetFieldID(clazz, "mRebufferingWatermarkHighMs", "I");
+ rebuffering_watermark_low_kb = env->GetFieldID(clazz, "mRebufferingWatermarkLowKB", "I");
+ rebuffering_watermark_high_kb = env->GetFieldID(clazz, "mRebufferingWatermarkHighKB", "I");
+
+ env->DeleteLocalRef(lclazz);
+ }
+
+ void exit(JNIEnv *env) {
+ env->DeleteGlobalRef(clazz);
+ clazz = NULL;
+ }
+ };
+
+ void fillFromJobject(JNIEnv *env, const fields_t& fields, jobject params) {
+ settings.mInitialBufferingMode =
+ (BufferingMode)env->GetIntField(params, fields.initial_buffering_mode);
+ settings.mRebufferingMode =
+ (BufferingMode)env->GetIntField(params, fields.rebuffering_mode);
+ settings.mInitialWatermarkMs =
+ env->GetIntField(params, fields.initial_watermark_ms);
+ settings.mInitialWatermarkKB =
+ env->GetIntField(params, fields.initial_watermark_kb);
+ settings.mRebufferingWatermarkLowMs =
+ env->GetIntField(params, fields.rebuffering_watermark_low_ms);
+ settings.mRebufferingWatermarkHighMs =
+ env->GetIntField(params, fields.rebuffering_watermark_high_ms);
+ settings.mRebufferingWatermarkLowKB =
+ env->GetIntField(params, fields.rebuffering_watermark_low_kb);
+ settings.mRebufferingWatermarkHighKB =
+ env->GetIntField(params, fields.rebuffering_watermark_high_kb);
+ }
+
+ jobject asJobject(JNIEnv *env, const fields_t& fields) {
+ jobject params = env->NewObject(fields.clazz, fields.constructID);
+ if (params == NULL) {
+ return NULL;
+ }
+ env->SetIntField(params, fields.initial_buffering_mode, (jint)settings.mInitialBufferingMode);
+ env->SetIntField(params, fields.rebuffering_mode, (jint)settings.mRebufferingMode);
+ env->SetIntField(params, fields.initial_watermark_ms, (jint)settings.mInitialWatermarkMs);
+ env->SetIntField(params, fields.initial_watermark_kb, (jint)settings.mInitialWatermarkKB);
+ env->SetIntField(params, fields.rebuffering_watermark_low_ms, (jint)settings.mRebufferingWatermarkLowMs);
+ env->SetIntField(params, fields.rebuffering_watermark_high_ms, (jint)settings.mRebufferingWatermarkHighMs);
+ env->SetIntField(params, fields.rebuffering_watermark_low_kb, (jint)settings.mRebufferingWatermarkLowKB);
+ env->SetIntField(params, fields.rebuffering_watermark_high_kb, (jint)settings.mRebufferingWatermarkHighKB);
+
+ return params;
+ }
+};
+
+} // namespace android
+
+#endif // _ANDROID_MEDIA_BUFFERING_PARAMS_H_
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index c52ed94..8225052 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -37,6 +37,7 @@
#include "utils/Errors.h" // for status_t
#include "utils/KeyedVector.h"
#include "utils/String8.h"
+#include "android_media_BufferingParams.h"
#include "android_media_MediaDataSource.h"
#include "android_media_PlaybackParams.h"
#include "android_media_SyncParams.h"
@@ -69,6 +70,7 @@
};
static fields_t fields;
+static BufferingParams::fields_t gBufferingParamsFields;
static PlaybackParams::fields_t gPlaybackParamsFields;
static SyncParams::fields_t gSyncParamsFields;
@@ -343,6 +345,66 @@
setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
}
+static jobject
+android_media_MediaPlayer_getDefaultBufferingParams(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ BufferingParams bp;
+ BufferingSettings &settings = bp.settings;
+ process_media_player_call(
+ env, thiz, mp->getDefaultBufferingSettings(&settings),
+ "java/lang/IllegalStateException", "unexpected error");
+ ALOGV("getDefaultBufferingSettings:{%s}", settings.toString().string());
+
+ return bp.asJobject(env, gBufferingParamsFields);
+}
+
+static jobject
+android_media_MediaPlayer_getBufferingParams(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ BufferingParams bp;
+ BufferingSettings &settings = bp.settings;
+ process_media_player_call(
+ env, thiz, mp->getBufferingSettings(&settings),
+ "java/lang/IllegalStateException", "unexpected error");
+ ALOGV("getBufferingSettings:{%s}", settings.toString().string());
+
+ return bp.asJobject(env, gBufferingParamsFields);
+}
+
+static void
+android_media_MediaPlayer_setBufferingParams(JNIEnv *env, jobject thiz, jobject params)
+{
+ if (params == NULL) {
+ return;
+ }
+
+ sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ BufferingParams bp;
+ bp.fillFromJobject(env, gBufferingParamsFields, params);
+ ALOGV("setBufferingParams:{%s}", bp.settings.toString().string());
+
+ process_media_player_call(
+ env, thiz, mp->setBufferingSettings(bp.settings),
+ "java/lang/IllegalStateException", "unexpected error");
+}
+
static void
android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)
{
@@ -860,6 +922,7 @@
env->DeleteLocalRef(clazz);
+ gBufferingParamsFields.init(env);
gPlaybackParamsFields.init(env);
gSyncParamsFields.init(env);
}
@@ -1046,6 +1109,9 @@
{"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD},
{"_setDataSource", "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback },
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
+ {"getDefaultBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getDefaultBufferingParams},
+ {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getBufferingParams},
+ {"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer_setBufferingParams},
{"_prepare", "()V", (void *)android_media_MediaPlayer_prepare},
{"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync},
{"_start", "()V", (void *)android_media_MediaPlayer_start},
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 6979995..25e1f16 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -80,16 +80,20 @@
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
@@ -996,7 +1000,7 @@
continue;
}
- // As of Android O (API 24), the SSAID is read from an app-specific entry in table
+ // As of Android O, the SSAID is read from an app-specific entry in table
// SETTINGS_FILE_SSAID, unless accessed by a system process.
final Setting setting;
if (isNewSsaidSetting(name)) {
@@ -1035,7 +1039,7 @@
// Get the value.
synchronized (mLock) {
- // As of Android O (API 24), the SSAID is read from an app-specific entry in table
+ // As of Android O, the SSAID is read from an app-specific entry in table
// SETTINGS_FILE_SSAID, unless accessed by a system process.
if (isNewSsaidSetting(name)) {
return getSsaidSettingLocked(owningUserId);
@@ -1978,12 +1982,12 @@
private void generateUserKeyLocked(int userId) {
// Generate a random key for each user used for creating a new ssaid.
- final byte[] keyBytes = new byte[16];
+ final byte[] keyBytes = new byte[32];
final SecureRandom rand = new SecureRandom();
rand.nextBytes(keyBytes);
// Convert to string for storage in settings table.
- final String userKey = ByteStringUtils.toString(keyBytes);
+ final String userKey = ByteStringUtils.toHexString(keyBytes);
// Store the key in the ssaid table.
final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
@@ -1995,6 +1999,10 @@
}
}
+ private byte[] getLengthPrefix(byte[] data) {
+ return ByteBuffer.allocate(4).putInt(data.length).array();
+ }
+
public Setting generateSsaidLocked(String packageName, int userId) {
final PackageInfo packageInfo;
try {
@@ -2019,25 +2027,37 @@
final String userKey = userKeySetting.getValue();
// Convert the user's key back to a byte array.
- final byte[] keyBytes = ByteStringUtils.toByteArray(userKey);
- if (keyBytes == null || keyBytes.length != 16) {
+ final byte[] keyBytes = ByteStringUtils.fromHexToByteArray(userKey);
+
+ // Validate that the key is of expected length.
+ // Keys are currently 32 bytes, but were once 16 bytes during Android O development.
+ if (keyBytes == null || (keyBytes.length != 16 && keyBytes.length != 32)) {
throw new IllegalStateException("User key invalid");
}
- final MessageDigest md;
+ final Mac m;
try {
- // Hash package name and signature.
- md = MessageDigest.getInstance("SHA-256");
+ m = Mac.getInstance("HmacSHA256");
+ m.init(new SecretKeySpec(keyBytes, m.getAlgorithm()));
} catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("HmacSHA256 is not available");
+ throw new IllegalStateException("HmacSHA256 is not available", e);
+ } catch (InvalidKeyException e) {
+ throw new IllegalStateException("Key is corrupted", e);
}
- md.update(keyBytes);
- md.update(packageInfo.packageName.getBytes(StandardCharsets.UTF_8));
- md.update(packageInfo.signatures[0].toByteArray());
+
+ // Mac the package name and each of the signatures.
+ byte[] packageNameBytes = packageInfo.packageName.getBytes(StandardCharsets.UTF_8);
+ m.update(getLengthPrefix(packageNameBytes), 0, 4);
+ m.update(packageNameBytes);
+ for (int i = 0; i < packageInfo.signatures.length; i++) {
+ byte[] sig = packageInfo.signatures[i].toByteArray();
+ m.update(getLengthPrefix(sig), 0, 4);
+ m.update(sig);
+ }
// Convert result to a string for storage in settings table. Only want first 64 bits.
- final String ssaid = ByteStringUtils.toString(md.digest()).substring(0, 16)
- .toLowerCase();
+ final String ssaid = ByteStringUtils.toHexString(m.doFinal()).substring(0, 16)
+ .toLowerCase(Locale.US);
// Save the ssaid in the ssaid table.
final String uid = Integer.toString(packageInfo.applicationInfo.uid);
diff --git a/packages/SystemUI/res/xml/other_settings.xml b/packages/SystemUI/res/xml/other_settings.xml
index 18cb930..7719d5e 100644
--- a/packages/SystemUI/res/xml/other_settings.xml
+++ b/packages/SystemUI/res/xml/other_settings.xml
@@ -23,10 +23,5 @@
android:key="power_notification_controls"
android:title="@string/tuner_full_importance_settings"
android:fragment="com.android.systemui.tuner.PowerNotificationControlsFragment"/>
-e
- <com.android.systemui.tuner.ThemePreference
- android:key="theme"
- android:title="@string/theme"
- android:summary="%s" />
</PreferenceScreen>
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 56947e5..964fefa 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -377,16 +377,18 @@
mCurrentPipBounds = mPipBounds;
break;
}
- try {
- int animationDurationMs = -1;
- if (wasRecentsShown
- && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
- animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
+ if (mCurrentPipBounds != null) {
+ try {
+ int animationDurationMs = -1;
+ if (wasRecentsShown
+ && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
+ animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
+ }
+ mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+ true, true, true, animationDurationMs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "resizeStack failed", e);
}
- mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
- true, true, true, animationDurationMs);
- } catch (RemoteException e) {
- Log.e(TAG, "resizeStack failed", e);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 0bf3f15..1835afd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -60,7 +60,7 @@
private AlertDialog mDialog;
private QSTileHost mHost;
- protected Handler mHandler;
+ protected H mHandler;
private boolean mIsVisible;
private boolean mIsIconVisible;
@@ -83,7 +83,7 @@
mMainHandler = new Handler(Looper.getMainLooper());
mActivityStarter = Dependency.get(ActivityStarter.class);
mSecurityController = Dependency.get(SecurityController.class);
- mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
+ mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
}
public void setHostEnvironment(QSTileHost host) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index d9298ed..926f610 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1524,8 +1524,9 @@
entry.notification.getUser().getIdentifier());
final StatusBarNotification sbn = entry.notification;
+ boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
try {
- entry.cacheContentViews(mContext, null);
+ entry.cacheContentViews(mContext, null, isLowPriority);
} catch (RuntimeException e) {
Log.e(TAG, "Unable to get notification remote views", e);
return false;
@@ -1597,6 +1598,7 @@
workAroundBadLayerDrawableOpacity(row);
bindDismissRunnable(row);
+ row.setIsLowPriority(isLowPriority);
// NB: the large icon is now handled entirely by the template
@@ -2283,7 +2285,8 @@
boolean applyInPlace;
try {
- applyInPlace = entry.cacheContentViews(mContext, notification.getNotification());
+ applyInPlace = entry.cacheContentViews(mContext, notification.getNotification(),
+ mNotificationData.isAmbient(key));
} catch (RuntimeException e) {
Log.e(TAG, "Unable to get notification remote views", e);
applyInPlace = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 93c48f8..013554c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -22,6 +22,7 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -51,6 +52,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.statusbar.notification.HybridNotificationView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -201,6 +203,7 @@
private boolean mShowAmbient;
private boolean mIsLastChild;
private Runnable mOnDismissRunnable;
+ private boolean mIsLowPriority;
public boolean isGroupExpansionChanging() {
if (isChildInGroup()) {
@@ -309,6 +312,18 @@
mPublicLayout.updateExpandButtons(true);
updateLimits();
updateIconVisibilities();
+ updateShelfIconColor();
+ }
+
+ private void updateShelfIconColor() {
+ StatusBarIconView expandedIcon = mEntry.expandedIcon;
+ boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
+ boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
+ NotificationColorUtil.getInstance(mContext));
+ if (colorize) {
+ int color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded());
+ expandedIcon.setImageTintList(ColorStateList.valueOf(color));
+ }
}
private void updateLimits() {
@@ -943,6 +958,14 @@
return mPrivateLayout.getTranslationY();
}
+ public void setIsLowPriority(boolean isLowPriority) {
+ mIsLowPriority = isLowPriority;
+ mPrivateLayout.setIsLowPriority(isLowPriority);
+ if (mChildrenContainer != null) {
+ mChildrenContainer.setIsLowPriority(isLowPriority);
+ }
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -1043,6 +1066,7 @@
@Override
public void onInflate(ViewStub stub, View inflated) {
mChildrenContainer = (NotificationChildrenContainer) inflated;
+ mChildrenContainer.setIsLowPriority(mIsLowPriority);
mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this);
mChildrenContainer.onNotificationUpdated();
mTranslateableViews.add(mChildrenContainer);
@@ -1242,6 +1266,7 @@
*/
public void setUserExpanded(boolean userExpanded) {
setUserExpanded(userExpanded, false /* allowChildExpansion */);
+ updateShelfIconColor();
}
/**
@@ -1301,6 +1326,7 @@
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
+ updateShelfIconColor();
notifyHeightChanged(false /* needsAnimation */);
logExpansionEvent(false, wasExpanded);
if (mIsSummaryWithChildren) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index b45cde8..4d0ce12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -129,6 +129,7 @@
private boolean mHeadsUpAnimatingAway;
private boolean mIconsVisible;
private int mClipBottomAmount;
+ private boolean mIsLowPriority;
public NotificationContentView(Context context, AttributeSet attrs) {
@@ -163,20 +164,31 @@
if (mExpandedChild != null) {
int size = Math.min(maxSize, mNotificationMaxHeight);
ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
+ boolean useExactly = false;
if (layoutParams.height >= 0) {
// An actual height is set
size = Math.min(maxSize, layoutParams.height);
+ useExactly = true;
}
int spec = size == Integer.MAX_VALUE
? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
- : MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+ : MeasureSpec.makeMeasureSpec(size, useExactly
+ ? MeasureSpec.EXACTLY
+ : MeasureSpec.AT_MOST);
mExpandedChild.measure(widthMeasureSpec, spec);
maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
}
if (mContractedChild != null) {
int heightSpec;
int size = Math.min(maxSize, mSmallHeight);
- if (shouldContractedBeFixedSize()) {
+ ViewGroup.LayoutParams layoutParams = mContractedChild.getLayoutParams();
+ boolean useExactly = false;
+ if (layoutParams.height >= 0) {
+ // An actual height is set
+ size = Math.min(size, layoutParams.height);
+ useExactly = true;
+ }
+ if (shouldContractedBeFixedSize() || useExactly) {
heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
} else {
heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
@@ -202,12 +214,15 @@
if (mHeadsUpChild != null) {
int size = Math.min(maxSize, mHeadsUpHeight);
ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
+ boolean useExactly = false;
if (layoutParams.height >= 0) {
// An actual height is set
size = Math.min(size, layoutParams.height);
+ useExactly = true;
}
mHeadsUpChild.measure(widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+ MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
+ : MeasureSpec.AT_MOST));
maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
}
if (mSingleLineView != null) {
@@ -225,12 +240,15 @@
if (mAmbientChild != null) {
int size = Math.min(maxSize, mNotificationAmbientHeight);
ViewGroup.LayoutParams layoutParams = mAmbientChild.getLayoutParams();
+ boolean useExactly = false;
if (layoutParams.height >= 0) {
// An actual height is set
size = Math.min(size, layoutParams.height);
+ useExactly = true;
}
mAmbientChild.measure(widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+ MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
+ : MeasureSpec.AT_MOST));
maxChildHeight = Math.max(maxChildHeight, mAmbientChild.getMeasuredHeight());
}
int ownHeight = Math.min(maxChildHeight, maxSize);
@@ -606,7 +624,7 @@
}
public int getMinHeight(boolean likeGroupExpanded) {
- if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
+ if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded() || mIsLowPriority) {
return mContractedChild.getHeight();
} else {
return mSingleLineView.getHeight();
@@ -868,7 +886,7 @@
height = mContentHeight;
}
int expandedVisualType = getVisualTypeForHeight(height);
- int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
+ int collapsedVisualType = mIsChildInGroup && !isGroupExpanded() && !mIsLowPriority
? VISIBLE_TYPE_SINGLELINE
: getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
return mTransformationStartVisibleType == collapsedVisualType
@@ -889,7 +907,7 @@
if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
return VISIBLE_TYPE_EXPANDED;
}
- if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
+ if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded() && !mIsLowPriority) {
return VISIBLE_TYPE_SINGLELINE;
}
@@ -976,16 +994,16 @@
updateSingleLineView();
applyRemoteInput(entry);
if (mContractedChild != null) {
- mContractedWrapper.notifyContentUpdated(entry.notification);
+ mContractedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
if (mExpandedChild != null) {
- mExpandedWrapper.notifyContentUpdated(entry.notification);
+ mExpandedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.notifyContentUpdated(entry.notification);
+ mHeadsUpWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
if (mAmbientChild != null) {
- mAmbientWrapper.notifyContentUpdated(entry.notification);
+ mAmbientWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
}
updateShowingLegacyBackground();
mForceSelectNextLayout = true;
@@ -1282,4 +1300,8 @@
}
}
}
+
+ public void setIsLowPriority(boolean isLowPriority) {
+ mIsLowPriority = isLowPriority;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 3a89186..8c04a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -117,14 +117,16 @@
return row.getPublicLayout().getContractedChild();
}
- public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
+ public boolean cacheContentViews(Context ctx, Notification updatedNotification,
+ boolean isLowPriority) {
boolean applyInPlace = false;
if (updatedNotification != null) {
final Notification.Builder updatedNotificationBuilder
= Notification.Builder.recoverBuilder(ctx, updatedNotification);
- final RemoteViews newContentView = updatedNotificationBuilder.createContentView();
- final RemoteViews newBigContentView =
- updatedNotificationBuilder.createBigContentView();
+ final RemoteViews newContentView = createContentView(updatedNotificationBuilder,
+ isLowPriority);
+ final RemoteViews newBigContentView = createBigContentView(
+ updatedNotificationBuilder, isLowPriority);
final RemoteViews newHeadsUpContentView =
updatedNotificationBuilder.createHeadsUpContentView();
final RemoteViews newPublicNotification
@@ -152,8 +154,8 @@
final Notification.Builder builder
= Notification.Builder.recoverBuilder(ctx, notification.getNotification());
- cachedContentView = builder.createContentView();
- cachedBigContentView = builder.createBigContentView();
+ cachedContentView = createContentView(builder, isLowPriority);
+ cachedBigContentView = createBigContentView(builder, isLowPriority);
cachedHeadsUpContentView = builder.createHeadsUpContentView();
cachedPublicContentView = builder.makePublicContentView();
cachedAmbientContentView = builder.makeAmbientNotification();
@@ -163,6 +165,28 @@
return applyInPlace;
}
+ private RemoteViews createBigContentView(Notification.Builder builder,
+ boolean isLowPriority) {
+ RemoteViews bigContentView = builder.createBigContentView();
+ if (bigContentView != null) {
+ return bigContentView;
+ }
+ if (isLowPriority) {
+ RemoteViews contentView = builder.createContentView();
+ Notification.Builder.makeHeaderExpanded(contentView);
+ return contentView;
+ }
+ return null;
+ }
+
+ private RemoteViews createContentView(Notification.Builder builder,
+ boolean isAmbient) {
+ if (isAmbient) {
+ return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
+ }
+ return builder.createContentView();
+ }
+
// Returns true if the RemoteViews are the same.
private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
return (a == null && b == null) ||
@@ -256,8 +280,9 @@
}
}
- public int getContrastedColor(Context context) {
- int rawColor = notification.getNotification().color;
+ public int getContrastedColor(Context context, boolean ambient) {
+ int rawColor = ambient ? Notification.COLOR_DEFAULT :
+ notification.getNotification().color;
if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
return mCachedContrastColor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
index dd7c4c7..063252f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
@@ -22,7 +22,7 @@
* A view that can be transformed to and from.
*/
public interface TransformableView {
- int TRANSFORMING_VIEW_HEADER = 0;
+ int TRANSFORMING_VIEW_ICON = 0;
int TRANSFORMING_VIEW_TITLE = 1;
int TRANSFORMING_VIEW_TEXT = 2;
int TRANSFORMING_VIEW_IMAGE = 3;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
deleted file mode 100644
index 9501f90..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.util.Pools;
-import android.view.NotificationHeaderView;
-import android.view.View;
-
-import com.android.systemui.statusbar.CrossFadeHelper;
-
-/**
- * A transform state of a text view.
-*/
-public class HeaderTransformState extends TransformState {
-
- private static Pools.SimplePool<HeaderTransformState> sInstancePool
- = new Pools.SimplePool<>(40);
- private View mExpandButton;
- private View mWorkProfileIcon;
- private TransformState mWorkProfileState;
-
- @Override
- public void initFrom(View view) {
- super.initFrom(view);
- if (view instanceof NotificationHeaderView) {
- NotificationHeaderView header = (NotificationHeaderView) view;
- mExpandButton = header.getExpandButton();
- mWorkProfileState = TransformState.obtain();
- mWorkProfileIcon = header.getWorkProfileIcon();
- mWorkProfileState.initFrom(mWorkProfileIcon);
- }
- }
-
- @Override
- public boolean transformViewTo(TransformState otherState, float transformationAmount) {
- // if the transforming notification has a header, we have ensured that it looks the same
- // but the expand button, so lets fade just that one and transform the work profile icon.
- if (!(mTransformedView instanceof NotificationHeaderView)) {
- return false;
- }
- NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
- int childCount = header.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View headerChild = header.getChildAt(i);
- if (headerChild.getVisibility() == View.GONE) {
- continue;
- }
- if (headerChild != mExpandButton) {
- headerChild.setVisibility(View.INVISIBLE);
- } else {
- CrossFadeHelper.fadeOut(mExpandButton, transformationAmount);
- }
- }
- return true;
- }
-
- @Override
- public void transformViewFrom(TransformState otherState, float transformationAmount) {
- // if the transforming notification has a header, we have ensured that it looks the same
- // but the expand button, so lets fade just that one and transform the work profile icon.
- if (!(mTransformedView instanceof NotificationHeaderView)) {
- return;
- }
- NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
- header.setVisibility(View.VISIBLE);
- header.setAlpha(1.0f);
- int childCount = header.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View headerChild = header.getChildAt(i);
- if (headerChild.getVisibility() == View.GONE) {
- continue;
- }
- if (headerChild == mExpandButton) {
- CrossFadeHelper.fadeIn(mExpandButton, transformationAmount);
- } else {
- headerChild.setVisibility(View.VISIBLE);
- if (headerChild == mWorkProfileIcon) {
- mWorkProfileState.transformViewFullyFrom(
- ((HeaderTransformState) otherState).mWorkProfileState,
- transformationAmount);
- }
- }
- }
- return;
- }
-
- public static HeaderTransformState obtain() {
- HeaderTransformState instance = sInstancePool.acquire();
- if (instance != null) {
- return instance;
- }
- return new HeaderTransformState();
- }
-
- @Override
- public void recycle() {
- super.recycle();
- sInstancePool.release(this);
- }
-
- @Override
- protected void reset() {
- super.reset();
- mExpandButton = null;
- mWorkProfileState = null;
- if (mWorkProfileState != null) {
- mWorkProfileState.recycle();
- mWorkProfileState = null;
- }
- }
-
- @Override
- public void setVisible(boolean visible, boolean force) {
- super.setVisible(visible, force);
- if (!(mTransformedView instanceof NotificationHeaderView)) {
- return;
- }
- NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
- int childCount = header.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View headerChild = header.getChildAt(i);
- if (!force && headerChild.getVisibility() == View.GONE) {
- continue;
- }
- headerChild.animate().cancel();
- if (headerChild.getVisibility() != View.GONE) {
- headerChild.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
- if (headerChild == mExpandButton) {
- headerChild.setAlpha(visible ? 1.0f : 0.0f);
- }
- if (headerChild == mWorkProfileIcon) {
- headerChild.setTranslationX(0);
- headerChild.setTranslationY(0);
- }
- }
- }
-
- @Override
- public void prepareFadeIn() {
- super.prepareFadeIn();
- if (!(mTransformedView instanceof NotificationHeaderView)) {
- return;
- }
- NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
- int childCount = header.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View headerChild = header.getChildAt(i);
- if (headerChild.getVisibility() == View.GONE) {
- continue;
- }
- headerChild.animate().cancel();
- headerChild.setVisibility(View.VISIBLE);
- headerChild.setAlpha(1.0f);
- if (headerChild == mWorkProfileIcon) {
- headerChild.setTranslationX(0);
- headerChild.setTranslationY(0);
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
index 6084770..78b967a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
@@ -36,8 +36,8 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
- super.notifyContentUpdated(notification);
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ super.notifyContentUpdated(notification, isLowPriority);
updateImageTag(notification);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
index 3f49125..39db243 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
@@ -41,11 +41,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews(notification);
- super.notifyContentUpdated(notification);
+ super.notifyContentUpdated(notification, isLowPriority);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index 85e87dd..d564741 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -106,8 +106,8 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
- super.notifyContentUpdated(notification);
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ super.notifyContentUpdated(notification, isLowPriority);
Drawable background = mView.getBackground();
mBackgroundColor = 0;
if (background instanceof ColorDrawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 3e4c758..e1f553c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -32,6 +32,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
@@ -60,6 +61,9 @@
private ImageView mExpandButton;
private NotificationHeaderView mNotificationHeader;
+ private TextView mHeaderText;
+ private ImageView mWorkProfileImage;
+ private boolean mIsLowPriority;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(view, row);
@@ -72,7 +76,9 @@
protected void resolveHeaderViews() {
mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
+ mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text);
mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
+ mWorkProfileImage = (ImageView) mView.findViewById(com.android.internal.R.id.profile_badge);
mColor = resolveColor(mExpandButton);
mNotificationHeader = (NotificationHeaderView) mView.findViewById(
com.android.internal.R.id.notification_header);
@@ -89,9 +95,9 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
- super.notifyContentUpdated(notification);
-
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+ super.notifyContentUpdated(notification, isLowPriority);
+ mIsLowPriority = isLowPriority;
ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
// Reinspect the notification.
@@ -100,6 +106,11 @@
updateTransformedTypes();
addRemainingTransformTypes();
updateCropToPaddingForImageViews();
+ mIcon.setTag(ImageTransformState.ICON_TAG, notification.getNotification().getSmallIcon());
+ // The work profile image is always the same lets just set the icon tag for it not to
+ // animate
+ mWorkProfileImage.setTag(ImageTransformState.ICON_TAG,
+ notification.getNotification().getSmallIcon());
// We need to reset all views that are no longer transforming in case a view was previously
// transformed, but now we decided to transform its container instead.
@@ -154,8 +165,11 @@
protected void updateTransformedTypes() {
mTransformationHelper.reset();
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER,
- mNotificationHeader);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
+ if (mIsLowPriority) {
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
+ mHeaderText);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index 4ce330c..04ee6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -40,11 +40,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews(notification);
- super.notifyContentUpdated(notification);
+ super.notifyContentUpdated(notification, isLowPriority);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index 58f01c8..61457df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -59,11 +59,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews();
- super.notifyContentUpdated(notification);
+ super.notifyContentUpdated(notification, isLowPriority);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index b984c0b..e9956ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -133,11 +133,11 @@
}
@Override
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveTemplateViews(notification);
- super.notifyContentUpdated(notification);
+ super.notifyContentUpdated(notification, isLowPriority);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 16348dfe..8106223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -80,9 +80,10 @@
/**
* Notifies this wrapper that the content of the view might have changed.
- * @param notification
+ * @param notification the notification this is wrapped around
+ * @param isLowPriority is this notification low priority
*/
- public void notifyContentUpdated(StatusBarNotification notification) {
+ public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
mDarkInitialized = false;
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 770ec95..6b3d25d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -402,11 +402,6 @@
result.initFrom(view);
return result;
}
- if (view instanceof NotificationHeaderView) {
- HeaderTransformState result = HeaderTransformState.obtain();
- result.initFrom(view);
- return result;
- }
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
result.initFrom(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 345dcbd..32b9969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -154,18 +154,6 @@
NotificationShelf.SHOW_AMBIENT_ICONS);
applyNotificationIconsTint();
- ArrayList<NotificationData.Entry> activeNotifications
- = notificationData.getActiveNotifications();
- for (int i = 0; i < activeNotifications.size(); i++) {
- NotificationData.Entry entry = activeNotifications.get(i);
- boolean isPreL = Boolean.TRUE.equals(entry.expandedIcon.getTag(R.id.icon_is_pre_L));
- boolean colorize = !isPreL
- || NotificationUtils.isGrayscale(entry.expandedIcon, mNotificationColorUtil);
- if (colorize) {
- int color = entry.getContrastedColor(mContext);
- entry.expandedIcon.setImageTintList(ColorStateList.valueOf(color));
- }
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index a706408..5536209 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -162,6 +162,10 @@
BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery);
int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary);
battery.setRawColors(colorForeground, colorSecondary);
+
+ mNextAlarmController = Dependency.get(NextAlarmController.class);
+ mUserInfoController = Dependency.get(UserInfoController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
}
@Override
@@ -260,14 +264,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mNextAlarmController = Dependency.get(NextAlarmController.class);
- mUserInfoController = Dependency.get(UserInfoController.class);
- mActivityStarter = Dependency.get(ActivityStarter.class);
- }
-
- @Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 1a2d778..e6a3add 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -78,6 +78,7 @@
private NotificationHeaderUtil mHeaderUtil;
private ViewState mHeaderViewState;
private int mClipBottomAmount;
+ private boolean mIsLowPriority;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -246,10 +247,13 @@
return mChildren.size();
}
- public void recreateNotificationHeader(OnClickListener listener, StatusBarNotification notification) {
+ public void recreateNotificationHeader(OnClickListener listener,
+ StatusBarNotification notification) {
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
mNotificationParent.getStatusBarNotification().getNotification());
- final RemoteViews header = builder.makeNotificationHeader();
+ final RemoteViews header = mIsLowPriority
+ ? builder.makeLowPriorityContentView(true /* useRegularSubtext */)
+ : builder.makeNotificationHeader();
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
final View expandButton = mNotificationHeader.findViewById(
@@ -262,7 +266,7 @@
invalidate();
} else {
header.reapply(getContext(), mNotificationHeader);
- mNotificationHeaderWrapper.notifyContentUpdated(notification);
+ mNotificationHeaderWrapper.notifyContentUpdated(notification, mIsLowPriority);
}
updateChildrenHeaderAppearance();
}
@@ -367,6 +371,9 @@
* @return the intrinsic size of this children container, i.e the natural fully expanded state
*/
public int getIntrinsicHeight() {
+ if (mIsLowPriority && !mChildrenExpanded) {
+ return mNotificationHeader.getHeight();
+ }
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
return getIntrinsicHeight(maxAllowedVisibleChildren);
}
@@ -480,7 +487,7 @@
childState.clipTopAmount = 0;
childState.alpha = 0;
if (i < firstOverflowIndex) {
- childState.alpha = 1;
+ childState.alpha = mIsLowPriority && !mChildrenExpanded ? expandFactor : 1.0f;
} else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
@@ -828,6 +835,9 @@
}
private int getMinHeight(int maxAllowedVisibleChildren) {
+ if (mIsLowPriority && !mChildrenExpanded) {
+ return mNotificationHeader.getHeight();
+ }
int minExpandHeight = mNotificationHeaderMargin;
int visibleChildren = 0;
boolean firstChild = true;
@@ -922,4 +932,8 @@
mClipBottomAmount = clipBottomAmount;
updateChildrenClipping();
}
+
+ public void setIsLowPriority(boolean isLowPriority) {
+ mIsLowPriority = isLowPriority;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java
deleted file mode 100644
index a068172..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-import android.app.AlertDialog;
-import android.app.UiModeManager;
-import android.content.Context;
-import android.os.SystemProperties;
-import android.support.v7.preference.ListPreference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import com.android.systemui.R;
-
-import libcore.util.Objects;
-
-import com.google.android.collect.Lists;
-
-import java.io.File;
-import java.util.ArrayList;
-
-public class ThemePreference extends ListPreference {
-
- public ThemePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void onAttached() {
- super.onAttached();
- String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
- if (TextUtils.isEmpty(def)) {
- def = getContext().getString(R.string.default_theme);
- }
- String[] fileList = new File("/vendor/overlay").list();
- ArrayList<String> options = fileList != null
- ? Lists.newArrayList(fileList) : new ArrayList<>();
- if (!options.contains(def)) {
- options.add(0, def);
- }
- String[] list = options.toArray(new String[options.size()]);
- setVisible(options.size() > 1);
- setEntries(list);
- setEntryValues(list);
- updateValue();
- }
-
- private void updateValue() {
- setValue(getContext().getSystemService(UiModeManager.class).getTheme());
- }
-
- @Override
- protected void notifyChanged() {
- super.notifyChanged();
- if (!Objects.equal(getValue(),
- getContext().getSystemService(UiModeManager.class).getTheme())) {
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.change_theme_reboot)
- .setPositiveButton(com.android.internal.R.string.global_action_restart, (d, i)
- -> getContext().getSystemService(UiModeManager.class)
- .setTheme(getValue()))
- .setNegativeButton(android.R.string.cancel, (d, i) -> updateValue())
- .show();
- }
- }
-}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 6516369..90e9321 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -30,6 +30,9 @@
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 973f1f2..fb4b6bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -15,6 +15,7 @@
package com.android.systemui;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -25,6 +26,8 @@
import org.junit.Test;
+import java.io.PrintWriter;
+
public class DependencyTest extends SysuiTestCase {
@Test
@@ -46,8 +49,8 @@
Dumpable d = mock(Dumpable.class);
injectTestDependency("test", d);
Dependency.get("test");
- mDependency.dump(null, null, null);
- verify(d).dump(eq(null), eq(null), eq(null));
+ mDependency.dump(null, mock(PrintWriter.class), null);
+ verify(d).dump(eq(null), any(), eq(null));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 5fe5174..f258e5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -43,6 +43,7 @@
public void SysuiSetup() throws Exception {
System.setProperty("dexmaker.share_classloader", "true");
mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
+ SystemUIFactory.createFromConfig(mContext);
mDependency = new TestDependency();
mDependency.mContext = mContext;
mDependency.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index e3ee851..5345031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -58,6 +58,7 @@
@Before
public void addLeakCheckDependencies() {
+ injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
injectMockDependency(UserSwitcherController.class);
injectLeakCheckedDependencies(BluetoothController.class, LocationController.class,
RotationLockController.class, NetworkController.class, ZenModeController.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 9fcb5f7..525a361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -40,6 +40,7 @@
mContext.putComponent(PhoneStatusBar.class, mock(PhoneStatusBar.class));
mContext.putComponent(Recents.class, mock(Recents.class));
mContext.putComponent(Divider.class, mock(Divider.class));
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mock(WindowManager.class));
}
@Test
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 036b6c2..a474759 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3336,6 +3336,9 @@
// OS: 8.0
TTS_SLIDERS = 815;
+ // ACTION: Settings -> Display -> Theme
+ ACTION_THEME = 816;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 87eaf29..6779657 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -19,10 +19,6 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
-
-import static com.android.server.autofill.AutoFillUI.MSG_SHOW_ALL_NOTIFICATIONS;
-import static com.android.server.autofill.AutoFillUI.SHOW_ALL_NOTIFICATIONS_DELAY_MS;
import android.Manifest;
import android.app.AppGlobals;
@@ -32,11 +28,13 @@
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -51,9 +49,12 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.view.autofill.AutoFillId;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
import com.android.server.FgThread;
import com.android.server.SystemService;
@@ -70,13 +71,12 @@
public final class AutoFillManagerService extends SystemService {
private static final String TAG = "AutoFillManagerService";
- static final boolean DEBUG = true; // TODO: change to false once stable
+ static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable
private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
- private static final int ARG_NOT_USED = 0;
-
protected static final int MSG_UNBIND = 1;
+ protected static final int MSG_SHOW_AUTO_FILL = 2;
private final AutoFillManagerServiceStub mServiceStub;
private final AutoFillUI mUi;
@@ -85,23 +85,28 @@
private final Object mLock = new Object();
- private final Handler mHandler = new Handler() {
+ private final HandlerCaller.Callback mHandlerCallback = new HandlerCaller.Callback() {
+
@Override
- public void handleMessage(Message msg) {
+ public void executeMessage(Message msg) {
switch (msg.what) {
- case MSG_UNBIND:
+ case MSG_UNBIND: {
removeStaleServiceForUser(msg.arg1);
return;
- case MSG_SHOW_ALL_NOTIFICATIONS:
- mUi.showAllNotifications();
+ } case MSG_SHOW_AUTO_FILL: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ showAutoFillInput(msg.arg1, (AutoFillId) args.arg1, (Rect) args.arg2);
return;
- default:
+ } default: {
Slog.w(TAG, "Invalid message: " + msg);
+ }
}
}
};
+ private HandlerCaller mHandlerCaller;
+
/**
* Cache of {@link AutoFillManagerServiceImpl} per user id.
* <p>
@@ -122,6 +127,8 @@
public AutoFillManagerService(Context context) {
super(context);
+ mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);
+
mContext = context;
mUi = new AutoFillUI(context, this, mLock);
mResolver = context.getContentResolver();
@@ -139,14 +146,6 @@
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
new SettingsObserver(BackgroundThread.getHandler());
}
- if (phase == PHASE_BOOT_COMPLETED) {
- // TODO: if sent right away, the notification is not displayed. Since the notification
- // mechanism is a temporary approach anyways, just delay it..
- if (DEBUG)
- Slog.d(TAG, "Showing notifications in " + SHOW_ALL_NOTIFICATIONS_DELAY_MS + "ms");
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ALL_NOTIFICATIONS),
- SHOW_ALL_NOTIFICATIONS_DELAY_MS);
- }
}
private AutoFillManagerServiceImpl newServiceForUser(int userId) {
@@ -200,7 +199,7 @@
}
// Keep service connection alive for a while, in case user needs to interact with it
// (for example, to save the data that was inputted in)
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UNBIND, userId, ARG_NOT_USED),
+ mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId),
SERVICE_BINDING_LIFETIME_MS);
return service;
}
@@ -245,18 +244,41 @@
}
+
+ private void requestAutoFillLocked(IBinder activityToken, int userId, Bundle extras,
+ int flags) {
+ final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+ if (service != null) {
+ service.requestAutoFill(activityToken, extras, flags);
+ }
+ }
+
+ private void showAutoFillInput(int userId, AutoFillId id, Rect rect) {
+ if (DEBUG) Slog.d(TAG, "handler.showAutoFillInput(): id=" + id + ", rect=" + rect);
+
+ synchronized (mLock) {
+ requestAutoFillLocked(null, userId, null, AUTO_FILL_FLAG_TYPE_FILL);
+ }
+ }
+
final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
@Override
+ public void showAutoFillInput(AutoFillId id, Rect boundaries) {
+ if (DEBUG) Slog.d(TAG, "showAutoFillInput(): id=" + id + ", boundaries=" + boundaries);
+
+ // TODO(b/33197203): fail if it's not called by same uid as the top activity
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW_AUTO_FILL,
+ UserHandle.getCallingUserId(), id, boundaries));
+ }
+
+ @Override
public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) {
if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
synchronized (mLock) {
- final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
- if (service != null) {
- service.requestAutoFill(activityToken, extras, flags);
- }
+ requestAutoFillLocked(activityToken, userId, extras, flags);
}
}
@@ -307,7 +329,6 @@
if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);
synchronized (mLock) {
removeCachedServiceForUserLocked(userId);
- mUi.updateNotification(userId);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index ae21b07..83faf1b 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -163,7 +163,6 @@
}
final AssistStructure structure = resultData
.getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
- final Bundle data = resultData.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
final ServerCallback serverCallback;
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index 08e81d3..6fafd5d 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -16,40 +16,29 @@
package com.android.server.autofill;
import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
import static com.android.server.autofill.AutoFillManagerService.DEBUG;
import android.app.Activity;
-import android.app.AppGlobals;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.provider.Settings;
import android.service.autofill.AutoFillService;
-import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
-import android.view.autofill.Dataset;
import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
import android.view.autofill.FillResponse;
import android.widget.Toast;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.UiThread;
import java.util.Arrays;
@@ -69,9 +58,10 @@
AutoFillUI(Context context, AutoFillManagerService service, Object lock) {
mContext = context;
- mResolver = context.getContentResolver();
mService = service;
mLock = lock;
+
+ setNotificationListener();
}
/**
@@ -121,56 +111,60 @@
private static final String EXTRA_FILL_RESPONSE = "fill_response";
private static final String EXTRA_DATASET = "dataset";
- private static final String TYPE_EMULATE = "emulate";
private static final String TYPE_OPTIONS = "options";
private static final String TYPE_DELETE_CALLBACK = "delete_callback";
private static final String TYPE_PICK_DATASET = "pick_dataset";
private static final String TYPE_SAVE = "save";
- static final int MSG_SHOW_ALL_NOTIFICATIONS = 42;
- static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000;
-
+ @GuardedBy("mLock")
private BroadcastReceiver mNotificationReceiver;
- private final ContentResolver mResolver;
+ @GuardedBy("mLock")
private final AutoFillManagerService mService;
private final Object mLock;
// Hack used to generate unique pending intents
static int sResultCode = 0;
+ private void setNotificationListener() {
+ synchronized (mLock) {
+ if (mNotificationReceiver == null) {
+ mNotificationReceiver = new NotificationReceiver();
+ mContext.registerReceiver(mNotificationReceiver,
+ new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
+ }
+ }
+ }
+
final class NotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
- final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
- if (service == null) {
- Slog.w(TAG, "no auto-fill service for user " + userId);
- return;
- }
-
- final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1);
- final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
- if (type == null) {
- Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
- return;
- }
- final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
- final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
- final Bundle datasetArgs = dataset == null ? null : dataset.getExtras();
- final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras();
-
- // Bundle sent on AutoFillService methods - only set if service provided a bundle
- final Bundle extras = (datasetArgs == null && fillDataArgs == null)
- ? null : new Bundle();
-
- if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
- + ", callbackId=" + callbackId);
synchronized (mLock) {
+ final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "no auto-fill service for user " + userId);
+ return;
+ }
+
+ final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1);
+ final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
+ if (type == null) {
+ Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
+ return;
+ }
+ final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
+ final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
+ final Bundle datasetArgs = dataset == null ? null : dataset.getExtras();
+ final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras();
+
+ // Bundle sent on AutoFillService methods - only set if service provided a bundle
+ final Bundle extras = (datasetArgs == null && fillDataArgs == null)
+ ? null : new Bundle();
+
+ if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
+ + ", callbackId=" + callbackId);
switch (type) {
- case TYPE_EMULATE:
- service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_FILL);
- break;
case TYPE_SAVE:
if (datasetArgs != null) {
if (DEBUG) Log.d(TAG, "filldata args on save notificataion: " +
@@ -210,54 +204,6 @@
}
}
- private ComponentName getProviderForUser(int userId) {
- ComponentName serviceComponent = null;
- ServiceInfo serviceInfo = null;
- final String componentName = Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.AUTO_FILL_SERVICE, userId);
- if (!TextUtils.isEmpty(componentName)) {
- try {
- serviceComponent = ComponentName.unflattenFromString(componentName);
- serviceInfo =
- AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, userId);
- } catch (RuntimeException | RemoteException e) {
- Slog.wtf(TAG, "Bad auto-fill service name " + componentName, e);
- return null;
- }
- }
-
- if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
- + serviceComponent + ", info: " + serviceInfo);
- if (serviceInfo == null) {
- Slog.w(TAG, "no service info for " + serviceComponent);
- return null;
- }
- return serviceComponent;
- }
-
- void showAllNotifications() {
- final UserManager userManager =
- (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-
- final List<UserInfo> allUsers = userManager.getUsers(true);
-
- for (UserInfo user : allUsers) {
- final ComponentName serviceComponent = getProviderForUser(user.id);
- if (serviceComponent != null) {
- showMainNotification(serviceComponent, user.id);
- }
- }
- }
-
- void updateNotification(int userId) {
- final ComponentName serviceComponent = getProviderForUser(userId);
- if (serviceComponent == null) {
- cancelMainNotification(userId);
- } else {
- showMainNotification(serviceComponent, userId);
- }
- }
-
private static Intent newNotificationIntent(int userId, String type) {
final Intent intent = new Intent(NOTIFICATION_AUTO_FILL_INTENT);
intent.putExtra(EXTRA_USER_ID, userId);
@@ -296,65 +242,6 @@
}
/**
- * Shows a permanent notification that triggers the auto-fill workflow for the given user.
- *
- * <p>It emulates calling the auto-fill service when the IME is shown.
- */
- private void showMainNotification(ComponentName serviceComponent, int userId) {
- if (DEBUG) Log.d(TAG, "showNotification() for " + userId + ": " + serviceComponent);
-
- synchronized (mLock) {
- if (mNotificationReceiver == null) {
- mNotificationReceiver = new NotificationReceiver();
- mContext.registerReceiver(mNotificationReceiver,
- new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
- }
- }
-
- final Intent fillIntent = newNotificationIntent(userId, TYPE_EMULATE);
- final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext,
- -1, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- final String packageName = serviceComponent.getPackageName();
- String providerName = null;
- final PackageManager pm = mContext.getPackageManager();
- try {
- final ApplicationInfo info = pm.getApplicationInfoAsUser(packageName, 0, userId);
- if (info != null) {
- providerName = pm.getApplicationLabel(info).toString();
- }
- } catch (Exception e) {
- providerName = packageName;
- }
- final String title = "AutoFill IME Emulation";
- final String subTitle = "Tap notification to start auto-fill workflow (by '" + providerName
- + "' on top activity on user " + userId + ".\n"
- + "Once provider replies, a new notification will show your options.";
-
- final Notification notification = new Notification.Builder(mContext)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .setOngoing(true)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setLocalOnly(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setContentTitle(title)
- .setStyle(new Notification.BigTextStyle().bigText(subTitle))
- .setContentIntent(fillPendingIntent)
- .build();
- NotificationManager.from(mContext).notify(TYPE_EMULATE, userId, notification);
- }
-
- /**
- * Cancels the permament notification created by
- * {@link #showMainNotification(ComponentName, int)}.
- */
- private void cancelMainNotification(int userId) {
- if (DEBUG) Log.d(TAG, "cancelNotificationLocked(): " + userId);
- NotificationManager.from(mContext).cancel(TYPE_EMULATE, userId);
- }
-
- /**
* Shows a notification with the results of an auto-fill request, using notications actions
* to emulate the auto-fill bar buttons displaying the dataset names.
*/
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 0e07ec0..b1560e6 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -275,7 +275,7 @@
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
- Slog.e(TAG, "Bad device idle settings", e);
+ Slog.e(TAG, "Bad alarm manager settings", e);
}
MIN_FUTURITY = mParser.getLong(KEY_MIN_FUTURITY, DEFAULT_MIN_FUTURITY);
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 440ac90..8f99127 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -45,12 +45,16 @@
import android.service.dreams.Sandman;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
+import android.text.TextUtils;
import android.util.Slog;
import android.view.WindowManagerInternal;
import android.view.WindowManagerPolicy;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
import com.android.internal.R;
import com.android.internal.app.DisableCarModeActivity;
@@ -343,6 +347,28 @@
}
@Override
+ public String[] getAvailableThemes() {
+ if (getContext().checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_THEME_OVERLAY)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.e(TAG, "getAvailableThemes requires MODIFY_THEME_OVERLAY permission");
+ return null;
+ }
+ String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
+ if (TextUtils.isEmpty(def)) {
+ def = null;
+ }
+ String[] fileList = new File("/vendor/overlay").list();
+ if (fileList == null) return new String[0];
+ ArrayList<String> options = new ArrayList(fileList.length + 1);
+ Collections.addAll(options, fileList);
+ if (!options.contains(def)) {
+ options.add(0, def);
+ }
+ return options.toArray(new String[options.size()]);
+ }
+
+ @Override
public int getNightMode() {
synchronized (mLock) {
return mNightMode;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
new file mode 100644
index 0000000..3c90f93
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+/**
+ * Settings constants that can modify the activity manager's behavior.
+ */
+final class ActivityManagerConstants extends ContentObserver {
+ // Key names stored in the settings value.
+ private static final String KEY_ENFORCE_BG_CHECK = "enforce_bg_check";
+ private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+
+ private static final boolean DEFAULT_ENFORCE_BG_CHECK = SystemProperties.getBoolean(
+ "debug.bgcheck", false);
+ private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
+
+ // Enforce background check on apps targeting O?
+ public boolean ENFORCE_BG_CHECK = DEFAULT_ENFORCE_BG_CHECK;
+
+ // Maximum number of cached processes we will allow.
+ public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
+
+ private final ActivityManagerService mService;
+ private ContentResolver mResolver;
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ private int mOverrideMaxCachedProcesses = -1;
+
+ // The maximum number of cached processes we will keep around before killing them.
+ // NOTE: this constant is *only* a control to not let us go too crazy with
+ // keeping around processes on devices with large amounts of RAM. For devices that
+ // are tighter on RAM, the out of memory killer is responsible for killing background
+ // processes as RAM is needed, and we should *never* be relying on this limit to
+ // kill them. Also note that this limit only applies to cached background processes;
+ // we have no limit on the number of service, visible, foreground, or other such
+ // processes and the number of those processes does not count against the cached
+ // process limit.
+ public int CUR_MAX_CACHED_PROCESSES;
+
+ // The maximum number of empty app processes we will let sit around.
+ public int CUR_MAX_EMPTY_PROCESSES;
+
+ // The number of empty apps at which we don't consider it necessary to do
+ // memory trimming.
+ public int CUR_TRIM_EMPTY_PROCESSES;
+
+ // The number of cached at which we don't consider it necessary to do
+ // memory trimming.
+ public int CUR_TRIM_CACHED_PROCESSES;
+
+ public ActivityManagerConstants(ActivityManagerService service, Handler handler) {
+ super(handler);
+ mService = service;
+ updateMaxCachedProcesses();
+ }
+
+ public void start(ContentResolver resolver) {
+ mResolver = resolver;
+ mResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.ACTIVITY_MANAGER_CONSTANTS), false, this);
+ updateConstants();
+ }
+
+ public void setOverrideMaxCachedProcesses(int value) {
+ mOverrideMaxCachedProcesses = value;
+ updateMaxCachedProcesses();
+ }
+
+ public int getOverrideMaxCachedProcesses() {
+ return mOverrideMaxCachedProcesses;
+ }
+
+ public static int computeEmptyProcessLimit(int totalProcessLimit) {
+ return totalProcessLimit/2;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ synchronized (mService) {
+ try {
+ mParser.setString(Settings.Global.getString(mResolver,
+ Settings.Global.ACTIVITY_MANAGER_CONSTANTS));
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);
+ }
+
+ ENFORCE_BG_CHECK = mParser.getBoolean(KEY_ENFORCE_BG_CHECK, DEFAULT_ENFORCE_BG_CHECK);
+ MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,
+ DEFAULT_MAX_CACHED_PROCESSES);
+ updateMaxCachedProcesses();
+ }
+ }
+
+ private void updateMaxCachedProcesses() {
+ CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
+ ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses;
+ CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
+
+ // Note the trim levels do NOT depend on the override process limit, we want
+ // to consider the same level the point where we do trimming regardless of any
+ // additional enforced limit.
+ final int rawMaxEmptyProcesses = computeEmptyProcessLimit(MAX_CACHED_PROCESSES);
+ CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses/2;
+ CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
+ + Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":");
+
+ pw.print(" "); pw.print(KEY_ENFORCE_BG_CHECK); pw.print("=");
+ pw.println(ENFORCE_BG_CHECK);
+
+ pw.print(" "); pw.print(KEY_MAX_CACHED_PROCESSES); pw.print("=");
+ pw.println(MAX_CACHED_PROCESSES);
+
+ pw.println();
+ if (mOverrideMaxCachedProcesses >= 0) {
+ pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
+ }
+ pw.print(" CUR_MAX_CACHED_PROCESSES="); pw.println(CUR_MAX_CACHED_PROCESSES);
+ pw.print(" CUR_MAX_EMPTY_PROCESSES="); pw.println(CUR_MAX_EMPTY_PROCESSES);
+ pw.print(" CUR_TRIM_EMPTY_PROCESSES="); pw.println(CUR_TRIM_EMPTY_PROCESSES);
+ pw.print(" CUR_TRIM_CACHED_PROCESSES="); pw.println(CUR_TRIM_CACHED_PROCESSES);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a5742c2..43afbd1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -761,11 +761,6 @@
ProcessRecord mHeavyWeightProcess = null;
/**
- * Are we enforcing background restrictions?
- */
- final boolean mEnforceBackgroundCheck;
-
- /**
* Non-persistent app uid whitelist for background restrictions
*/
int[] mBackgroundUidWhitelist = new int[] {
@@ -1515,9 +1510,6 @@
*/
boolean mBooted = false;
- int mProcessLimit = ProcessList.MAX_CACHED_APPS;
- int mProcessLimitOverride = -1;
-
WindowManagerService mWindowManager;
final ActivityThread mSystemThread;
@@ -1639,6 +1631,8 @@
final MainHandler mHandler;
final UiHandler mUiHandler;
+ final ActivityManagerConstants mConstants;
+
PackageManagerInternal mPackageManagerInt;
// VoiceInteraction session ID that changes for each new request except when
@@ -2620,10 +2614,9 @@
mPermissionReviewRequired = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_permissionReviewRequired);
- mEnforceBackgroundCheck = SystemProperties.getBoolean("debug.bgcheck", false);
mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
if (DEBUG_BACKGROUND_CHECK) {
- Slog.d(TAG, "Enforcing O+ bg restrictions: " + mEnforceBackgroundCheck);
+ Slog.d(TAG, "Enforcing O+ bg restrictions: " + mConstants.ENFORCE_BG_CHECK);
StringBuilder sb = new StringBuilder(200);
sb.append(" ");
for (String a : mBackgroundLaunchBroadcasts) {
@@ -2639,6 +2632,8 @@
mHandler = new MainHandler(mHandlerThread.getLooper());
mUiHandler = new UiHandler();
+ mConstants = new ActivityManagerConstants(this, mHandler);
+
/* static; one-time init here */
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
@@ -7276,9 +7271,6 @@
/**
* Whitelists {@code targetUid} to temporarily bypass Power Save mode.
- *
- * <p>{@code callerUid} must be allowed to request such whitelist by calling
- * {@link #addTempPowerSaveWhitelistGrantorUid(int)}.
*/
void tempWhitelistAppForPowerSave(int callerPid, int callerUid, int targetUid, long duration) {
if (DEBUG_WHITELISTS) {
@@ -7470,8 +7462,7 @@
enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
"setProcessLimit()");
synchronized (this) {
- mProcessLimit = max < 0 ? ProcessList.MAX_CACHED_APPS : max;
- mProcessLimitOverride = max;
+ mConstants.setOverrideMaxCachedProcesses(max);
}
trimApplications();
}
@@ -7479,7 +7470,7 @@
@Override
public int getProcessLimit() {
synchronized (this) {
- return mProcessLimitOverride;
+ return mConstants.getOverrideMaxCachedProcesses();
}
}
@@ -8034,7 +8025,7 @@
// Unified app-op and target sdk check
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Apps that target O+ are always subject to background check
- if (mEnforceBackgroundCheck && packageTargetSdk >= Build.VERSION_CODES.O) {
+ if (mConstants.ENFORCE_BG_CHECK && packageTargetSdk >= Build.VERSION_CODES.O) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
}
@@ -11559,6 +11550,7 @@
mSystemThread.installSystemProviders(providers);
}
+ mConstants.start(mContext.getContentResolver());
mCoreSettingsObserver = new CoreSettingsObserver(this);
mFontScaleSettingObserver = new FontScaleSettingObserver();
@@ -14493,6 +14485,10 @@
synchronized (this) {
dumpAssociationsLocked(fd, pw, args, opti, true, dumpClient, dumpPackage);
}
+ } else if ("settings".equals(cmd)) {
+ synchronized (this) {
+ mConstants.dump(pw);
+ }
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
ActiveServices.ServiceDumper dumper;
@@ -14533,6 +14529,11 @@
} else if (dumpClient) {
ActiveServices.ServiceDumper sdumper;
synchronized (this) {
+ mConstants.dump(pw);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
@@ -14591,6 +14592,11 @@
} else {
synchronized (this) {
+ mConstants.dump(pw);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
@@ -21392,17 +21398,8 @@
mNewNumServiceProcs = 0;
mNewNumAServiceProcs = 0;
- final int emptyProcessLimit;
- final int cachedProcessLimit;
- if (mProcessLimit <= 0) {
- emptyProcessLimit = cachedProcessLimit = 0;
- } else if (mProcessLimit == 1) {
- emptyProcessLimit = 1;
- cachedProcessLimit = 0;
- } else {
- emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit);
- cachedProcessLimit = mProcessLimit - emptyProcessLimit;
- }
+ final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
+ final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit;
// Let's determine how many processes we have running vs.
// how many slots we have for background processes; we may want
@@ -21510,7 +21507,7 @@
}
break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
- if (numEmpty > ProcessList.TRIM_EMPTY_APPS
+ if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
&& app.lastActivityTime < oldTime) {
app.kill("empty for "
+ ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
@@ -21563,8 +21560,8 @@
// memory they want.
final int numCachedAndEmpty = numCached + numEmpty;
int memFactor;
- if (numCached <= ProcessList.TRIM_CACHED_APPS
- && numEmpty <= ProcessList.TRIM_EMPTY_APPS) {
+ if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES
+ && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {
if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
} else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 1a2a31b..df8dd6b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2406,6 +2406,7 @@
pw.println(" provider [COMP_SPEC]: provider client-side state");
pw.println(" s[ervices] [COMP_SPEC ...]: service state");
pw.println(" as[sociations]: tracked app associations");
+ pw.println(" settings: currently applied config settings");
pw.println(" service [COMP_SPEC]: service client-side state");
pw.println(" package [PACKAGE_NAME]: all state related to given package");
pw.println(" all: dump all activities");
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 973951e..98b5835c 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -557,6 +557,7 @@
void getWindowContainerBounds(Rect outBounds) {
if (mWindowContainerController != null) {
mWindowContainerController.getBounds(outBounds);
+ return;
}
outBounds.setEmpty();
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 1322ecf..40effff 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -138,31 +138,9 @@
// without empty apps being able to push them out of memory.
static final int MIN_CACHED_APPS = 2;
- // The maximum number of cached processes we will keep around before killing them.
- // NOTE: this constant is *only* a control to not let us go too crazy with
- // keeping around processes on devices with large amounts of RAM. For devices that
- // are tighter on RAM, the out of memory killer is responsible for killing background
- // processes as RAM is needed, and we should *never* be relying on this limit to
- // kill them. Also note that this limit only applies to cached background processes;
- // we have no limit on the number of service, visible, foreground, or other such
- // processes and the number of those processes does not count against the cached
- // process limit.
- static final int MAX_CACHED_APPS = 32;
-
// We allow empty processes to stick around for at most 30 minutes.
static final long MAX_EMPTY_TIME = 30*60*1000;
- // The maximum number of empty app processes we will let sit around.
- private static final int MAX_EMPTY_APPS = computeEmptyProcessLimit(MAX_CACHED_APPS);
-
- // The number of empty apps at which we don't consider it necessary to do
- // memory trimming.
- static final int TRIM_EMPTY_APPS = MAX_EMPTY_APPS/2;
-
- // The number of cached at which we don't consider it necessary to do
- // memory trimming.
- static final int TRIM_CACHED_APPS = (MAX_CACHED_APPS-MAX_EMPTY_APPS)/3;
-
// Threshold of number of cached+empty where we consider memory critical.
static final int TRIM_CRITICAL_THRESHOLD = 3;
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 8d4f0a9..5fed397 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -40,6 +40,7 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -50,18 +51,106 @@
import java.util.HashSet;
import java.util.List;
+import java.lang.Thread;
+import java.lang.Runnable;
+import java.lang.InterruptedException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+// The following class is Android Emulator specific. It is used to read and
+// write contents of the host system's clipboard.
+class HostClipboardMonitor implements Runnable {
+ public interface HostClipboardCallback {
+ void onHostClipboardUpdated(String contents);
+ }
+
+ private RandomAccessFile mPipe = null;
+ private HostClipboardCallback mHostClipboardCallback;
+ private static final String PIPE_NAME = "pipe:clipboard";
+ private static final String PIPE_DEVICE = "/dev/qemu_pipe";
+
+ private void openPipe() {
+ try {
+ // String.getBytes doesn't include the null terminator,
+ // but the QEMU pipe device requires the pipe service name
+ // to be null-terminated.
+ byte[] b = new byte[PIPE_NAME.length() + 1];
+ b[PIPE_NAME.length()] = 0;
+ System.arraycopy(
+ PIPE_NAME.getBytes(),
+ 0,
+ b,
+ 0,
+ PIPE_NAME.length());
+ mPipe = new RandomAccessFile(PIPE_DEVICE, "rw");
+ mPipe.write(b);
+ } catch (IOException e) {
+ try {
+ if (mPipe != null) mPipe.close();
+ } catch (IOException ee) {}
+ mPipe = null;
+ }
+ }
+
+ public HostClipboardMonitor(HostClipboardCallback cb) {
+ mHostClipboardCallback = cb;
+ }
+
+ @Override
+ public void run() {
+ while(!Thread.interrupted()) {
+ try {
+ // There's no guarantee that QEMU pipes will be ready at the moment
+ // this method is invoked. We simply try to get the pipe open and
+ // retry on failure indefinitely.
+ while (mPipe == null) {
+ openPipe();
+ Thread.sleep(100);
+ }
+ int size = mPipe.readInt();
+ size = Integer.reverseBytes(size);
+ byte[] receivedData = new byte[size];
+ mPipe.readFully(receivedData);
+ mHostClipboardCallback.onHostClipboardUpdated(
+ new String(receivedData));
+ } catch (IOException e) {
+ try {
+ mPipe.close();
+ } catch (IOException ee) {}
+ mPipe = null;
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ public void setHostClipboard(String content) {
+ try {
+ if (mPipe != null) {
+ mPipe.writeInt(Integer.reverseBytes(content.getBytes().length));
+ mPipe.write(content.getBytes());
+ }
+ } catch(IOException e) {
+ Slog.e("HostClipboardMonitor",
+ "Failed to set host clipboard " + e.getMessage());
+ }
+ }
+}
+
/**
* Implementation of the clipboard for copy and paste.
*/
public class ClipboardService extends SystemService {
private static final String TAG = "ClipboardService";
+ private static final boolean IS_EMULATOR =
+ SystemProperties.getBoolean("ro.kernel.qemu", false);
private final IActivityManager mAm;
private final IUserManager mUm;
private final PackageManager mPm;
private final AppOpsManager mAppOps;
private final IBinder mPermissionOwner;
+ private HostClipboardMonitor mHostClipboardMonitor = null;
+ private Thread mHostMonitorThread = null;
private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
@@ -82,6 +171,23 @@
Slog.w("clipboard", "AM dead", e);
}
mPermissionOwner = permOwner;
+ if (IS_EMULATOR) {
+ mHostClipboardMonitor = new HostClipboardMonitor(
+ new HostClipboardMonitor.HostClipboardCallback() {
+ @Override
+ public void onHostClipboardUpdated(String contents){
+ ClipData clip =
+ new ClipData("host clipboard",
+ new String[]{"text/plain"},
+ new ClipData.Item(contents));
+ synchronized(mClipboards) {
+ setPrimaryClipInternal(getClipboard(0), clip);
+ }
+ }
+ });
+ mHostMonitorThread = new Thread(mHostClipboardMonitor);
+ mHostMonitorThread.start();
+ }
}
@Override
@@ -142,6 +248,11 @@
if (clip != null && clip.getItemCount() <= 0) {
throw new IllegalArgumentException("No items");
}
+ if (clip.getItemAt(0).getText() != null &&
+ mHostClipboardMonitor != null) {
+ mHostClipboardMonitor.setHostClipboard(
+ clip.getItemAt(0).getText().toString());
+ }
final int callingUid = Binder.getCallingUid();
if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
callingPackage) != AppOpsManager.MODE_ALLOWED) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e1b6c87..238866a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -57,6 +57,7 @@
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.util.EventLog;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -522,6 +523,65 @@
// True if we are currently in VR Mode.
private boolean mIsVrModeEnabled;
+ /**
+ * All times are in milliseconds. These constants are kept synchronized with the system
+ * global Settings. Any access to this class or its fields should be done while
+ * holding the PowerManagerService.mLock lock.
+ */
+ private final class Constants extends ContentObserver {
+ // Key names stored in the settings value.
+ private static final String KEY_NO_CACHED_WAKE_LOCKS = "no_cached_wake_locks";
+
+ private static final boolean DEFAULT_NO_CACHED_WAKE_LOCKS = true;
+
+ // Prevent processes that are cached from holding wake locks?
+ public boolean NO_CACHED_WAKE_LOCKS = DEFAULT_NO_CACHED_WAKE_LOCKS;
+
+ private ContentResolver mResolver;
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ public Constants(Handler handler) {
+ super(handler);
+ }
+
+ public void start(ContentResolver resolver) {
+ mResolver = resolver;
+ mResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.POWER_MANAGER_CONSTANTS), false, this);
+ updateConstants();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ synchronized (mLock) {
+ try {
+ mParser.setString(Settings.Global.getString(mResolver,
+ Settings.Global.POWER_MANAGER_CONSTANTS));
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e(TAG, "Bad alarm manager settings", e);
+ }
+
+ NO_CACHED_WAKE_LOCKS = mParser.getBoolean(KEY_NO_CACHED_WAKE_LOCKS,
+ DEFAULT_NO_CACHED_WAKE_LOCKS);
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println(" Settings " + Settings.Global.POWER_MANAGER_CONSTANTS + ":");
+
+ pw.print(" "); pw.print(KEY_NO_CACHED_WAKE_LOCKS); pw.print("=");
+ pw.println(NO_CACHED_WAKE_LOCKS);
+ }
+ }
+
+ final Constants mConstants;
+
private native void nativeInit();
private static native void nativeAcquireSuspendBlocker(String name);
@@ -538,6 +598,7 @@
Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
mHandlerThread.start();
mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
+ mConstants = new Constants(mHandler);
synchronized (mLock) {
mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
@@ -616,6 +677,9 @@
mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"),
mPolicy);
+ final ContentResolver resolver = mContext.getContentResolver();
+ mConstants.start(resolver);
+
mWirelessChargerDetector = new WirelessChargerDetector(sensorManager,
createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"),
mHandler);
@@ -629,7 +693,6 @@
mDisplayPowerCallbacks, mHandler, sensorManager);
// Register for settings changes.
- final ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.SCREENSAVER_ENABLED),
false, mSettingsObserver, UserHandle.USER_ALL);
@@ -2795,7 +2858,7 @@
state.mProcState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
disabled = true;
}
- } else {
+ } else if (mConstants.NO_CACHED_WAKE_LOCKS) {
disabled = !wakeLock.mUidState.mActive &&
wakeLock.mUidState.mProcState
!= ActivityManager.PROCESS_STATE_NONEXISTENT &&
@@ -3005,6 +3068,7 @@
final WirelessChargerDetector wcd;
synchronized (mLock) {
pw.println("Power Manager State:");
+ mConstants.dump(pw);
pw.println(" mDirty=0x" + Integer.toHexString(mDirty));
pw.println(" mWakefulness=" + PowerManagerInternal.wakefulnessToString(mWakefulness));
pw.println(" mWakefulnessChanging=" + mWakefulnessChanging);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 13358da8..5e458d4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3199,8 +3199,10 @@
if (task == null) {
return false;
}
+ if (!StackId.isStackAffectedByDragResizing(getStackId())) {
+ return false;
+ }
if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
-
// Floating windows never enter drag resize mode.
return false;
}
diff --git a/services/tests/servicestests/src/com/android/server/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
index 9316516..031a3b3 100644
--- a/services/tests/servicestests/src/com/android/server/MockStorageManager.java
+++ b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
@@ -32,6 +32,8 @@
import android.util.ArrayMap;
import android.util.Pair;
+import com.android.internal.os.AppFuseMount;
+
import junit.framework.AssertionFailedError;
import java.util.ArrayList;
@@ -463,12 +465,28 @@
}
@Override
- public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+ public void fstrim(int flags) throws RemoteException {
throw new UnsupportedOperationException();
}
@Override
- public void fstrim(int flags) throws RemoteException {
+ public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode)
+ throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCacheQuotaBytes(String volumeUuid, int uid) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getCacheSizeBytes(String volumeUuid, int uid) throws RemoteException {
throw new UnsupportedOperationException();
}