Merge "Add a check to investigate b/37893215." into oc-dev
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4dea0e0..6fa0a6d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7439,14 +7439,6 @@
@Override
final public boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width,
int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) {
- final Rect actualAnchorBounds = new Rect();
- anchor.getBoundsOnScreen(actualAnchorBounds);
-
- final int offsetX = (anchorBounds != null)
- ? anchorBounds.left - actualAnchorBounds.left : 0;
- int offsetY = (anchorBounds != null)
- ? anchorBounds.bottom - actualAnchorBounds.bottom : 0;
-
final boolean wasShowing;
if (mAutofillPopupWindow == null) {
@@ -7455,8 +7447,7 @@
} else {
wasShowing = mAutofillPopupWindow.isShowing();
}
- mAutofillPopupWindow.update(anchor, offsetX, offsetY, width, height, anchorBounds,
- actualAnchorBounds);
+ mAutofillPopupWindow.update(anchor, 0, 0, width, height, anchorBounds);
return !wasShowing && mAutofillPopupWindow.isShowing();
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index b84161c..0708b0b 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1214,7 +1214,7 @@
RuntimeException mUnregisterLocation;
boolean mForgotten;
- final class Args extends BroadcastReceiver.PendingResult implements Runnable {
+ final class Args extends BroadcastReceiver.PendingResult {
private Intent mCurIntent;
private final boolean mOrdered;
private boolean mDispatched;
@@ -1228,66 +1228,68 @@
mCurIntent = intent;
mOrdered = ordered;
}
-
- public void run() {
- final BroadcastReceiver receiver = mReceiver;
- final boolean ordered = mOrdered;
-
- if (ActivityThread.DEBUG_BROADCAST) {
- int seq = mCurIntent.getIntExtra("seq", -1);
- Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
- + " seq=" + seq + " to " + mReceiver);
- Slog.i(ActivityThread.TAG, " mRegistered=" + mRegistered
- + " mOrderedHint=" + ordered);
- }
-
- final IActivityManager mgr = ActivityManager.getService();
- final Intent intent = mCurIntent;
- if (intent == null) {
- Log.wtf(TAG, "Null intent being dispatched, mDispatched=" + mDispatched
- + ": run() previously called at "
- + Log.getStackTraceString(mPreviousRunStacktrace));
- }
- mCurIntent = null;
- mDispatched = true;
- mPreviousRunStacktrace = new Throwable("Previous stacktrace");
- if (receiver == null || intent == null || mForgotten) {
- if (mRegistered && ordered) {
- if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
- "Finishing null broadcast to " + mReceiver);
- sendFinished(mgr);
- }
- return;
- }
+ public final Runnable getRunnable() {
+ return () -> {
+ final BroadcastReceiver receiver = mReceiver;
+ final boolean ordered = mOrdered;
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg");
- try {
- ClassLoader cl = mReceiver.getClass().getClassLoader();
- intent.setExtrasClassLoader(cl);
- intent.prepareToEnterProcess();
- setExtrasClassLoader(cl);
- receiver.setPendingResult(this);
- receiver.onReceive(mContext, intent);
- } catch (Exception e) {
- if (mRegistered && ordered) {
- if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
- "Finishing failed broadcast to " + mReceiver);
- sendFinished(mgr);
+ if (ActivityThread.DEBUG_BROADCAST) {
+ int seq = mCurIntent.getIntExtra("seq", -1);
+ Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
+ + " seq=" + seq + " to " + mReceiver);
+ Slog.i(ActivityThread.TAG, " mRegistered=" + mRegistered
+ + " mOrderedHint=" + ordered);
}
- if (mInstrumentation == null ||
- !mInstrumentation.onException(mReceiver, e)) {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- throw new RuntimeException(
- "Error receiving broadcast " + intent
- + " in " + mReceiver, e);
+
+ final IActivityManager mgr = ActivityManager.getService();
+ final Intent intent = mCurIntent;
+ if (intent == null) {
+ Log.wtf(TAG, "Null intent being dispatched, mDispatched=" + mDispatched
+ + ": run() previously called at "
+ + Log.getStackTraceString(mPreviousRunStacktrace));
}
- }
-
- if (receiver.getPendingResult() != null) {
- finish();
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ mCurIntent = null;
+ mDispatched = true;
+ mPreviousRunStacktrace = new Throwable("Previous stacktrace");
+ if (receiver == null || intent == null || mForgotten) {
+ if (mRegistered && ordered) {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing null broadcast to " + mReceiver);
+ sendFinished(mgr);
+ }
+ return;
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg");
+ try {
+ ClassLoader cl = mReceiver.getClass().getClassLoader();
+ intent.setExtrasClassLoader(cl);
+ intent.prepareToEnterProcess();
+ setExtrasClassLoader(cl);
+ receiver.setPendingResult(this);
+ receiver.onReceive(mContext, intent);
+ } catch (Exception e) {
+ if (mRegistered && ordered) {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing failed broadcast to " + mReceiver);
+ sendFinished(mgr);
+ }
+ if (mInstrumentation == null ||
+ !mInstrumentation.onException(mReceiver, e)) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ throw new RuntimeException(
+ "Error receiving broadcast " + intent
+ + " in " + mReceiver, e);
+ }
+ }
+
+ if (receiver.getPendingResult() != null) {
+ finish();
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ };
}
}
@@ -1356,7 +1358,7 @@
+ " seq=" + seq + " to " + mReceiver);
}
}
- if (intent == null || !mActivityThread.post(args)) {
+ if (intent == null || !mActivityThread.post(args.getRunnable())) {
if (mRegistered && ordered) {
IActivityManager mgr = ActivityManager.getService();
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0041879..06509ae 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4257,15 +4257,17 @@
* Construct a RemoteViews for the final notification header only. This will not be
* colorized.
*
+ * @param ambient if true, generate the header for the ambient display layout.
* @hide
*/
- public RemoteViews makeNotificationHeader() {
+ public RemoteViews makeNotificationHeader(boolean ambient) {
Boolean colorized = (Boolean) mN.extras.get(EXTRA_COLORIZED);
mN.extras.putBoolean(EXTRA_COLORIZED, false);
RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
- R.layout.notification_template_header);
+ ambient ? R.layout.notification_template_ambient_header
+ : R.layout.notification_template_header);
resetNotificationHeader(header);
- bindNotificationHeader(header, false /* ambient */);
+ bindNotificationHeader(header, ambient);
if (colorized != null) {
mN.extras.putBoolean(EXTRA_COLORIZED, colorized);
} else {
@@ -4407,7 +4409,7 @@
}
}
- RemoteViews header = makeNotificationHeader();
+ RemoteViews header = makeNotificationHeader(false /* ambient */);
header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
if (summary != null) {
mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary);
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index d0c6397..d3a3560 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -97,6 +97,9 @@
/** @hide */
public static final int FLAG_RETURNED_BY_SERVICE = 1 << 10;
+ /** @hide When this is set, the bitmap icon is waiting to be saved. */
+ public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -110,7 +113,8 @@
FLAG_STRINGS_RESOLVED,
FLAG_IMMUTABLE,
FLAG_ADAPTIVE_BITMAP,
- FLAG_RETURNED_BY_SERVICE
+ FLAG_RETURNED_BY_SERVICE,
+ FLAG_ICON_FILE_PENDING_SAVE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
@@ -1471,6 +1475,21 @@
return hasFlags(FLAG_ADAPTIVE_BITMAP);
}
+ /** @hide */
+ public boolean isIconPendingSave() {
+ return hasFlags(FLAG_ICON_FILE_PENDING_SAVE);
+ }
+
+ /** @hide */
+ public void setIconPendingSave() {
+ addFlags(FLAG_ICON_FILE_PENDING_SAVE);
+ }
+
+ /** @hide */
+ public void clearIconPendingSave() {
+ clearFlags(FLAG_ICON_FILE_PENDING_SAVE);
+ }
+
/**
* Return whether a shortcut only contains "key" information only or not. If true, only the
* following fields are available.
@@ -1534,7 +1553,12 @@
return mIconResId;
}
- /** @hide */
+ /**
+ * Bitmap path. Note this will be null even if {@link #hasIconFile()} is set when the save
+ * is pending. Use {@link #isIconPendingSave()} to check it.
+ *
+ * @hide
+ */
public String getBitmapPath() {
return mBitmapPath;
}
@@ -1780,6 +1804,9 @@
if (hasIconFile()) {
sb.append("If");
}
+ if (isIconPendingSave()) {
+ sb.append("^");
+ }
if (hasIconResource()) {
sb.append("Ir");
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index d61fb97..fccc877 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -938,7 +938,6 @@
private void sendAuthenticatedFailed() {
if (mAuthenticationCallback != null) {
mAuthenticationCallback.onAuthenticationFailed();
- mAuthenticationCallback = null;
}
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 28bdacf..c5c743b 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -427,7 +427,13 @@
* @param size The new number of bytes in the Parcel.
*/
public final void setDataSize(int size) {
- updateNativeSize(nativeSetDataSize(mNativePtr, size));
+ // STOPSHIP: Try/catch for exception is for temporary debug. Remove once bug resolved
+ try {
+ updateNativeSize(nativeSetDataSize(mNativePtr, size));
+ } catch (IllegalArgumentException iae) {
+ Log.e(TAG,"Caught Exception representing a known bug in Parcel",iae);
+ Log.wtfStack(TAG, "This flow is using SetDataSize incorrectly");
+ }
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0840549..cf44c7d 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8315,6 +8315,17 @@
public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
/**
+ * Value to specify if Wi-Fi Wakeup is available.
+ *
+ * Wi-Fi Wakeup will only operate if it's available
+ * and {@link #WIFI_WAKEUP_ENABLED} is true.
+ *
+ * Type: int (0 for false, 1 for true)
+ * @hide
+ */
+ public static final String WIFI_WAKEUP_AVAILABLE = "wifi_wakeup_available";
+
+ /**
* Value to specify whether network quality scores and badging should be shown in the UI.
*
* Type: int (0 for false, 1 for true)
@@ -9222,6 +9233,23 @@
public static final String SHORTCUT_MANAGER_CONSTANTS = "shortcut_manager_constants";
/**
+ * DevicePolicyManager specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * <pre>
+ * das_died_service_reconnect_backoff_sec (long)
+ * das_died_service_reconnect_backoff_increase (float)
+ * das_died_service_reconnect_max_backoff_sec (long)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * @hide
+ * see also com.android.server.devicepolicy.DevicePolicyConstants
+ */
+ public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants";
+
+ /**
* Get the key that retrieves a bluetooth headset's priority.
* @hide
*/
diff --git a/core/java/android/service/vr/VrListenerService.java b/core/java/android/service/vr/VrListenerService.java
index c76d793..5da4560 100644
--- a/core/java/android/service/vr/VrListenerService.java
+++ b/core/java/android/service/vr/VrListenerService.java
@@ -110,7 +110,7 @@
* transition.</p>
*
* @param component the {@link ComponentName} of the VR activity that the system has
- * switched to.
+ * switched to, or null if the system is displaying a 2D activity in VR compatibility mode.
*
* @see android.app.Activity#setVrModeEnabled
* @see android.R.attr#enableVrMode
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 79c81b2..249fd91 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -365,8 +365,9 @@
* <b>NOTIFICATION TYPES</b></br>
* </p>
* <p>
- * <b>Notification state changed</b> - represents the event showing
- * {@link android.app.Notification}.</br>
+ * <b>Notification state changed</b> - represents the event showing a transient piece of information
+ * to the user. This information may be a {@link android.app.Notification} or
+ * {@link android.widget.Toast}.</br>
* <em>Type:</em> {@link #TYPE_NOTIFICATION_STATE_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
@@ -374,18 +375,12 @@
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}.</li>
- * <li>{@link #getText()} - Text for providing more context.</li>
+ * <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}, if
+ * applicable.</li>
+ * <li>{@link #getText()} - Displayed text of the {@link android.widget.Toast}, if applicable,
+ * or may contain text from the {@link android.app.Notification}, although
+ * {@link #getParcelableData()} is a richer set of data for {@link android.app.Notification}.</li>
* </ul>
- * <em>Note:</em> This event type is not dispatched to descendants though
- * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
- * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
- * source {@link android.view.View} and the sub-tree rooted at it will not receive
- * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
- * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
- * text content to such events is by setting the
- * {@link android.R.styleable#View_contentDescription contentDescription} of the source
- * view.</br>
* </p>
* <p>
* <b>EXPLORATION TYPES</b></br>
diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java
index 056e540..cd16a24 100644
--- a/core/java/android/view/autofill/AutofillPopupWindow.java
+++ b/core/java/android/view/autofill/AutofillPopupWindow.java
@@ -19,11 +19,13 @@
import android.annotation.NonNull;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.IBinder;
import android.os.RemoteException;
import android.transition.Transition;
import android.util.Log;
import android.view.View;
import android.view.View.OnTouchListener;
+import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.PopupWindow;
@@ -84,18 +86,93 @@
* The effective {@code update} method that should be called by its clients.
*/
public void update(View anchor, int offsetX, int offsetY, int width, int height,
- Rect anchorBounds, Rect actualAnchorBounds) {
+ Rect virtualBounds) {
+ // If we are showing the popup for a virtual view we use a fake view which
+ // delegates to the anchor but present itself with the same bounds as the
+ // virtual view. This ensures that the location logic in popup works
+ // symmetrically when the dropdown is below and above the anchor.
+ final View actualAnchor;
+ if (virtualBounds != null) {
+ actualAnchor = new View(anchor.getContext()) {
+ @Override
+ public void getLocationOnScreen(int[] location) {
+ location[0] = virtualBounds.left;
+ location[1] = virtualBounds.top;
+ }
+
+ @Override
+ public int getAccessibilityViewId() {
+ return anchor.getAccessibilityViewId();
+ }
+
+ @Override
+ public ViewTreeObserver getViewTreeObserver() {
+ return anchor.getViewTreeObserver();
+ }
+
+ @Override
+ public IBinder getApplicationWindowToken() {
+ return anchor.getApplicationWindowToken();
+ }
+
+ @Override
+ public View getRootView() {
+ return anchor.getRootView();
+ }
+
+ @Override
+ public int getLayoutDirection() {
+ return anchor.getLayoutDirection();
+ }
+
+ @Override
+ public void getWindowDisplayFrame(Rect outRect) {
+ anchor.getWindowDisplayFrame(outRect);
+ }
+
+ @Override
+ public void addOnAttachStateChangeListener(
+ OnAttachStateChangeListener listener) {
+ anchor.addOnAttachStateChangeListener(listener);
+ }
+
+ @Override
+ public void removeOnAttachStateChangeListener(
+ OnAttachStateChangeListener listener) {
+ anchor.removeOnAttachStateChangeListener(listener);
+ }
+
+ @Override
+ public boolean isAttachedToWindow() {
+ return anchor.isAttachedToWindow();
+ }
+
+ @Override
+ public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
+ return anchor.requestRectangleOnScreen(rectangle, immediate);
+ }
+
+ @Override
+ public IBinder getWindowToken() {
+ return anchor.getWindowToken();
+ }
+ };
+
+ actualAnchor.setLeftTopRightBottom(
+ virtualBounds.left, virtualBounds.top,
+ virtualBounds.right, virtualBounds.bottom);
+ actualAnchor.setScrollX(anchor.getScrollX());
+ actualAnchor.setScrollY(anchor.getScrollY());
+ } else {
+ actualAnchor = anchor;
+ }
+
if (!isShowing()) {
setWidth(width);
setHeight(height);
- showAsDropDown(anchor, offsetX, offsetY);
+ showAsDropDown(actualAnchor, offsetX, offsetY);
} else {
- update(anchor, offsetX, offsetY, width, height);
- }
-
- if (anchorBounds != null && mWindowLayoutParams.y > anchorBounds.bottom) {
- offsetY = anchorBounds.bottom - actualAnchorBounds.bottom;
- update(anchor, offsetX, offsetY, width, height);
+ update(actualAnchor, offsetX, offsetY, width, height);
}
}
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index bf0601d..13ebe5c 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -17,6 +17,8 @@
package android.widget;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.StringRes;
import android.app.INotificationManager;
import android.app.ITransientNotification;
@@ -26,6 +28,7 @@
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -62,7 +65,7 @@
* <a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Toast Notifications</a> developer
* guide.</p>
* </div>
- */
+ */
public class Toast {
static final String TAG = "Toast";
static final boolean localLOGV = false;
@@ -99,8 +102,16 @@
* or {@link android.app.Activity} object.
*/
public Toast(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructs an empty Toast object. If looper is null, Looper.myLooper() is used.
+ * @hide
+ */
+ public Toast(@NonNull Context context, @Nullable Looper looper) {
mContext = context;
- mTN = new TN(context.getPackageName());
+ mTN = new TN(context.getPackageName(), looper);
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
@@ -170,7 +181,7 @@
public int getDuration() {
return mDuration;
}
-
+
/**
* Set the margins of the view.
*
@@ -226,7 +237,7 @@
public int getXOffset() {
return mTN.mX;
}
-
+
/**
* Return the Y offset in pixels to apply to the gravity's location.
*/
@@ -241,7 +252,7 @@
public WindowManager.LayoutParams getWindowParams() {
return mTN.mParams;
}
-
+
/**
* Make a standard toast that just contains a text view.
*
@@ -253,14 +264,24 @@
*
*/
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
- Toast result = new Toast(context);
+ return makeText(context, null, text, duration);
+ }
+
+ /**
+ * Make a standard toast to display using the specified looper.
+ * If looper is null, Looper.myLooper() is used.
+ * @hide
+ */
+ public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
+ @NonNull CharSequence text, @Duration int duration) {
+ Toast result = new Toast(context, looper);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
-
+
result.mNextView = v;
result.mDuration = duration;
@@ -290,7 +311,7 @@
public void setText(@StringRes int resId) {
setText(mContext.getText(resId));
}
-
+
/**
* Update the text in a Toast that was previously created using one of the makeText() methods.
* @param s The new text for the Toast.
@@ -327,34 +348,7 @@
private static final int SHOW = 0;
private static final int HIDE = 1;
private static final int CANCEL = 2;
- final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SHOW: {
- IBinder token = (IBinder) msg.obj;
- handleShow(token);
- break;
- }
- case HIDE: {
- handleHide();
- // Don't do this in handleHide() because it is also invoked by handleShow()
- mNextView = null;
- break;
- }
- case CANCEL: {
- handleHide();
- // Don't do this in handleHide() because it is also invoked by handleShow()
- mNextView = null;
- try {
- getService().cancelToast(mPackageName, TN.this);
- } catch (RemoteException e) {
- }
- break;
- }
- }
- }
- };
+ final Handler mHandler;
int mGravity;
int mX, mY;
@@ -373,7 +367,7 @@
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
- TN(String packageName) {
+ TN(String packageName, @Nullable Looper looper) {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
@@ -388,6 +382,45 @@
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mPackageName = packageName;
+
+ if (looper == null) {
+ // Use Looper.myLooper() if looper is not specified.
+ looper = Looper.myLooper();
+ if (looper == null) {
+ throw new RuntimeException(
+ "Can't toast on a thread that has not called Looper.prepare()");
+ }
+ }
+ mHandler = new Handler(looper, null) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW: {
+ IBinder token = (IBinder) msg.obj;
+ handleShow(token);
+ break;
+ }
+ case HIDE: {
+ handleHide();
+ // Don't do this in handleHide() because it is also invoked by
+ // handleShow()
+ mNextView = null;
+ break;
+ }
+ case CANCEL: {
+ handleHide();
+ // Don't do this in handleHide() because it is also invoked by
+ // handleShow()
+ mNextView = null;
+ try {
+ getService().cancelToast(mPackageName, TN.this);
+ } catch (RemoteException e) {
+ }
+ break;
+ }
+ }
+ }
+ };
}
/**
@@ -469,7 +502,7 @@
event.setPackageName(mView.getContext().getPackageName());
mView.dispatchPopulateAccessibilityEvent(event);
accessibilityManager.sendAccessibilityEvent(event);
- }
+ }
public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index df65659..ece4981 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -16,6 +16,8 @@
package com.android.internal.app;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.NonNull;
import android.app.Activity;
@@ -1136,8 +1138,10 @@
* Set to true to reveal all service targets at once.
*/
public void setShowServiceTargets(boolean show) {
- mShowServiceTargets = show;
- notifyDataSetChanged();
+ if (show != mShowServiceTargets) {
+ mShowServiceTargets = show;
+ notifyDataSetChanged();
+ }
}
private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
@@ -1201,7 +1205,18 @@
return;
}
- mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION);
+ mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to)
+ .setDuration(DURATION);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mAdapter.onAnimationStart();
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAdapter.onAnimationEnd();
+ }
+ });
}
public RowScale setInterpolator(Interpolator interpolator) {
@@ -1234,6 +1249,7 @@
private final int mColumnCount = 4;
private RowScale[] mServiceTargetScale;
private final Interpolator mInterpolator;
+ private int mAnimationCount = 0;
public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
mChooserListAdapter = wrappedAdapter;
@@ -1302,6 +1318,21 @@
return 1.f;
}
+ public void onAnimationStart() {
+ final boolean lock = mAnimationCount == 0;
+ mAnimationCount++;
+ if (lock) {
+ mResolverDrawerLayout.setDismissLocked(true);
+ }
+ }
+
+ public void onAnimationEnd() {
+ mAnimationCount--;
+ if (mAnimationCount == 0) {
+ mResolverDrawerLayout.setDismissLocked(false);
+ }
+ }
+
@Override
public int getCount() {
return (int) (
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index e224b17..17c7ebd 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -96,6 +96,8 @@
private OnDismissedListener mOnDismissedListener;
private RunOnDismissedListener mRunOnDismissedListener;
+ private boolean mDismissLocked;
+
private float mInitialTouchX;
private float mInitialTouchY;
private float mLastTouchY;
@@ -187,6 +189,10 @@
invalidate();
}
+ public void setDismissLocked(boolean locked) {
+ mDismissLocked = locked;
+ }
+
private boolean isMoving() {
return mIsDragging || !mScroller.isFinished();
}
@@ -229,6 +235,10 @@
mOnDismissedListener = listener;
}
+ private boolean isDismissable() {
+ return mOnDismissedListener != null && !mDismissLocked;
+ }
+
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
@@ -296,7 +306,7 @@
mInitialTouchY = mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
final boolean hitView = findChildUnder(mInitialTouchX, mInitialTouchY) != null;
- handled = mOnDismissedListener != null || mCollapsibleHeight > 0;
+ handled = isDismissable() || mCollapsibleHeight > 0;
mIsDragging = hitView && handled;
abortAnimation();
}
@@ -348,7 +358,7 @@
mIsDragging = false;
if (!wasDragging && findChildUnder(mInitialTouchX, mInitialTouchY) == null &&
findChildUnder(ev.getX(), ev.getY()) == null) {
- if (mOnDismissedListener != null) {
+ if (isDismissable()) {
dispatchOnDismissed();
resetTouch();
return true;
@@ -362,7 +372,7 @@
mVelocityTracker.computeCurrentVelocity(1000);
final float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
if (Math.abs(yvel) > mMinFlingVelocity) {
- if (mOnDismissedListener != null
+ if (isDismissable()
&& yvel > 0 && mCollapseOffset > mCollapsibleHeight) {
smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel);
mDismissOnScrollerFinished = true;
@@ -656,7 +666,7 @@
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
if (!consumed && Math.abs(velocityY) > mMinFlingVelocity) {
- if (mOnDismissedListener != null
+ if (isDismissable()
&& velocityY < 0 && mCollapseOffset > mCollapsibleHeight) {
smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, velocityY);
mDismissOnScrollerFinished = true;
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index d740a76..56f68d4 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -119,7 +119,11 @@
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
const status_t err = parcel->setDataSize(size);
- if (err != NO_ERROR) {
+ //STOPSHIP: check for BADFLO is for a temporary debug using wtf. Remove once bug resolved.
+ if (err == UNKNOWN_ERROR + 0xBADF10) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Attempt to resize (size = %d) Parcel would corrupt object memory", size);
+ } else if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
return parcel->getOpenAshmemSize();
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 60df492..90b74ac 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -59,7 +59,8 @@
android:layout_marginLeft="16dp"
android:src="@android:drawable/ic_close"
android:alpha="0.54"
- android:background="?android:attr/selectableItemBackgroundBorderless">
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:contentDescription="@android:string/close_button_text">
</ImageView>
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_ambient_header.xml b/core/res/res/layout/notification_template_ambient_header.xml
new file mode 100644
index 0000000..c00acd5
--- /dev/null
+++ b/core/res/res/layout/notification_template_ambient_header.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<!-- hack to work around <include /> not being supported at the top level -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingStart="@dimen/notification_extra_margin_ambient"
+ android:paddingEnd="@dimen/notification_extra_margin_ambient"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <include
+ layout="@layout/notification_template_header"
+ android:theme="@style/Theme.Material.Notification.Ambient"/>
+</FrameLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8b9b0e2..b7e8467 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -393,6 +393,12 @@
<!-- Activity name to enable wifi tethering after provisioning app succeeds -->
<string translatable="false" name="config_wifi_tether_enable">com.android.settings/.TetherService</string>
+ <!-- Controls the WiFi wakeup feature.
+ 0 = Not available.
+ 1 = Available.
+ -->
+ <integer translatable="false" name="config_wifi_wakeup_available">0</integer>
+
<!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
<!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
<!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3551bc9..31d13c9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2085,6 +2085,7 @@
<java-symbol type="string" name="config_mobile_hotspot_provision_response" />
<java-symbol type="integer" name="config_mobile_hotspot_provision_check_period" />
<java-symbol type="string" name="config_wifi_tether_enable" />
+ <java-symbol type="integer" name="config_wifi_wakeup_available" />
<java-symbol type="bool" name="config_intrusiveNotificationLed" />
<java-symbol type="dimen" name="preference_fragment_padding_bottom" />
<java-symbol type="dimen" name="preference_fragment_padding_side" />
@@ -2935,6 +2936,8 @@
<java-symbol type="string" name="time_picker_text_input_mode_description"/>
<java-symbol type="string" name="time_picker_radial_mode_description"/>
+ <java-symbol type="layout" name="notification_template_ambient_header" />
+
<!-- resolver activity -->
<java-symbol type="drawable" name="resolver_icon_placeholder" />
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 8a36120..40d36aa 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1869,9 +1869,9 @@
public boolean sameAs(Bitmap other) {
checkRecycled("Can't call sameAs on a recycled bitmap!");
noteHardwareBitmapSlowCall();
- other.noteHardwareBitmapSlowCall();
if (this == other) return true;
if (other == null) return false;
+ other.noteHardwareBitmapSlowCall();
if (other.isRecycled()) {
throw new IllegalArgumentException("Can't compare to a recycled bitmap!");
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 6276ce3..c1f03fd 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -96,6 +96,10 @@
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
+ webSettings.setUseWideViewPort(true);
+ webSettings.setLoadWithOverviewMode(true);
+ webSettings.setSupportZoom(true);
+ webSettings.setBuiltInZoomControls(true);
mWebViewClient = new MyWebViewClient();
mWebView.setWebViewClient(mWebViewClient);
mWebView.setWebChromeClient(new MyWebChromeClient());
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 9d6505b..46fbb24 100755
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -240,7 +240,7 @@
private int getColorForLevel(int percent) {
// If we are in power save mode, always use the normal color.
if (mPowerSaveEnabled) {
- return mColors[mColors.length - 1];
+ return mIconTint;
}
int thresh, color = 0;
for (int i = 0; i < mColors.length; i += 2) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 14e2a85..455d9cb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2899,7 +2899,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 144;
+ private static final int SETTINGS_VERSION = 145;
private final int mUserId;
@@ -3481,6 +3481,25 @@
currentVersion = 144;
}
+ if (currentVersion == 144) {
+ // Version 145: Set the default value for WIFI_WAKEUP_AVAILABLE.
+ if (userId == UserHandle.USER_SYSTEM) {
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ final Setting currentSetting = globalSettings.getSettingLocked(
+ Settings.Global.WIFI_WAKEUP_AVAILABLE);
+ if (currentSetting.isNull()) {
+ final int defaultValue = getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_wifi_wakeup_available);
+ globalSettings.insertSettingLocked(
+ Settings.Global.WIFI_WAKEUP_AVAILABLE,
+ String.valueOf(defaultValue),
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 145;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/res/layout/hybrid_notification.xml b/packages/SystemUI/res/layout/hybrid_notification.xml
index 476f52b..f4501da 100644
--- a/packages/SystemUI/res/layout/hybrid_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_notification.xml
@@ -19,23 +19,22 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="@*android:dimen/notification_content_margin_start"
- android:paddingEnd="12dp"
- android:gravity="bottom|start">
+ android:gravity="bottom|start"
+ style="?attr/hybridNotificationStyle">
<TextView
android:id="@+id/notification_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingEnd="4dp"
- android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+ android:singleLine="true"
+ style="?attr/hybridNotificationTitleStyle"
/>
<TextView
android:id="@+id/notification_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingEnd="4dp"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification"
android:singleLine="true"
- />
-</com.android.systemui.statusbar.notification.HybridNotificationView>
+ android:textAppearance="@*android:style/TextAppearance.Material.Notification"
+ style="?attr/hybridNotificationTextStyle"
+ />
+</com.android.systemui.statusbar.notification.HybridNotificationView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hybrid_overflow_number.xml b/packages/SystemUI/res/layout/hybrid_overflow_number.xml
index f3dde8d..792f424 100644
--- a/packages/SystemUI/res/layout/hybrid_overflow_number.xml
+++ b/packages/SystemUI/res/layout/hybrid_overflow_number.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@*android:style/TextAppearance.Material.Notification"
- android:paddingEnd="@*android:dimen/notification_content_margin_end"
+ android:paddingEnd="@dimen/group_overflow_number_padding"
android:gravity="end"
android:singleLine="true"
/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/pip_dismiss_view.xml b/packages/SystemUI/res/layout/pip_dismiss_view.xml
index 058f59f..2cc4b22 100644
--- a/packages/SystemUI/res/layout/pip_dismiss_view.xml
+++ b/packages/SystemUI/res/layout/pip_dismiss_view.xml
@@ -17,7 +17,6 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/pip_dismiss_gradient_height"
- android:background="@drawable/pip_dismiss_scrim"
android:alpha="0">
<TextView
diff --git a/packages/SystemUI/res/layout/qs_footer.xml b/packages/SystemUI/res/layout/qs_footer.xml
index 0a848c0..047f7aa 100644
--- a/packages/SystemUI/res/layout/qs_footer.xml
+++ b/packages/SystemUI/res/layout/qs_footer.xml
@@ -37,78 +37,79 @@
android:layout_width="wrap_content"
android:layout_height="match_parent" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end">
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_alignParentEnd="true"
- android:background="@drawable/ripple_drawable"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_expanded_size"
- android:layout_height="@dimen/multi_user_avatar_expanded_size"
- android:layout_gravity="center"
- android:scaleType="centerInside"/>
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@android:id/edit"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:clickable="true"
- android:clipToPadding="false"
- android:contentDescription="@string/accessibility_quick_settings_edit"
- android:focusable="true"
- android:padding="16dp"
- android:src="@drawable/ic_mode_edit"
- android:tint="?android:attr/colorForeground"/>
-
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- style="@android:style/Widget.Material.Button.Borderless"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ <com.android.systemui.statusbar.phone.MultiUserSwitch
+ android:id="@+id/multi_user_switch"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_alignParentEnd="true"
android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:src="@drawable/ic_settings_16dp"
- android:tint="?android:attr/colorForeground"/>
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_expanded_size"
+ android:layout_height="@dimen/multi_user_avatar_expanded_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside"/>
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
<com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/tuner_icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingStart="36dp"
- android:paddingEnd="4dp"
- android:src="@drawable/tuner"
- android:tint="?android:attr/textColorTertiary"
- android:visibility="invisible"/>
+ android:id="@android:id/edit"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:contentDescription="@string/accessibility_quick_settings_edit"
+ android:focusable="true"
+ android:padding="16dp"
+ android:src="@drawable/ic_mode_edit"
+ android:tint="?android:attr/colorForeground"/>
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/settings_button_container"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:clipChildren="false"
+ android:clipToPadding="false">
- <com.android.systemui.statusbar.phone.ExpandableIndicator
- android:id="@+id/expand_indicator"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:clipToPadding="false"
- android:clickable="true"
- android:focusable="true"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:contentDescription="@string/accessibility_quick_settings_expand"
- android:padding="14dp" />
+ <com.android.systemui.statusbar.phone.SettingsButton
+ android:id="@+id/settings_button"
+ style="@android:style/Widget.Material.Button.Borderless"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/accessibility_quick_settings_settings"
+ android:src="@drawable/ic_settings_16dp"
+ android:tint="?android:attr/colorForeground"/>
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/tuner_icon"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="36dp"
+ android:paddingEnd="4dp"
+ android:src="@drawable/tuner"
+ android:tint="?android:attr/textColorTertiary"
+ android:visibility="invisible"/>
+
+ </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+ <com.android.systemui.statusbar.phone.ExpandableIndicator
+ android:id="@+id/expand_indicator"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:clipToPadding="false"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:contentDescription="@string/accessibility_quick_settings_expand"
+ android:padding="14dp" />
+ </LinearLayout>
</com.android.systemui.qs.QSFooter>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 008fcf0..a57b17e 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -108,6 +108,12 @@
<attr name="android:layout" />
</declare-styleable>
+ <declare-styleable name="HybridNotificationTheme">
+ <attr name="hybridNotificationStyle" format="reference" />
+ <attr name="hybridNotificationTitleStyle" format="reference" />
+ <attr name="hybridNotificationTextStyle" format="reference" />
+ </declare-styleable>
+
<declare-styleable name="AutoSizingList">
<!-- Whether AutoSizingList will show only as many items as fit on screen and
remove extra items instead of scrolling. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f072849..63abee7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -57,6 +57,11 @@
<!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
<item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
+ <dimen name="group_overflow_number_size">@*android:dimen/notification_text_size</dimen>
+ <dimen name="group_overflow_number_size_dark">16sp</dimen>
+ <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end</dimen>
+ <dimen name="group_overflow_number_extra_padding_dark">@*android:dimen/notification_extra_margin_ambient</dimen>
+
<!-- max height of a notification such that the content can still fade out when closing -->
<dimen name="max_notification_fadeout_height">100dp</dimen>
@@ -743,7 +748,7 @@
<dimen name="recents_fast_fling_velocity">600dp</dimen>
<!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
- <dimen name="pip_dismiss_gradient_height">196dp</dimen>
+ <dimen name="pip_dismiss_gradient_height">176dp</dimen>
<!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. -->
<dimen name="pip_dismiss_text_bottom_margin">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 64b5f4b..c656b17 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1892,21 +1892,6 @@
<!-- PiP BTW notification description. [CHAR LIMIT=NONE] -->
<string name="pip_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
- <!-- PiP section of the tuner. [CHAR LIMIT=NONE] -->
- <string name="picture_in_picture" translatable="false">Picture-in-Picture</string>
-
- <!-- PiP minimize title. [CHAR LIMIT=NONE]-->
- <string name="pip_minimize_title" translatable="false">Minimize</string>
-
- <!-- PiP minimize description. [CHAR LIMIT=NONE] -->
- <string name="pip_minimize_description" translatable="false">Drag or fling the PIP to the edges of the screen to minimize it.</string>
-
- <!-- PiP fling to dismiss title. [CHAR LIMIT=NONE]-->
- <string name="pip_fling_dismiss_title" translatable="false">Fling to dismiss</string>
-
- <!-- PiP fling to dismiss description. [CHAR LIMIT=NONE] -->
- <string name="pip_fling_dismiss_description" translatable="false">Fling from anywhere on the screen to the bottom of the screen to dismiss the PIP.</string>
-
<!-- Button to play the current media on picture-in-picture (PIP) [CHAR LIMIT=30] -->
<string name="pip_play">Play</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index dbdbd1e..9650cea 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -82,6 +82,56 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
+ <!-- HybridNotification themes and styles -->
+
+ <style name="HybridNotification">
+ <item name="hybridNotificationStyle">@style/hybrid_notification</item>
+ <item name="hybridNotificationTitleStyle">@style/hybrid_notification_title</item>
+ <item name="hybridNotificationTextStyle">@style/hybrid_notification_text</item>
+ </style>
+
+ <style name="HybridNotification.Ambient">
+ <item name="hybridNotificationStyle">@style/hybrid_notification_ambient</item>
+ <item name="hybridNotificationTitleStyle">@style/hybrid_notification_title_ambient</item>
+ <item name="hybridNotificationTextStyle">@style/hybrid_notification_text_ambient</item>
+ </style>
+
+ <style name="hybrid_notification_ambient">
+ <item name="android:paddingStart">@*android:dimen/notification_extra_margin_ambient</item>
+ <item name="android:paddingEnd">@*android:dimen/notification_extra_margin_ambient</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:paddingBottom">23.5dp</item>
+ </style>
+
+ <style name="hybrid_notification">
+ <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item>
+ <item name="android:paddingEnd">12dp</item>
+ </style>
+
+ <style name="hybrid_notification_title_ambient">
+ <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item>
+ <item name="android:paddingEnd">@*android:dimen/notification_content_margin_end</item>
+ <item name="android:textSize">20sp</item>
+ <item name="android:textColor">#ffffffff</item>
+ </style>
+
+ <style name="hybrid_notification_title">
+ <item name="android:paddingEnd">4dp</item>
+ </style>
+
+ <style name="hybrid_notification_text_ambient">
+ <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item>
+ <item name="android:paddingEnd">@*android:dimen/notification_content_margin_end</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">#eeffffff</item>
+ <item name="android:layout_marginTop">4dp</item>
+ </style>
+
+ <style name="hybrid_notification_text">
+ <item name="android:paddingEnd">4dp</item>
+ </style>
+
+
<style name="TextAppearance.StatusBar.HeadsUp"
parent="@*android:style/TextAppearance.StatusBar">
</style>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 24f29c8..223dafd 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -122,24 +122,6 @@
</PreferenceScreen>
<PreferenceScreen
- android:key="picture_in_picture"
- android:title="@string/picture_in_picture">
-
- <com.android.systemui.tuner.TunerSwitch
- android:key="pip_minimize"
- android:title="@string/pip_minimize_title"
- android:summary="@string/pip_minimize_description"
- sysui:defValue="false" />
-
- <com.android.systemui.tuner.TunerSwitch
- android:key="pip_fling_dismiss"
- android:title="@string/pip_fling_dismiss_title"
- android:summary="@string/pip_fling_dismiss_description"
- sysui:defValue="false" />
-
- </PreferenceScreen>
-
- <PreferenceScreen
android:key="doze"
android:title="@string/tuner_doze">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index d4d69ff..f0ff22d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -280,6 +280,9 @@
}
public void setDark(boolean dark) {
+ if (mDark == dark) {
+ return;
+ }
mDark = dark;
final int N = mClockContainer.getChildCount();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index ec56e15..3e424d0 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -36,6 +36,8 @@
void abortPulsing();
void extendPulse();
+ void setAnimateWakeup(boolean animateWakeup);
+
interface Callback {
default void onNotificationHeadsUp() {}
default void onPowerSaveChanged(boolean active) {}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 64a152e..ea33ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -95,6 +95,19 @@
unscheduleTimeTick();
break;
}
+ mHost.setAnimateWakeup(shouldAnimateWakeup(newState));
+ }
+
+ private boolean shouldAnimateWakeup(DozeMachine.State state) {
+ switch (state) {
+ case DOZE_AOD:
+ case DOZE_REQUEST_PULSE:
+ case DOZE_PULSING:
+ case DOZE_PULSE_DONE:
+ return true;
+ default:
+ return false;
+ }
}
private void scheduleTimeTick() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index afb62fc..e1a7e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -21,6 +21,7 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -72,6 +73,11 @@
mDismissView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mDismissView.forceHasOverlappingRendering(false);
+ // Set the gradient background
+ Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
+ gradient.setAlpha((int) (255 * 0.85f));
+ mDismissView.setBackground(gradient);
+
// Adjust bottom margins of the text
View text = mDismissView.findViewById(R.id.pip_dismiss_text);
FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) text.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 71d3d35..199b027 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -54,11 +54,13 @@
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
* the PIP.
*/
-public class PipTouchHandler implements TunerService.Tunable {
+public class PipTouchHandler {
private static final String TAG = "PipTouchHandler";
- private static final String TUNER_KEY_MINIMIZE = "pip_minimize";
- private static final String TUNER_KEY_FLING_DISMISS = "pip_fling_dismiss";
+ // Allow the PIP to be dragged to the edge of the screen to be minimized.
+ private static final boolean ENABLE_MINIMIZE = false;
+ // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed.
+ private static final boolean ENABLE_FLING_DISMISS = false;
// These values are used for metrics and should never change
private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
@@ -113,11 +115,6 @@
}
};
- // Allow the PIP to be dragged to the edge of the screen to be minimized.
- private boolean mEnableMinimize = false;
- // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed.
- private boolean mEnableFlingToDismiss = false;
-
// Behaviour states
private int mMenuState;
private boolean mIsMinimized;
@@ -196,10 +193,6 @@
mExpandedShortestEdgeSize = context.getResources().getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size);
- // Register any tuner settings changes
- Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_MINIMIZE);
- Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_FLING_DISMISS);
-
// Register the listener for input consumer touch events
inputConsumerController.setTouchListener(this::handleTouchEvent);
inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
@@ -240,18 +233,6 @@
}
}
- @Override
- public void onTuningChanged(String key, String newValue) {
- switch (key) {
- case TUNER_KEY_MINIMIZE:
- mEnableMinimize = newValue == null ? false : Integer.parseInt(newValue) != 0;
- break;
- case TUNER_KEY_FLING_DISMISS:
- mEnableFlingToDismiss = newValue == null ? false : Integer.parseInt(newValue) != 0;
- break;
- }
- }
-
public void onConfigurationChanged() {
mMotionHelper.onConfigurationChanged();
mMotionHelper.synchronizePinnedStackBounds();
@@ -451,7 +432,7 @@
* Sets the minimized state.
*/
void setMinimizedStateInternal(boolean isMinimized) {
- if (!mEnableMinimize) {
+ if (!ENABLE_MINIMIZE) {
return;
}
setMinimizedState(isMinimized, false /* fromController */);
@@ -461,7 +442,7 @@
* Sets the minimized state.
*/
void setMinimizedState(boolean isMinimized, boolean fromController) {
- if (!mEnableMinimize) {
+ if (!ENABLE_MINIMIZE) {
return;
}
if (mIsMinimized != isMinimized) {
@@ -597,7 +578,7 @@
final PointF lastDelta = touchState.getLastTouchDelta();
float left = mTmpBounds.left + lastDelta.x;
float top = mTmpBounds.top + lastDelta.y;
- if (!touchState.allowDraggingOffscreen() || !mEnableMinimize) {
+ if (!touchState.allowDraggingOffscreen() || !ENABLE_MINIMIZE) {
left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left));
}
if (ENABLE_DISMISS_DRAG_TO_EDGE) {
@@ -645,7 +626,7 @@
final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
final float velocity = PointF.length(vel.x, vel.y);
final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
- final boolean isUpWithinDimiss = mEnableFlingToDismiss
+ final boolean isUpWithinDimiss = ENABLE_FLING_DISMISS
&& touchState.getLastTouchPosition().y >= mMovementBounds.bottom
&& mMotionHelper.isGestureToDismissArea(mMotionHelper.getBounds(), vel.x,
vel.y, isFling);
@@ -666,7 +647,7 @@
if (touchState.isDragging()) {
final boolean isFlingToEdge = isFling && isHorizontal && mMovementWithinMinimize
&& (mStartedOnLeft ? vel.x < 0 : vel.x > 0);
- if (mEnableMinimize &&
+ if (ENABLE_MINIMIZE &&
!mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
// Pip should be minimized
setMinimizedStateInternal(true);
@@ -758,7 +739,7 @@
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
pw.println(innerPrefix + "mEnableDragToEdgeDismiss=" + ENABLE_DISMISS_DRAG_TO_EDGE);
- pw.println(innerPrefix + "mEnableMinimize=" + mEnableMinimize);
+ pw.println(innerPrefix + "mEnableMinimize=" + ENABLE_MINIMIZE);
mSnapAlgorithm.dump(pw, innerPrefix);
mTouchState.dump(pw, innerPrefix);
mMotionHelper.dump(pw, innerPrefix);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 4f484b6..063f5df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -33,6 +33,7 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -63,7 +64,7 @@
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.tuner.TunerService;
-public class QSFooter extends LinearLayout implements
+public class QSFooter extends FrameLayout implements
NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
SignalCallback {
private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 005206f..0b89dc1 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -22,6 +22,8 @@
import android.content.Intent;
import android.content.IntentFilter;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -32,7 +34,12 @@
private Consumer<Integer> mCallback = this::onUserSwitched;
public CurrentUserTracker(Context context) {
- mUserReceiver = UserReceiver.getInstance(context);
+ this(UserReceiver.getInstance(context));
+ }
+
+ @VisibleForTesting
+ CurrentUserTracker(UserReceiver receiver) {
+ mUserReceiver = receiver;
}
public int getCurrentUserId() {
@@ -49,7 +56,8 @@
public abstract void onUserSwitched(int newUserId);
- private static class UserReceiver extends BroadcastReceiver {
+ @VisibleForTesting
+ static class UserReceiver extends BroadcastReceiver {
private static UserReceiver sInstance;
private Context mAppContext;
@@ -58,7 +66,8 @@
private List<Consumer<Integer>> mCallbacks = new ArrayList<>();
- private UserReceiver(Context context) {
+ @VisibleForTesting
+ UserReceiver(Context context) {
mAppContext = context.getApplicationContext();
}
@@ -105,8 +114,12 @@
private void notifyUserSwitched(int newUserId) {
if (mCurrentUserId != newUserId) {
mCurrentUserId = newUserId;
- for (Consumer<Integer> consumer : mCallbacks) {
- consumer.accept(newUserId);
+ List<Consumer<Integer>> callbacks = new ArrayList<>(mCallbacks);
+ for (Consumer<Integer> consumer : callbacks) {
+ // Accepting may modify this list
+ if (mCallbacks.contains(consumer)) {
+ consumer.accept(newUserId);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index b91561e..f5718d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -50,7 +50,7 @@
private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
private static final int ACTIVATE_ANIMATION_LENGTH = 220;
- private static final int DARK_ANIMATION_LENGTH = 170;
+ private static final long DARK_ANIMATION_LENGTH = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
/**
* The amount of width, which is kept in the end when performing a disappear animation (also
@@ -418,7 +418,7 @@
}
mDark = dark;
updateBackground();
- updateBackgroundTint(fade);
+ updateBackgroundTint(false);
if (!dark && fade && !shouldHideBackground()) {
fadeInFromDark(delay);
}
@@ -555,23 +555,15 @@
final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
background.setAlpha(0f);
mBackgroundVisibilityUpdater.onAnimationUpdate(null);
- background.setPivotX(mBackgroundDimmed.getWidth() / 2f);
- background.setPivotY(getActualHeight() / 2f);
- background.setScaleX(DARK_EXIT_SCALE_START);
- background.setScaleY(DARK_EXIT_SCALE_START);
background.animate()
.alpha(1f)
- .scaleX(1f)
- .scaleY(1f)
.setDuration(DARK_ANIMATION_LENGTH)
.setStartDelay(delay)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .setInterpolator(Interpolators.ALPHA_IN)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
// Jump state if we are cancelled
- background.setScaleX(1f);
- background.setScaleY(1f);
background.setAlpha(1f);
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 21bf462..3a39e91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -234,6 +234,7 @@
private boolean mUseIncreasedHeadsUpHeight;
private float mTranslationWhenRemoved;
private boolean mWasChildInGroupWhenRemoved;
+ private int mNotificationColorAmbient;
@Override
public boolean isGroupExpansionChanging() {
@@ -876,12 +877,18 @@
mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext,
getStatusBarNotification().getNotification().color,
getBackgroundColorWithoutTint());
+ mNotificationColorAmbient = NotificationColorUtil.resolveAmbientColor(mContext,
+ getStatusBarNotification().getNotification().color);
}
public HybridNotificationView getSingleLineView() {
return mPrivateLayout.getSingleLineView();
}
+ public HybridNotificationView getAmbientSingleLineView() {
+ return getShowingLayout().getAmbientSingleLineChild();
+ }
+
public boolean isOnKeyguard() {
return mOnKeyguard;
}
@@ -1145,6 +1152,10 @@
return mNotificationInflater;
}
+ public int getNotificationColorAmbient() {
+ return mNotificationColorAmbient;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -1357,6 +1368,10 @@
@Override
public void setDark(boolean dark, boolean fade, long delay) {
super.setDark(dark, fade, delay);
+ if (!mIsHeadsUp) {
+ // Only fade the showing view of the pulsing notification.
+ fade = false;
+ }
final NotificationContentView showing = getShowingLayout();
if (showing != null) {
showing.setDark(dark, fade, delay);
@@ -1523,13 +1538,11 @@
return mGuts.getIntrinsicHeight();
} else if ((isChildInGroup() && !isGroupExpanded())) {
return mPrivateLayout.getMinHeight();
- } else if (mShowAmbient) {
- return getAmbientHeight();
} else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
return getMinHeight();
- } else if (mIsSummaryWithChildren && !mOnKeyguard) {
+ } else if (mIsSummaryWithChildren && (!mOnKeyguard || mShowAmbient)) {
return mChildrenContainer.getIntrinsicHeight();
- } else if (!mOnKeyguard && (mIsHeadsUp || mHeadsupDisappearRunning)) {
+ } else if (isHeadsUpAllowed() && (mIsHeadsUp || mHeadsupDisappearRunning)) {
if (isPinned() || mHeadsupDisappearRunning) {
return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
} else if (isExpanded()) {
@@ -1544,6 +1557,10 @@
}
}
+ private boolean isHeadsUpAllowed() {
+ return !mOnKeyguard && !mShowAmbient;
+ }
+
@Override
public boolean isGroupExpanded() {
return mGroupManager.isGroupExpanded(mStatusBarNotification);
@@ -1874,24 +1891,17 @@
public int getMinHeight() {
if (mGuts != null && mGuts.isExposed()) {
return mGuts.getIntrinsicHeight();
- } else if (!mOnKeyguard && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
+ } else if (isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
} else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
return mChildrenContainer.getMinHeight();
- } else if (!mOnKeyguard && mIsHeadsUp) {
+ } else if (isHeadsUpAllowed() && mIsHeadsUp) {
return mHeadsUpHeight;
}
NotificationContentView showingLayout = getShowingLayout();
return showingLayout.getMinHeight();
}
- private int getAmbientHeight() {
- NotificationContentView showingLayout = getShowingLayout();
- return showingLayout.getAmbientChild() != null
- ? showingLayout.getAmbientChild().getHeight()
- : getCollapsedHeight();
- }
-
@Override
public int getCollapsedHeight() {
if (mIsSummaryWithChildren && !mShowingPublic) {
@@ -2126,10 +2136,17 @@
public void setShowAmbient(boolean showAmbient) {
if (showAmbient != mShowAmbient) {
mShowAmbient = showAmbient;
+ if (mChildrenContainer != null) {
+ mChildrenContainer.notifyShowAmbientChanged();
+ }
notifyHeightChanged(false /* needsAnimation */);
}
}
+ public boolean isShowingAmbient() {
+ return mShowAmbient;
+ }
+
public void setAboveShelf(boolean aboveShelf) {
mAboveShelf = aboveShelf;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index e7bf983..baf0c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -54,6 +54,7 @@
private static final int VISIBLE_TYPE_HEADSUP = 2;
private static final int VISIBLE_TYPE_SINGLELINE = 3;
private static final int VISIBLE_TYPE_AMBIENT = 4;
+ private static final int VISIBLE_TYPE_AMBIENT_SINGLELINE = 5;
public static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
@@ -65,6 +66,7 @@
private View mHeadsUpChild;
private HybridNotificationView mSingleLineView;
private View mAmbientChild;
+ private HybridNotificationView mAmbientSingleLineChild;
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
@@ -252,6 +254,27 @@
: MeasureSpec.AT_MOST));
maxChildHeight = Math.max(maxChildHeight, mAmbientChild.getMeasuredHeight());
}
+ if (mAmbientSingleLineChild != null) {
+ int size = Math.min(maxSize, mNotificationAmbientHeight);
+ ViewGroup.LayoutParams layoutParams = mAmbientSingleLineChild.getLayoutParams();
+ boolean useExactly = false;
+ if (layoutParams.height >= 0) {
+ // An actual height is set
+ size = Math.min(size, layoutParams.height);
+ useExactly = true;
+ }
+ int ambientSingleLineWidthSpec = widthMeasureSpec;
+ if (mSingleLineWidthIndention != 0
+ && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
+ ambientSingleLineWidthSpec = MeasureSpec.makeMeasureSpec(
+ width - mSingleLineWidthIndention + mAmbientSingleLineChild.getPaddingEnd(),
+ MeasureSpec.EXACTLY);
+ }
+ mAmbientSingleLineChild.measure(ambientSingleLineWidthSpec,
+ MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
+ : MeasureSpec.AT_MOST));
+ maxChildHeight = Math.max(maxChildHeight, mAmbientSingleLineChild.getMeasuredHeight());
+ }
int ownHeight = Math.min(maxChildHeight, maxSize);
setMeasuredDimension(width, ownHeight);
}
@@ -345,6 +368,10 @@
return mAmbientChild;
}
+ public HybridNotificationView getAmbientSingleLineChild() {
+ return mAmbientSingleLineChild;
+ }
+
public void setContractedChild(View child) {
if (mContractedChild != null) {
mContractedChild.animate().cancel();
@@ -533,6 +560,9 @@
int hint;
if (mAmbientChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_AMBIENT)) {
hint = mAmbientChild.getHeight();
+ } else if (mAmbientSingleLineChild != null && isVisibleOrTransitioning(
+ VISIBLE_TYPE_AMBIENT_SINGLELINE)) {
+ hint = mAmbientSingleLineChild.getHeight();
} else if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
hint = mHeadsUpChild.getHeight();
} else if (mExpandedChild != null) {
@@ -622,7 +652,9 @@
}
public int getMaxHeight() {
- if (mExpandedChild != null) {
+ if (mContainingNotification.isShowingAmbient()) {
+ return getShowingAmbientView().getHeight();
+ } else if (mExpandedChild != null) {
return mExpandedChild.getHeight();
} else if (mIsHeadsUp && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) {
return mHeadsUpChild.getHeight();
@@ -635,13 +667,24 @@
}
public int getMinHeight(boolean likeGroupExpanded) {
- if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
+ if (mContainingNotification.isShowingAmbient()) {
+ return getShowingAmbientView().getHeight();
+ } else if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
return mContractedChild.getHeight();
} else {
return mSingleLineView.getHeight();
}
}
+ public View getShowingAmbientView() {
+ View v = mIsChildInGroup ? mAmbientSingleLineChild : mAmbientChild;
+ if (v != null) {
+ return v;
+ } else {
+ return mContractedChild;
+ }
+ }
+
private boolean isGroupExpanded() {
return mGroupManager.isGroupExpanded(mStatusBarNotification);
}
@@ -723,6 +766,8 @@
forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
forceUpdateVisibility(VISIBLE_TYPE_AMBIENT, mAmbientChild, mAmbientWrapper);
+ forceUpdateVisibility(VISIBLE_TYPE_AMBIENT_SINGLELINE, mAmbientSingleLineChild,
+ mAmbientSingleLineChild);
fireExpandedVisibleListenerIfVisible();
// forceUpdateVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
@@ -791,6 +836,8 @@
mSingleLineView, mSingleLineView);
updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT,
mAmbientChild, mAmbientWrapper);
+ updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT_SINGLELINE,
+ mAmbientSingleLineChild, mAmbientSingleLineChild);
fireExpandedVisibleListenerIfVisible();
// updateViewVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
@@ -853,6 +900,8 @@
return mSingleLineView;
case VISIBLE_TYPE_AMBIENT:
return mAmbientWrapper;
+ case VISIBLE_TYPE_AMBIENT_SINGLELINE:
+ return mAmbientSingleLineChild;
default:
return mContractedWrapper;
}
@@ -872,6 +921,8 @@
return mSingleLineView;
case VISIBLE_TYPE_AMBIENT:
return mAmbientChild;
+ case VISIBLE_TYPE_AMBIENT_SINGLELINE:
+ return mAmbientSingleLineChild;
default:
return mContractedChild;
}
@@ -896,9 +947,14 @@
* @return one of the static enum types in this view, calculated form the current state
*/
public int calculateVisibleType() {
- if (mDark && !mIsChildInGroup) {
- // TODO: Handle notification groups
- return VISIBLE_TYPE_AMBIENT;
+ if (mContainingNotification.isShowingAmbient()) {
+ if (mIsChildInGroup && mAmbientSingleLineChild != null) {
+ return VISIBLE_TYPE_AMBIENT_SINGLELINE;
+ } else if (mAmbientChild != null) {
+ return VISIBLE_TYPE_AMBIENT;
+ } else {
+ return VISIBLE_TYPE_CONTRACTED;
+ }
}
if (mUserExpanding) {
int height = !mIsChildInGroup || isGroupExpanded()
@@ -1021,13 +1077,13 @@
if (mAmbientChild != null) {
mAmbientWrapper.setIsChildInGroup(mIsChildInGroup);
}
- updateSingleLineView();
+ updateAllSingleLineViews();
}
public void onNotificationUpdated(NotificationData.Entry entry) {
mStatusBarNotification = entry.notification;
mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
- updateSingleLineView();
+ updateAllSingleLineViews();
if (mContractedChild != null) {
mContractedWrapper.notifyContentUpdated(entry.row);
}
@@ -1048,6 +1104,10 @@
mPreviousHeadsUpRemoteInputIntent = null;
}
+ private void updateAllSingleLineViews() {
+ updateSingleLineView();
+ updateAmbientSingleLineView();
+ }
private void updateSingleLineView() {
if (mIsChildInGroup) {
mSingleLineView = mHybridGroupManager.bindFromNotification(
@@ -1058,6 +1118,16 @@
}
}
+ private void updateAmbientSingleLineView() {
+ if (mIsChildInGroup) {
+ mAmbientSingleLineChild = mHybridGroupManager.bindAmbientFromNotification(
+ mAmbientSingleLineChild, mStatusBarNotification.getNotification());
+ } else if (mAmbientSingleLineChild != null) {
+ removeView(mAmbientSingleLineChild);
+ mAmbientSingleLineChild = null;
+ }
+ }
+
private void applyRemoteInput(final NotificationData.Entry entry) {
if (mRemoteInputController == null) {
return;
@@ -1255,7 +1325,7 @@
if (mIsChildInGroup && mSingleLineView != null) {
removeView(mSingleLineView);
mSingleLineView = null;
- updateSingleLineView();
+ updateAllSingleLineViews();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f41670e..8e7ab57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -427,7 +427,7 @@
if (iconState != null) {
iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
iconState.scaleY = iconState.scaleX;
- iconState.hidden = transitionAmount == 0.0f;
+ iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon);
iconState.alpha = alpha;
iconState.yTranslation = iconYTranslation;
if (stayingInShelf) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
index 7373607..3ed8cce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
@@ -18,20 +18,14 @@
import android.app.Notification;
import android.content.Context;
-import android.text.BidiFormatter;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-
-import java.util.List;
/**
* A class managing hybrid groups that include {@link HybridNotificationView} and the notification
@@ -40,16 +34,37 @@
public class HybridGroupManager {
private final Context mContext;
- private ViewGroup mParent;
+ private final NotificationDozeHelper mDozer;
+ private final ViewGroup mParent;
+
+ private final float mOverflowNumberSizeDark;
+ private final int mOverflowNumberPaddingDark;
+ private final float mOverflowNumberSize;
+ private final int mOverflowNumberPadding;
+
private int mOverflowNumberColor;
+ private int mOverflowNumberColorDark;
+ private float mDarkAmount = 0f;
public HybridGroupManager(Context ctx, ViewGroup parent) {
mContext = ctx;
mParent = parent;
+ mDozer = new NotificationDozeHelper();
+
+ Resources res = mContext.getResources();
+ mOverflowNumberSize = res.getDimensionPixelSize(
+ R.dimen.group_overflow_number_size);
+ mOverflowNumberSizeDark = res.getDimensionPixelSize(
+ R.dimen.group_overflow_number_size_dark);
+ mOverflowNumberPadding = res.getDimensionPixelSize(
+ R.dimen.group_overflow_number_padding);
+ mOverflowNumberPaddingDark = mOverflowNumberPadding + res.getDimensionPixelSize(
+ R.dimen.group_overflow_number_extra_padding_dark);
}
- private HybridNotificationView inflateHybridView() {
- LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
+ private HybridNotificationView inflateHybridViewWithStyle(int style) {
+ LayoutInflater inflater = new ContextThemeWrapper(mContext, style)
+ .getSystemService(LayoutInflater.class);
HybridNotificationView hybrid = (HybridNotificationView) inflater.inflate(
R.layout.hybrid_notification, mParent, false);
mParent.addView(hybrid);
@@ -66,11 +81,13 @@
}
private void updateOverFlowNumberColor(TextView numberView) {
- numberView.setTextColor(mOverflowNumberColor);
+ numberView.setTextColor(NotificationUtils.interpolateColors(
+ mOverflowNumberColor, mOverflowNumberColorDark, mDarkAmount));
}
- public void setOverflowNumberColor(TextView numberView, int overflowNumberColor) {
- mOverflowNumberColor = overflowNumberColor;
+ public void setOverflowNumberColor(TextView numberView, int colorRegular, int colorDark) {
+ mOverflowNumberColor = colorRegular;
+ mOverflowNumberColorDark = colorDark;
if (numberView != null) {
updateOverFlowNumberColor(numberView);
}
@@ -78,8 +95,20 @@
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
Notification notification) {
+ return bindFromNotificationWithStyle(reusableView, notification,
+ R.style.HybridNotification);
+ }
+
+ public HybridNotificationView bindAmbientFromNotification(HybridNotificationView reusableView,
+ Notification notification) {
+ return bindFromNotificationWithStyle(reusableView, notification,
+ R.style.HybridNotification_Ambient);
+ }
+
+ private HybridNotificationView bindFromNotificationWithStyle(
+ HybridNotificationView reusableView, Notification notification, int style) {
if (reusableView == null) {
- reusableView = inflateHybridView();
+ reusableView = inflateHybridViewWithStyle(style);
}
CharSequence titleText = resolveTitle(notification);
CharSequence contentText = resolveText(notification);
@@ -118,4 +147,16 @@
reusableView.setContentDescription(contentDescription);
return reusableView;
}
+
+ public void setOverflowNumberDark(TextView view, boolean dark, boolean fade, long delay) {
+ mDozer.setIntensityDark((f)->{
+ mDarkAmount = f;
+ updateOverFlowNumberColor(view);
+ }, dark, fade, delay);
+ view.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ dark ? mOverflowNumberSizeDark : mOverflowNumberSize);
+ int paddingEnd = dark ? mOverflowNumberPaddingDark : mOverflowNumberPadding;
+ view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(), paddingEnd,
+ view.getPaddingBottom());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index c78ec83..7370c03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -22,6 +22,7 @@
import android.view.animation.PathInterpolator;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationUtils;
/**
* Utility class to calculate the clock position and top padding of notifications on Keyguard.
@@ -69,7 +70,7 @@
private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
private int mClockBottom;
- private boolean mDark;
+ private float mDarkAmount;
/**
* Refreshes the dimension values.
@@ -89,7 +90,7 @@
public void setup(int maxKeyguardNotifications, int maxPanelHeight, float expandedHeight,
int notificationCount, int height, int keyguardStatusHeight, float emptyDragAmount,
- int clockBottom, boolean dark) {
+ int clockBottom, float dark) {
mMaxKeyguardNotifications = maxKeyguardNotifications;
mMaxPanelHeight = maxPanelHeight;
mExpandedHeight = expandedHeight;
@@ -98,7 +99,7 @@
mKeyguardStatusHeight = keyguardStatusHeight;
mEmptyDragAmount = emptyDragAmount;
mClockBottom = clockBottom;
- mDark = dark;
+ mDarkAmount = dark;
}
public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) {
@@ -120,9 +121,11 @@
result.clockY,
y + getClockNotificationsPadding() + mKeyguardStatusHeight);
result.clockAlpha = getClockAlpha(result.clockScale);
- if (mDark) {
- result.stackScrollerPadding = mClockBottom + y;
- }
+
+ result.stackScrollerPadding = (int) NotificationUtils.interpolate(
+ result.stackScrollerPadding,
+ mClockBottom + y,
+ mDarkAmount);
}
private float getClockScale(int notificationPadding, int clockY, int startPadding) {
@@ -149,7 +152,11 @@
}
private int getClockY() {
- return (int) (getClockYFraction() * mHeight);
+ // Dark: Align the bottom edge of the clock at one third:
+ // clockBottomEdge = result - mKeyguardStatusHeight / 2 + mClockBottom
+ float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom);
+ float clockYRegular = getClockYFraction() * mHeight;
+ return (int) NotificationUtils.interpolate(clockYRegular, clockYDark, mDarkAmount);
}
private float getClockYExpansionAdjustment() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 930191e..9000eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.stack.AnimationFilter;
import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.statusbar.stack.ViewState;
import java.util.HashMap;
@@ -87,6 +88,16 @@
return mAnimationFilter;
}
}.setDuration(200).setDelay(50);
+
+ private static final AnimationProperties UNDARK_PROPERTIES = new AnimationProperties() {
+ private AnimationFilter mAnimationFilter = new AnimationFilter()
+ .animateX();
+
+ @Override
+ public AnimationFilter getAnimationFilter() {
+ return mAnimationFilter;
+ }
+ }.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
private boolean mShowAllIcons = true;
@@ -404,11 +415,14 @@
public void setDark(boolean dark, boolean fade, long delay) {
mDark = dark;
- mDisallowNextAnimation = true;
+ mDisallowNextAnimation |= !fade;
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view instanceof StatusBarIconView) {
((StatusBarIconView) view).setDark(dark, fade, delay);
+ if (!dark && fade) {
+ getIconState((StatusBarIconView) view).justUndarkened = true;
+ }
}
}
}
@@ -465,6 +479,7 @@
public boolean useFullTransitionAmount;
public boolean useLinearTransitionAmount;
public boolean translateContent;
+ public boolean justUndarkened;
public int iconColor = StatusBarIconView.NO_COLOR;
public boolean noAnimations;
@@ -474,7 +489,8 @@
StatusBarIconView icon = (StatusBarIconView) view;
boolean animate = false;
AnimationProperties animationProperties = null;
- boolean animationsAllowed = mAnimationsEnabled && !mDisallowNextAnimation
+ boolean animationsAllowed = (mAnimationsEnabled || justUndarkened)
+ && !mDisallowNextAnimation
&& !noAnimations;
if (animationsAllowed) {
if (justAdded) {
@@ -486,6 +502,9 @@
animationProperties = ADD_ICON_PROPERTIES;
animate = true;
}
+ } else if (justUndarkened) {
+ animationProperties = UNDARK_PROPERTIES;
+ animate = true;
} else if (visibleState != icon.getVisibleState()) {
animationProperties = DOT_ANIMATION_PROPERTIES;
animate = true;
@@ -536,6 +555,7 @@
}
justAdded = false;
needsCannedAnimation = false;
+ justUndarkened = false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index f7480bc..c5853ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -31,6 +31,7 @@
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -91,6 +92,19 @@
public static final long DOZE_ANIMATION_DURATION = 700;
+ private static final FloatProperty<NotificationPanelView> SET_DARK_AMOUNT_PROPERTY =
+ new FloatProperty<NotificationPanelView>("mDarkAmount") {
+ @Override
+ public void setValue(NotificationPanelView object, float value) {
+ object.setDarkAmount(value);
+ }
+
+ @Override
+ public Float get(NotificationPanelView object) {
+ return object.mDarkAmount;
+ }
+ };
+
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private KeyguardStatusBarView mKeyguardStatusBar;
@@ -211,9 +225,10 @@
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
private boolean mIsFullWidth;
- private boolean mDark;
+ private float mDarkAmount;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private boolean mNoVisibleNotifications = true;
+ private ValueAnimator mDarkAnimator;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -397,7 +412,7 @@
mKeyguardStatusView.getHeight(),
mEmptyDragAmount,
mKeyguardStatusView.getClockBottom(),
- mDark);
+ mDarkAmount);
mClockPositionAlgorithm.run(mClockPositionResult);
if (animate || mClockAnimator != null) {
startClockAnimation(mClockPositionResult.clockY);
@@ -2473,9 +2488,27 @@
}
}
- public void setDark(boolean dark) {
- mDark = dark;
- mKeyguardStatusView.setDark(dark);
+ public void setDark(boolean dark, boolean animate) {
+ float darkAmount = dark ? 1 : 0;
+ if (mDarkAmount == darkAmount) {
+ return;
+ }
+ if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
+ mDarkAnimator.cancel();
+ }
+ if (animate) {
+ mDarkAnimator = ObjectAnimator.ofFloat(this, SET_DARK_AMOUNT_PROPERTY, darkAmount);
+ mDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mDarkAnimator.start();
+ } else {
+ setDarkAmount(darkAmount);
+ }
+ }
+
+ private void setDarkAmount(float amount) {
+ mDarkAmount = amount;
+ mKeyguardStatusView.setDark(amount == 1);
positionClockAndNotifications();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index b33d509..fc73c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4355,12 +4355,12 @@
private void updateDozingState() {
Trace.beginSection("StatusBar#updateDozingState");
- boolean animate = !mDozing && mDozeScrimController.isPulsing();
+ boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
mScrimController.setDozing(mDozing);
mKeyguardIndicationController.setDozing(mDozing);
- mNotificationPanel.setDark(mDozing);
+ mNotificationPanel.setDark(mDozing, animate);
updateQsExpansionEnabled();
// Immediately abort the dozing from the doze scrim controller in case of wake-and-unlock
@@ -5007,6 +5007,7 @@
private final class DozeServiceHost implements DozeHost {
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private boolean mAnimateWakeup;
@Override
public String toString() {
@@ -5114,6 +5115,14 @@
mDozeScrimController.extendPulse();
}
+ @Override
+ public void setAnimateWakeup(boolean animateWakeup) {
+ mAnimateWakeup = animateWakeup;
+ }
+
+ private boolean shouldAnimateWakeup() {
+ return mAnimateWakeup;
+ }
}
// Begin Extra BaseStatusBar methods.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index 34fa658..53377d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -21,7 +21,7 @@
import android.view.View;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Set;
/**
* Filters the animations for only a certain type of properties.
@@ -30,6 +30,7 @@
boolean animateAlpha;
boolean animateX;
boolean animateY;
+ ArraySet<View> animateYViews = new ArraySet<>();
boolean animateZ;
boolean animateHeight;
boolean animateTopInset;
@@ -39,9 +40,7 @@
public boolean animateShadowAlpha;
boolean hasDelays;
boolean hasGoToFullShadeEvent;
- boolean hasDarkEvent;
boolean hasHeadsUpDisappearClickEvent;
- int darkAnimationOriginIndex;
private ArraySet<Property> mAnimatedProperties = new ArraySet<>();
public AnimationFilter animateAlpha() {
@@ -105,6 +104,15 @@
return this;
}
+ public AnimationFilter animateY(View view) {
+ animateYViews.add(view);
+ return this;
+ }
+
+ public boolean shouldAnimateY(View view) {
+ return animateY || animateYViews.contains(view);
+ }
+
/**
* Combines multiple filters into {@code this} filter, using or as the operand .
*
@@ -120,11 +128,6 @@
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE) {
hasGoToFullShadeEvent = true;
}
- if (ev.animationType ==
- NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_DARK) {
- hasDarkEvent = true;
- darkAnimationOriginIndex = ev.darkAnimationOriginIndex;
- }
if (ev.animationType == NotificationStackScrollLayout.AnimationEvent
.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
hasHeadsUpDisappearClickEvent = true;
@@ -136,6 +139,7 @@
animateAlpha |= filter.animateAlpha;
animateX |= filter.animateX;
animateY |= filter.animateY;
+ animateYViews.addAll(filter.animateYViews);
animateZ |= filter.animateZ;
animateHeight |= filter.animateHeight;
animateTopInset |= filter.animateTopInset;
@@ -151,6 +155,7 @@
animateAlpha = false;
animateX = false;
animateY = false;
+ animateYViews.clear();
animateZ = false;
animateHeight = false;
animateShadowAlpha = false;
@@ -160,10 +165,7 @@
animateHideSensitive = false;
hasDelays = false;
hasGoToFullShadeEvent = false;
- hasDarkEvent = false;
hasHeadsUpDisappearClickEvent = false;
- darkAnimationOriginIndex =
- NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
mAnimatedProperties.clear();
}
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 fe249a6..cb1f44e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -30,7 +30,6 @@
import android.widget.TextView;
import com.android.systemui.R;
-import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationHeaderUtil;
@@ -39,7 +38,6 @@
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
import java.util.ArrayList;
import java.util.List;
@@ -52,6 +50,7 @@
private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
+ private static final int NUMBER_OF_CHILDREN_WHEN_AMBIENT = 3;
private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
@@ -70,7 +69,6 @@
private int mNotificationHeaderMargin;
private int mNotificatonTopPadding;
private float mCollapsedBottompadding;
- private ViewInvertHelper mOverflowInvertHelper;
private boolean mChildrenExpanded;
private ExpandableNotificationRow mContainingNotification;
private TextView mOverflowNumber;
@@ -85,12 +83,14 @@
private NotificationViewWrapper mNotificationHeaderWrapper;
private NotificationHeaderView mNotificationHeaderLowPriority;
private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
+ private ViewGroup mNotificationHeaderAmbient;
+ private NotificationViewWrapper mNotificationHeaderWrapperAmbient;
private NotificationHeaderUtil mHeaderUtil;
private ViewState mHeaderViewState;
private int mClipBottomAmount;
private boolean mIsLowPriority;
private OnClickListener mHeaderClickListener;
- private boolean mShowingNormalHeader;
+ private ViewGroup mCurrentHeader;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -152,6 +152,11 @@
mNotificationHeaderLowPriority.getMeasuredWidth(),
mNotificationHeaderLowPriority.getMeasuredHeight());
}
+ if (mNotificationHeaderAmbient != null) {
+ mNotificationHeaderAmbient.layout(0, 0,
+ mNotificationHeaderAmbient.getMeasuredWidth(),
+ mNotificationHeaderAmbient.getMeasuredHeight());
+ }
}
@Override
@@ -203,6 +208,10 @@
headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec);
}
+ if (mNotificationHeaderAmbient != null) {
+ headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
+ mNotificationHeaderAmbient.measure(widthMeasureSpec, headerHeightSpec);
+ }
setMeasuredDimension(width, height);
}
@@ -273,7 +282,7 @@
StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
- RemoteViews header = builder.makeNotificationHeader();
+ RemoteViews header = builder.makeNotificationHeader(false /* ambient */);
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
final View expandButton = mNotificationHeader.findViewById(
@@ -289,10 +298,33 @@
}
mNotificationHeaderWrapper.notifyContentUpdated(mContainingNotification);
recreateLowPriorityHeader(builder);
- updateHeaderVisibility(false /* animate */);
+ recreateAmbientHeader(builder);
+ resetHeaderVisibilityIfNeeded(mNotificationHeader, calculateDesiredHeader());
updateChildrenHeaderAppearance();
}
+ private void recreateAmbientHeader(Notification.Builder builder) {
+ RemoteViews header;
+ StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
+ if (builder == null) {
+ builder = Notification.Builder.recoverBuilder(getContext(),
+ notification.getNotification());
+ }
+ header = builder.makeNotificationHeader(true /* ambient */);
+ if (mNotificationHeaderAmbient == null) {
+ mNotificationHeaderAmbient = (ViewGroup) header.apply(getContext(), this);
+ mNotificationHeaderWrapperAmbient = NotificationViewWrapper.wrap(getContext(),
+ mNotificationHeaderAmbient, mContainingNotification);
+ mNotificationHeaderWrapperAmbient.notifyContentUpdated(mContainingNotification);
+ addView(mNotificationHeaderAmbient, 0);
+ invalidate();
+ } else {
+ header.reapply(getContext(), mNotificationHeaderAmbient);
+ }
+ resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, calculateDesiredHeader());
+ mNotificationHeaderWrapperAmbient.notifyContentUpdated(mContainingNotification);
+ }
+
/**
* Recreate the low-priority header.
*
@@ -322,6 +354,7 @@
header.reapply(getContext(), mNotificationHeaderLowPriority);
}
mNotificationHeaderWrapperLowPriority.notifyContentUpdated(mContainingNotification);
+ resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader());
} else {
removeView(mNotificationHeaderLowPriority);
mNotificationHeaderLowPriority = null;
@@ -339,10 +372,6 @@
if (childCount > maxAllowedVisibleChildren) {
mOverflowNumber = mHybridGroupManager.bindOverflowNumber(
mOverflowNumber, childCount - maxAllowedVisibleChildren);
- if (mOverflowInvertHelper == null) {
- mOverflowInvertHelper = new ViewInvertHelper(mOverflowNumber,
- NotificationPanelView.DOZE_ANIMATION_DURATION);
- }
if (mGroupOverFlowState == null) {
mGroupOverFlowState = new ViewState();
mNeverAppliedGroupState = true;
@@ -360,7 +389,6 @@
});
}
mOverflowNumber = null;
- mOverflowInvertHelper = null;
mGroupOverFlowState = null;
}
}
@@ -449,6 +477,7 @@
if (mUserLocked) {
expandFactor = getGroupExpandFraction();
}
+ boolean childrenExpanded = mChildrenExpanded || mContainingNotification.isShowingAmbient();
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
break;
@@ -458,7 +487,7 @@
intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
expandFactor);
} else {
- intrinsicHeight += mChildrenExpanded ? mDividerHeight : mChildPadding;
+ intrinsicHeight += childrenExpanded ? mDividerHeight : mChildPadding;
}
} else {
if (mUserLocked) {
@@ -467,7 +496,7 @@
mNotificatonTopPadding + mDividerHeight,
expandFactor);
} else {
- intrinsicHeight += mChildrenExpanded
+ intrinsicHeight += childrenExpanded
? mNotificatonTopPadding + mDividerHeight
: 0;
}
@@ -480,7 +509,7 @@
if (mUserLocked) {
intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
expandFactor);
- } else if (!mChildrenExpanded) {
+ } else if (!childrenExpanded) {
intrinsicHeight += mCollapsedBottompadding;
}
return intrinsicHeight;
@@ -515,7 +544,7 @@
yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
expandFactor);
} else {
- yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
+ yPosition += childrenExpanded ? mDividerHeight : mChildPadding;
}
} else {
if (expandingToExpandedGroup) {
@@ -524,7 +553,7 @@
mNotificatonTopPadding + mDividerHeight,
expandFactor);
} else {
- yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
+ yPosition += childrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
}
firstChild = false;
}
@@ -560,15 +589,21 @@
ExpandableNotificationRow overflowView = mChildren.get(Math.min(
getMaxAllowedVisibleChildren(true /* likeCollpased */), childCount) - 1);
mGroupOverFlowState.copyFrom(resultState.getViewStateForView(overflowView));
- if (!mChildrenExpanded) {
- if (mUserLocked) {
- HybridNotificationView singleLineView = overflowView.getSingleLineView();
- View mirrorView = singleLineView.getTextView();
+
+ if (mContainingNotification.isShowingAmbient() || !mChildrenExpanded) {
+ HybridNotificationView alignView = null;
+ if (mContainingNotification.isShowingAmbient()) {
+ alignView = overflowView.getAmbientSingleLineView();
+ } else if (mUserLocked) {
+ alignView = overflowView.getSingleLineView();
+ }
+ if (alignView != null) {
+ View mirrorView = alignView.getTextView();
if (mirrorView.getVisibility() == GONE) {
- mirrorView = singleLineView.getTitleView();
+ mirrorView = alignView.getTitleView();
}
if (mirrorView.getVisibility() == GONE) {
- mirrorView = singleLineView;
+ mirrorView = alignView;
}
mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
mirrorView, overflowView);
@@ -620,6 +655,9 @@
}
private int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
+ if (mContainingNotification.isShowingAmbient()) {
+ return NUMBER_OF_CHILDREN_WHEN_AMBIENT;
+ }
if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())) {
return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
}
@@ -794,40 +832,78 @@
return mNotificationHeaderLowPriority;
}
+ public void notifyShowAmbientChanged() {
+ updateHeaderVisibility(false);
+ }
+
private void updateHeaderVisibility(boolean animate) {
- NotificationHeaderView visibleHeader = mNotificationHeader;
- NotificationHeaderView hiddenHeader = mNotificationHeaderLowPriority;
- boolean normalHeaderVisible = true;
- if (showingAsLowPriority()) {
- visibleHeader = mNotificationHeaderLowPriority;
- hiddenHeader = mNotificationHeader;
- normalHeaderVisible = false;
+ ViewGroup desiredHeader;
+ ViewGroup currentHeader = mCurrentHeader;
+ desiredHeader = calculateDesiredHeader();
+
+ if (currentHeader == desiredHeader) {
+ return;
}
+ if (desiredHeader == mNotificationHeaderAmbient
+ || currentHeader == mNotificationHeaderAmbient) {
+ animate = false;
+ }
+
if (animate) {
- if (visibleHeader != null && hiddenHeader != null
- && mShowingNormalHeader != normalHeaderVisible) {
- hiddenHeader.setVisibility(VISIBLE);
- visibleHeader.setVisibility(VISIBLE);
- NotificationViewWrapper visibleWrapper = getWrapperForView(visibleHeader);
- NotificationViewWrapper hiddenWrapper = getWrapperForView(hiddenHeader);
+ if (desiredHeader != null && currentHeader != null) {
+ currentHeader.setVisibility(VISIBLE);
+ desiredHeader.setVisibility(VISIBLE);
+ NotificationViewWrapper visibleWrapper = getWrapperForView(desiredHeader);
+ NotificationViewWrapper hiddenWrapper = getWrapperForView(currentHeader);
visibleWrapper.transformFrom(hiddenWrapper);
hiddenWrapper.transformTo(visibleWrapper, () -> updateHeaderVisibility(false));
- startChildAlphaAnimations(normalHeaderVisible);
+ startChildAlphaAnimations(desiredHeader == mNotificationHeader);
} else {
animate = false;
}
}
if (!animate) {
- if (visibleHeader != null) {
- getWrapperForView(visibleHeader).setVisible(true);
- visibleHeader.setVisibility(VISIBLE);
+ if (desiredHeader != null) {
+ getWrapperForView(desiredHeader).setVisible(true);
+ desiredHeader.setVisibility(VISIBLE);
}
- if (hiddenHeader != null) {
- getWrapperForView(hiddenHeader).setVisible(false);
- hiddenHeader.setVisibility(INVISIBLE);
+ if (currentHeader != null) {
+ getWrapperForView(currentHeader).setVisible(false);
+ currentHeader.setVisibility(INVISIBLE);
}
}
- mShowingNormalHeader = normalHeaderVisible;
+
+ resetHeaderVisibilityIfNeeded(mNotificationHeader, desiredHeader);
+ resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, desiredHeader);
+ resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, desiredHeader);
+
+ mCurrentHeader = currentHeader;
+ }
+
+ private void resetHeaderVisibilityIfNeeded(View header, View desiredHeader) {
+ if (header == null) {
+ return;
+ }
+ if (header != mCurrentHeader && header != desiredHeader) {
+ getWrapperForView(header).setVisible(false);
+ header.setVisibility(INVISIBLE);
+ }
+ if (header == desiredHeader && header.getVisibility() != VISIBLE) {
+ getWrapperForView(header).setVisible(true);
+ header.setVisibility(VISIBLE);
+ }
+ }
+
+ private ViewGroup calculateDesiredHeader() {
+ ViewGroup desiredHeader;
+ if (mContainingNotification.isShowingAmbient()) {
+ desiredHeader = mNotificationHeaderAmbient;
+ } else if (showingAsLowPriority()) {
+ desiredHeader = mNotificationHeaderLowPriority;
+ } else {
+ desiredHeader = mNotificationHeader;
+ }
+ return desiredHeader;
}
private void startChildAlphaAnimations(boolean toVisible) {
@@ -861,10 +937,13 @@
}
- private NotificationViewWrapper getWrapperForView(NotificationHeaderView visibleHeader) {
+ private NotificationViewWrapper getWrapperForView(View visibleHeader) {
if (visibleHeader == mNotificationHeader) {
return mNotificationHeaderWrapper;
}
+ if (visibleHeader == mNotificationHeaderAmbient) {
+ return mNotificationHeaderWrapperAmbient;
+ }
return mNotificationHeaderWrapperLowPriority;
}
@@ -971,7 +1050,9 @@
}
public int getMinHeight() {
- return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
+ return getMinHeight(mContainingNotification.isShowingAmbient()
+ ? NUMBER_OF_CHILDREN_WHEN_AMBIENT
+ : NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
}
public int getCollapsedHeight() {
@@ -1016,7 +1097,7 @@
public void setDark(boolean dark, boolean fade, long delay) {
if (mOverflowNumber != null) {
- mOverflowInvertHelper.setInverted(dark, fade, delay);
+ mHybridGroupManager.setOverflowNumberDark(mOverflowNumber, dark, fade, delay);
}
mNotificationHeaderWrapper.setDark(dark, fade, delay);
}
@@ -1030,6 +1111,10 @@
removeView(mNotificationHeaderLowPriority);
mNotificationHeaderLowPriority = null;
}
+ if (mNotificationHeaderAmbient != null) {
+ removeView(mNotificationHeaderAmbient);
+ mNotificationHeaderAmbient = null;
+ }
recreateNotificationHeader(listener);
initDimens();
for (int i = 0; i < mDividers.size(); i++) {
@@ -1042,7 +1127,6 @@
}
removeView(mOverflowNumber);
mOverflowNumber = null;
- mOverflowInvertHelper = null;
mGroupOverFlowState = null;
updateGroupOverflow();
}
@@ -1061,7 +1145,8 @@
public void onNotificationUpdated() {
mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
- mContainingNotification.getNotificationColor());
+ mContainingNotification.getNotificationColor(),
+ mContainingNotification.getNotificationColorAmbient());
}
public int getPositionInLinearLayout(View childInGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 5f83e3d..2f7a4ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -53,6 +53,7 @@
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.OverScroller;
@@ -3119,7 +3120,11 @@
private void generateDarkEvent() {
if (mDarkNeedsAnimation) {
- AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
+ AnimationEvent ev = new AnimationEvent(null,
+ AnimationEvent.ANIMATION_TYPE_DARK,
+ new AnimationFilter()
+ .animateDark()
+ .animateY(mShelf));
ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
mAnimationEvents.add(ev);
startBackgroundFadeIn();
@@ -3701,18 +3706,7 @@
private void startBackgroundFadeIn() {
ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f);
- int maxLength;
- if (mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE
- || mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
- maxLength = getNotGoneChildCount() - 1;
- } else {
- maxLength = Math.max(mDarkAnimationOriginIndex,
- getNotGoneChildCount() - mDarkAnimationOriginIndex - 1);
- }
- maxLength = Math.max(0, maxLength);
- long delay = maxLength * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_DARK;
- fadeAnimator.setStartDelay(delay);
- fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
fadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
fadeAnimator.start();
}
@@ -4563,9 +4557,7 @@
.animateZ(),
// ANIMATION_TYPE_DARK
- new AnimationFilter()
- .animateDark()
- .hasDelays(),
+ null, // Unused
// ANIMATION_TYPE_GO_TO_FULL_SHADE
new AnimationFilter()
@@ -4674,7 +4666,7 @@
StackStateAnimator.ANIMATION_DURATION_STANDARD,
// ANIMATION_TYPE_DARK
- StackStateAnimator.ANIMATION_DURATION_STANDARD,
+ StackStateAnimator.ANIMATION_DURATION_WAKEUP,
// ANIMATION_TYPE_GO_TO_FULL_SHADE
StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
@@ -4740,12 +4732,20 @@
this(view, type, LENGTHS[type]);
}
+ AnimationEvent(View view, int type, AnimationFilter filter) {
+ this(view, type, LENGTHS[type], filter);
+ }
+
AnimationEvent(View view, int type, long length) {
+ this(view, type, length, FILTERS[type]);
+ }
+
+ AnimationEvent(View view, int type, long length, AnimationFilter filter) {
eventStartTime = AnimationUtils.currentAnimationTimeMillis();
changingView = view;
animationType = type;
- filter = FILTERS[type];
this.length = length;
+ this.filter = filter;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 9893434..f78a718 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -40,6 +40,7 @@
public class StackStateAnimator {
public static final int ANIMATION_DURATION_STANDARD = 360;
+ public static final int ANIMATION_DURATION_WAKEUP = 200;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
@@ -49,7 +50,6 @@
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
- public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
public static final int ANIMATION_DELAY_HEADS_UP = 120;
@@ -219,9 +219,6 @@
private long calculateChildAnimationDelay(ExpandableViewState viewState,
StackScrollState finalState) {
- if (mAnimationFilter.hasDarkEvent) {
- return calculateDelayDark(viewState);
- }
if (mAnimationFilter.hasGoToFullShadeEvent) {
return calculateDelayGoToFullShade(viewState);
}
@@ -278,20 +275,6 @@
return minDelay;
}
- private long calculateDelayDark(ExpandableViewState viewState) {
- int referenceIndex;
- if (mAnimationFilter.darkAnimationOriginIndex ==
- NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
- referenceIndex = 0;
- } else if (mAnimationFilter.darkAnimationOriginIndex ==
- NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
- referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
- } else {
- referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
- }
- return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
- }
-
private long calculateDelayGoToFullShade(ExpandableViewState viewState) {
int shelfIndex = mShelf.getNotGoneIndex();
float index = viewState.notGoneIndex;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 5b594be..d664b12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -21,6 +21,7 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.app.Notification;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -28,6 +29,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -223,7 +225,7 @@
}
}
- protected boolean isAnimating(View view) {
+ public boolean isAnimating(View view) {
if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) {
return true;
}
@@ -540,7 +542,7 @@
}
ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
AnimationFilter filter = properties.getAnimationFilter();
- if (!filter.animateY) {
+ if (!filter.shouldAnimateY(child)) {
// just a local update was performed
if (previousAnimator != null) {
// we need to increase all animation keyframes of the previous animator by the
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index d2afa2a..ee0fe7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -24,8 +24,9 @@
*/
class DozeHostFake implements DozeHost {
Callback callback;
- private boolean pulseAborted;
- private boolean pulseExtended;
+ boolean pulseAborted;
+ boolean pulseExtended;
+ boolean animateWakeup;
@Override
public void addCallback(@NonNull Callback callback) {
@@ -81,4 +82,9 @@
public void extendPulse() {
pulseExtended = true;
}
+
+ @Override
+ public void setAnimateWakeup(boolean animateWakeup) {
+ this.animateWakeup = animateWakeup;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
new file mode 100644
index 0000000..9e15a05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.settings;
+
+import android.content.Intent;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Testing functionality of the current user tracker
+ */
+public class CurrentUserTrackerTest extends SysuiTestCase {
+
+ private CurrentUserTracker mTracker;
+ private CurrentUserTracker.UserReceiver mReceiver;
+
+ @Before
+ public void setUp() {
+ mReceiver = new CurrentUserTracker.UserReceiver(getContext());
+ mTracker = new CurrentUserTracker(mReceiver) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ stopTracking();
+ }
+ };
+ }
+
+ @Test
+ public void testBroadCastDoesntCrashOnConcurrentModification() {
+ mTracker.startTracking();
+ CurrentUserTracker secondTracker = new CurrentUserTracker(mReceiver) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ stopTracking();
+ }
+ };
+ secondTracker.startTracking();
+ triggerUserSwitch();
+ }
+ /**
+ * Simulates a user switch event.
+ */
+ private void triggerUserSwitch() {
+ Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, 1);
+ mReceiver.onReceive(getContext(), intent);
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 4dd0b35..0999580 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -144,6 +144,9 @@
final int userId = users.get(i).id;
final boolean disabled = umi.getUserRestriction(userId, UserManager.DISALLOW_AUTOFILL);
if (disabled) {
+ if (disabled) {
+ Slog.i(TAG, "Disabling Autofill for user " + userId);
+ }
mDisabledUsers.put(userId, disabled);
}
}
@@ -155,11 +158,12 @@
if (disabledBefore == disabledNow) {
// Nothing changed, do nothing.
if (sDebug) {
- Slog.d(TAG, "Restriction not changed for user " + userId + ": "
+ Slog.d(TAG, "Autofill restriction did not change for user " + userId + ": "
+ bundleToString(newRestrictions));
return;
}
}
+ Slog.i(TAG, "Updating Autofill for user " + userId + ": disabled=" + disabledNow);
mDisabledUsers.put(userId, disabledNow);
updateCachedServiceLocked(userId, disabledNow);
}
@@ -606,7 +610,7 @@
pw.println("Usage: dumpsys autofill [--ui-only|--no-history]");
return;
default:
- throw new IllegalArgumentException("Invalid dump arg: " + arg);
+ Slog.w(TAG, "Ignoring invalid dump arg: " + arg);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 70771e8..c455eda 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -384,6 +384,7 @@
}
}
if (response == null) {
+ if (sVerbose) Slog.v(TAG, "canceling session " + id + " when server returned null");
if ((requestFlags & FLAG_MANUAL_REQUEST) != 0) {
getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
}
@@ -906,7 +907,10 @@
// If it's not, then check if it it should start a partition.
if (shouldStartNewPartitionLocked(id)) {
- if (sDebug) Slog.d(TAG, "Starting partition for view id " + id);
+ if (sDebug) {
+ Slog.d(TAG, "Starting partition for view id " + id + ": "
+ + viewState.getStateAsString());
+ }
viewState.setState(ViewState.STATE_STARTED_PARTITION);
requestNewFillResponseLocked(flags);
}
@@ -1344,15 +1348,19 @@
}
void dumpLocked(String prefix, PrintWriter pw) {
+ final String prefix2 = prefix + " ";
pw.print(prefix); pw.print("id: "); pw.println(id);
pw.print(prefix); pw.print("uid: "); pw.println(uid);
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
- pw.print(prefix); pw.print("mResponses: "); pw.println(mResponses);
+ pw.print(prefix); pw.print("mResponses: "); pw.println(mResponses.size());
+ for (int i = 0; i < mResponses.size(); i++) {
+ pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(' ');
+ pw.println(mResponses.valueAt(i));
+ }
pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId);
pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size());
pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
pw.print(prefix); pw.print("mIsSaving: "); pw.println(mIsSaving);
- final String prefix2 = prefix + " ";
for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
entry.getValue().dump(prefix2, pw);
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 20a6d14..bff39b3 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -56,6 +56,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
import android.app.AlertDialog;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -3562,7 +3563,6 @@
private void showInputMethodMenu(boolean showAuxSubtypes) {
if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
- final Context context = mContext;
final boolean isScreenLocked = isScreenLocked();
final String lastInputMethodId = mSettings.getSelectedInputMethod();
@@ -3610,7 +3610,8 @@
}
}
- final Context settingsContext = new ContextThemeWrapper(context,
+ final Context settingsContext = new ContextThemeWrapper(
+ ActivityThread.currentActivityThread().getSystemUiContext(),
com.android.internal.R.style.Theme_DeviceDefault_Settings);
mDialogBuilder = new AlertDialog.Builder(settingsContext);
diff --git a/services/core/java/com/android/server/am/PersistentConnection.java b/services/core/java/com/android/server/am/PersistentConnection.java
index c34c097..52eaca1 100644
--- a/services/core/java/com/android/server/am/PersistentConnection.java
+++ b/services/core/java/com/android/server/am/PersistentConnection.java
@@ -22,32 +22,77 @@
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
/**
* Connects to a given service component on a given user.
*
- * - Call {@link #connect()} to create a connection.
- * - Call {@link #disconnect()} to disconnect. Make sure to disconnect when the user stops.
+ * - Call {@link #bind()} to create a connection.
+ * - Call {@link #unbind()} to disconnect. Make sure to disconnect when the user stops.
*
* Add onConnected/onDisconnected callbacks as needed.
+ *
+ * When the target process gets killed (by OOM-killer, etc), then the activity manager will
+ * re-connect the connection automatically, in which case onServiceDisconnected() gets called
+ * and then onServiceConnected().
+ *
+ * However sometimes the activity manager just "kills" the connection -- like when the target
+ * package gets updated or the target process crashes multiple times in a row, in which case
+ * onBindingDied() gets called. This class handles this case by re-connecting in the time
+ * {@link #mRebindBackoffMs}. If this happens again, this class increases the back-off time
+ * by {@link #mRebindBackoffIncrease} and retry. The back-off time is capped at
+ * {@link #mRebindMaxBackoffMs}.
+ *
+ * The back-off time will never be reset until {@link #unbind()} and {@link #bind()} are called
+ * explicitly.
+ *
+ * NOTE: This class does *not* handle package-updates -- i.e. even if the binding dies due to
+ * the target package being updated, this class won't reconnect. This is because this class doesn't
+ * know what to do when the service component has gone missing, for example. If the user of this
+ * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind}
+ * explicitly.
*/
public abstract class PersistentConnection<T> {
private final Object mLock = new Object();
+ private final static boolean DEBUG = false;
+
private final String mTag;
private final Context mContext;
private final Handler mHandler;
private final int mUserId;
private final ComponentName mComponentName;
+ private long mNextBackoffMs;
+
+ private final long mRebindBackoffMs;
+ private final double mRebindBackoffIncrease;
+ private final long mRebindMaxBackoffMs;
+
+ private long mReconnectTime;
+
+ // TODO too many booleans... Should clean up.
+
@GuardedBy("mLock")
- private boolean mStarted;
+ private boolean mBound;
+
+ /**
+ * Whether {@link #bind()} has been called and {@link #unbind()} hasn't been yet; meaning this
+ * is the expected bind state from the caller's point of view.
+ */
+ @GuardedBy("mLock")
+ private boolean mShouldBeBound;
+
+ @GuardedBy("mLock")
+ private boolean mRebindScheduled;
@GuardedBy("mLock")
private boolean mIsConnected;
@@ -59,6 +104,14 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
+ if (!mBound) {
+ // Callback came in after PersistentConnection.unbind() was called.
+ // We just ignore this.
+ // (We've already called unbindService() already in unbind)
+ Slog.w(mTag, "Connected: " + mComponentName.flattenToShortString()
+ + " u" + mUserId + " but not bound, ignore.");
+ return;
+ }
Slog.i(mTag, "Connected: " + mComponentName.flattenToShortString()
+ " u" + mUserId);
@@ -76,15 +129,41 @@
cleanUpConnectionLocked();
}
}
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ // Activity manager gave up; we'll schedule a re-connect by ourselves.
+ synchronized (mLock) {
+ if (!mBound) {
+ // Callback came in late?
+ Slog.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
+ + " u" + mUserId + " but not bound, ignore.");
+ return;
+ }
+
+ Slog.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
+ + " u" + mUserId);
+ scheduleRebindLocked();
+ }
+ }
};
+ private final Runnable mBindForBackoffRunnable = () -> bindForBackoff();
+
public PersistentConnection(@NonNull String tag, @NonNull Context context,
- @NonNull Handler handler, int userId, @NonNull ComponentName componentName) {
+ @NonNull Handler handler, int userId, @NonNull ComponentName componentName,
+ long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds) {
mTag = tag;
mContext = context;
mHandler = handler;
mUserId = userId;
mComponentName = componentName;
+
+ mRebindBackoffMs = rebindBackoffSeconds * 1000;
+ mRebindBackoffIncrease = rebindBackoffIncrease;
+ mRebindMaxBackoffMs = rebindMaxBackoffSeconds * 1000;
+
+ mNextBackoffMs = mRebindBackoffMs;
}
public final ComponentName getComponentName() {
@@ -92,6 +171,27 @@
}
/**
+ * @return whether {@link #bind()} has been called and {@link #unbind()} hasn't.
+ *
+ * Note when the AM gives up on connection, this class detects it and un-bind automatically,
+ * and schedule rebind, and {@link #isBound} returns false when it's waiting for a retry.
+ */
+ public final boolean isBound() {
+ synchronized (mLock) {
+ return mBound;
+ }
+ }
+
+ /**
+ * @return whether re-bind is scheduled after the AM gives up on a connection.
+ */
+ public final boolean isRebindScheduled() {
+ synchronized (mLock) {
+ return mRebindScheduled;
+ }
+ }
+
+ /**
* @return whether connected.
*/
public final boolean isConnected() {
@@ -112,23 +212,51 @@
/**
* Connects to the service.
*/
- public final void connect() {
+ public final void bind() {
synchronized (mLock) {
- if (mStarted) {
+ mShouldBeBound = true;
+
+ bindInnerLocked(/* resetBackoff= */ true);
+ }
+ }
+
+ public final void bindInnerLocked(boolean resetBackoff) {
+ unscheduleRebindLocked();
+
+ if (mBound) {
+ return;
+ }
+ mBound = true;
+
+ if (resetBackoff) {
+ // Note this is the only place we reset the backoff time.
+ mNextBackoffMs = mRebindBackoffMs;
+ }
+
+ final Intent service = new Intent().setComponent(mComponentName);
+
+ if (DEBUG) {
+ Slog.d(mTag, "Attempting to connect to " + mComponentName);
+ }
+
+ final boolean success = mContext.bindServiceAsUser(service, mServiceConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ mHandler, UserHandle.of(mUserId));
+
+ if (!success) {
+ Slog.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
+ + " failed.");
+ }
+ }
+
+ final void bindForBackoff() {
+ synchronized (mLock) {
+ if (!mShouldBeBound) {
+ // Race condition -- by the time we got here, unbind() has already been called.
return;
}
- mStarted = true;
- final Intent service = new Intent().setComponent(mComponentName);
-
- final boolean success = mContext.bindServiceAsUser(service, mServiceConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- mHandler, UserHandle.of(mUserId));
-
- if (!success) {
- Slog.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
- + " failed.");
- }
+ bindInnerLocked(/* resetBackoff= */ false);
}
}
@@ -140,16 +268,46 @@
/**
* Disconnect from the service.
*/
- public final void disconnect() {
+ public final void unbind() {
synchronized (mLock) {
- if (!mStarted) {
- return;
- }
- Slog.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
- mStarted = false;
- mContext.unbindService(mServiceConnection);
+ mShouldBeBound = false;
- cleanUpConnectionLocked();
+ unbindLocked();
+ }
+ }
+
+ private final void unbindLocked() {
+ unscheduleRebindLocked();
+
+ if (!mBound) {
+ return;
+ }
+ Slog.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
+ mBound = false;
+ mContext.unbindService(mServiceConnection);
+
+ cleanUpConnectionLocked();
+ }
+
+ void unscheduleRebindLocked() {
+ injectRemoveCallbacks(mBindForBackoffRunnable);
+ mRebindScheduled = false;
+ }
+
+ void scheduleRebindLocked() {
+ unbindLocked();
+
+ if (!mRebindScheduled) {
+ Slog.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)");
+
+ mReconnectTime = injectUptimeMillis() + mNextBackoffMs;
+
+ injectPostAtTime(mBindForBackoffRunnable, mReconnectTime);
+
+ mNextBackoffMs = Math.min(mRebindMaxBackoffMs,
+ (long) (mNextBackoffMs * mRebindBackoffIncrease));
+
+ mRebindScheduled = true;
}
}
@@ -160,9 +318,57 @@
synchronized (mLock) {
pw.print(prefix);
pw.print(mComponentName.flattenToShortString());
- pw.print(mStarted ? " [started]" : " [not started]");
+ pw.print(mBound ? " [bound]" : " [not bound]");
pw.print(mIsConnected ? " [connected]" : " [not connected]");
+ if (mRebindScheduled) {
+ pw.print(" reconnect in ");
+ TimeUtils.formatDuration((mReconnectTime - injectUptimeMillis()), pw);
+ }
pw.println();
+
+ pw.print(prefix);
+ pw.print(" Next backoff(sec): ");
+ pw.print(mNextBackoffMs / 1000);
}
}
+
+ @VisibleForTesting
+ void injectRemoveCallbacks(Runnable r) {
+ mHandler.removeCallbacks(r);
+ }
+
+ @VisibleForTesting
+ void injectPostAtTime(Runnable r, long uptimeMillis) {
+ mHandler.postAtTime(r, uptimeMillis);
+ }
+
+ @VisibleForTesting
+ long injectUptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+
+ @VisibleForTesting
+ long getNextBackoffMsForTest() {
+ return mNextBackoffMs;
+ }
+
+ @VisibleForTesting
+ long getReconnectTimeForTest() {
+ return mReconnectTime;
+ }
+
+ @VisibleForTesting
+ ServiceConnection getServiceConnectionForTest() {
+ return mServiceConnection;
+ }
+
+ @VisibleForTesting
+ Runnable getBindForBackoffRunnableForTest() {
+ return mBindForBackoffRunnable;
+ }
+
+ @VisibleForTesting
+ boolean shouldBeBoundForTest() {
+ return mShouldBeBound;
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 23a1a78..7dcd6cd 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3269,12 +3269,8 @@
final boolean warningEnabled = Settings.System.getInt(getContext().getContentResolver(),
Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, 0) != 0;
if (warningEnabled || Build.IS_DEBUGGABLE) {
- try {
- Toast toast = Toast.makeText(getContext(), toastText, Toast.LENGTH_LONG);
- toast.show();
- } catch (RuntimeException e) {
- Slog.w(TAG, "Unable to toast with text: " + toastText, e);
- }
+ Toast toast = Toast.makeText(getContext(), mHandler.getLooper(), toastText, Toast.LENGTH_LONG);
+ toast.show();
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6d13c85..f1e4e12 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1741,13 +1741,14 @@
int ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
- if (getDefaultVerificationResponse() == PackageManager.VERIFICATION_ALLOW) {
+ final UserHandle user = args.getUser();
+ if (getDefaultVerificationResponse(user)
+ == PackageManager.VERIFICATION_ALLOW) {
Slog.i(TAG, "Continuing with installation of " + originUri);
state.setVerifierResponse(Binder.getCallingUid(),
PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
broadcastPackageVerified(verificationId, originUri,
- PackageManager.VERIFICATION_ALLOW,
- state.getInstallArgs().getUser());
+ PackageManager.VERIFICATION_ALLOW, user);
try {
ret = args.copyApk(mContainerService, true);
} catch (RemoteException e) {
@@ -1755,8 +1756,7 @@
}
} else {
broadcastPackageVerified(verificationId, originUri,
- PackageManager.VERIFICATION_REJECT,
- state.getInstallArgs().getUser());
+ PackageManager.VERIFICATION_REJECT, user);
}
Trace.asyncTraceEnd(
@@ -14207,7 +14207,10 @@
*
* @return default verification response code
*/
- private int getDefaultVerificationResponse() {
+ private int getDefaultVerificationResponse(UserHandle user) {
+ if (sUserManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS, user.getIdentifier())) {
+ return PackageManager.VERIFICATION_REJECT;
+ }
return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
android.provider.Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
DEFAULT_VERIFICATION_RESPONSE);
diff --git a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
new file mode 100644
index 0000000..4f5d156
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
@@ -0,0 +1,311 @@
+/*
+ * 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.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.drawable.Icon;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
+
+import libcore.io.IoUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Deque;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class to save shortcut bitmaps on a worker thread.
+ *
+ * The methods with the "Locked" prefix must be called with the service lock held.
+ */
+public class ShortcutBitmapSaver {
+ private static final String TAG = ShortcutService.TAG;
+ private static final boolean DEBUG = ShortcutService.DEBUG;
+
+ private static final boolean ADD_DELAY_BEFORE_SAVE_FOR_TEST = false; // DO NOT submit with true.
+ private static final long SAVE_DELAY_MS_FOR_TEST = 1000; // DO NOT submit with true.
+
+ /**
+ * Before saving shortcuts.xml, and returning icons to the launcher, we wait for all pending
+ * saves to finish. However if it takes more than this long, we just give up and proceed.
+ */
+ private final long SAVE_WAIT_TIMEOUT_MS = 30 * 1000;
+
+ private final ShortcutService mService;
+
+ /**
+ * Bitmaps are saved on this thread.
+ *
+ * Note: Just before saving shortcuts into the XML, we need to wait on all pending saves to
+ * finish, and we need to do it with the service lock held, which would still block incoming
+ * binder calls, meaning saving bitmaps *will* still actually block API calls too, which is
+ * not ideal but fixing it would be tricky, so this is still a known issue on the current
+ * version.
+ *
+ * In order to reduce the conflict, we use an own thread for this purpose, rather than
+ * reusing existing background threads, and also to avoid possible deadlocks.
+ */
+ private final Executor mExecutor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>());
+
+ /** Represents a bitmap to save. */
+ private static class PendingItem {
+ /** Hosting shortcut. */
+ public final ShortcutInfo shortcut;
+
+ /** Compressed bitmap data. */
+ public final byte[] bytes;
+
+ /** Instantiated time, only for dogfooding. */
+ private final long mInstantiatedUptimeMillis; // Only for dumpsys.
+
+ private PendingItem(ShortcutInfo shortcut, byte[] bytes) {
+ this.shortcut = shortcut;
+ this.bytes = bytes;
+ mInstantiatedUptimeMillis = SystemClock.uptimeMillis();
+ }
+
+ @Override
+ public String toString() {
+ return "PendingItem{size=" + bytes.length
+ + " age=" + (SystemClock.uptimeMillis() - mInstantiatedUptimeMillis) + "ms"
+ + " shortcut=" + shortcut.toInsecureString()
+ + "}";
+ }
+ }
+
+ @GuardedBy("mPendingItems")
+ private final Deque<PendingItem> mPendingItems = new LinkedBlockingDeque<>();
+
+ public ShortcutBitmapSaver(ShortcutService service) {
+ mService = service;
+ // mLock = lock;
+ }
+
+ public boolean waitForAllSavesLocked() {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ mExecutor.execute(() -> latch.countDown());
+
+ try {
+ if (latch.await(SAVE_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ return true;
+ }
+ mService.wtf("Timed out waiting on saving bitmaps.");
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "interrupted");
+ }
+ return false;
+ }
+
+ /**
+ * Wait for all pending saves to finish, and then return the given shortcut's bitmap path.
+ */
+ @Nullable
+ public String getBitmapPathMayWaitLocked(ShortcutInfo shortcut) {
+ final boolean success = waitForAllSavesLocked();
+ if (success && shortcut.hasIconFile()) {
+ return shortcut.getBitmapPath();
+ } else {
+ return null;
+ }
+ }
+
+ public void removeIcon(ShortcutInfo shortcut) {
+ // Do not remove the actual bitmap file yet, because if the device crashes before saving
+ // the XML we'd lose the icon. We just remove all dangling files after saving the XML.
+ shortcut.setIconResourceId(0);
+ shortcut.setIconResName(null);
+ shortcut.setBitmapPath(null);
+ shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE |
+ ShortcutInfo.FLAG_ADAPTIVE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES |
+ ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
+ }
+
+ public void saveBitmapLocked(ShortcutInfo shortcut,
+ int maxDimension, CompressFormat format, int quality) {
+ final Icon icon = shortcut.getIcon();
+ Preconditions.checkNotNull(icon);
+
+ final Bitmap original = icon.getBitmap();
+ if (original == null) {
+ Log.e(TAG, "Missing icon: " + shortcut);
+ return;
+ }
+
+ // Compress it and enqueue to the requests.
+ final byte[] bytes;
+ try {
+ final Bitmap shrunk = mService.shrinkBitmap(original, maxDimension);
+ try {
+ try (final ByteArrayOutputStream out = new ByteArrayOutputStream(64 * 1024)) {
+ if (!shrunk.compress(format, quality, out)) {
+ Slog.wtf(ShortcutService.TAG, "Unable to compress bitmap");
+ }
+ out.flush();
+ bytes = out.toByteArray();
+ out.close();
+ }
+ } finally {
+ if (shrunk != original) {
+ shrunk.recycle();
+ }
+ }
+ } catch (IOException | RuntimeException | OutOfMemoryError e) {
+ Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
+ return;
+ }
+
+ shortcut.addFlags(
+ ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
+
+ if (icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ shortcut.addFlags(ShortcutInfo.FLAG_ADAPTIVE_BITMAP);
+ }
+
+ // Enqueue a pending save.
+ final PendingItem item = new PendingItem(shortcut, bytes);
+ synchronized (mPendingItems) {
+ mPendingItems.add(item);
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling to save: " + item);
+ }
+
+ mExecutor.execute(mRunnable);
+ }
+
+ private final Runnable mRunnable = () -> {
+ // Process all pending items.
+ while (processPendingItems()) {
+ }
+ };
+
+ /**
+ * Takes a {@link PendingItem} from {@link #mPendingItems} and process it.
+ *
+ * Must be called {@link #mExecutor}.
+ *
+ * @return true if it processed an item, false if the queue is empty.
+ */
+ private boolean processPendingItems() {
+ if (ADD_DELAY_BEFORE_SAVE_FOR_TEST) {
+ Slog.w(TAG, "*** ARTIFICIAL SLEEP ***");
+ try {
+ Thread.sleep(SAVE_DELAY_MS_FOR_TEST);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // NOTE:
+ // Ideally we should be holding the service lock when accessing shortcut instances,
+ // but that could cause a deadlock so we don't do it.
+ //
+ // Instead, waitForAllSavesLocked() uses a latch to make sure changes made on this
+ // thread is visible on the caller thread.
+
+ ShortcutInfo shortcut = null;
+ try {
+ final PendingItem item;
+
+ synchronized (mPendingItems) {
+ if (mPendingItems.size() == 0) {
+ return false;
+ }
+ item = mPendingItems.pop();
+ }
+
+ shortcut = item.shortcut;
+
+ // See if the shortcut is still relevant. (It might have been removed already.)
+ if (!shortcut.isIconPendingSave()) {
+ return true;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Saving bitmap: " + item);
+ }
+
+ File file = null;
+ try {
+ final FileOutputStreamWithPath out = mService.openIconFileForWrite(
+ shortcut.getUserId(), shortcut);
+ file = out.getFile();
+
+ try {
+ out.write(item.bytes);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+
+ shortcut.setBitmapPath(file.getAbsolutePath());
+
+ } catch (IOException | RuntimeException e) {
+ Slog.e(ShortcutService.TAG, "Unable to write bitmap to file", e);
+
+ if (file != null && file.exists()) {
+ file.delete();
+ }
+ return true;
+ }
+ } finally {
+ if (DEBUG) {
+ Slog.d(TAG, "Saved bitmap.");
+ }
+ if (shortcut != null) {
+ if (shortcut.getBitmapPath() == null) {
+ removeIcon(shortcut);
+ }
+
+ // Whatever happened, remove this flag.
+ shortcut.clearFlags(ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
+ }
+ }
+ return true;
+ }
+
+ public void dumpLocked(@NonNull PrintWriter pw, @NonNull String prefix) {
+ synchronized (mPendingItems) {
+ final int N = mPendingItems.size();
+ pw.print(prefix);
+ pw.println("Pending saves: Num=" + N + " Executor=" + mExecutor);
+
+ for (PendingItem item : mPendingItems) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.println(item);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 5035e68..103b25d 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -198,7 +198,7 @@
private ShortcutInfo deleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) {
- mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut);
+ mShortcutUser.mService.removeIconLocked(shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
| ShortcutInfo.FLAG_MANIFEST);
}
@@ -211,7 +211,7 @@
deleteShortcutInner(newShortcut.getId());
// Extract Icon and update the icon res ID and the bitmap path.
- s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
+ s.saveIconAndFixUpShortcutLocked(newShortcut);
s.fixUpShortcutResourceNamesAndValues(newShortcut);
mShortcuts.put(newShortcut.getId(), newShortcut);
}
@@ -1263,13 +1263,21 @@
out.endTag(null, TAG_ROOT);
}
- private static void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup)
+ private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup)
throws IOException, XmlPullParserException {
+
+ final ShortcutService s = mShortcutUser.mService;
+
if (forBackup) {
if (!(si.isPinned() && si.isEnabled())) {
return; // We only backup pinned shortcuts that are enabled.
}
}
+ // Note: at this point no shortcuts should have bitmaps pending save, but if they do,
+ // just remove the bitmap.
+ if (si.isIconPendingSave()) {
+ s.removeIconLocked(si);
+ }
out.startTag(null, TAG_SHORTCUT);
ShortcutService.writeAttr(out, ATTR_ID, si.getId());
// writeAttr(out, "package", si.getPackageName()); // not needed
@@ -1293,6 +1301,7 @@
ShortcutService.writeAttr(out, ATTR_FLAGS,
si.getFlags() &
~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
+ | ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE
| ShortcutInfo.FLAG_DYNAMIC));
} else {
// When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 7f0528a..ac4b828 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -306,6 +306,7 @@
private final ActivityManagerInternal mActivityManagerInternal;
private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
+ private final ShortcutBitmapSaver mShortcutBitmapSaver;
@GuardedBy("mLock")
final SparseIntArray mUidState = new SparseIntArray();
@@ -426,6 +427,7 @@
LocalServices.getService(ActivityManagerInternal.class));
mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
+ mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
if (onlyForPackageManagerApis) {
return; // Don't do anything further. For unit tests only.
@@ -926,6 +928,9 @@
if (DEBUG) {
Slog.d(TAG, "Saving to " + path);
}
+
+ mShortcutBitmapSaver.waitForAllSavesLocked();
+
path.getParentFile().mkdirs();
final AtomicFile file = new AtomicFile(path);
FileOutputStream os = null;
@@ -1213,13 +1218,8 @@
// === Caller validation ===
- void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
- // Do not remove the actual bitmap file yet, because if the device crashes before saving
- // he XML we'd lose the icon. We just remove all dangling files after saving the XML.
- shortcut.setIconResourceId(0);
- shortcut.setIconResName(null);
- shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE |
- ShortcutInfo.FLAG_ADAPTIVE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES);
+ void removeIconLocked(ShortcutInfo shortcut) {
+ mShortcutBitmapSaver.removeIcon(shortcut);
}
public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
@@ -1232,6 +1232,13 @@
}
}
+ /**
+ * Remove dangling bitmap files for a user.
+ *
+ * Note this method must be called with the lock held after calling
+ * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
+ * saves are going on.
+ */
private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
@@ -1265,6 +1272,13 @@
logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
}
+ /**
+ * Remove dangling bitmap files for a package.
+ *
+ * Note this method must be called with the lock held after calling
+ * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
+ * saves are going on.
+ */
private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
@NonNull String packageName, @NonNull File path) {
final ArraySet<String> usedFiles =
@@ -1303,7 +1317,6 @@
*
* The filename will be based on the ID, except certain characters will be escaped.
*/
- @VisibleForTesting
FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
throws IOException {
final File packagePath = new File(getUserBitmapFilePath(userId),
@@ -1329,7 +1342,7 @@
}
}
- void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) {
+ void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) {
if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
return;
}
@@ -1337,7 +1350,7 @@
final long token = injectClearCallingIdentity();
try {
// Clear icon info on the shortcut.
- removeIcon(userId, shortcut);
+ removeIconLocked(shortcut);
final Icon icon = shortcut.getIcon();
if (icon == null) {
@@ -1364,41 +1377,8 @@
// just in case.
throw ShortcutInfo.getInvalidIconException();
}
- if (bitmap == null) {
- Slog.e(TAG, "Null bitmap detected");
- return;
- }
- // Shrink and write to the file.
- File path = null;
- try {
- final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut);
- try {
- path = out.getFile();
-
- Bitmap shrunk = shrinkBitmap(bitmap, mMaxIconDimension);
- try {
- shrunk.compress(mIconPersistFormat, mIconPersistQuality, out);
- } finally {
- if (bitmap != shrunk) {
- shrunk.recycle();
- }
- }
-
- shortcut.setBitmapPath(out.getFile().getAbsolutePath());
- shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
- if (icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
- shortcut.addFlags(ShortcutInfo.FLAG_ADAPTIVE_BITMAP);
- }
- } finally {
- IoUtils.closeQuietly(out);
- }
- } catch (IOException | RuntimeException e) {
- // STOPSHIP Change wtf to e
- Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
- if (path != null && path.exists()) {
- path.delete();
- }
- }
+ mShortcutBitmapSaver.saveBitmapLocked(shortcut,
+ mMaxIconDimension, mIconPersistFormat, mIconPersistQuality);
} finally {
// Once saved, we won't use the original icon information, so null it out.
shortcut.clearIcon();
@@ -1418,7 +1398,6 @@
}
}
- @VisibleForTesting
static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
// Original width/height.
final int ow = in.getWidth();
@@ -1787,7 +1766,7 @@
final boolean replacingIcon = (source.getIcon() != null);
if (replacingIcon) {
- removeIcon(userId, target);
+ removeIconLocked(target);
}
// Note copyNonNullFieldsFrom() does the "updatable with?" check too.
@@ -1795,7 +1774,7 @@
target.setTimestamp(injectCurrentTimeMillis());
if (replacingIcon) {
- saveIconAndFixUpShortcut(userId, target);
+ saveIconAndFixUpShortcutLocked(target);
}
// When we're updating any resource related fields, re-extract the res names and
@@ -2613,16 +2592,17 @@
if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
return null;
}
+ final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo);
+ if (path == null) {
+ Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
+ return null;
+ }
try {
- if (shortcutInfo.getBitmapPath() == null) {
- Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
- return null;
- }
return ParcelFileDescriptor.open(
- new File(shortcutInfo.getBitmapPath()),
+ new File(path),
ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
- Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath());
+ Slog.e(TAG, "Icon file not found: " + path);
return null;
}
}
@@ -3384,6 +3364,9 @@
scheduleSaveUser(userId);
saveDirtyInfo();
+ // Note, in case of backup, we don't have to wait on bitmap saving, because we don't
+ // back up bitmaps anyway.
+
// Then create the backup payload.
final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
try {
@@ -3516,6 +3499,9 @@
pw.println(Log.getStackTraceString(mLastWtfStacktrace));
}
+ pw.println();
+ mShortcutBitmapSaver.dumpLocked(pw, " ");
+
for (int i = 0; i < mUsers.size(); i++) {
pw.println();
mUsers.valueAt(i).dump(pw, " ");
@@ -3827,6 +3813,11 @@
return SystemClock.elapsedRealtime();
}
+ @VisibleForTesting
+ long injectUptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+
// Injection point.
@VisibleForTesting
int injectBinderCallingUid() {
@@ -3997,4 +3988,11 @@
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
}
}
+
+ @VisibleForTesting
+ void waitForBitmapSavesForTest() {
+ synchronized (mLock) {
+ mShortcutBitmapSaver.waitForAllSavesLocked();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 9ef7410..fb7ccc7 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -735,7 +735,8 @@
}
}
- if (calling != null && !Objects.equals(calling, mCurrentVrModeComponent)) {
+ if ((calling != null || mPersistentVrModeEnabled)
+ && !Objects.equals(calling, mCurrentVrModeComponent)) {
sendUpdatedCaller = true;
}
mCurrentVrModeComponent = calling;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
index c7b8f02..60f204d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
@@ -53,6 +53,7 @@
private final DevicePolicyManagerService mService;
private final DevicePolicyManagerService.Injector mInjector;
+ private final DevicePolicyConstants mConstants;
private final Handler mHandler; // needed?
@@ -66,7 +67,10 @@
private class DevicePolicyServiceConnection
extends PersistentConnection<IDeviceAdminService> {
public DevicePolicyServiceConnection(int userId, @NonNull ComponentName componentName) {
- super(TAG, mContext, mHandler, userId, componentName);
+ super(TAG, mContext, mHandler, userId, componentName,
+ mConstants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC,
+ mConstants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE,
+ mConstants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
}
@Override
@@ -81,11 +85,13 @@
@GuardedBy("mLock")
private final SparseArray<DevicePolicyServiceConnection> mConnections = new SparseArray<>();
- public DeviceAdminServiceController(DevicePolicyManagerService service) {
+ public DeviceAdminServiceController(DevicePolicyManagerService service,
+ DevicePolicyConstants constants) {
mService = service;
mInjector = service.mInjector;
mContext = mInjector.mContext;
mHandler = new Handler(BackgroundThread.get().getLooper());
+ mConstants = constants;
}
/**
@@ -150,9 +156,11 @@
final PersistentConnection<IDeviceAdminService> existing =
mConnections.get(userId);
if (existing != null) {
- if (existing.getComponentName().equals(service.getComponentName())) {
- return;
- }
+ // Note even when we're already connected to the same service, the binding
+ // would have died at this point due to a package update. So we disconnect
+ // anyway and re-connect.
+ debug("Disconnecting from existing service connection.",
+ packageName, userId);
disconnectServiceOnUserLocked(userId, actionForLog);
}
@@ -164,7 +172,7 @@
new DevicePolicyServiceConnection(
userId, service.getComponentName());
mConnections.put(userId, conn);
- conn.connect();
+ conn.bind();
}
} finally {
mInjector.binderRestoreCallingIdentity(token);
@@ -190,7 +198,7 @@
if (conn != null) {
debug("Stopping service for u%d if already running for %s.",
userId, actionForLog);
- conn.disconnect();
+ conn.unbind();
mConnections.remove(userId);
}
}
@@ -209,6 +217,7 @@
final DevicePolicyServiceConnection con = mConnections.valueAt(i);
con.dump(prefix + " ", pw);
}
+ pw.println();
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java
new file mode 100644
index 0000000..616c669
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyConstants.java
@@ -0,0 +1,115 @@
+/*
+ * 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.devicepolicy;
+
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Constants that are configurable via the global settings for {@link DevicePolicyManagerService}.
+ *
+ * Example of setting the values for testing.
+ * adb shell settings put global device_policy_constants das_died_service_reconnect_backoff_sec=10,das_died_service_reconnect_backoff_increase=1.5,das_died_service_reconnect_max_backoff_sec=30
+ */
+public class DevicePolicyConstants {
+ private static final String TAG = DevicePolicyManagerService.LOG_TAG;
+
+ private static final String DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC_KEY
+ = "das_died_service_reconnect_backoff_sec";
+
+ private static final String DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE_KEY
+ = "das_died_service_reconnect_backoff_increase";
+
+ private static final String DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY
+ = "das_died_service_reconnect_max_backoff_sec";
+
+ /**
+ * The back-off before re-connecting, when a service binding died, due to the owner
+ * crashing repeatedly.
+ */
+ public final long DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC;
+
+ /**
+ * The exponential back-off increase factor when a binding dies multiple times.
+ */
+ public final double DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE;
+
+ /**
+ * The max back-off
+ */
+ public final long DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC;
+
+ private DevicePolicyConstants(String settings) {
+
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(settings);
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e(TAG, "Bad device policy settings: " + settings);
+ }
+
+ long dasDiedServiceReconnectBackoffSec = parser.getLong(
+ DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC_KEY, TimeUnit.HOURS.toSeconds(1));
+
+ double dasDiedServiceReconnectBackoffIncrease = parser.getFloat(
+ DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE_KEY, 2f);
+
+ long dasDiedServiceReconnectMaxBackoffSec = parser.getLong(
+ DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.DAYS.toSeconds(1));
+
+ // Set minimum: 5 seconds.
+ dasDiedServiceReconnectBackoffSec = Math.max(5, dasDiedServiceReconnectBackoffSec);
+
+ // Set minimum: 1.0.
+ dasDiedServiceReconnectBackoffIncrease =
+ Math.max(1, dasDiedServiceReconnectBackoffIncrease);
+
+ // Make sure max >= default back off.
+ dasDiedServiceReconnectMaxBackoffSec = Math.max(dasDiedServiceReconnectBackoffSec,
+ dasDiedServiceReconnectMaxBackoffSec);
+
+ DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC = dasDiedServiceReconnectBackoffSec;
+ DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE = dasDiedServiceReconnectBackoffIncrease;
+ DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC = dasDiedServiceReconnectMaxBackoffSec;
+
+ }
+
+ public static DevicePolicyConstants loadFromString(String settings) {
+ return new DevicePolicyConstants(settings);
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.println("Constants:");
+
+ pw.print(prefix);
+ pw.print(" DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC: ");
+ pw.println( DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC);
+
+ pw.print(prefix);
+ pw.print(" DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE: ");
+ pw.println( DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE);
+
+ pw.print(prefix);
+ pw.print(" DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC: ");
+ pw.println( DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 87fb8c8..911bb2a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -141,6 +141,7 @@
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
import android.provider.Settings;
+import android.provider.Settings.Global;
import android.security.IKeyChainAliasCallback;
import android.security.IKeyChainService;
import android.security.KeyChain;
@@ -364,6 +365,7 @@
final UserManagerInternal mUserManagerInternal;
final TelephonyManager mTelephonyManager;
private final LockPatternUtils mLockPatternUtils;
+ private final DevicePolicyConstants mConstants;
private final DeviceAdminServiceController mDeviceAdminServiceController;
/**
@@ -1447,7 +1449,10 @@
private void handlePackagesChanged(@Nullable String packageName, int userHandle) {
boolean removedAdmin = false;
- if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
+ if (VERBOSE_LOG) {
+ Slog.d(LOG_TAG, "Handling package changes package " + packageName
+ + " for user " + userHandle);
+ }
DevicePolicyData policy = getUserData(userHandle);
synchronized (this) {
for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
@@ -1760,6 +1765,10 @@
return Settings.Global.getInt(mContext.getContentResolver(), name, def);
}
+ String settingsGlobalGetString(String name) {
+ return Settings.Global.getString(mContext.getContentResolver(), name);
+ }
+
void settingsGlobalPutInt(String name, int value) {
Settings.Global.putInt(mContext.getContentResolver(), name, value);
}
@@ -1801,6 +1810,9 @@
mInjector = injector;
mContext = Preconditions.checkNotNull(injector.mContext);
mHandler = new Handler(Preconditions.checkNotNull(injector.getMyLooper()));
+ mConstants = DevicePolicyConstants.loadFromString(
+ mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS));
+
mOwners = Preconditions.checkNotNull(injector.newOwners());
mUserManager = Preconditions.checkNotNull(injector.getUserManager());
@@ -1823,7 +1835,7 @@
// Needed when mHasFeature == false, because it controls the certificate warning text.
mCertificateMonitor = new CertificateMonitor(this, mInjector, mBackgroundHandler);
- mDeviceAdminServiceController = new DeviceAdminServiceController(this);
+ mDeviceAdminServiceController = new DeviceAdminServiceController(this, mConstants);
if (!mHasFeature) {
// Skip the rest of the initialization
@@ -7354,6 +7366,7 @@
synchronized (this) {
pw.println("Current Device Policy Manager state:");
+
mOwners.dump(" ", pw);
mDeviceAdminServiceController.dump(" ", pw);
int userCount = mUserData.size();
@@ -7380,7 +7393,9 @@
pw.print(" mPasswordOwner="); pw.println(policy.mPasswordOwner);
}
pw.println();
- pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
+ mConstants.dump(" ", pw);
+ pw.println();
+ pw.println(" Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
new file mode 100644
index 0000000..f287386
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
@@ -0,0 +1,352 @@
+/*
+ * 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.IDeviceAdminService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+import android.util.Pair;
+
+import org.mockito.ArgumentMatchers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+public class PersistentConnectionTest extends AndroidTestCase {
+ private static class MyConnection extends PersistentConnection<IDeviceAdminService> {
+ public long uptimeMillis = 12345;
+
+ public ArrayList<Pair<Runnable, Long>> scheduledRunnables = new ArrayList<>();
+
+ public MyConnection(String tag, Context context, Handler handler, int userId,
+ ComponentName componentName, long rebindBackoffSeconds,
+ double rebindBackoffIncrease, long rebindMaxBackoffSeconds) {
+ super(tag, context, handler, userId, componentName,
+ rebindBackoffSeconds, rebindBackoffIncrease, rebindMaxBackoffSeconds);
+ }
+
+ @Override
+ protected IDeviceAdminService asInterface(IBinder binder) {
+ return (IDeviceAdminService) binder;
+ }
+
+ @Override
+ long injectUptimeMillis() {
+ return uptimeMillis;
+ }
+
+ @Override
+ void injectPostAtTime(Runnable r, long uptimeMillis) {
+ scheduledRunnables.add(Pair.create(r, uptimeMillis));
+ }
+
+ @Override
+ void injectRemoveCallbacks(Runnable r) {
+ for (int i = scheduledRunnables.size() - 1; i >= 0; i--) {
+ if (scheduledRunnables.get(i).first.equals(r)) {
+ scheduledRunnables.remove(i);
+ }
+ }
+ }
+
+ void elapse(long milliSeconds) {
+ uptimeMillis += milliSeconds;
+
+ // Fire the scheduled runnables.
+
+ // Note we collect first and then run all, because sometimes a scheduled runnable
+ // calls removeCallbacks.
+ final ArrayList<Runnable> list = new ArrayList<>();
+
+ for (int i = scheduledRunnables.size() - 1; i >= 0; i--) {
+ if (scheduledRunnables.get(i).second <= uptimeMillis) {
+ list.add(scheduledRunnables.get(i).first);
+ scheduledRunnables.remove(i);
+ }
+ }
+
+ Collections.reverse(list);
+ for (Runnable r : list) {
+ r.run();
+ }
+ }
+ }
+
+ public void testAll() {
+ final Context context = mock(Context.class);
+ final int userId = 11;
+ final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def");
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final MyConnection conn = new MyConnection("tag", context, handler, userId, cn,
+ /* rebindBackoffSeconds= */ 5,
+ /* rebindBackoffIncrease= */ 1.5,
+ /* rebindMaxBackoffSeconds= */ 11);
+
+ assertFalse(conn.isBound());
+ assertFalse(conn.isConnected());
+ assertFalse(conn.isRebindScheduled());
+ assertEquals(5000, conn.getNextBackoffMsForTest());
+ assertNull(conn.getServiceBinder());
+
+ when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(),
+ any(Handler.class), any(UserHandle.class)))
+ .thenReturn(true);
+
+ // Call bind.
+ conn.bind();
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertFalse(conn.isRebindScheduled());
+ assertNull(conn.getServiceBinder());
+
+ assertEquals(5000, conn.getNextBackoffMsForTest());
+
+ verify(context).bindServiceAsUser(
+ ArgumentMatchers.argThat(intent -> cn.equals(intent.getComponent())),
+ eq(conn.getServiceConnectionForTest()),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+ eq(handler), eq(UserHandle.of(userId)));
+
+ // AM responds...
+ conn.getServiceConnectionForTest().onServiceConnected(cn,
+ new IDeviceAdminService.Stub() {});
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertTrue(conn.isConnected());
+ assertNotNull(conn.getServiceBinder());
+ assertFalse(conn.isRebindScheduled());
+
+ assertEquals(5000, conn.getNextBackoffMsForTest());
+
+
+ // Now connected.
+
+ // Call unbind...
+ conn.unbind();
+ assertFalse(conn.isBound());
+ assertFalse(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertNull(conn.getServiceBinder());
+ assertFalse(conn.isRebindScheduled());
+
+ // Caller bind again...
+ conn.bind();
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertFalse(conn.isRebindScheduled());
+ assertNull(conn.getServiceBinder());
+
+ assertEquals(5000, conn.getNextBackoffMsForTest());
+
+
+ // Now connected again.
+
+ // The service got killed...
+ conn.getServiceConnectionForTest().onServiceDisconnected(cn);
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertNull(conn.getServiceBinder());
+ assertFalse(conn.isRebindScheduled());
+
+ assertEquals(5000, conn.getNextBackoffMsForTest());
+
+ // Connected again...
+ conn.getServiceConnectionForTest().onServiceConnected(cn,
+ new IDeviceAdminService.Stub() {});
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertTrue(conn.isConnected());
+ assertNotNull(conn.getServiceBinder());
+ assertFalse(conn.isRebindScheduled());
+
+ assertEquals(5000, conn.getNextBackoffMsForTest());
+
+
+ // Then the binding is "died"...
+ conn.getServiceConnectionForTest().onBindingDied(cn);
+
+ assertFalse(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertNull(conn.getServiceBinder());
+ assertTrue(conn.isRebindScheduled());
+
+ assertEquals(7500, conn.getNextBackoffMsForTest());
+
+ assertEquals(
+ Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
+ conn.uptimeMillis + 5000)),
+ conn.scheduledRunnables);
+
+ // 5000 ms later...
+ conn.elapse(5000);
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertNull(conn.getServiceBinder());
+ assertFalse(conn.isRebindScheduled());
+
+ assertEquals(7500, conn.getNextBackoffMsForTest());
+
+ // Connected.
+ conn.getServiceConnectionForTest().onServiceConnected(cn,
+ new IDeviceAdminService.Stub() {});
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertTrue(conn.isConnected());
+ assertNotNull(conn.getServiceBinder());
+ assertFalse(conn.isRebindScheduled());
+
+ assertEquals(7500, conn.getNextBackoffMsForTest());
+
+ // Then the binding is "died"...
+ conn.getServiceConnectionForTest().onBindingDied(cn);
+
+ assertFalse(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertNull(conn.getServiceBinder());
+ assertTrue(conn.isRebindScheduled());
+
+ assertEquals(11000, conn.getNextBackoffMsForTest());
+
+ assertEquals(
+ Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
+ conn.uptimeMillis + 7500)),
+ conn.scheduledRunnables);
+
+ // Later...
+ conn.elapse(7500);
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertNull(conn.getServiceBinder());
+ assertFalse(conn.isRebindScheduled());
+
+ assertEquals(11000, conn.getNextBackoffMsForTest());
+
+
+ // Then the binding is "died"...
+ conn.getServiceConnectionForTest().onBindingDied(cn);
+
+ assertFalse(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertNull(conn.getServiceBinder());
+ assertTrue(conn.isRebindScheduled());
+
+ assertEquals(11000, conn.getNextBackoffMsForTest());
+
+ assertEquals(
+ Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(),
+ conn.uptimeMillis + 11000)),
+ conn.scheduledRunnables);
+
+ // Call unbind...
+ conn.unbind();
+ assertFalse(conn.isBound());
+ assertFalse(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertNull(conn.getServiceBinder());
+ assertFalse(conn.isRebindScheduled());
+
+ // Call bind again... And now the backoff is reset to 5000.
+ conn.bind();
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isConnected());
+ assertFalse(conn.isRebindScheduled());
+ assertNull(conn.getServiceBinder());
+
+ assertEquals(5000, conn.getNextBackoffMsForTest());
+ }
+
+ public void testReconnectFiresAfterUnbind() {
+ final Context context = mock(Context.class);
+ final int userId = 11;
+ final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def");
+ final Handler handler = new Handler(Looper.getMainLooper());
+
+ final MyConnection conn = new MyConnection("tag", context, handler, userId, cn,
+ /* rebindBackoffSeconds= */ 5,
+ /* rebindBackoffIncrease= */ 1.5,
+ /* rebindMaxBackoffSeconds= */ 11);
+
+ when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(),
+ any(Handler.class), any(UserHandle.class)))
+ .thenReturn(true);
+
+ // Bind.
+ conn.bind();
+
+ assertTrue(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertFalse(conn.isRebindScheduled());
+
+ conn.elapse(1000);
+
+ // Service crashes.
+ conn.getServiceConnectionForTest().onBindingDied(cn);
+
+ assertFalse(conn.isBound());
+ assertTrue(conn.shouldBeBoundForTest());
+ assertTrue(conn.isRebindScheduled());
+
+ assertEquals(7500, conn.getNextBackoffMsForTest());
+
+ // Call unbind.
+ conn.unbind();
+ assertFalse(conn.isBound());
+ assertFalse(conn.shouldBeBoundForTest());
+
+ // Now, at this point, it's possible that the scheduled runnable had already been fired
+ // before during the unbind() call, and waiting on mLock.
+ // To simulate it, we just call the runnable here.
+ conn.getBindForBackoffRunnableForTest().run();
+
+ // Should still not be bound.
+ assertFalse(conn.isBound());
+ assertFalse(conn.shouldBeBoundForTest());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java
new file mode 100644
index 0000000..3819914
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyConstantsTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.devicepolicy;
+
+import android.test.AndroidTestCase;
+
+/**
+ * Test for {@link DevicePolicyConstants}.
+ *
+ m FrameworksServicesTests &&
+ adb install \
+ -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ adb shell am instrument -e class com.android.server.devicepolicy.DevicePolicyConstantsTest \
+ -w com.android.frameworks.servicestests
+
+
+ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+public class DevicePolicyConstantsTest extends AndroidTestCase {
+ private static final String TAG = "DevicePolicyConstantsTest";
+
+ public void testDefaultValues() throws Exception {
+ final DevicePolicyConstants constants = DevicePolicyConstants.loadFromString("");
+
+ assertEquals(1 * 60 * 60, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC);
+ assertEquals(24 * 60 * 60, constants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
+ assertEquals(2.0, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE);
+ }
+
+ public void testCustomValues() throws Exception {
+ final DevicePolicyConstants constants = DevicePolicyConstants.loadFromString(
+ "das_died_service_reconnect_backoff_sec=10,"
+ + "das_died_service_reconnect_backoff_increase=1.25,"
+ + "das_died_service_reconnect_max_backoff_sec=15"
+ );
+
+ assertEquals(10, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC);
+ assertEquals(15, constants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
+ assertEquals(1.25, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE);
+ }
+
+ public void testMinMax() throws Exception {
+ final DevicePolicyConstants constants = DevicePolicyConstants.loadFromString(
+ "das_died_service_reconnect_backoff_sec=3,"
+ + "das_died_service_reconnect_backoff_increase=.25,"
+ + "das_died_service_reconnect_max_backoff_sec=1"
+ );
+
+ assertEquals(5, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_SEC);
+ assertEquals(5, constants.DAS_DIED_SERVICE_RECONNECT_MAX_BACKOFF_SEC);
+ assertEquals(1.0, constants.DAS_DIED_SERVICE_RECONNECT_BACKOFF_INCREASE);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 46da3de..b870d94 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -369,6 +369,11 @@
}
@Override
+ String settingsGlobalGetString(String name) {
+ return context.settings.settingsGlobalGetString(name);
+ }
+
+ @Override
void securityLogSetLoggingEnabledProperty(boolean enabled) {
context.settings.securityLogSetLoggingEnabledProperty(enabled);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 23fada4..87106ec 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -233,6 +233,10 @@
return 0;
}
+ public String settingsGlobalGetString(String name) {
+ return "";
+ }
+
public void securityLogSetLoggingEnabledProperty(boolean enabled) {
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index d281e5a..f1d5927 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -279,6 +279,11 @@
}
@Override
+ long injectUptimeMillis() {
+ return mInjectedCurrentTimeMillis - START_TIME - mDeepSleepTime;
+ }
+
+ @Override
int injectBinderCallingUid() {
return mInjectedCallingUid;
}
@@ -557,6 +562,7 @@
protected boolean mSafeMode;
protected long mInjectedCurrentTimeMillis;
+ protected long mDeepSleepTime; // Used to calculate "uptimeMillis".
protected boolean mInjectedIsLowRamDevice;
@@ -1707,9 +1713,19 @@
if (si == null) {
return null;
}
+ mService.waitForBitmapSavesForTest();
return new File(si.getBitmapPath()).getName();
}
+ protected String getBitmapAbsPath(int userId, String packageName, String shortcutId) {
+ final ShortcutInfo si = mService.getPackageShortcutForTest(packageName, shortcutId, userId);
+ if (si == null) {
+ return null;
+ }
+ mService.waitForBitmapSavesForTest();
+ return new File(si.getBitmapPath()).getAbsolutePath();
+ }
+
/**
* @return all shortcuts stored internally for the caller. This reflects the *internal* view
* of shortcuts, which may be different from what {@link #getCallerVisibleShortcuts} would
@@ -1826,6 +1842,7 @@
}
protected boolean bitmapDirectoryExists(String packageName, int userId) {
+ mService.waitForBitmapSavesForTest();
final File path = new File(mService.getUserBitmapFilePath(userId), packageName);
return path.isDirectory();
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 2b40c51..3220ea9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -2026,12 +2026,11 @@
makeShortcutWithIcon("bmp32x32", bmp32x32),
makeShortcutWithIcon("bmp64x64", bmp64x64))));
});
+
// We can't predict the compressed bitmap sizes, so get the real sizes here.
final long bitmapTotal =
- new File(getPackageShortcut(CALLING_PACKAGE_2, "bmp32x32", USER_0)
- .getBitmapPath()).length() +
- new File(getPackageShortcut(CALLING_PACKAGE_2, "bmp64x64", USER_0)
- .getBitmapPath()).length();
+ new File(getBitmapAbsPath(USER_0, CALLING_PACKAGE_2, "bmp32x32")).length() +
+ new File(getBitmapAbsPath(USER_0, CALLING_PACKAGE_2, "bmp64x64")).length();
// Read the expected output and inject the bitmap size.
final String expected = readTestAsset("shortcut/dumpsys_expected.txt")
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index ba70374..9d91cc3 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -111,10 +111,11 @@
}
@Override
- public void removeImsFeature(int slotId, int feature) throws RemoteException {
+ public void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c)
+ throws RemoteException {
synchronized (mFeatures) {
enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "removeImsFeature");
- onRemoveImsFeatureInternal(slotId, feature);
+ onRemoveImsFeatureInternal(slotId, feature, c);
}
}
@@ -364,7 +365,7 @@
if (f != null) {
f.setContext(this);
f.setSlotId(slotId);
- f.setImsFeatureStatusCallback(c);
+ f.addImsFeatureStatusCallback(c);
featureMap.put(featureType, f);
}
@@ -377,7 +378,8 @@
* defined in {@link ImsFeature}.
*/
// Be sure to lock on mFeatures before accessing this method
- private void onRemoveImsFeatureInternal(int slotId, int featureType) {
+ private void onRemoveImsFeatureInternal(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
if (featureMap == null) {
return;
@@ -388,7 +390,7 @@
featureMap.remove(featureType);
featureToRemove.notifyFeatureRemoved(slotId);
// Remove reference to Binder
- featureToRemove.setImsFeatureStatusCallback(null);
+ featureToRemove.removeImsFeatureStatusCallback(c);
}
}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 395f1cc..9d880b7 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -28,7 +28,11 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
/**
* Base class for all IMS features that are supported by the framework.
@@ -88,7 +92,8 @@
public static final int STATE_READY = 2;
private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>();
- private IImsFeatureStatusCallback mStatusCallback;
+ private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap(
+ new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
private @ImsState int mState = STATE_NOT_AVAILABLE;
private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
private Context mContext;
@@ -136,11 +141,29 @@
}
}
- // Not final for testing.
- public void setImsFeatureStatusCallback(IImsFeatureStatusCallback c) {
- mStatusCallback = c;
- // If we have just connected, send queued status.
- notifyFeatureState(mState);
+ public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) {
+ if (c == null) {
+ return;
+ }
+ try {
+ // If we have just connected, send queued status.
+ c.notifyImsFeatureStatus(mState);
+ // Add the callback if the callback completes successfully without a RemoteException.
+ synchronized (mStatusCallbacks) {
+ mStatusCallbacks.add(c);
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
+ }
+ }
+
+ public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) {
+ if (c == null) {
+ return;
+ }
+ synchronized (mStatusCallbacks) {
+ mStatusCallbacks.remove(c);
+ }
}
/**
@@ -148,13 +171,18 @@
* @param state
*/
private void notifyFeatureState(@ImsState int state) {
- if (mStatusCallback != null) {
- try {
- Log.i(LOG_TAG, "notifying ImsFeatureState=" + state);
- mStatusCallback.notifyImsFeatureStatus(state);
- } catch (RemoteException e) {
- mStatusCallback = null;
- Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
+ synchronized (mStatusCallbacks) {
+ for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator();
+ iter.hasNext(); ) {
+ IImsFeatureStatusCallback callback = iter.next();
+ try {
+ Log.i(LOG_TAG, "notifying ImsFeatureState=" + state);
+ callback.notifyImsFeatureStatus(state);
+ } catch (RemoteException e) {
+ // remove if the callback is no longer alive.
+ iter.remove();
+ Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
+ }
}
}
sendImsServiceIntent(state);
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index 712816f..bb06d7e 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -37,7 +37,7 @@
interface IImsServiceController {
// ImsService Control
void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
- void removeImsFeature(int slotId, int feature);
+ void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
// MMTel Feature
int startSession(int slotId, int featureType, in PendingIntent incomingCallIntent,
in IImsRegistrationListener listener);