Merge "Make --no-window-animation flag apply to animators, too" into oc-dev
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 3574f8d..24c144c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -719,7 +719,7 @@
public static final int FINISH_TASK_WITH_ACTIVITY = 2;
static final String FRAGMENTS_TAG = "android:fragments";
- private static final String LAST_ACCESSIBILITY_ID = "android:lastAccessibilityId";
+ private static final String LAST_AUTOFILL_ID = "android:lastAutofillId";
private static final String AUTOFILL_RESET_NEEDED = "@android:autofillResetNeeded";
private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
@@ -853,8 +853,8 @@
private boolean mAutoFillResetNeeded;
- /** The last accessibility id that was returned from {@link #getNextAccessibilityId()} */
- private int mLastAccessibilityId = View.LAST_APP_ACCESSIBILITY_ID;
+ /** The last autofill id that was returned from {@link #getNextAutofillId()} */
+ private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
private AutofillPopupWindow mAutofillPopupWindow;
@@ -999,7 +999,7 @@
}
if (savedInstanceState != null) {
mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false);
- mLastAccessibilityId = savedInstanceState.getInt(LAST_ACCESSIBILITY_ID, View.NO_ID);
+ mLastAutofillId = savedInstanceState.getInt(LAST_AUTOFILL_ID, View.NO_ID);
if (mAutoFillResetNeeded) {
getAutofillManager().onCreate(savedInstanceState);
@@ -1348,24 +1348,23 @@
}
/**
- * Gets the next accessibility ID.
+ * Gets the next autofill ID.
*
- * <p>All IDs will be bigger than {@link View#LAST_APP_ACCESSIBILITY_ID}. All IDs returned
+ * <p>All IDs will be bigger than {@link View#LAST_APP_AUTOFILL_ID}. All IDs returned
* will be unique.
*
* @return A ID that is unique in the activity
*
* {@hide}
*/
- @Override
- public int getNextAccessibilityId() {
- if (mLastAccessibilityId == Integer.MAX_VALUE - 1) {
- mLastAccessibilityId = View.LAST_APP_ACCESSIBILITY_ID;
+ public int getNextAutofillId() {
+ if (mLastAutofillId == Integer.MAX_VALUE - 1) {
+ mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
}
- mLastAccessibilityId++;
+ mLastAutofillId++;
- return mLastAccessibilityId;
+ return mLastAutofillId;
}
/**
@@ -1563,7 +1562,7 @@
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
- outState.putInt(LAST_ACCESSIBILITY_ID, mLastAccessibilityId);
+ outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
@@ -7455,7 +7454,7 @@
/** @hide */
@Override
- @NonNull public View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds) {
+ @NonNull public View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds) {
final View[] views = new View[viewIds.length];
final ArrayList<ViewRootImpl> roots =
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
@@ -7466,7 +7465,7 @@
if (rootView != null) {
for (int viewNum = 0; viewNum < viewIds.length; viewNum++) {
if (views[viewNum] == null) {
- views[viewNum] = rootView.findViewByAccessibilityIdTraversal(
+ views[viewNum] = rootView.findViewByAutofillIdTraversal(
viewIds[viewNum]);
}
}
@@ -7478,14 +7477,14 @@
/** @hide */
@Override
- @Nullable public View findViewByAccessibilityIdTraversal(int viewId) {
+ @Nullable public View findViewByAutofillIdTraversal(int viewId) {
final ArrayList<ViewRootImpl> roots =
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
final View rootView = roots.get(rootNum).getView();
if (rootView != null) {
- final View view = rootView.findViewByAccessibilityIdTraversal(viewId);
+ final View view = rootView.findViewByAutofillIdTraversal(viewId);
if (view != null) {
return view;
}
@@ -7499,7 +7498,7 @@
@Override
@NonNull public boolean[] getViewVisibility(@NonNull int[] viewIds) {
final boolean[] isVisible = new boolean[viewIds.length];
- final View views[] = findViewsByAccessibilityIdTraversal(viewIds);
+ final View views[] = findViewsByAutofillIdTraversal(viewIds);
for (int i = 0; i < viewIds.length; i++) {
View view = views[i];
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index d3b4b40..e5fe240 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -24,6 +24,7 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.SystemClock;
import android.service.voice.IVoiceInteractionSession;
import android.util.SparseIntArray;
@@ -134,8 +135,10 @@
*
* @param reasons A map from stack id to a reason integer why the transition was started,, which
* must be one of the APP_TRANSITION_* values.
+ * @param timestamp The time at which the app transition started in
+ * {@link SystemClock#uptimeMillis()} timebase.
*/
- public abstract void notifyAppTransitionStarting(SparseIntArray reasons);
+ public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp);
/**
* Callback for window manager to let activity manager know that the app transition was
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index cbb93a0..6dead3e 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1244,7 +1244,7 @@
// Once we parcel the thumbnail for transfering over to the system, create a copy of
// the bitmap to a hardware bitmap and pass through the GraphicBuffer
if (mThumbnail != null) {
- final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, true /* immutable */);
+ final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, false /* isMutable */);
if (hwBitmap != null) {
b.putParcelable(KEY_ANIM_THUMBNAIL, hwBitmap.createGraphicBufferHandle());
} else {
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
index 3665d1b..4ee38fe 100644
--- a/core/java/android/companion/BluetoothDeviceFilterUtils.java
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -58,7 +58,7 @@
static boolean matchesAddress(String deviceAddress, BluetoothDevice device) {
final boolean result = deviceAddress == null
- || (device == null || !deviceAddress.equals(device.getAddress()));
+ || (device != null && deviceAddress.equals(device.getAddress()));
if (DEBUG) debugLogMatchResult(result, device, deviceAddress);
return result;
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index dabe608..4400ad33 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -288,6 +288,7 @@
@Override
public void onActivityDestroyed(Activity activity) {
+ if (activity != getActivity()) return;
try {
mService.stopScan(mRequest, this, getCallingPackage());
} catch (RemoteException e) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index db80c72..2303a38 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -488,27 +488,27 @@
*/
public abstract Context getApplicationContext();
- /** Non-activity related accessibility ids are unique in the app */
- private static int sLastAccessibilityId = View.NO_ID;
+ /** Non-activity related autofill ids are unique in the app */
+ private static int sLastAutofillId = View.NO_ID;
/**
- * Gets the next accessibility ID.
+ * Gets the next autofill ID.
*
- * <p>All IDs will be smaller or the same as {@link View#LAST_APP_ACCESSIBILITY_ID}. All IDs
+ * <p>All IDs will be smaller or the same as {@link View#LAST_APP_AUTOFILL_ID}. All IDs
* returned will be unique.
*
* @return A ID that is unique in the process
*
* {@hide}
*/
- public int getNextAccessibilityId() {
- if (sLastAccessibilityId == View.LAST_APP_ACCESSIBILITY_ID - 1) {
- sLastAccessibilityId = View.NO_ID;
+ public int getNextAutofillId() {
+ if (sLastAutofillId == View.LAST_APP_AUTOFILL_ID - 1) {
+ sLastAutofillId = View.NO_ID;
}
- sLastAccessibilityId++;
+ sLastAutofillId++;
- return sLastAccessibilityId;
+ return sLastAutofillId;
}
/**
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index e127ca3..3b27905 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -956,8 +956,7 @@
/**
* @hide
*/
- @Override
- public int getNextAccessibilityId() {
- return mBase.getNextAccessibilityId();
+ public int getNextAutofillId() {
+ return mBase.getNextAutofillId();
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9b0bab4..fdb0f2ba 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6852,7 +6852,7 @@
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.resourceDirs;
+ ai.resourceDirs = state.overlayPaths;
}
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
@@ -7000,6 +7000,7 @@
return null;
}
if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) {
+ updateApplicationInfo(a.info.applicationInfo, flags, state);
return a.info;
}
// Make shallow copies so we can store the metadata safely
@@ -7088,6 +7089,7 @@
return null;
}
if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) {
+ updateApplicationInfo(s.info.applicationInfo, flags, state);
return s.info;
}
// Make shallow copies so we can store the metadata safely
@@ -7183,6 +7185,7 @@
if (!copyNeeded(flags, p.owner, state, p.metaData, userId)
&& ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
|| p.info.uriPermissionPatterns == null)) {
+ updateApplicationInfo(p.info.applicationInfo, flags, state);
return p.info;
}
// Make shallow copies so we can store the metadata safely
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 4e53914..470336c 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -55,7 +55,7 @@
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
- public String[] resourceDirs;
+ public String[] overlayPaths;
public PackageUserState() {
installed = true;
@@ -83,8 +83,8 @@
installReason = o.installReason;
disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
- resourceDirs =
- o.resourceDirs == null ? null : Arrays.copyOf(o.resourceDirs, o.resourceDirs.length);
+ overlayPaths =
+ o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
}
/**
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 8678d95..b69a23a 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -696,6 +696,14 @@
}
/**
+ * Return whether there are any messages or callbacks currently scheduled on this handler.
+ * @hide
+ */
+ public final boolean hasMessagesOrCallbacks() {
+ return mQueue.hasMessages(this);
+ }
+
+ /**
* Check if there are any pending posts of messages with code 'what' and
* whose obj is 'object' in the message queue.
*/
@@ -728,6 +736,18 @@
}
}
+ /**
+ * @hide
+ */
+ public final void dumpMine(Printer pw, String prefix) {
+ pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
+ if (mLooper == null) {
+ pw.println(prefix + "looper uninitialized");
+ } else {
+ mLooper.dump(pw, prefix + " ", this);
+ }
+ }
+
@Override
public String toString() {
return "Handler (" + getClass().getName() + ") {"
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 44dbcfb..04cceb8 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -310,7 +310,20 @@
*/
public void dump(@NonNull Printer pw, @NonNull String prefix) {
pw.println(prefix + toString());
- mQueue.dump(pw, prefix + " ");
+ mQueue.dump(pw, prefix + " ", null);
+ }
+
+ /**
+ * Dumps the state of the looper for debugging purposes.
+ *
+ * @param pw A printer to receive the contents of the dump.
+ * @param prefix A prefix to prepend to each line which is printed.
+ * @param handler Only dump messages for this Handler.
+ * @hide
+ */
+ public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
+ pw.println(prefix + toString());
+ mQueue.dump(pw, prefix + " ", handler);
}
/** @hide */
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 2a8c52e..624e28a 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -620,6 +620,23 @@
}
}
+ boolean hasMessages(Handler h) {
+ if (h == null) {
+ return false;
+ }
+
+ synchronized (this) {
+ Message p = mMessages;
+ while (p != null) {
+ if (p.target == h) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
@@ -759,12 +776,14 @@
}
}
- void dump(Printer pw, String prefix) {
+ void dump(Printer pw, String prefix, Handler h) {
synchronized (this) {
long now = SystemClock.uptimeMillis();
int n = 0;
for (Message msg = mMessages; msg != null; msg = msg.next) {
- pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ }
n++;
}
pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f1ce9d5..ee3e986 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9029,7 +9029,10 @@
* <pre>
* max_cached_processes (int)
* background_settle_time (long)
- * foreground_service_ui_min_time (long)
+ * fgservice_min_shown_time (long)
+ * fgservice_min_report_time (long)
+ * fgservice_screen_on_before_time (long)
+ * fgservice_screen_on_after_time (long)
* content_provider_retain_time (long)
* gc_timeout (long)
* gc_min_interval (long)
@@ -9818,6 +9821,16 @@
public static final String ENABLE_EPHEMERAL_FEATURE = "enable_ephemeral_feature";
/**
+ * Toggle to enable/disable dexopt for instant applications. The default is for dexopt
+ * to be disabled.
+ * <p>
+ * Type: int (0 to disable, 1 to enable)
+ *
+ * @hide
+ */
+ public static final String INSTANT_APP_DEXOPT_ENABLED = "instant_app_dexopt_enabled";
+
+ /**
* The min period for caching installed instant apps in milliseconds.
* <p>
* Type: long
diff --git a/core/java/android/service/autofill/FillContext.java b/core/java/android/service/autofill/FillContext.java
index 6956c8a..f8a8751 100644
--- a/core/java/android/service/autofill/FillContext.java
+++ b/core/java/android/service/autofill/FillContext.java
@@ -106,15 +106,15 @@
}
/**
- * Finds {@link ViewNode}s that have the requested ids.
+ * Finds {@link ViewNode ViewNodes} that have the requested ids.
*
- * @param ids The ids of the node to find
+ * @param ids The ids of the node to find.
*
- * @return The nodes indexed in the same way as the ids
+ * @return The nodes indexed in the same way as the ids.
*
* @hide
*/
- @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId... ids) {
+ @NonNull public ViewNode[] findViewNodesByAutofillIds(@NonNull AutofillId[] ids) {
final LinkedList<ViewNode> nodesToProcess = new LinkedList<>();
final ViewNode[] foundNodes = new AssistStructure.ViewNode[ids.length];
@@ -178,6 +178,30 @@
return foundNodes;
}
+ /**
+ * Finds the {@link ViewNode} that has the requested {@code id}, if any.
+ *
+ * @hide
+ */
+ @Nullable public ViewNode findViewNodeByAutofillId(@NonNull AutofillId id) {
+ final LinkedList<ViewNode> nodesToProcess = new LinkedList<>();
+ final int numWindowNodes = mStructure.getWindowNodeCount();
+ for (int i = 0; i < numWindowNodes; i++) {
+ nodesToProcess.add(mStructure.getWindowNodeAt(i).getRootViewNode());
+ }
+ while (!nodesToProcess.isEmpty()) {
+ final ViewNode node = nodesToProcess.removeFirst();
+ if (id.equals(node.getAutofillId())) {
+ return node;
+ }
+ for (int i = 0; i < node.getChildCount(); i++) {
+ nodesToProcess.addLast(node.getChildAt(i));
+ }
+ }
+
+ return null;
+ }
+
public static final Parcelable.Creator<FillContext> CREATOR =
new Parcelable.Creator<FillContext>() {
@Override
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index fa3f55b..6ea7d5e 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -273,13 +273,24 @@
*
* <p>See {@link SaveInfo} for more info.
*
- * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty.
+ * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty, or if
+ * it contains any {@code null} entry.
*/
public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) {
- Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0,
- "must have at least one required id: " + Arrays.toString(requiredIds));
+ // TODO: add CTS unit tests (not integration) to assert the null cases
mType = type;
- mRequiredIds = requiredIds;
+ mRequiredIds = assertValid(requiredIds);
+ }
+
+ private AutofillId[] assertValid(AutofillId[] ids) {
+ Preconditions.checkArgument(ids != null && ids.length > 0,
+ "must have at least one id: " + Arrays.toString(ids));
+ for (int i = 0; i < ids.length; i++) {
+ final AutofillId id = ids[i];
+ Preconditions.checkArgument(id != null,
+ "cannot have null id: " + Arrays.toString(ids));
+ }
+ return ids;
}
/**
@@ -302,12 +313,14 @@
*
* @param ids The ids of the optional views.
* @return This builder.
+ *
+ * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
+ * it contains any {@code null} entry.
*/
- public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) {
+ public @NonNull Builder setOptionalIds(@NonNull AutofillId[] ids) {
+ // TODO: add CTS unit tests (not integration) to assert the null cases
throwIfDestroyed();
- if (ids != null && ids.length != 0) {
- mOptionalIds = ids;
- }
+ mOptionalIds = assertValid(ids);
return this;
}
@@ -421,7 +434,10 @@
final Builder builder = new Builder(parcel.readInt(),
parcel.readParcelableArray(null, AutofillId.class));
builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
- builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class));
+ final AutofillId[] optionalIds = parcel.readParcelableArray(null, AutofillId.class);
+ if (optionalIds != null) {
+ builder.setOptionalIds(optionalIds);
+ }
builder.setDescription(parcel.readCharSequence());
builder.setFlags(parcel.readInt());
return builder.build();
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index caadc36..cb98c88 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -72,6 +72,15 @@
* Creates a display event receiver.
*
* @param looper The looper to use when invoking callbacks.
+ */
+ public DisplayEventReceiver(Looper looper) {
+ this(looper, VSYNC_SOURCE_APP);
+ }
+
+ /**
+ * Creates a display event receiver.
+ *
+ * @param looper The looper to use when invoking callbacks.
* @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values.
*/
public DisplayEventReceiver(Looper looper, int vsyncSource) {
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 8bb3fa9..4f9dbd5 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -52,7 +52,9 @@
private static native long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
throws OutOfResourcesException;
+
private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject);
+ private static native long nativeGetFromSurfaceControl(long surfaceControlNativeObject);
private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
throws OutOfResourcesException;
@@ -410,6 +412,9 @@
* back from a client, converting it from the representation being managed
* by the window manager to the representation the client uses to draw
* in to it.
+ *
+ * @param other {@link SurfaceControl} to copy from.
+ *
* @hide
*/
public void copyFrom(SurfaceControl other) {
@@ -420,7 +425,39 @@
long surfaceControlPtr = other.mNativeObject;
if (surfaceControlPtr == 0) {
throw new NullPointerException(
- "SurfaceControl native object is null. Are you using a released SurfaceControl?");
+ "null SurfaceControl native object. Are you using a released SurfaceControl?");
+ }
+ long newNativeObject = nativeGetFromSurfaceControl(surfaceControlPtr);
+
+ synchronized (mLock) {
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
+ setNativeObjectLocked(newNativeObject);
+ }
+ }
+
+ /**
+ * Gets a reference a surface created from this one. This surface now holds a reference
+ * to the same data as the original surface, and is -not- the owner.
+ * This is for use by the window manager when returning a window surface
+ * back from a client, converting it from the representation being managed
+ * by the window manager to the representation the client uses to draw
+ * in to it.
+ *
+ * @param other {@link SurfaceControl} to create surface from.
+ *
+ * @hide
+ */
+ public void createFrom(SurfaceControl other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null");
+ }
+
+ long surfaceControlPtr = other.mNativeObject;
+ if (surfaceControlPtr == 0) {
+ throw new NullPointerException(
+ "null SurfaceControl native object. Are you using a released SurfaceControl?");
}
long newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 679a9cd..34ceeb7 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -641,6 +641,16 @@
mSurface.copyFrom(mSurfaceControl);
}
+ if (getContext().getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ mSurface.createFrom(mSurfaceControl);
+ }
+
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 7cec957..a140f28 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -970,6 +970,9 @@
observer.mNative = null;
}
+ /** Not actually public - internal use only. This doc to make lint happy */
+ public static native void disableVsync();
+
static native void setupShadersDiskCache(String cacheFile);
private static native void nRotateProcessStatsBuffer();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b093284..4882165 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -802,7 +802,7 @@
*
* {@hide}
*/
- public static final int LAST_APP_ACCESSIBILITY_ID = Integer.MAX_VALUE / 2;
+ public static final int LAST_APP_AUTOFILL_ID = Integer.MAX_VALUE / 2;
/**
* Attribute to find the autofilled highlight
@@ -2045,6 +2045,11 @@
private SparseArray<Object> mKeyedTags;
/**
+ * The next available accessibility id.
+ */
+ private static int sNextAccessibilityViewId;
+
+ /**
* The animation currently associated with this view.
* @hide
*/
@@ -2086,16 +2091,19 @@
@ViewDebug.ExportedProperty(resolveId = true)
int mID = NO_ID;
- /** The ID of this view for accessibility and autofill purposes.
+ /** The ID of this view for autofill purposes.
* <ul>
* <li>== {@link #NO_ID}: ID has not been assigned yet
- * <li>≤ {@link #LAST_APP_ACCESSIBILITY_ID}: View is not part of a activity. The ID is
+ * <li>≤ {@link #LAST_APP_AUTOFILL_ID}: View is not part of a activity. The ID is
* unique in the process. This might change
* over activity lifecycle events.
- * <li>> {@link #LAST_APP_ACCESSIBILITY_ID}: View is part of a activity. The ID is
+ * <li>> {@link #LAST_APP_AUTOFILL_ID}: View is part of a activity. The ID is
* unique in the activity. This stays the same
* over activity lifecycle events.
*/
+ private int mAutofillViewId = NO_ID;
+
+ // ID for accessibility purposes. This ID must be unique for every window
private int mAccessibilityViewId = NO_ID;
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
@@ -7723,7 +7731,7 @@
if (mAutofillId == null) {
// The autofill id needs to be unique, but its value doesn't matter,
// so it's better to reuse the accessibility id to save space.
- mAutofillId = new AutofillId(getAccessibilityViewId());
+ mAutofillId = new AutofillId(getAutofillViewId());
}
return mAutofillId;
}
@@ -7956,7 +7964,7 @@
private boolean isAutofillable() {
return getAutofillType() != AUTOFILL_TYPE_NONE && isImportantForAutofill()
- && getAccessibilityViewId() > LAST_APP_ACCESSIBILITY_ID;
+ && getAutofillViewId() > LAST_APP_AUTOFILL_ID;
}
private void populateVirtualStructure(ViewStructure structure,
@@ -8474,12 +8482,26 @@
*/
public int getAccessibilityViewId() {
if (mAccessibilityViewId == NO_ID) {
- mAccessibilityViewId = mContext.getNextAccessibilityId();
+ mAccessibilityViewId = sNextAccessibilityViewId++;
}
return mAccessibilityViewId;
}
/**
+ * Gets the unique identifier of this view on the screen for autofill purposes.
+ *
+ * @return The view autofill id.
+ *
+ * @hide
+ */
+ public int getAutofillViewId() {
+ if (mAutofillViewId == NO_ID) {
+ mAutofillViewId = mContext.getNextAutofillId();
+ }
+ return mAutofillViewId;
+ }
+
+ /**
* Gets the unique identifier of the window in which this View reseides.
*
* @return The window accessibility id.
@@ -12117,7 +12139,7 @@
if (isAutofillable()) {
AutofillManager afm = getAutofillManager();
- if (afm != null && getAccessibilityViewId() > LAST_APP_ACCESSIBILITY_ID) {
+ if (afm != null && getAutofillViewId() > LAST_APP_AUTOFILL_ID) {
if (mVisibilityChangeForAutofillHandler != null) {
mVisibilityChangeForAutofillHandler.removeMessages(0);
}
@@ -17547,16 +17569,16 @@
*
* @return Returns a Parcelable object containing the view's current dynamic
* state, or null if there is nothing interesting to save.
- * @see #onRestoreInstanceState(android.os.Parcelable)
- * @see #saveHierarchyState(android.util.SparseArray)
- * @see #dispatchSaveInstanceState(android.util.SparseArray)
+ * @see #onRestoreInstanceState(Parcelable)
+ * @see #saveHierarchyState(SparseArray)
+ * @see #dispatchSaveInstanceState(SparseArray)
* @see #setSaveEnabled(boolean)
*/
@CallSuper
@Nullable protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (mStartActivityRequestWho != null || isAutofilled()
- || mAccessibilityViewId > LAST_APP_ACCESSIBILITY_ID) {
+ || mAutofillViewId > LAST_APP_AUTOFILL_ID) {
BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
if (mStartActivityRequestWho != null) {
@@ -17567,13 +17589,13 @@
state.mSavedData |= BaseSavedState.IS_AUTOFILLED;
}
- if (mAccessibilityViewId > LAST_APP_ACCESSIBILITY_ID) {
- state.mSavedData |= BaseSavedState.ACCESSIBILITY_ID;
+ if (mAutofillViewId > LAST_APP_AUTOFILL_ID) {
+ state.mSavedData |= BaseSavedState.AUTOFILL_ID;
}
state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
state.mIsAutofilled = isAutofilled();
- state.mAccessibilityViewId = mAccessibilityViewId;
+ state.mAutofillViewId = mAutofillViewId;
return state;
}
return BaseSavedState.EMPTY_STATE;
@@ -17651,8 +17673,8 @@
if ((baseState.mSavedData & BaseSavedState.IS_AUTOFILLED) != 0) {
setAutofilled(baseState.mIsAutofilled);
}
- if ((baseState.mSavedData & BaseSavedState.ACCESSIBILITY_ID) != 0) {
- mAccessibilityViewId = baseState.mAccessibilityViewId;
+ if ((baseState.mSavedData & BaseSavedState.AUTOFILL_ID) != 0) {
+ mAutofillViewId = baseState.mAutofillViewId;
}
}
}
@@ -21476,7 +21498,7 @@
* @param accessibilityId The searched accessibility id.
* @return The found view.
*/
- final <T extends View> T findViewByAccessibilityId(int accessibilityId) {
+ final <T extends View> T findViewByAccessibilityId(int accessibilityId) {
if (accessibilityId < 0) {
return null;
}
@@ -21488,11 +21510,11 @@
}
/**
- * Performs the traversal to find a view by its unuque and stable accessibility id.
+ * Performs the traversal to find a view by its unique and stable accessibility id.
*
* <strong>Note:</strong>This method does not stop at the root namespace
* boundary since the user can touch the screen at an arbitrary location
- * potentially crossing the root namespace bounday which will send an
+ * potentially crossing the root namespace boundary which will send an
* accessibility event to accessibility services and they should be able
* to obtain the event source. Also accessibility ids are guaranteed to be
* unique in the window.
@@ -21509,6 +21531,23 @@
}
/**
+ * Performs the traversal to find a view by its autofill id.
+ *
+ * <strong>Note:</strong>This method does not stop at the root namespace
+ * boundary.
+ *
+ * @param autofillId The autofill id.
+ * @return The found view.
+ * @hide
+ */
+ public <T extends View> T findViewByAutofillIdTraversal(int autofillId) {
+ if (getAutofillViewId() == autofillId) {
+ return (T) this;
+ }
+ return null;
+ }
+
+ /**
* Look for a child view with the given tag. If this view has the given
* tag, return this view.
*
@@ -24974,13 +25013,13 @@
public static class BaseSavedState extends AbsSavedState {
static final int START_ACTIVITY_REQUESTED_WHO_SAVED = 0b1;
static final int IS_AUTOFILLED = 0b10;
- static final int ACCESSIBILITY_ID = 0b100;
+ static final int AUTOFILL_ID = 0b100;
// Flags that describe what data in this state is valid
int mSavedData;
String mStartActivityRequestWhoSaved;
boolean mIsAutofilled;
- int mAccessibilityViewId;
+ int mAutofillViewId;
/**
* Constructor used when reading from a parcel. Reads the state of the superclass.
@@ -25003,7 +25042,7 @@
mSavedData = source.readInt();
mStartActivityRequestWhoSaved = source.readString();
mIsAutofilled = source.readBoolean();
- mAccessibilityViewId = source.readInt();
+ mAutofillViewId = source.readInt();
}
/**
@@ -25022,7 +25061,7 @@
out.writeInt(mSavedData);
out.writeString(mStartActivityRequestWhoSaved);
out.writeBoolean(mIsAutofilled);
- out.writeInt(mAccessibilityViewId);
+ out.writeInt(mAutofillViewId);
}
public static final Parcelable.Creator<BaseSavedState> CREATOR
@@ -26273,9 +26312,6 @@
mTooltipInfo.mHideTooltipRunnable = this::hideTooltip;
}
mTooltipInfo.mTooltipText = tooltipText;
- if (mTooltipInfo.mTooltipPopup != null && mTooltipInfo.mTooltipPopup.isShowing()) {
- mTooltipInfo.mTooltipPopup.updateContent(mTooltipInfo.mTooltipText);
- }
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 4b79b8c..66df335 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1361,6 +1361,27 @@
return null;
}
+ /** @hide */
+ @Override
+ public View findViewByAutofillIdTraversal(int autofillId) {
+ View foundView = super.findViewByAutofillIdTraversal(autofillId);
+ if (foundView != null) {
+ return foundView;
+ }
+
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ foundView = child.findViewByAutofillIdTraversal(autofillId);
+ if (foundView != null) {
+ return foundView;
+ }
+ }
+
+ return null;
+ }
+
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
super.dispatchWindowFocusChanged(hasFocus);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2605b4a..fd950db 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2471,6 +2471,9 @@
mInLayout = true;
final View host = mView;
+ if (host == null) {
+ return;
+ }
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
@@ -2778,6 +2781,8 @@
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
+ } else if (mView == null) {
+ return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 41a8b8a..bdadcc7 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -912,14 +912,14 @@
}
/**
- * Notifies that the availability of the accessibility button in the system's navigation area
+ * Notifies that the visibility of the accessibility button in the system's navigation area
* has changed.
*
- * @param available {@code true} if the accessibility button is available within the system
+ * @param shown {@code true} if the accessibility button is visible within the system
* navigation area, {@code false} otherwise
* @hide
*/
- public void notifyAccessibilityButtonAvailabilityChanged(boolean available) {
+ public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -928,9 +928,9 @@
}
}
try {
- service.notifyAccessibilityButtonAvailabilityChanged(available);
+ service.notifyAccessibilityButtonVisibilityChanged(shown);
} catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while dispatching accessibility button availability change", re);
+ Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
}
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 06cb5dc..3f499ab 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -64,7 +64,7 @@
void notifyAccessibilityButtonClicked();
- void notifyAccessibilityButtonAvailabilityChanged(boolean available);
+ void notifyAccessibilityButtonVisibilityChanged(boolean available);
// Requires WRITE_SECURE_SETTINGS
void performAccessibilityShortcut();
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 310ec1c..5b04f41 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -246,20 +246,20 @@
/**
* Finds views by traversing the hierarchies of the client.
*
- * @param viewIds The accessibility ids of the views to find
+ * @param viewIds The autofill ids of the views to find
*
* @return And array containing the views (empty if no views found).
*/
- @NonNull View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds);
+ @NonNull View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds);
/**
* Finds a view by traversing the hierarchies of the client.
*
- * @param viewId The accessibility id of the views to find
+ * @param viewId The autofill id of the views to find
*
* @return The view, or {@code null} if not found
*/
- @Nullable View findViewByAccessibilityIdTraversal(int viewId);
+ @Nullable View findViewByAutofillIdTraversal(int viewId);
/**
* Runs the specified action on the UI thread.
@@ -795,11 +795,11 @@
}
private static AutofillId getAutofillId(View view) {
- return new AutofillId(view.getAccessibilityViewId());
+ return new AutofillId(view.getAutofillViewId());
}
private static AutofillId getAutofillId(View parent, int virtualId) {
- return new AutofillId(parent.getAccessibilityViewId(), virtualId);
+ return new AutofillId(parent.getAutofillViewId(), virtualId);
}
private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
@@ -1039,7 +1039,7 @@
final int itemCount = ids.size();
int numApplied = 0;
ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
- final View[] views = client.findViewsByAccessibilityIdTraversal(getViewIds(ids));
+ final View[] views = client.findViewsByAutofillIdTraversal(getViewIds(ids));
for (int i = 0; i < itemCount; i++) {
final AutofillId id = ids.get(i);
@@ -1232,7 +1232,7 @@
return null;
}
- return client.findViewByAccessibilityIdTraversal(autofillId.getViewId());
+ return client.findViewByAutofillIdTraversal(autofillId.getViewId());
}
/** @hide */
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index 1e8207a..f712d5f 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -293,9 +293,19 @@
* @param setSelected whether to set the specified day as selected
*/
private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
+ boolean dateClamped = false;
+ // Clamp the target day in milliseconds to the min or max if outside the range.
+ if (timeInMillis < mMinDate.getTimeInMillis()) {
+ timeInMillis = mMinDate.getTimeInMillis();
+ dateClamped = true;
+ } else if (timeInMillis > mMaxDate.getTimeInMillis()) {
+ timeInMillis = mMaxDate.getTimeInMillis();
+ dateClamped = true;
+ }
+
getTempCalendarForTime(timeInMillis);
- if (setSelected) {
+ if (setSelected || dateClamped) {
mSelectedDay.setTimeInMillis(timeInMillis);
}
@@ -353,13 +363,6 @@
public void onRangeChanged() {
mAdapter.setRange(mMinDate, mMaxDate);
- // Clamp the selected day to the new min/max.
- if (mSelectedDay.before(mMinDate)) {
- mSelectedDay.setTimeInMillis(mMinDate.getTimeInMillis());
- } else if (mSelectedDay.after(mMaxDate)) {
- mSelectedDay.setTimeInMillis(mMaxDate.getTimeInMillis());
- }
-
// Changing the min/max date changes the selection position since we
// don't really have stable IDs. Jumps immediately to the new position.
setDate(mSelectedDay.getTimeInMillis(), false, false);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0e6e3ae..45e5f8a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2584,14 +2584,18 @@
if (offset == -1) {
return;
}
+
stopTextActionModeWithPreservingSelection();
- final boolean isOnSelection = mTextView.hasSelection()
- && offset >= mTextView.getSelectionStart() && offset <= mTextView.getSelectionEnd();
- if (!isOnSelection) {
- // Right clicked position is not on the selection. Remove the selection and move the
- // cursor to the right clicked position.
- Selection.setSelection((Spannable) mTextView.getText(), offset);
- stopTextActionMode();
+ if (mTextView.canSelectText()) {
+ final boolean isOnSelection = mTextView.hasSelection()
+ && offset >= mTextView.getSelectionStart()
+ && offset <= mTextView.getSelectionEnd();
+ if (!isOnSelection) {
+ // Right clicked position is not on the selection. Remove the selection and move the
+ // cursor to the right clicked position.
+ Selection.setSelection((Spannable) mTextView.getText(), offset);
+ stopTextActionMode();
+ }
}
if (shouldOfferToShowSuggestions()) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7117137..aa6ffce 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2342,24 +2342,26 @@
}
- public synchronized RemoteViews clone() {
- Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. "
- + "May only clone the root of a RemoteView hierarchy.");
+ public RemoteViews clone() {
+ synchronized (this) {
+ Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. "
+ + "May only clone the root of a RemoteView hierarchy.");
- Parcel p = Parcel.obtain();
+ Parcel p = Parcel.obtain();
- // Do not parcel the Bitmap cache - doing so creates an expensive copy of all bitmaps.
- // Instead pretend we're not owning the cache while parceling.
- mIsRoot = false;
- writeToParcel(p, PARCELABLE_ELIDE_DUPLICATES);
- p.setDataPosition(0);
- mIsRoot = true;
+ // Do not parcel the Bitmap cache - doing so creates an expensive copy of all bitmaps.
+ // Instead pretend we're not owning the cache while parceling.
+ mIsRoot = false;
+ writeToParcel(p, PARCELABLE_ELIDE_DUPLICATES);
+ p.setDataPosition(0);
+ mIsRoot = true;
- RemoteViews rv = new RemoteViews(p, mBitmapCache.clone(), mApplication, 0);
- rv.mIsRoot = true;
+ RemoteViews rv = new RemoteViews(p, mBitmapCache.clone(), mApplication, 0);
+ rv.mIsRoot = true;
- p.recycle();
- return rv;
+ p.recycle();
+ return rv;
+ }
}
public String getPackage() {
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 96b443d..1f84061 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -25,6 +25,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -101,7 +102,7 @@
/**
* Returns the given list, or an immutable empty list if the provided list is null
*
- * This can be used to guaranty null-safety without paying the price of extra allocations
+ * This can be used to guarantee null-safety without paying the price of extra allocations
*
* @see Collections#emptyList
*/
@@ -110,6 +111,17 @@
}
/**
+ * Returns the given set, or an immutable empty set if the provided set is null
+ *
+ * This can be used to guarantee null-safety without paying the price of extra allocations
+ *
+ * @see Collections#emptySet
+ */
+ public static @NonNull <T> Set<T> emptyIfNull(@Nullable Set<T> cur) {
+ return cur == null ? Collections.emptySet() : cur;
+ }
+
+ /**
* Returns the size of the given list, or 0 if the list is null
*/
public static int size(@Nullable Collection<?> cur) {
diff --git a/core/java/com/android/internal/view/TooltipPopup.java b/core/java/com/android/internal/view/TooltipPopup.java
index 52357ac..3930214 100644
--- a/core/java/com/android/internal/view/TooltipPopup.java
+++ b/core/java/com/android/internal/view/TooltipPopup.java
@@ -91,10 +91,6 @@
return mContentView.getParent() != null;
}
- public void updateContent(CharSequence tooltipText) {
- mMessageView.setText(tooltipText);
- }
-
private void computePosition(View anchorView, int anchorX, int anchorY, boolean fromTouch,
WindowManager.LayoutParams outParams) {
outParams.token = anchorView.getWindowToken();
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index de67c50..26b0034 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -29,6 +29,7 @@
#include <sys/types.h>
#include <unistd.h>
+#include <android-base/stringprintf.h>
#include <binder/IInterface.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
@@ -194,10 +195,34 @@
/*
* It's an Error: Reraise the exception and ask the runtime to abort.
*/
+
+ // Try to get the exception string. Sometimes logcat isn't available,
+ // so try to add it to the abort message.
+ std::string exc_msg = "(Unknown exception message)";
+ {
+ ScopedLocalRef<jclass> exc_class(env, env->GetObjectClass(excep));
+ jmethodID method_id = env->GetMethodID(exc_class.get(),
+ "toString",
+ "()Ljava/lang/String;");
+ ScopedLocalRef<jstring> jstr(
+ env,
+ reinterpret_cast<jstring>(
+ env->CallObjectMethod(excep, method_id)));
+ env->ExceptionClear(); // Just for good measure.
+ if (jstr.get() != nullptr) {
+ ScopedUtfChars jstr_utf(env, jstr.get());
+ exc_msg = jstr_utf.c_str();
+ }
+ }
+
env->Throw(excep);
ALOGE("java.lang.Error thrown during binder transaction (stack trace follows) : ");
env->ExceptionDescribe();
- env->FatalError("java.lang.Error thrown during binder transaction.");
+
+ std::string error_msg = base::StringPrintf(
+ "java.lang.Error thrown during binder transaction: %s",
+ exc_msg.c_str());
+ env->FatalError(error_msg.c_str());
}
bail:
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 5839fd5..7744e0c 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -394,6 +394,16 @@
static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
jlong surfaceControlNativeObj) {
+ sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
+ sp<Surface> surface(ctrl->createSurface());
+ if (surface != NULL) {
+ surface->incStrong(&sRefBaseOwner);
+ }
+ return reinterpret_cast<jlong>(surface.get());
+}
+
+static jlong nativeGetFromSurfaceControl(JNIEnv* env, jclass clazz,
+ jlong surfaceControlNativeObj) {
/*
* This is used by the WindowManagerService just after constructing
* a Surface and is necessary for returning the Surface reference to
@@ -590,6 +600,8 @@
(void*)nativeAllocateBuffers },
{"nativeCreateFromSurfaceControl", "(J)J",
(void*)nativeCreateFromSurfaceControl },
+ {"nativeGetFromSurfaceControl", "(J)J",
+ (void*)nativeGetFromSurfaceControl },
{"nativeReadFromParcel", "(JLandroid/os/Parcel;)J",
(void*)nativeReadFromParcel },
{"nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 438b123..c9251bc 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -932,6 +932,10 @@
return createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Mutable);
}
+static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
+ RenderProxy::disableVsync();
+}
+
// ----------------------------------------------------------------------------
// FrameMetricsObserver
// ----------------------------------------------------------------------------
@@ -1030,6 +1034,7 @@
(void*)android_view_ThreadedRenderer_copySurfaceInto },
{ "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
(void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode },
+ { "disableVsync", "()V", (void*)android_view_ThreadedRenderer_disableVsync },
};
int register_android_view_ThreadedRenderer(JNIEnv* env) {
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 3251771..8139176 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1288,10 +1288,10 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN aktiveres af <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="1610714069627824309">"Tryk for at administrere netværket."</string>
<string name="vpn_text_long" msgid="4907843483284977618">"Forbundet til <xliff:g id="SESSION">%s</xliff:g>. Tryk for at administrere netværket."</string>
- <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til altid aktiveret VPN…"</string>
- <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Altid aktiveret VPN er forbundet"</string>
- <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Forbindelsen til altid aktiveret VPN er afbrudt"</string>
- <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i altid aktiveret VPN"</string>
+ <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til konstant VPN…"</string>
+ <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Konstant VPN er forbundet"</string>
+ <string name="vpn_lockdown_disconnected" msgid="4532298952570796327">"Forbindelsen til konstant VPN er afbrudt"</string>
+ <string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i konstant VPN"</string>
<string name="vpn_lockdown_config" msgid="5099330695245008680">"Tryk for at konfigurere"</string>
<string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 778163c..a341a8a 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -447,7 +447,7 @@
<string name="permlab_changeTetherState" msgid="5952584964373017960">"Tethering-Konnektivität ändern"</string>
<string name="permdesc_changeTetherState" msgid="1524441344412319780">"Ermöglicht der App, den Status der Tethering-Konnektivität zu ändern"</string>
<string name="permlab_accessWifiState" msgid="5202012949247040011">"WLAN-Verbindungen abrufen"</string>
- <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Ermöglicht der App, Informationen zu WLANs abzurufen, etwa ob ein WLAN aktiviert ist, und den Namen verbundener WLAN-Geräte."</string>
+ <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Ermöglicht der App, Informationen zu WLAN-Netzwerken abzurufen, etwa ob ein WLAN aktiviert ist, und den Namen verbundener WLAN-Geräte."</string>
<string name="permlab_changeWifiState" msgid="6550641188749128035">"WLAN-Verbindungen herstellen und trennen"</string>
<string name="permdesc_changeWifiState" msgid="7137950297386127533">"Ermöglicht der App, eine Verbindung zu WLAN-Zugangspunkten herzustellen und solche zu trennen und Änderungen an der Gerätekonfiguration für WLAN-Netzwerke vorzunehmen."</string>
<string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"WLAN-Multicast-Empfang zulassen"</string>
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index aad81df..b587248 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -69,6 +69,7 @@
bool Properties::forceDrawFrame = false;
bool Properties::filterOutTestOverhead = false;
+bool Properties::disableVsync = false;
static int property_get_int(const char* key, int defaultValue) {
char buf[PROPERTY_VALUE_MAX] = {'\0',};
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 9db6449..91b4a2d 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -318,6 +318,12 @@
// any overhead they add
static bool filterOutTestOverhead;
+ // Workaround a device lockup in edge cases by switching to async mode
+ // instead of the default vsync (b/38372997). Only system_server should hit this.
+ // Any existing RenderProxy & Surface combination will be unaffected, only things
+ // created after changing this.
+ static bool disableVsync;
+
// Used for testing only to change the render pipeline.
#ifdef HWUI_GLES_WRAP_ENABLED
static void overrideRenderPipelineType(RenderPipelineType);
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 44af5fd..ed30708 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -279,6 +279,9 @@
}
}
mCurrentSurface = surface;
+ if (Properties::disableVsync) {
+ eglSwapInterval(mEglDisplay, 0);
+ }
return true;
}
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index eed5238..d842be9 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -18,6 +18,7 @@
#include "DeferredLayerUpdater.h"
#include "DisplayList.h"
+#include "Properties.h"
#include "Readback.h"
#include "Rect.h"
#include "renderthread/CanvasContext.h"
@@ -708,6 +709,10 @@
thread.queue(task);
}
+void RenderProxy::disableVsync() {
+ Properties::disableVsync = true;
+}
+
void RenderProxy::post(RenderTask* task) {
mRenderThread.queue(task);
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index b21772c..6f4e8ce 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -137,6 +137,8 @@
static int copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap);
static void onBitmapDestroyed(uint32_t pixelRefId);
+
+ ANDROID_API static void disableVsync();
private:
RenderThread& mRenderThread;
CanvasContext* mContext;
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 163c4b0..f408e57 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -177,6 +177,7 @@
}
void JNIImageReaderContext::returnBufferItem(BufferItem* buffer) {
+ buffer->mGraphicBuffer = nullptr;
mBuffers.push_back(buffer);
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index 2a4ab0f..3b29a6c 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -20,6 +20,8 @@
import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
import static com.android.internal.util.ArrayUtils.isEmpty;
+import static com.android.internal.util.CollectionUtils.emptyIfNull;
+import static com.android.internal.util.CollectionUtils.size;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -154,6 +156,25 @@
onReadyToShowUI();
}
+ // If filtering to get single device by mac address, also search in the set of already
+ // bonded devices to allow linking those directly
+ String singleMacAddressFilter = null;
+ if (mRequest.isSingleDevice()) {
+ int numFilters = size(mBluetoothFilters);
+ for (int i = 0; i < numFilters; i++) {
+ BluetoothDeviceFilter filter = mBluetoothFilters.get(i);
+ if (!TextUtils.isEmpty(filter.getAddress())) {
+ singleMacAddressFilter = filter.getAddress();
+ break;
+ }
+ }
+ }
+ if (singleMacAddressFilter != null) {
+ for (BluetoothDevice dev : emptyIfNull(mBluetoothAdapter.getBondedDevices())) {
+ onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
+ }
+ }
+
if (shouldScan(mBluetoothFilters)) {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
@@ -211,6 +232,8 @@
}
private void onDeviceFound(@Nullable DeviceFilterPair device) {
+ if (device == null) return;
+
if (mDevicesFound.contains(device)) {
return;
}
@@ -444,12 +467,9 @@
}
for (int i = 0; i < scanResults.size(); i++) {
- DeviceFilterPair<android.net.wifi.ScanResult> deviceFilterPair =
- DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters);
- if (deviceFilterPair != null) onDeviceFound(deviceFilterPair);
+ onDeviceFound(DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters));
}
}
-
}
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index 6140428..41952df 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -16,6 +16,8 @@
package com.android.printspooler.model;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -29,21 +31,27 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.print.PageRange;
import android.print.PrintAttributes;
-import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
import android.print.PrintDocumentInfo;
import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
+
import com.android.internal.annotations.GuardedBy;
import com.android.printspooler.renderer.IPdfRenderer;
import com.android.printspooler.renderer.PdfManipulationService;
import com.android.printspooler.util.BitmapSerializeUtils;
+import com.android.printspooler.util.PageRangeUtils;
+
import dalvik.system.CloseGuard;
+
import libcore.io.IoUtils;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -69,8 +77,9 @@
private RenderSpec mLastRenderSpec;
- private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
- private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
+ @Nullable private PageRange mScheduledPreloadVisiblePages;
+ @Nullable private PageRange[] mScheduledPreloadSelectedPages;
+ @Nullable private PageRange[] mScheduledPreloadWrittenPages;
private int mState;
@@ -129,14 +138,24 @@
}
}
- public void startPreload(int firstShownPage, int lastShownPage) {
+ /**
+ * Preload selected, written pages around visiblePages.
+ *
+ * @param visiblePages The pages currently visible
+ * @param selectedPages The pages currently selected (e.g. they might become visible by
+ * scrolling)
+ * @param writtenPages The pages currently in the document
+ */
+ public void startPreload(@NonNull PageRange visiblePages, @NonNull PageRange[] selectedPages,
+ @NonNull PageRange[] writtenPages) {
// If we do not have a render spec we have no clue what size the
// preloaded bitmaps should be, so just take a note for what to do.
if (mLastRenderSpec == null) {
- mScheduledPreloadFirstShownPage = firstShownPage;
- mScheduledPreloadLastShownPage = lastShownPage;
+ mScheduledPreloadVisiblePages = visiblePages;
+ mScheduledPreloadSelectedPages = selectedPages;
+ mScheduledPreloadWrittenPages = writtenPages;
} else if (mState == STATE_OPENED) {
- mRenderer.startPreload(firstShownPage, lastShownPage, mLastRenderSpec);
+ mRenderer.startPreload(visiblePages, selectedPages, writtenPages, mLastRenderSpec);
}
}
@@ -222,11 +241,12 @@
// We tired to preload but didn't know the bitmap size, now
// that we know let us do the work.
- if (mScheduledPreloadFirstShownPage != INVALID_PAGE_INDEX
- && mScheduledPreloadLastShownPage != INVALID_PAGE_INDEX) {
- startPreload(mScheduledPreloadFirstShownPage, mScheduledPreloadLastShownPage);
- mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
- mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
+ if (mScheduledPreloadVisiblePages != null) {
+ startPreload(mScheduledPreloadVisiblePages, mScheduledPreloadSelectedPages,
+ mScheduledPreloadWrittenPages);
+ mScheduledPreloadVisiblePages = null;
+ mScheduledPreloadSelectedPages = null;
+ mScheduledPreloadWrittenPages = null;
}
if (mState == STATE_OPENED) {
@@ -523,10 +543,45 @@
mDestroyed = true;
}
- public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) {
+ /**
+ * How many pages are {@code pages} before pageNum. E.g. page 5 in [0-1], [4-7] has the
+ * index 4.
+ *
+ * @param pageNum The number of the page to find
+ * @param pages A normalized array of page ranges
+ *
+ * @return The index or {@link #INVALID_PAGE_INDEX} if not found
+ */
+ private int findIndexOfPage(int pageNum, @NonNull PageRange[] pages) {
+ int pagesBefore = 0;
+ for (int i = 0; i < pages.length; i++) {
+ if (pages[i].contains(pageNum)) {
+ return pagesBefore + pageNum - pages[i].getStart();
+ } else {
+ pagesBefore += pages[i].getSize();
+ }
+ }
+
+ return INVALID_PAGE_INDEX;
+ }
+
+ void startPreload(@NonNull PageRange visiblePages, @NonNull PageRange[] selectedPages,
+ @NonNull PageRange[] writtenPages, RenderSpec renderSpec) {
+ if (PageRangeUtils.isAllPages(selectedPages)) {
+ selectedPages = new PageRange[]{new PageRange(0, mPageCount - 1)};
+ }
+
if (DEBUG) {
- Log.i(LOG_TAG, "Preloading pages around [" + firstShownPage
- + "-" + lastShownPage + "]");
+ Log.i(LOG_TAG, "Preloading pages around " + visiblePages + " from "
+ + Arrays.toString(selectedPages));
+ }
+
+ int firstVisiblePageIndex = findIndexOfPage(visiblePages.getStart(), selectedPages);
+ int lastVisiblePageIndex = findIndexOfPage(visiblePages.getEnd(), selectedPages);
+
+ if (firstVisiblePageIndex == INVALID_PAGE_INDEX
+ || lastVisiblePageIndex == INVALID_PAGE_INDEX) {
+ return;
}
final int bitmapSizeInBytes = renderSpec.bitmapWidth * renderSpec.bitmapHeight
@@ -534,28 +589,33 @@
final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes()
/ bitmapSizeInBytes;
final int halfPreloadCount = (maxCachedPageCount
- - (lastShownPage - firstShownPage)) / 2 - 1;
+ - (lastVisiblePageIndex - firstVisiblePageIndex)) / 2 - 1;
- final int excessFromStart;
- if (firstShownPage - halfPreloadCount < 0) {
- excessFromStart = halfPreloadCount - firstShownPage;
- } else {
- excessFromStart = 0;
+ final int fromIndex = Math.max(firstVisiblePageIndex - halfPreloadCount, 0);
+ final int toIndex = lastVisiblePageIndex + halfPreloadCount;
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "fromIndex=" + fromIndex + " toIndex=" + toIndex);
}
- final int excessFromEnd;
- if (lastShownPage + halfPreloadCount >= mPageCount) {
- excessFromEnd = (lastShownPage + halfPreloadCount) - mPageCount;
- } else {
- excessFromEnd = 0;
- }
+ int previousRangeSizes = 0;
+ for (int rangeNum = 0; rangeNum < selectedPages.length; rangeNum++) {
+ PageRange range = selectedPages[rangeNum];
- final int fromIndex = Math.max(firstShownPage - halfPreloadCount - excessFromEnd, 0);
- final int toIndex = Math.min(lastShownPage + halfPreloadCount + excessFromStart,
- mPageCount - 1);
+ int thisRangeStart = Math.max(0, fromIndex - previousRangeSizes);
+ int thisRangeEnd = Math.min(range.getSize(), toIndex - previousRangeSizes + 1);
- for (int i = fromIndex; i <= toIndex; i++) {
- renderPage(i, renderSpec, null);
+ for (int i = thisRangeStart; i < thisRangeEnd; i++) {
+ if (PageRangeUtils.contains(writtenPages, range.getStart() + i)) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Preloading " + (range.getStart() + i));
+ }
+
+ renderPage(range.getStart() + i, renderSpec, null);
+ }
+ }
+
+ previousRangeSizes += range.getSize();
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 54400b3..1eadb8e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -16,6 +16,7 @@
package com.android.printspooler.ui;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -24,8 +25,8 @@
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
-import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
import android.print.PrintDocumentInfo;
import android.support.v7.widget.RecyclerView.Adapter;
import android.support.v7.widget.RecyclerView.ViewHolder;
@@ -33,11 +34,12 @@
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.view.View.MeasureSpec;
import android.widget.TextView;
+
import com.android.printspooler.R;
import com.android.printspooler.model.OpenDocumentCallback;
import com.android.printspooler.model.PageContentRepository;
@@ -45,6 +47,7 @@
import com.android.printspooler.util.PageRangeUtils;
import com.android.printspooler.widget.PageContentView;
import com.android.printspooler.widget.PreviewPageFrame;
+
import dalvik.system.CloseGuard;
import java.util.ArrayList;
@@ -794,14 +797,16 @@
page.setTag(null);
}
- public void startPreloadContent(PageRange pageRangeInAdapter) {
- final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
- final int startPageInFile = computePageIndexInFile(startPageInDocument);
- final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
- final int endPageInFile = computePageIndexInFile(endPageInDocument);
- if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
- mPageContentRepository.startPreload(startPageInFile, endPageInFile);
+ void startPreloadContent(@NonNull PageRange visiblePagesInAdapter) {
+ int startVisibleDocument = computePageIndexInDocument(visiblePagesInAdapter.getStart());
+ int endVisibleDocument = computePageIndexInDocument(visiblePagesInAdapter.getEnd());
+ if (startVisibleDocument == INVALID_PAGE_INDEX
+ || endVisibleDocument == INVALID_PAGE_INDEX) {
+ return;
}
+
+ mPageContentRepository.startPreload(new PageRange(startVisibleDocument, endVisibleDocument),
+ mSelectedPages, mWrittenPages);
}
public void stopPreloadContent() {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
index a36f583..17d820a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
@@ -426,7 +426,7 @@
// be based off the start of the written ones instead of zero.
// The written pages are always non-null and not empty.
final int offset = -pagesWrittenToFile[0].getStart();
- PageRangeUtils.offset(pagesInDocRequested, offset);
+ PageRangeUtils.offset(pagesInDocRequested.clone(), offset);
return pagesInDocRequested;
} else if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE)
&& isAllPages(pagesWrittenToFile, pageCount)) {
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 48bf180..f5d5140 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -273,7 +273,7 @@
<string name="show_all_anrs" msgid="28462979638729082">"Tots els errors sense resposta"</string>
<string name="show_all_anrs_summary" msgid="641908614413544127">"Informa que una aplicació en segon pla no respon"</string>
<string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostra avisos del canal de notificacions"</string>
- <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra un avís a la pantalla quan una app publica una notificació sense canal vàlid"</string>
+ <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra un avís a la pantalla quan una aplicació publica una notificació sense un canal vàlid"</string>
<string name="force_allow_on_external" msgid="3215759785081916381">"Força permís d\'aplicacions a l\'emmagatzem. extern"</string>
<string name="force_allow_on_external_summary" msgid="3640752408258034689">"Permet que qualsevol aplicació es pugui escriure en un dispositiu d’emmagatzematge extern, independentment dels valors definits"</string>
<string name="force_resizable_activities" msgid="8615764378147824985">"Força l\'ajust de la mida de les activitats"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 00f0b5e..6889e01 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -272,8 +272,8 @@
<string name="app_process_limit_title" msgid="4280600650253107163">"Limite proc. em 2º plano"</string>
<string name="show_all_anrs" msgid="28462979638729082">"Mostrar todos os ANR"</string>
<string name="show_all_anrs_summary" msgid="641908614413544127">"Mostrar erro \"Aplic. não Resp.\" p/ aplic. 2º plano"</string>
- <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostrar avisos do canal de notif."</string>
- <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra um aviso no ecrã quando uma aplic. publica uma notific. sem um canal válido"</string>
+ <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Mostrar avisos do canal de notificações"</string>
+ <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Mostra um aviso no ecrã quando uma aplicação publica uma notificação sem o canal ser válido"</string>
<string name="force_allow_on_external" msgid="3215759785081916381">"Forçar perm. de aplicações no armazenamento ext."</string>
<string name="force_allow_on_external_summary" msgid="3640752408258034689">"Torna qualquer aplicação elegível para ser gravada no armazenamento externo, independentemente dos valores do manifesto"</string>
<string name="force_resizable_activities" msgid="8615764378147824985">"Forçar as atividades a serem redimensionáveis"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 33b973d..9a0b1bf 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -79,7 +79,7 @@
<string name="bluetooth_pairing_decline" msgid="4185420413578948140">"取消"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"配對完成後,所配對的裝置即可在連線後存取你的聯絡人和通話紀錄。"</string>
<string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對。"</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對,因為 PIN 或密碼金鑰不正確。"</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 配對,因為 PIN 碼或密碼金鑰不正確。"</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"無法與 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 通訊。"</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」拒絕配對要求。"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"已關閉 Wi-Fi。"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 474de90..1cbb745 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -88,21 +88,16 @@
private static final String XMLTAG_TIMEZONE = "timezone";
public static CharSequence getTimeZoneOffsetAndName(Context context, TimeZone tz, Date now) {
- final Locale locale = Locale.getDefault();
- final CharSequence gmtText = getGmtOffsetText(context, locale, tz, now);
- final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
- final ZoneGetterData data = new ZoneGetterData(context);
-
- final boolean useExemplarLocationForLocalNames =
- shouldUseExemplarLocationForLocalNames(data, timeZoneNames);
- final CharSequence zoneName = getTimeZoneDisplayName(data, timeZoneNames,
- useExemplarLocationForLocalNames, tz, tz.getID());
- if (zoneName == null) {
+ Locale locale = Locale.getDefault();
+ CharSequence gmtText = getGmtOffsetText(context, locale, tz, now);
+ TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
+ String zoneNameString = getZoneLongName(timeZoneNames, tz, now);
+ if (zoneNameString == null) {
return gmtText;
}
// We don't use punctuation here to avoid having to worry about localizing that too!
- return TextUtils.concat(gmtText, " ", zoneName);
+ return TextUtils.concat(gmtText, " ", zoneNameString);
}
public static List<Map<String, Object>> getZonesList(Context context) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 623d1a6..0ea9d96 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -511,7 +511,10 @@
}
}
- mSeen = seen;
+ // Only replace the previous value if we have a recent scan result to use
+ if (seen != 0) {
+ mSeen = seen;
+ }
}
/**
@@ -940,8 +943,10 @@
security = getSecurity(result);
if (security == SECURITY_PSK)
pskType = getPskType(result);
- mRssi = result.level;
- mSeen = result.timestamp;
+
+ mScanResultCache.put(result.BSSID, result);
+ updateRssi();
+ mSeen = result.timestamp; // even if the timestamp is old it is still valid
}
public void saveWifiState(Bundle savedState) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 40a59cf..fcc9090 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -142,6 +142,7 @@
@VisibleForTesting
Scanner mScanner;
+ private boolean mStaleScanResults = false;
public WifiTracker(Context context, WifiListener wifiListener,
boolean includeSaved, boolean includeScans) {
@@ -330,7 +331,11 @@
* Stop tracking wifi networks and scores.
*
* <p>This should always be called when done with a WifiTracker (if startTracking was called) to
- * ensure proper cleanup and prevent any further callbacks from occuring.
+ * ensure proper cleanup and prevent any further callbacks from occurring.
+ *
+ * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
+ * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
+ * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
*/
@MainThread
public void stopTracking() {
@@ -346,6 +351,7 @@
mWorkHandler.removePendingMessages();
mMainHandler.removePendingMessages();
}
+ mStaleScanResults = true;
}
private void unregisterAndClearScoreCache() {
@@ -712,6 +718,11 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+
+ if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
+ mStaleScanResults = false;
+ }
+
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN));
@@ -822,7 +833,9 @@
switch (msg.what) {
case MSG_UPDATE_ACCESS_POINTS:
- updateAccessPointsLocked();
+ if (!mStaleScanResults) {
+ updateAccessPointsLocked();
+ }
break;
case MSG_UPDATE_NETWORK_INFO:
updateNetworkInfo((NetworkInfo) msg.obj);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
index 703e9d2..a3345ee 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/utils/ZoneGetterTest.java
@@ -47,9 +47,9 @@
}
@Test
- public void getTimeZoneOffsetAndName_setLondon_returnLondon() {
- // Check it will ends with 'London', not 'British Summer Time' or sth else
- testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "London");
+ public void getTimeZoneOffsetAndName_setLondon_returnBritishSummerTime() {
+ // Check it will ends with 'British Summer Time', not 'London' or sth else
+ testTimeZoneOffsetAndNameInner(TIME_ZONE_LONDON_ID, "British Summer Time");
}
@Test
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index c526432..eb9a7f6 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -748,4 +749,36 @@
verifyNoMoreInteractions(mockWifiListener);
}
+
+ @Test
+ public void stopTrackingShouldSetStaleBitWhichPreventsCallbacksUntilNextScanResult()
+ throws Exception {
+ WifiTracker tracker = createMockedWifiTracker();
+ startTracking(tracker);
+ tracker.stopTracking();
+
+ CountDownLatch latch1 = new CountDownLatch(1);
+ tracker.mMainHandler.post(() -> {
+ latch1.countDown();
+ });
+ assertTrue("Latch 1 timed out", latch1.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
+ startTracking(tracker);
+
+ tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
+ tracker.mReceiver.onReceive(
+ mContext, new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION));
+ tracker.mReceiver.onReceive(
+ mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
+
+ CountDownLatch latch2 = new CountDownLatch(1);
+ tracker.mMainHandler.post(() -> {
+ latch2.countDown();
+ });
+ assertTrue("Latch 2 timed out", latch2.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
+ verify(mockWifiListener, never()).onAccessPointsChanged();
+
+ sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
+ }
}
diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml
index 61ac6f69..0b7bce1 100644
--- a/packages/SystemUI/res/layout/tv_pip_controls.xml
+++ b/packages/SystemUI/res/layout/tv_pip_controls.xml
@@ -22,24 +22,24 @@
<com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/full_button"
- android:layout_width="100dp"
+ android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
android:src="@drawable/ic_fullscreen_white_24dp"
android:text="@string/pip_fullscreen" />
<com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/close_button"
- android:layout_width="100dp"
+ android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
- android:layout_marginStart="-50dp"
+ android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
android:src="@drawable/ic_close_white"
android:text="@string/pip_close" />
<com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/play_pause_button"
- android:layout_width="100dp"
+ android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
- android:layout_marginStart="-50dp"
+ android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
android:src="@drawable/ic_pause_white"
android:text="@string/pip_pause"
android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/tv_pip_custom_control.xml b/packages/SystemUI/res/layout/tv_pip_custom_control.xml
new file mode 100644
index 0000000..dd0fce4
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_custom_control.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.systemui.pip.tv.PipControlButtonView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/picture_in_picture_button_width"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" />
diff --git a/packages/SystemUI/res/values-tvdpi/dimens.xml b/packages/SystemUI/res/values-tvdpi/dimens.xml
index 5327cee..4d978aa 100644
--- a/packages/SystemUI/res/values-tvdpi/dimens.xml
+++ b/packages/SystemUI/res/values-tvdpi/dimens.xml
@@ -24,4 +24,8 @@
<fraction name="battery_subpixel_smoothing_right">10%</fraction>
<dimen name="battery_margin_bottom">1px</dimen>
+
+ <!-- The dimensions to user for picture-in-picture action buttons. -->
+ <dimen name="picture_in_picture_button_width">100dp</dimen>
+ <dimen name="picture_in_picture_button_start_margin">-50dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 0f69f47..a5ee198 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -427,11 +427,7 @@
} else {
actionsContainer.setVisibility(View.VISIBLE);
if (mActionsGroup != null) {
- // Hide extra views
- for (int i = mActions.size(); i < mActionsGroup.getChildCount(); i++) {
- mActionsGroup.getChildAt(i).setVisibility(View.GONE);
- }
- // Add needed views
+ // Ensure we have as many buttons as actions
final LayoutInflater inflater = LayoutInflater.from(this);
while (mActionsGroup.getChildCount() < mActions.size()) {
final ImageView actionView = (ImageView) inflater.inflate(
@@ -439,6 +435,13 @@
mActionsGroup.addView(actionView);
}
+ // Update the visibility of all views
+ for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+ mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
+ ? View.VISIBLE
+ : View.GONE);
+ }
+
// Recreate the layout
final boolean isLandscapePip = stackBounds != null &&
(stackBounds.width() > stackBounds.height());
@@ -460,10 +463,9 @@
Log.w(TAG, "Failed to send action", e);
}
});
- } else {
- actionView.setAlpha(DISABLED_ACTION_ALPHA);
}
actionView.setEnabled(action.isEnabled());
+ actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
// Update the margin between actions
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
index 40a63d7..b21cd95 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorInflater;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,6 +34,7 @@
* A view containing PIP controls including fullscreen, close, and media controls.
*/
public class PipControlButtonView extends RelativeLayout {
+
private OnFocusChangeListener mFocusChangeListener;
private ImageView mIconImageView;
ImageView mButtonImageView;
@@ -122,18 +124,37 @@
}
/**
+ * Sets the drawable for the button with the given drawable.
+ */
+ public void setImageDrawable(Drawable d) {
+ mIconImageView.setImageDrawable(d);
+ }
+
+ /**
* Sets the drawable for the button with the given resource id.
*/
public void setImageResource(int resId) {
- mIconImageView.setImageResource(resId);
+ if (resId != 0) {
+ mIconImageView.setImageResource(resId);
+ }
+ }
+
+ /**
+ * Sets the text for description the with the given string.
+ */
+ public void setText(CharSequence text) {
+ mButtonImageView.setContentDescription(text);
+ mDescriptionTextView.setText(text);
}
/**
* Sets the text for description the with the given resource id.
*/
public void setText(int resId) {
- mButtonImageView.setContentDescription(getContext().getString(resId));
- mDescriptionTextView.setText(resId);
+ if (resId != 0) {
+ mButtonImageView.setContentDescription(getContext().getString(resId));
+ mDescriptionTextView.setText(resId);
+ }
}
private static void cancelAnimator(Animator animator) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index acea3b6..10206d4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -16,12 +16,20 @@
package com.android.systemui.pip.tv;
+import android.app.ActivityManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteAction;
import android.content.Context;
+import android.graphics.Color;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.View;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.util.AttributeSet;
@@ -30,11 +38,19 @@
import static android.media.session.PlaybackState.ACTION_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A view containing PIP controls including fullscreen, close, and media controls.
*/
public class PipControlsView extends LinearLayout {
+
+ private static final String TAG = PipControlsView.class.getSimpleName();
+
+ private static final float DISABLED_ACTION_ALPHA = 0.54f;
+
/**
* An interface to listen user action.
*/
@@ -47,19 +63,23 @@
private MediaController mMediaController;
- final PipManager mPipManager = PipManager.getInstance();
- Listener mListener;
+ private final PipManager mPipManager = PipManager.getInstance();
+ private final LayoutInflater mLayoutInflater;
+ private final Handler mHandler;
+ private Listener mListener;
private PipControlButtonView mFullButtonView;
private PipControlButtonView mCloseButtonView;
private PipControlButtonView mPlayPauseButtonView;
+ private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
+ private List<RemoteAction> mCustomActions = new ArrayList<>();
private PipControlButtonView mFocusedChild;
private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
- updatePlayPauseView();
+ updateUserActions();
}
};
@@ -95,9 +115,10 @@
public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- LayoutInflater inflater = (LayoutInflater) getContext()
+ mLayoutInflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.tv_pip_controls, this);
+ mLayoutInflater.inflate(R.layout.tv_pip_controls, this);
+ mHandler = new Handler();
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
@@ -176,21 +197,74 @@
if (mMediaController != null) {
mMediaController.registerCallback(mMediaControllerCallback);
}
- updatePlayPauseView();
+ updateUserActions();
}
- private void updatePlayPauseView() {
- int state = mPipManager.getPlaybackState();
- if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
+ /**
+ * Updates the actions for the PIP. If there are no custom actions, then the media session
+ * actions are shown.
+ */
+ private void updateUserActions() {
+ if (!mCustomActions.isEmpty()) {
+ // Ensure we have as many buttons as actions
+ while (mCustomButtonViews.size() < mCustomActions.size()) {
+ PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate(
+ R.layout.tv_pip_custom_control, this, false);
+ addView(buttonView);
+ mCustomButtonViews.add(buttonView);
+ }
+
+ // Update the visibility of all views
+ for (int i = 0; i < mCustomButtonViews.size(); i++) {
+ mCustomButtonViews.get(i).setVisibility(i < mCustomActions.size()
+ ? View.VISIBLE
+ : View.GONE);
+ }
+
+ // Update the state and visibility of the action buttons, and hide the rest
+ for (int i = 0; i < mCustomActions.size(); i++) {
+ final RemoteAction action = mCustomActions.get(i);
+ PipControlButtonView actionView = mCustomButtonViews.get(i);
+
+ // TODO: Check if the action drawable has changed before we reload it
+ action.getIcon().loadDrawableAsync(getContext(), d -> {
+ d.setTint(Color.WHITE);
+ actionView.setImageDrawable(d);
+ }, mHandler);
+ actionView.setText(action.getContentDescription());
+ if (action.isEnabled()) {
+ actionView.setOnClickListener(v -> {
+ try {
+ action.getActionIntent().send();
+ } catch (CanceledException e) {
+ Log.w(TAG, "Failed to send action", e);
+ }
+ });
+ }
+ actionView.setEnabled(action.isEnabled());
+ actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
+ }
+
+ // Hide the media session buttons
mPlayPauseButtonView.setVisibility(View.GONE);
} else {
- mPlayPauseButtonView.setVisibility(View.VISIBLE);
- if (state == PipManager.PLAYBACK_STATE_PLAYING) {
- mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
- mPlayPauseButtonView.setText(R.string.pip_pause);
+ int state = mPipManager.getPlaybackState();
+ if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
+ mPlayPauseButtonView.setVisibility(View.GONE);
} else {
- mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
- mPlayPauseButtonView.setText(R.string.pip_play);
+ mPlayPauseButtonView.setVisibility(View.VISIBLE);
+ if (state == PipManager.PLAYBACK_STATE_PLAYING) {
+ mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white);
+ mPlayPauseButtonView.setText(R.string.pip_pause);
+ } else {
+ mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white);
+ mPlayPauseButtonView.setText(R.string.pip_play);
+ }
+ }
+
+ // Hide all the custom action buttons
+ for (int i = 0; i < mCustomButtonViews.size(); i++) {
+ mCustomButtonViews.get(i).setVisibility(View.GONE);
}
}
}
@@ -203,6 +277,9 @@
mCloseButtonView.reset();
mPlayPauseButtonView.reset();
mFullButtonView.requestFocus();
+ for (int i = 0; i < mCustomButtonViews.size(); i++) {
+ mCustomButtonViews.get(i).reset();
+ }
}
/**
@@ -213,6 +290,15 @@
}
/**
+ * Updates the set of activity-defined actions.
+ */
+ public void setActions(List<RemoteAction> actions) {
+ mCustomActions.clear();
+ mCustomActions.addAll(actions);
+ updateUserActions();
+ }
+
+ /**
* Returns the focused control button view to animate focused button.
*/
PipControlButtonView getFocusedButton() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index f98310d..ca58080 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
+import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -124,6 +125,7 @@
private MediaController mPipMediaController;
private String[] mLastPackagesResourceGranted;
private PipNotification mPipNotification;
+ private ParceledListSlice mCustomActions;
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
@@ -187,7 +189,14 @@
}
@Override
- public void onActionsChanged(ParceledListSlice actions) {}
+ public void onActionsChanged(ParceledListSlice actions) {
+ mCustomActions = actions;
+ mHandler.post(() -> {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onPipMenuActionsChanged(mCustomActions);
+ }
+ });
+ }
}
private PipManager() { }
@@ -432,6 +441,7 @@
}
Intent intent = new Intent(mContext, PipMenuActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
mContext.startActivity(intent);
}
@@ -690,6 +700,8 @@
void onPipActivityClosed();
/** Invoked when the PIP menu gets shown. */
void onShowPipMenu();
+ /** Invoked when the PIP menu actions change. */
+ void onPipMenuActionsChanged(ParceledListSlice actions);
/** Invoked when the PIPed activity is about to return back to the fullscreen. */
void onMoveToFullscreen();
/** Invoked when we are above to start resizing the Pip. */
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index ce1bea1..82018ce 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -19,22 +19,27 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.view.View;
import com.android.systemui.R;
+import java.util.Collections;
/**
* Activity to show the PIP menu to control PIP.
*/
public class PipMenuActivity extends Activity implements PipManager.Listener {
private static final String TAG = "PipMenuActivity";
+ static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
+
private final PipManager mPipManager = PipManager.getInstance();
private Animator mFadeInAnimation;
private Animator mFadeOutAnimation;
- private View mPipControlsView;
+ private PipControlsView mPipControlsView;
private boolean mRestorePipSizeWhenClose;
@Override
@@ -51,6 +56,15 @@
mFadeOutAnimation = AnimatorInflater.loadAnimator(
this, R.anim.tv_pip_menu_fade_out_animation);
mFadeOutAnimation.setTarget(mPipControlsView);
+
+ onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+
+ onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
}
private void restorePipAndFinish() {
@@ -96,6 +110,12 @@
}
@Override
+ public void onPipMenuActionsChanged(ParceledListSlice actions) {
+ boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
+ mPipControlsView.setActions(hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
+ }
+
+ @Override
public void onShowPipMenu() { }
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index c8f4185..f0745a0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
@@ -81,6 +82,11 @@
}
@Override
+ public void onPipMenuActionsChanged(ParceledListSlice actions) {
+ // no-op.
+ }
+
+ @Override
public void onMoveToFullscreen() {
dismissPipNotification();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
index eaf715f..5b3ec08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
@@ -35,9 +35,7 @@
public CellTileView(Context context) {
super(context);
mSignalDrawable = new SignalDrawable(mContext);
- float dark = Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000
- ? 1 : 0;
- mSignalDrawable.setDarkIntensity(dark);
+ mSignalDrawable.setDarkIntensity(isDark(mContext));
mSignalDrawable.setIntrinsicSize(context.getResources().getDimensionPixelSize(
R.dimen.qs_tile_icon_size));
}
@@ -50,6 +48,10 @@
}
}
+ private static int isDark(Context context) {
+ return Utils.getColorAttr(context, android.R.attr.colorForeground) == 0xff000000 ? 1 : 0;
+ }
+
public static class SignalIcon extends Icon {
private final int mState;
@@ -64,7 +66,11 @@
@Override
public Drawable getDrawable(Context context) {
- return null;
+ //TODO: Not the optimal solution to create this drawable
+ SignalDrawable d = new SignalDrawable(context);
+ d.setDarkIntensity(isDark(context));
+ d.setLevel(getState());
+ return d;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index f124e86..697db5f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -73,6 +73,7 @@
private int mOpenY;
private boolean mAnimatingOpen;
private boolean mSwitchState;
+ private View mFooter;
public QSDetail(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -116,9 +117,10 @@
mDetailDoneButton.setOnClickListener(doneListener);
}
- public void setQsPanel(QSPanel panel, QuickStatusBarHeader header) {
+ public void setQsPanel(QSPanel panel, QuickStatusBarHeader header, View footer) {
mQsPanel = panel;
mHeader = header;
+ mFooter = footer;
mHeader.setCallback(mQsPanelCallback);
mQsPanel.setCallback(mQsPanelCallback);
}
@@ -214,6 +216,7 @@
mDetailAdapter = null;
listener = mTeardownDetailWhenDone;
mHeader.setVisibility(View.VISIBLE);
+ mFooter.setVisibility(View.VISIBLE);
mQsPanel.setGridContentVisibility(true);
mQsPanelCallback.onScanStateChanged(false);
}
@@ -345,6 +348,7 @@
if (mDetailAdapter != null) {
mQsPanel.setGridContentVisibility(false);
mHeader.setVisibility(View.INVISIBLE);
+ mFooter.setVisibility(View.INVISIBLE);
}
mAnimatingOpen = false;
checkPendingAnimations();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index c9c3a7f..aa01c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -82,7 +82,7 @@
mContainer = view.findViewById(id.quick_settings_container);
mGutterHeight = getContext().getResources().getDimensionPixelSize(R.dimen.qs_gutter_height);
- mQSDetail.setQsPanel(mQSPanel, mHeader);
+ mQSDetail.setQsPanel(mQSPanel, mHeader, mFooter);
// If the quick settings row is not shown, then there is no need for the animation from
// the row to the full QS panel.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9b20a7a..79fb5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -179,12 +179,6 @@
@Override
public void onBluetoothDevicesChanged() {
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- mDetailAdapter.updateItems();
- }
- });
refreshState();
if (isShowingDetail()) {
mDetailAdapter.updateItems();
@@ -198,6 +192,9 @@
}
protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
+ // We probably won't ever have space in the UI for more than 20 devices, so don't
+ // get info for them.
+ private static final int MAX_DEVICES = 20;
private QSDetailItems mItems;
@Override
@@ -260,13 +257,14 @@
final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
int connectedDevices = 0;
+ int count = 0;
for (CachedBluetoothDevice device : devices) {
- if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
+ if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
item.icon = R.drawable.ic_qs_bluetooth_on;
item.line1 = device.getName();
item.tag = device;
- int state = device.getMaxConnectionState();
+ int state = mController.getMaxConnectionState(device);
if (state == BluetoothProfile.STATE_CONNECTED) {
item.icon = R.drawable.ic_qs_bluetooth_connected;
item.line2 = mContext.getString(R.string.quick_settings_connected);
@@ -280,6 +278,9 @@
} else {
items.add(item);
}
+ if (++count == MAX_DEVICES) {
+ break;
+ }
}
}
mItems.setItems(items.toArray(new Item[items.size()]));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index bd59fb0..82e6a35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -106,6 +106,7 @@
mPanel.setPanelScrimMinFraction((float) expandedHeight
/ mPanel.getMaxPanelHeight());
mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight);
+ mPanel.startExpandingFromPeek();
// This call needs to be after the expansion start otherwise we will get a
// flicker of one frame as it's not expanded yet.
mHeadsUpManager.unpinAll();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index b1d82b1..0b46c21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -406,6 +406,10 @@
return Math.abs(yDiff) >= Math.abs(xDiff);
}
+ protected void startExpandingFromPeek() {
+ mStatusBar.handlePeekToExpandTransistion();
+ }
+
protected void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
mInitialOffsetOnTouch = expandedHeight;
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 cfbdc92..f58fe82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -648,7 +648,7 @@
// Tracks notifications currently visible in mNotificationStackScroller and
// emits visibility events via NoMan on changes.
- private final Runnable mVisibilityReporter = new Runnable() {
+ protected final Runnable mVisibilityReporter = new Runnable() {
private final ArraySet<NotificationVisibility> mTmpNewlyVisibleNotifications =
new ArraySet<>();
private final ArraySet<NotificationVisibility> mTmpCurrentlyVisibleNotifications =
@@ -3755,6 +3755,17 @@
}
}
+ void handlePeekToExpandTransistion() {
+ try {
+ // consider the transition from peek to expanded to be a panel open,
+ // but not one that clears notification effects.
+ int notificationLoad = mNotificationData.getActiveNotifications().size();
+ mBarService.onPanelRevealed(false, notificationLoad);
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ }
+
/**
* The LEDs are turned off when the notification panel is shown, even just a little bit.
* See also StatusBar.setPanelExpanded for another place where we attempt to do this.
@@ -3770,8 +3781,6 @@
int notificationLoad = mNotificationData.getActiveNotifications().size();
if (pinnedHeadsUp && isPanelFullyCollapsed()) {
notificationLoad = 1;
- } else {
- mMetricsLogger.histogram("note_load", notificationLoad);
}
mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
} else {
@@ -5041,6 +5050,12 @@
mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
|| mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+ // When in wake-and-unlock we may not have received a change to mState
+ // but we still should not be dozing, manually set to false.
+ if (mFingerprintUnlockController.getMode() ==
+ FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
+ mDozing = false;
+ }
mStatusBarWindowManager.setDozing(mDozing);
updateDozingState();
Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index df30e20..9daa199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -37,6 +37,9 @@
void disconnect(CachedBluetoothDevice device);
boolean canConfigBluetooth();
+ int getMaxConnectionState(CachedBluetoothDevice device);
+ int getBondState(CachedBluetoothDevice device);
+
public interface Callback {
void onBluetoothStateChange(boolean enabled);
void onBluetoothDevicesChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 36d24b3..dc4b115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -18,6 +18,8 @@
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
@@ -33,8 +35,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.WeakHashMap;
public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
CachedBluetoothDevice.Callback {
@@ -44,18 +48,22 @@
private final LocalBluetoothManager mLocalBluetoothManager;
private final UserManager mUserManager;
private final int mCurrentUser;
+ private final WeakHashMap<CachedBluetoothDevice, ActuallyCachedState> mCachedState =
+ new WeakHashMap<>();
+ private final Handler mBgHandler;
private boolean mEnabled;
private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
private CachedBluetoothDevice mLastDevice;
- private final H mHandler = new H();
+ private final H mHandler = new H(Looper.getMainLooper());
private int mState;
public BluetoothControllerImpl(Context context, Looper bgLooper) {
mLocalBluetoothManager = Dependency.get(LocalBluetoothManager.class);
+ mBgHandler = new Handler(bgLooper);
if (mLocalBluetoothManager != null) {
- mLocalBluetoothManager.getEventManager().setReceiverHandler(new Handler(bgLooper));
+ mLocalBluetoothManager.getEventManager().setReceiverHandler(mBgHandler);
mLocalBluetoothManager.getEventManager().registerCallback(this);
onBluetoothStateChanged(
mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
@@ -106,6 +114,16 @@
}
@Override
+ public int getBondState(CachedBluetoothDevice device) {
+ return getCachedState(device).mBondState;
+ }
+
+ @Override
+ public int getMaxConnectionState(CachedBluetoothDevice device) {
+ return getCachedState(device).mMaxConnectionState;
+ }
+
+ @Override
public void addCallback(Callback cb) {
mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
@@ -225,12 +243,14 @@
@Override
public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+ mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@Override
public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+ mCachedState.remove(cachedDevice);
updateConnected();
mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
}
@@ -243,11 +263,44 @@
@Override
public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ mCachedState.remove(cachedDevice);
mLastDevice = cachedDevice;
updateConnected();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
+ private ActuallyCachedState getCachedState(CachedBluetoothDevice device) {
+ ActuallyCachedState state = mCachedState.get(device);
+ if (state == null) {
+ state = new ActuallyCachedState(device, mHandler);
+ mBgHandler.post(state);
+ mCachedState.put(device, state);
+ return state;
+ }
+ return state;
+ }
+
+ private static class ActuallyCachedState implements Runnable {
+
+ private final WeakReference<CachedBluetoothDevice> mDevice;
+ private final Handler mUiHandler;
+ private int mBondState = BluetoothDevice.BOND_NONE;
+ private int mMaxConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+
+ private ActuallyCachedState(CachedBluetoothDevice device, Handler uiHandler) {
+ mDevice = new WeakReference<>(device);
+ mUiHandler = uiHandler;
+ }
+
+ @Override
+ public void run() {
+ mBondState = mDevice.get().getBondState();
+ mMaxConnectionState = mDevice.get().getMaxConnectionState();
+ mUiHandler.removeMessages(H.MSG_PAIRED_DEVICES_CHANGED);
+ mUiHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
+ }
+ }
+
private final class H extends Handler {
private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>();
@@ -256,6 +309,10 @@
private static final int MSG_ADD_CALLBACK = 3;
private static final int MSG_REMOVE_CALLBACK = 4;
+ public H(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index 7cbe985..d270de8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -23,6 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import org.junit.After;
+import org.junit.Ignore;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -37,8 +39,6 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
-import org.junit.After;
-import org.junit.Ignore;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,7 +65,7 @@
mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
mQsPanel = mock(QSPanel.class);
mQuickHeader = mock(QuickStatusBarHeader.class);
- mQsDetail.setQsPanel(mQsPanel, mQuickHeader);
+ mQsDetail.setQsPanel(mQsPanel, mQuickHeader, mock(View.class));
mMockDetailAdapter = mock(DetailAdapter.class);
when(mMockDetailAdapter.createDetailView(any(), any(), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index db6647c..0e3ea7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -20,37 +20,54 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
import android.app.Notification;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IPowerManager;
-import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
import android.support.test.metricshelper.MetricsAsserts;
import android.support.test.runner.AndroidJUnit4;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.MessageHandler;
+import android.testing.TestableLooper.RunWithLooper;
import android.util.DisplayMetrics;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -58,21 +75,26 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
public class StatusBarTest extends SysuiTestCase {
StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
UnlockMethodCache mUnlockMethodCache;
KeyguardIndicationController mKeyguardIndicationController;
NotificationStackScrollLayout mStackScroller;
- StatusBar mStatusBar;
+ TestableStatusBar mStatusBar;
FakeMetricsLogger mMetricsLogger;
HeadsUpManager mHeadsUpManager;
NotificationData mNotificationData;
PowerManager mPowerManager;
SystemServicesProxy mSystemServicesProxy;
NotificationPanelView mNotificationPanelView;
+ IStatusBarService mBarService;
+ ArrayList<Entry> mNotificationList;
private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
@Before
@@ -86,18 +108,20 @@
mNotificationData = mock(NotificationData.class);
mSystemServicesProxy = mock(SystemServicesProxy.class);
mNotificationPanelView = mock(NotificationPanelView.class);
+ mNotificationList = mock(ArrayList.class);
IPowerManager powerManagerService = mock(IPowerManager.class);
HandlerThread handlerThread = new HandlerThread("TestThread");
handlerThread.start();
mPowerManager = new PowerManager(mContext, powerManagerService,
new Handler(handlerThread.getLooper()));
when(powerManagerService.isInteractive()).thenReturn(true);
+ mBarService = mock(IStatusBarService.class);
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
- mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView);
-
+ mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
+ mBarService);
doAnswer(invocation -> {
OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
onDismissAction.onDismiss();
@@ -111,6 +135,15 @@
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
when(mStackScroller.getActivatedChild()).thenReturn(null);
+ TestableLooper.get(this).setMessageHandler(new MessageHandler() {
+ @Override
+ public boolean onMessageHandled(Message m) {
+ if (m.getCallback() == mStatusBar.mVisibilityReporter) {
+ return false;
+ }
+ return true;
+ }
+ });
}
@Test
@@ -284,11 +317,80 @@
assertFalse(mStatusBar.shouldPeek(entry, sbn));
}
+ @Test
+ public void testLogHidden() {
+ try {
+ mStatusBar.handleVisibleToUserChanged(false);
+ verify(mBarService, times(1)).onPanelHidden();
+ verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
+ } catch (RemoteException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testPanelOpenForPeek() {
+ when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
+ when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+ when(mNotificationList.size()).thenReturn(5);
+ when(mNotificationPanelView.isFullyCollapsed()).thenReturn(true);
+ mStatusBar.setBarStateForTest(StatusBarState.SHADE);
+
+ try {
+ mStatusBar.handleVisibleToUserChanged(true);
+
+ verify(mBarService, never()).onPanelHidden();
+ verify(mBarService, times(1)).onPanelRevealed(false, 1);
+ } catch (RemoteException e) {
+ fail();
+ }
+ TestableLooper.get(this).processAllMessages();
+ }
+
+ @Test
+ public void testPanelOpenAndClear() {
+ when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
+ when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+ when(mNotificationList.size()).thenReturn(5);
+ when(mNotificationPanelView.isFullyCollapsed()).thenReturn(false);
+ mStatusBar.setBarStateForTest(StatusBarState.SHADE);
+
+ try {
+ mStatusBar.handleVisibleToUserChanged(true);
+
+ verify(mBarService, never()).onPanelHidden();
+ verify(mBarService, times(1)).onPanelRevealed(true, 5);
+ } catch (RemoteException e) {
+ fail();
+ }
+ TestableLooper.get(this).processAllMessages();
+ }
+
+ @Test
+ public void testPanelOpenAndNoClear() {
+ when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
+ when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList);
+ when(mNotificationList.size()).thenReturn(5);
+ when(mNotificationPanelView.isFullyCollapsed()).thenReturn(false);
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+
+ try {
+ mStatusBar.handleVisibleToUserChanged(true);
+
+ verify(mBarService, never()).onPanelHidden();
+ verify(mBarService, times(1)).onPanelRevealed(false, 5);
+ } catch (RemoteException e) {
+ fail();
+ }
+ TestableLooper.get(this).processAllMessages();
+ }
+
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
- PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView) {
+ PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
+ IStatusBarService barService) {
mStatusBarKeyguardViewManager = man;
mUnlockMethodCache = unlock;
mKeyguardIndicationController = key;
@@ -299,11 +401,11 @@
mPowerManager = pm;
mSystemServicesProxy = ssp;
mNotificationPanel = panelView;
+ mBarService = barService;
}
- @Override
- protected H createHandler() {
- return null;
+ public void setBarStateForTest(int state) {
+ mState = state;
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 2eb9560..4cc8bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -14,16 +14,21 @@
package com.android.systemui.statusbar.policy;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.os.Looper;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.Log;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -80,4 +85,56 @@
BluetoothAdapter.STATE_DISCONNECTED);
assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
}
+
+ @Test
+ public void testDefaultConnectionState() {
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
+ assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ mBluetoothControllerImpl.getMaxConnectionState(device));
+ }
+
+ @Test
+ public void testAsyncBondState() throws Exception {
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ when(device.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+ mBluetoothControllerImpl.addCallback(callback);
+
+ // Grab the main looper, we'll need it later.
+ TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+
+ // Trigger the state getting.
+ assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
+
+ mTestableLooper.processMessages(1);
+ mainLooper.processAllMessages();
+
+ assertEquals(BluetoothDevice.BOND_BONDED, mBluetoothControllerImpl.getBondState(device));
+ verify(callback).onBluetoothDevicesChanged();
+ mainLooper.destroy();
+ }
+
+ @Test
+ public void testAsyncConnectionState() throws Exception {
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+ BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+ mBluetoothControllerImpl.addCallback(callback);
+
+ // Grab the main looper, we'll need it later.
+ TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+
+ // Trigger the state getting.
+ assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ mBluetoothControllerImpl.getMaxConnectionState(device));
+
+ mTestableLooper.processMessages(1);
+ mainLooper.processAllMessages();
+
+ assertEquals(BluetoothProfile.STATE_CONNECTED,
+ mBluetoothControllerImpl.getMaxConnectionState(device));
+ verify(callback).onBluetoothDevicesChanged();
+ mainLooper.destroy();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index 0ba0319..9ec096a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -83,4 +83,14 @@
public boolean canConfigBluetooth() {
return false;
}
+
+ @Override
+ public int getMaxConnectionState(CachedBluetoothDevice device) {
+ return 0;
+ }
+
+ @Override
+ public int getBondState(CachedBluetoothDevice device) {
+ return 0;
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index fa78f10..7ddc1a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -243,6 +243,8 @@
private WindowsForAccessibilityCallback mWindowsForAccessibilityCallback;
+ private boolean mIsAccessibilityButtonShown;
+
private UserState getCurrentUserStateLocked() {
return getUserStateLocked(mCurrentUserId);
}
@@ -881,21 +883,21 @@
}
/**
- * Invoked remotely over AIDL by SysUi when the availability of the accessibility
+ * Invoked remotely over AIDL by SysUi when the visibility of the accessibility
* button within the system's navigation area has changed.
*
- * @param available {@code true} if the accessibility button is available to the
+ * @param shown {@code true} if the accessibility button is shown to the
* user, {@code false} otherwise
*/
@Override
- public void notifyAccessibilityButtonAvailabilityChanged(boolean available) {
+ public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller does not hold permission "
+ android.Manifest.permission.STATUS_BAR_SERVICE);
}
synchronized (mLock) {
- notifyAccessibilityButtonAvailabilityChangedLocked(available);
+ notifyAccessibilityButtonVisibilityChangedLocked(shown);
}
}
@@ -1200,13 +1202,14 @@
}
}
- private void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+ private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
final UserState state = getCurrentUserStateLocked();
- state.mIsAccessibilityButtonAvailable = available;
+ mIsAccessibilityButtonShown = available;
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final Service service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
- service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+ service.notifyAccessibilityButtonAvailabilityChangedLocked(
+ service.isAccessibilityButtonAvailableLocked(state));
}
}
}
@@ -1733,7 +1736,7 @@
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
updateRelevantEventsLocked(userState);
- updateAccessibilityButtonTargets(userState);
+ updateAccessibilityButtonTargetsLocked(userState);
}
private void updateAccessibilityFocusBehaviorLocked(UserState userState) {
@@ -2183,18 +2186,12 @@
}
}
- private void updateAccessibilityButtonTargets(UserState userState) {
- final List<Service> services;
- synchronized (mLock) {
- services = userState.mBoundServices;
- int numServices = services.size();
- for (int i = 0; i < numServices; i++) {
- final Service service = services.get(i);
- if (service.mRequestAccessibilityButton) {
- boolean available = service.mComponentName.equals(
- userState.mServiceAssignedToAccessibilityButton);
- service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
- }
+ private void updateAccessibilityButtonTargetsLocked(UserState userState) {
+ for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = userState.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton) {
+ service.notifyAccessibilityButtonAvailabilityChangedLocked(
+ service.isAccessibilityButtonAvailableLocked(userState));
}
}
}
@@ -2501,7 +2498,7 @@
case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: {
showAccessibilityButtonTargetSelection();
- }
+ } break;
}
}
@@ -2656,6 +2653,10 @@
boolean mRequestAccessibilityButton;
+ boolean mReceivedAccessibilityButtonCallbackSinceBind;
+
+ boolean mLastAccessibilityButtonCallbackState;
+
int mFetchFlags;
long mNotificationTimeout;
@@ -3596,9 +3597,8 @@
return false;
}
userState = getCurrentUserStateLocked();
+ return isAccessibilityButtonAvailableLocked(userState);
}
-
- return mRequestAccessibilityButton && userState.mIsAccessibilityButtonAvailable;
}
@Override
@@ -3656,6 +3656,7 @@
mService = null;
}
mServiceInterface = null;
+ mReceivedAccessibilityButtonCallbackSinceBind = false;
}
public boolean isConnectedLocked() {
@@ -3728,6 +3729,48 @@
}
}
+ private boolean isAccessibilityButtonAvailableLocked(UserState userState) {
+ // If the service does not request the accessibility button, it isn't available
+ if (!mRequestAccessibilityButton) {
+ return false;
+ }
+
+ // If the accessibility button isn't currently shown, it cannot be available to services
+ if (!mIsAccessibilityButtonShown) {
+ return false;
+ }
+
+ // If magnification is on and assigned to the accessibility button, services cannot be
+ if (userState.mIsNavBarMagnificationEnabled
+ && userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ return false;
+ }
+
+ int requestingServices = 0;
+ for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = userState.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton) {
+ requestingServices++;
+ }
+ }
+
+ if (requestingServices == 1) {
+ // If only a single service is requesting, it must be this service, and the
+ // accessibility button is available to it
+ return true;
+ } else {
+ // With more than one active service, we derive the target from the user's settings
+ if (userState.mServiceAssignedToAccessibilityButton == null) {
+ // If the user has not made an assignment, we treat the button as available to
+ // all services until the user interacts with the button to make an assignment
+ return true;
+ } else {
+ // If an assignment was made, it defines availability
+ return mComponentName.equals(userState.mServiceAssignedToAccessibilityButton);
+ }
+ }
+ }
+
/**
* Notifies an accessibility service client for a scheduled event given the event type.
*
@@ -3875,6 +3918,13 @@
}
private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
+ // Only notify the service if it's not been notified or the state has changed
+ if (mReceivedAccessibilityButtonCallbackSinceBind
+ && (mLastAccessibilityButtonCallbackState == available)) {
+ return;
+ }
+ mReceivedAccessibilityButtonCallbackSinceBind = true;
+ mLastAccessibilityButtonCallbackState = available;
final IAccessibilityServiceClient listener;
synchronized (mLock) {
listener = mServiceInterface;
@@ -4874,7 +4924,6 @@
public int mSoftKeyboardShowMode = 0;
- public boolean mIsAccessibilityButtonAvailable;
public boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
public ComponentName mServiceAssignedToAccessibilityButton;
@@ -4954,9 +5003,6 @@
mIsNavBarMagnificationAssignedToAccessibilityButton = false;
mIsAutoclickEnabled = false;
mSoftKeyboardShowMode = 0;
-
- // Clear state tracked from system UI
- mIsAccessibilityButtonAvailable = false;
}
public void destroyUiAutomationService() {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 073d7b2..3ae0511 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -255,7 +255,9 @@
final ViewNode node = nodes[i];
if (node == null) {
- Slog.w(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id);
+ if (sVerbose) {
+ Slog.v(TAG, "fillStructureWithAllowedValues(): no node for " + viewState.id);
+ }
continue;
}
@@ -862,11 +864,9 @@
final int numContexts = mContexts.size();
for (int i = 0; i < numContexts; i++) {
final FillContext context = mContexts.get(i);
- // TODO: create a function that gets just one node so it doesn't create an array
- // unnecessarily
- final ViewNode[] nodes = context.findViewNodesByAutofillIds(id);
- if (nodes != null) {
- AutofillValue candidate = nodes[0].getAutofillValue();
+ final ViewNode node = context.findViewNodeByAutofillId(id);
+ if (node != null) {
+ final AutofillValue candidate = node.getAutofillValue();
if (sDebug) {
Slog.d(TAG, "getValueFromContexts(" + id + ") at " + i + ": " + candidate);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 73f1705..4810f4f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -47,6 +47,7 @@
import android.os.IDeviceIdleController;
import android.os.IInterface;
import android.os.Parcel;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -345,7 +346,7 @@
}
private static boolean isCallerSystem() {
- return getCallingUserId() == UserHandle.USER_SYSTEM;
+ return Binder.getCallingUid() == Process.SYSTEM_UID;
}
private ServiceConnection createServiceConnection(
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c417484..756e274 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -81,6 +81,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.EventLog;
+import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -175,6 +176,9 @@
long mStartVisibleTime;
long mEndTime;
int mNumActive;
+
+ // Temp output of foregroundAppShownEnoughLocked
+ long mHideTime;
}
/**
@@ -622,19 +626,19 @@
!= ActivityManager.APP_START_MODE_NORMAL) {
if (stopping == null) {
stopping = new ArrayList<>();
- String compName = service.name.flattenToShortString();
- EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
- StringBuilder sb = new StringBuilder(64);
- sb.append("Stopping service due to app idle: ");
- UserHandle.formatUid(sb, service.appInfo.uid);
- sb.append(" ");
- TimeUtils.formatDuration(service.createTime
- - SystemClock.elapsedRealtime(), sb);
- sb.append(" ");
- sb.append(compName);
- Slog.w(TAG, sb.toString());
- stopping.add(service);
}
+ String compName = service.name.flattenToShortString();
+ EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("Stopping service due to app idle: ");
+ UserHandle.formatUid(sb, service.appInfo.uid);
+ sb.append(" ");
+ TimeUtils.formatDuration(service.createTime
+ - SystemClock.elapsedRealtime(), sb);
+ sb.append(" ");
+ sb.append(compName);
+ Slog.w(TAG, sb.toString());
+ stopping.add(service);
}
}
}
@@ -736,50 +740,90 @@
}
}
+ boolean foregroundAppShownEnoughLocked(ActiveForegroundApp aa, long nowElapsed) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Shown enough: pkg=" + aa.mPackageName + ", uid="
+ + aa.mUid);
+ boolean canRemove = false;
+ aa.mHideTime = Long.MAX_VALUE;
+ if (aa.mShownWhileTop) {
+ // If the app was ever at the top of the screen while the foreground
+ // service was running, then we can always just immediately remove it.
+ canRemove = true;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown while on top");
+ } else if (mScreenOn || aa.mShownWhileScreenOn) {
+ final long minTime = aa.mStartVisibleTime
+ + (aa.mStartTime != aa.mStartVisibleTime
+ ? mAm.mConstants.FGSERVICE_SCREEN_ON_AFTER_TIME
+ : mAm.mConstants.FGSERVICE_MIN_SHOWN_TIME);
+ if (nowElapsed >= minTime) {
+ // If shown while the screen is on, and it has been shown for
+ // at least the minimum show time, then we can now remove it.
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown long enough with screen on");
+ canRemove = true;
+ } else {
+ // This is when we will be okay to stop telling the user.
+ long reportTime = nowElapsed + mAm.mConstants.FGSERVICE_MIN_REPORT_TIME;
+ aa.mHideTime = reportTime > minTime ? reportTime : minTime;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed)
+ + " with screen on");
+ }
+ } else {
+ final long minTime = aa.mEndTime
+ + mAm.mConstants.FGSERVICE_SCREEN_ON_BEFORE_TIME;
+ if (nowElapsed >= minTime) {
+ // If the foreground service has only run while the screen is
+ // off, but it has been gone now for long enough that we won't
+ // care to tell the user about it when the screen comes back on,
+ // then we can remove it now.
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - gone long enough with screen off");
+ canRemove = true;
+ } else {
+ // This is when we won't care about this old fg service.
+ aa.mHideTime = minTime;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed)
+ + " with screen off");
+ }
+ }
+ return canRemove;
+ }
+
void updateForegroundApps(ServiceMap smap) {
// This is called from the handler without the lock held.
ArrayList<ActiveForegroundApp> active = null;
synchronized (mAm) {
final long now = SystemClock.elapsedRealtime();
- final long nowPlusMin = now + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME;
long nextUpdateTime = Long.MAX_VALUE;
if (smap != null) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Updating foreground apps for user "
+ + smap.mUserId);
for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) {
ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i);
- if (aa.mEndTime != 0 && (mScreenOn || aa.mShownWhileScreenOn)) {
- if (!aa.mShownWhileTop && aa.mEndTime < (aa.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) {
- // Check to see if this should still be displayed... we continue
- // until it has been shown for at least the timeout duration.
- if (nowPlusMin >= aa.mStartVisibleTime) {
- // All over!
- smap.mActiveForegroundApps.removeAt(i);
- smap.mActiveForegroundAppsChanged = true;
- continue;
- } else {
- long hideTime = aa.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME;
- if (hideTime < nextUpdateTime) {
- nextUpdateTime = hideTime;
- }
- }
- } else {
+ if (aa.mEndTime != 0) {
+ boolean canRemove = foregroundAppShownEnoughLocked(aa, now);
+ if (canRemove) {
// This was up for longer than the timeout, so just remove immediately.
smap.mActiveForegroundApps.removeAt(i);
smap.mActiveForegroundAppsChanged = true;
continue;
}
+ if (aa.mHideTime < nextUpdateTime) {
+ nextUpdateTime = aa.mHideTime;
+ }
}
if (!aa.mAppOnTop) {
if (active == null) {
active = new ArrayList<>();
}
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Adding active: pkg="
+ + aa.mPackageName + ", uid=" + aa.mUid);
active.add(aa);
}
}
smap.removeMessages(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
if (nextUpdateTime < Long.MAX_VALUE) {
- Message msg = smap.obtainMessage();
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Next update time in: "
+ + (nextUpdateTime-now));
+ Message msg = smap.obtainMessage(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
smap.sendMessageAtTime(msg, nextUpdateTime
+ SystemClock.uptimeMillis() - SystemClock.elapsedRealtime());
}
@@ -882,15 +926,14 @@
active.mNumActive--;
if (active.mNumActive <= 0) {
active.mEndTime = SystemClock.elapsedRealtime();
- if (active.mShownWhileTop || active.mEndTime >= (active.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Ended running of service");
+ if (foregroundAppShownEnoughLocked(active, active.mEndTime)) {
// Have been active for long enough that we will remove it immediately.
smap.mActiveForegroundApps.remove(r.packageName);
smap.mActiveForegroundAppsChanged = true;
requestUpdateActiveForegroundAppsLocked(smap, 0);
- } else {
- requestUpdateActiveForegroundAppsLocked(smap, active.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME);
+ } else if (active.mHideTime < Long.MAX_VALUE){
+ requestUpdateActiveForegroundAppsLocked(smap, active.mHideTime);
}
}
}
@@ -904,26 +947,44 @@
// services that were started while the screen was off.
if (screenOn) {
final long nowElapsed = SystemClock.elapsedRealtime();
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Screen turned on");
for (int i = mServiceMap.size()-1; i >= 0; i--) {
ServiceMap smap = mServiceMap.valueAt(i);
+ long nextUpdateTime = Long.MAX_VALUE;
boolean changed = false;
for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) {
ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j);
- if (!active.mShownWhileScreenOn) {
- changed = true;
- active.mShownWhileScreenOn = mScreenOn;
- active.mStartVisibleTime = nowElapsed;
- if (active.mEndTime != 0) {
- active.mEndTime = nowElapsed;
+ if (active.mEndTime == 0) {
+ if (!active.mShownWhileScreenOn) {
+ active.mShownWhileScreenOn = true;
+ active.mStartVisibleTime = nowElapsed;
+ }
+ } else {
+ if (!active.mShownWhileScreenOn
+ && active.mStartVisibleTime == active.mStartTime) {
+ // If this was never shown while the screen was on, then we will
+ // count the time it started being visible as now, to tell the user
+ // about it now that they have a screen to look at.
+ active.mEndTime = active.mStartVisibleTime = nowElapsed;
+ }
+ if (foregroundAppShownEnoughLocked(active, nowElapsed)) {
+ // Have been active for long enough that we will remove it
+ // immediately.
+ smap.mActiveForegroundApps.remove(active.mPackageName);
+ smap.mActiveForegroundAppsChanged = true;
+ changed = true;
+ } else {
+ if (active.mHideTime < nextUpdateTime) {
+ nextUpdateTime = active.mHideTime;
+ }
}
}
}
if (changed) {
- requestUpdateActiveForegroundAppsLocked(smap,
- nowElapsed + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME);
- } else if (smap.mActiveForegroundApps.size() > 0) {
- // Just being paranoid.
+ // Need to immediately update.
requestUpdateActiveForegroundAppsLocked(smap, 0);
+ } else if (nextUpdateTime < Long.MAX_VALUE) {
+ requestUpdateActiveForegroundAppsLocked(smap, nextUpdateTime);
}
}
}
@@ -2318,7 +2379,7 @@
return true;
}
- // Is someone still bound to us keepign us running?
+ // Is someone still bound to us keeping us running?
if (!knowConn) {
hasConn = r.hasAutoCreateConnections();
}
@@ -3741,6 +3802,17 @@
pw.println();
}
}
+ if (smap.hasMessagesOrCallbacks()) {
+ if (needSep) {
+ pw.println();
+ }
+ printedAnything = true;
+ needSep = true;
+ pw.print(" Handler - user ");
+ pw.print(user);
+ pw.println(":");
+ smap.dumpMine(new PrintWriterPrinter(pw), " ");
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5749f31..6c3fe91 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -35,8 +35,14 @@
// Key names stored in the settings value.
private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
- private static final String KEY_FOREGROUND_SERVICE_UI_MIN_TIME
- = "foreground_service_ui_min_time";
+ private static final String KEY_FGSERVICE_MIN_SHOWN_TIME
+ = "fgservice_min_shown_time";
+ private static final String KEY_FGSERVICE_MIN_REPORT_TIME
+ = "fgservice_min_report_time";
+ private static final String KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME
+ = "fgservice_screen_on_before_time";
+ private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
+ = "fgservice_screen_on_after_time";
private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
private static final String KEY_GC_TIMEOUT = "gc_timeout";
private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -58,7 +64,10 @@
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
- private static final long DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME = 30*1000;
+ private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
+ private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
+ private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
+ private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
private static final long DEFAULT_GC_TIMEOUT = 5*1000;
private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -85,8 +94,26 @@
// before we start restricting what it can do.
public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;
- // The minimum time a foreground service will be shown as running in the notification UI.
- public long FOREGROUND_SERVICE_UI_MIN_TIME = DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME;
+ // The minimum time we allow a foreground service to run with a notification and the
+ // screen on without otherwise telling the user about it. (If it runs for less than this,
+ // it will still be reported to the user as a running app for at least this amount of time.)
+ public long FGSERVICE_MIN_SHOWN_TIME = DEFAULT_FGSERVICE_MIN_SHOWN_TIME;
+
+ // If a foreground service is shown for less than FGSERVICE_MIN_SHOWN_TIME, we will display
+ // the background app running notification about it for at least this amount of time (if it
+ // is larger than the remaining shown time).
+ public long FGSERVICE_MIN_REPORT_TIME = DEFAULT_FGSERVICE_MIN_REPORT_TIME;
+
+ // The minimum amount of time the foreground service needs to have remain being shown
+ // before the screen goes on for us to consider it not worth showing to the user. That is
+ // if an app has a foreground service that stops itself this amount of time or more before
+ // the user turns on the screen, we will just let it go without the user being told about it.
+ public long FGSERVICE_SCREEN_ON_BEFORE_TIME = DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME;
+
+ // The minimum amount of time a foreground service should remain reported to the user if
+ // it is stopped when the screen turns on. This is the time from when the screen turns
+ // on until we will stop reporting it.
+ public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
// How long we will retain processes hosting content providers in the "last activity"
// state before allowing them to drop down to the regular cached LRU list. This is
@@ -225,8 +252,14 @@
DEFAULT_MAX_CACHED_PROCESSES);
BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,
DEFAULT_BACKGROUND_SETTLE_TIME);
- FOREGROUND_SERVICE_UI_MIN_TIME = mParser.getLong(KEY_FOREGROUND_SERVICE_UI_MIN_TIME,
- DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME);
+ FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME,
+ DEFAULT_FGSERVICE_MIN_SHOWN_TIME);
+ FGSERVICE_MIN_REPORT_TIME = mParser.getLong(KEY_FGSERVICE_MIN_REPORT_TIME,
+ DEFAULT_FGSERVICE_MIN_REPORT_TIME);
+ FGSERVICE_SCREEN_ON_BEFORE_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME,
+ DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
+ FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
+ DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -284,8 +317,14 @@
pw.println(MAX_CACHED_PROCESSES);
pw.print(" "); pw.print(KEY_BACKGROUND_SETTLE_TIME); pw.print("=");
pw.println(BACKGROUND_SETTLE_TIME);
- pw.print(" "); pw.print(KEY_FOREGROUND_SERVICE_UI_MIN_TIME); pw.print("=");
- pw.println(FOREGROUND_SERVICE_UI_MIN_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_MIN_SHOWN_TIME); pw.print("=");
+ pw.println(FGSERVICE_MIN_SHOWN_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_MIN_REPORT_TIME); pw.print("=");
+ pw.println(FGSERVICE_MIN_REPORT_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME); pw.print("=");
+ pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
+ pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
pw.println(CONTENT_PROVIDER_RETAIN_TIME);
pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index ff5efde..f440100 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -79,6 +79,7 @@
static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
+ static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false;
static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
static final boolean DEBUG_STACK = DEBUG_ALL || false;
static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 30df978..14be25b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1808,7 +1808,7 @@
}
AppErrorResult res = (AppErrorResult) data.get("result");
if (mShowDialogs && !mSleeping && !mShuttingDown) {
- Dialog d = new StrictModeViolationDialog(mContext,
+ Dialog d = new StrictModeViolationDialog(mUiContext,
ActivityManagerService.this, res, proc);
d.show();
proc.crashDialog = d;
@@ -10254,11 +10254,11 @@
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToFront: moving taskId=" + taskId);
synchronized(this) {
- moveTaskToFrontLocked(taskId, flags, bOptions);
+ moveTaskToFrontLocked(taskId, flags, bOptions, false /* fromRecents */);
}
}
- void moveTaskToFrontLocked(int taskId, int flags, Bundle bOptions) {
+ void moveTaskToFrontLocked(int taskId, int flags, Bundle bOptions, boolean fromRecents) {
ActivityOptions options = ActivityOptions.fromBundle(bOptions);
if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
@@ -10291,7 +10291,7 @@
// We are reshowing a task, use a starting window to hide the initial draw delay
// so the transition can start earlier.
topActivity.showStartingWindow(null /* prev */, false /* newTask */,
- true /* taskSwitch */);
+ true /* taskSwitch */, fromRecents);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -23752,9 +23752,10 @@
}
@Override
- public void notifyAppTransitionStarting(SparseIntArray reasons) {
+ public void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp) {
synchronized (ActivityManagerService.this) {
- mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(reasons);
+ mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(
+ reasons, timestamp);
}
}
@@ -24014,6 +24015,13 @@
if (reason != null) {
pw.println(" Reason: " + reason);
}
+ pw.println(" mLastHomeActivityStartResult: "
+ + mActivityStarter.mLastHomeActivityStartResult);
+ final ActivityRecord r = mActivityStarter.mLastHomeActivityStartRecord[0];
+ if (r != null) {
+ pw.println(" mLastHomeActivityStartRecord:");
+ r.dump(pw, " ");
+ }
pw.println();
dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */,
true /* dumpAll */, false /* dumpClient */, null /* dumpPackage */,
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index bf7b663..98815d7 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -230,12 +230,12 @@
/**
* Notifies the tracker that all windows of the app have been drawn.
*/
- void notifyWindowsDrawn(int stackId) {
+ void notifyWindowsDrawn(int stackId, long timestamp) {
final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
if (info == null || info.loggedWindowsDrawn) {
return;
}
- info.windowsDrawnDelayMs = calculateCurrentDelay();
+ info.windowsDrawnDelayMs = calculateDelay(timestamp);
info.loggedWindowsDrawn = true;
if (allStacksWindowsDrawn() && mLoggedTransitionStarting) {
reset(false /* abort */);
@@ -245,13 +245,13 @@
/**
* Notifies the tracker that the starting window was drawn.
*/
- void notifyStartingWindowDrawn(int stackId) {
+ void notifyStartingWindowDrawn(int stackId, long timestamp) {
final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
if (info == null || info.loggedStartingWindowDrawn) {
return;
}
info.loggedStartingWindowDrawn = true;
- info.startingWindowDelayMs = calculateCurrentDelay();
+ info.startingWindowDelayMs = calculateDelay(timestamp);
}
/**
@@ -260,11 +260,11 @@
* @param stackIdReasons A map from stack id to a reason integer, which must be on of
* ActivityManagerInternal.APP_TRANSITION_* reasons.
*/
- void notifyTransitionStarting(SparseIntArray stackIdReasons) {
+ void notifyTransitionStarting(SparseIntArray stackIdReasons, long timestamp) {
if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
return;
}
- mCurrentTransitionDelayMs = calculateCurrentDelay();
+ mCurrentTransitionDelayMs = calculateDelay(timestamp);
mLoggedTransitionStarting = true;
for (int index = stackIdReasons.size() - 1; index >= 0; index--) {
final int stackId = stackIdReasons.keyAt(index);
@@ -344,6 +344,11 @@
return (int) (SystemClock.uptimeMillis() - mCurrentTransitionStartTime);
}
+ private int calculateDelay(long timestamp) {
+ // Shouldn't take more than 25 days to launch an app, so int is fine here.
+ return (int) (timestamp - mCurrentTransitionStartTime);
+ }
+
private void logAppTransitionMultiEvents() {
for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
final StackTransitionInfo info = mStackTransitionInfo.valueAt(index);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index ec6a4f6..68f4d0d 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1928,18 +1928,19 @@
}
@Override
- public void onStartingWindowDrawn() {
+ public void onStartingWindowDrawn(long timestamp) {
synchronized (service) {
- mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(getStackId());
+ mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(
+ getStackId(), timestamp);
}
}
@Override
- public void onWindowsDrawn() {
+ public void onWindowsDrawn(long timestamp) {
synchronized (service) {
- mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId());
+ mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId(), timestamp);
if (displayStartTime != 0) {
- reportLaunchTimeLocked(SystemClock.uptimeMillis());
+ reportLaunchTimeLocked(timestamp);
}
mStackSupervisor.sendWaitingVisibleReportLocked(this);
startTime = 0;
@@ -2153,6 +2154,11 @@
}
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
+ showStartingWindow(prev, newTask, taskSwitch, false /* fromRecents */);
+ }
+
+ void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
+ boolean fromRecents) {
if (mWindowContainerController == null) {
return;
}
@@ -2167,7 +2173,8 @@
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
- state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal());
+ state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal(),
+ fromRecents);
if (shown) {
mStartingWindowState = STARTING_WINDOW_SHOWN;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 53afe78..7de56fa 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -5131,7 +5131,7 @@
&& task.getRootActivity() != null) {
mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */);
mActivityMetricsLogger.notifyActivityLaunching();
- mService.moveTaskToFrontLocked(task.taskId, 0, bOptions);
+ mService.moveTaskToFrontLocked(task.taskId, 0, bOptions, true /* fromRecents */);
mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
task.getTopActivity());
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 1ed2ac1..d74d1d6 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -188,6 +188,11 @@
private boolean mUsingVr2dDisplay;
+ // Last home activity record we attempted to start
+ final ActivityRecord[] mLastHomeActivityStartRecord = new ActivityRecord[1];
+ // The result of the last home activity we attempted to start.
+ int mLastHomeActivityStartResult;
+
private void reset() {
mStartActivity = null;
mIntent = null;
@@ -592,12 +597,13 @@
void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) {
mSupervisor.moveHomeStackTaskToTop(reason);
- startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/,
- null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/,
- null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/,
- 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/,
- 0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/,
- false /*ignoreTargetSecurity*/, false /*componentSpecified*/, null /*outActivity*/,
+ mLastHomeActivityStartResult = startActivityLocked(null /*caller*/, intent,
+ null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/,
+ null /*voiceSession*/, null /*voiceInteractor*/, null /*resultTo*/,
+ null /*resultWho*/, 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/,
+ null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/,
+ 0 /*startFlags*/, null /*options*/, false /*ignoreTargetSecurity*/,
+ false /*componentSpecified*/, mLastHomeActivityStartRecord /*outActivity*/,
null /*container*/, null /*inTask*/);
if (mSupervisor.inResumeTopActivity) {
// If we are in resume section already, home activity will be initialized, but not
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 639b7a9..b3a2c29 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -614,9 +614,10 @@
skip = true;
}
- if (!skip && (filter.receiverList.app == null || filter.receiverList.app.crashing)) {
+ if (!skip && (filter.receiverList.app == null || filter.receiverList.app.killed
+ || filter.receiverList.app.crashing)) {
Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
- + " to " + filter.receiverList + ": process crashing");
+ + " to " + filter.receiverList + ": process gone or crashing");
skip = true;
}
@@ -1317,7 +1318,7 @@
}
// Is this receiver's application already running?
- if (app != null && app.thread != null) {
+ if (app != null && app.thread != null && !app.killed) {
try {
app.addPackage(info.activityInfo.packageName,
info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6a310f2..eeab605 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -599,6 +599,7 @@
void finishUserStopping(final int userId, final UserState uss) {
// On to the next.
final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+ shutdownIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
// This is the result receiver for the final shutdown broadcast.
final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
@Override
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index 9970c82..b89586d 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -69,7 +69,6 @@
public boolean setState(int oldState, int newState) {
if (state == oldState) {
setState(newState);
- EventLogTags.writeAmUserStateChanged(mHandle.getIdentifier(), newState);
return true;
} else {
Slog.w(TAG, "Expected user " + mHandle.getIdentifier() + " in state "
@@ -84,6 +83,7 @@
}
Slog.i(TAG, "User " + mHandle.getIdentifier() + " state changed from "
+ stateToString(state) + " to " + stateToString(newState));
+ EventLogTags.writeAmUserStateChanged(mHandle.getIdentifier(), newState);
lastState = state;
state = newState;
}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
index f23caf2..0fdf2da 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -30,6 +30,7 @@
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.TextureView;
+import android.view.ThreadedRenderer;
import android.view.View;
import android.view.WindowManager;
import android.view.TextureView.SurfaceTextureListener;
@@ -95,6 +96,8 @@
public OverlayDisplayWindow(Context context, String name,
int width, int height, int densityDpi, int gravity, boolean secure,
Listener listener) {
+ // Workaround device freeze (b/38372997)
+ ThreadedRenderer.disableVsync();
mContext = context;
mName = name;
mGravity = gravity;
diff --git a/services/core/java/com/android/server/job/GrantedUriPermissions.java b/services/core/java/com/android/server/job/GrantedUriPermissions.java
index e413d8d..c23b109 100644
--- a/services/core/java/com/android/server/job/GrantedUriPermissions.java
+++ b/services/core/java/com/android/server/job/GrantedUriPermissions.java
@@ -29,7 +29,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-public class GrantedUriPermissions {
+public final class GrantedUriPermissions {
private final int mGrantFlags;
private final int mSourceUserId;
private final String mTag;
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
index 2d2f61f..a53c088 100644
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -26,7 +26,7 @@
import java.io.PrintWriter;
-public class JobSchedulerShellCommand extends ShellCommand {
+public final class JobSchedulerShellCommand extends ShellCommand {
public static final int CMD_ERR_NO_PACKAGE = -1000;
public static final int CMD_ERR_NO_JOB = -1001;
public static final int CMD_ERR_CONSTRAINTS = -1002;
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 637db11..5d3f6f7 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -55,13 +55,13 @@
* job lands, and again when it is complete.
* - Cancelling is trickier, because there are also interactions from the client. It's possible
* the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
- * {@link #doCancelLocked(int)} after the client has already finished. This is handled by having
+ * {@link #doCancelLocked} after the client has already finished. This is handled by having
* {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether
* the context is still valid.
* To mitigate this, we avoid sending duplicate onStopJob()
* calls to the client after they've specified jobFinished().
*/
-public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
+public final class JobServiceContext implements ServiceConnection {
private static final boolean DEBUG = JobSchedulerService.DEBUG;
private static final String TAG = "JobServiceContext";
/** Amount of time a job is allowed to execute for before being considered timed-out. */
@@ -112,6 +112,7 @@
* Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
*/
private JobStatus mRunningJob;
+ private JobCallback mRunningCallback;
/** Used to store next job to run when current job is to be preempted. */
private int mPreferredUid;
IJobService service;
@@ -133,6 +134,36 @@
// Debugging: time this job was last stopped.
public long mStoppedTime;
+ final class JobCallback extends IJobCallback.Stub {
+ public String mStoppedReason;
+ public long mStoppedTime;
+
+ @Override
+ public void acknowledgeStartMessage(int jobId, boolean ongoing) {
+ doAcknowledgeStartMessage(this, jobId, ongoing);
+ }
+
+ @Override
+ public void acknowledgeStopMessage(int jobId, boolean reschedule) {
+ doAcknowledgeStopMessage(this, jobId, reschedule);
+ }
+
+ @Override
+ public JobWorkItem dequeueWork(int jobId) {
+ return doDequeueWork(this, jobId);
+ }
+
+ @Override
+ public boolean completeWork(int jobId, int workId) {
+ return doCompleteWork(this, jobId, workId);
+ }
+
+ @Override
+ public void jobFinished(int jobId, boolean reschedule) {
+ doJobFinished(this, jobId, reschedule);
+ }
+ }
+
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
JobPackageTracker tracker, Looper looper) {
this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
@@ -168,6 +199,7 @@
mPreferredUid = NO_PREFERRED_UID;
mRunningJob = job;
+ mRunningCallback = new JobCallback();
final boolean isDeadlineExpired =
job.hasDeadlineConstraint() &&
(job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
@@ -182,7 +214,7 @@
job.changedAuthorities.toArray(triggeredAuthorities);
}
final JobInfo ji = job.getJob();
- mParams = new JobParameters(this, job.getJobId(), ji.getExtras(),
+ mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, triggeredUris, triggeredAuthorities);
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
@@ -198,6 +230,7 @@
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
}
mRunningJob = null;
+ mRunningCallback = null;
mParams = null;
mExecutionStartTimeElapsed = 0L;
mVerb = VERB_FINISHED;
@@ -263,28 +296,29 @@
return false;
}
- @Override
- public void jobFinished(int jobId, boolean reschedule) {
- doCallback(reschedule, "app called jobFinished");
+ void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
+ doCallback(cb, reschedule, "app called jobFinished");
}
- @Override
- public void acknowledgeStopMessage(int jobId, boolean reschedule) {
- doCallback(reschedule, null);
+ void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
+ doCallback(cb, reschedule, null);
}
- @Override
- public void acknowledgeStartMessage(int jobId, boolean ongoing) {
- doCallback(ongoing, "finished start");
+ void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
+ doCallback(cb, ongoing, "finished start");
}
- @Override
- public JobWorkItem dequeueWork(int jobId) {
- final int callingUid = Binder.getCallingUid();
+ JobWorkItem doDequeueWork(JobCallback cb, int jobId) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- assertCallingUidLocked(callingUid);
+ assertCallerLocked(cb);
+ if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) {
+ // This job is either all done, or on its way out. Either way, it
+ // should not dispatch any more work. We will pick up any remaining
+ // work the next time we start the job again.
+ return null;
+ }
final JobWorkItem work = mRunningJob.dequeueWorkLocked();
if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
// This will finish the job.
@@ -297,13 +331,11 @@
}
}
- @Override
- public boolean completeWork(int jobId, int workId) {
- final int callingUid = Binder.getCallingUid();
+ boolean doCompleteWork(JobCallback cb, int jobId, int workId) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- assertCallingUidLocked(callingUid);
+ assertCallerLocked(cb);
return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
}
} finally {
@@ -369,8 +401,8 @@
* whether the client exercising the callback is the client we expect.
* @return True if the binder calling is coming from the client we expect.
*/
- private boolean verifyCallingUidLocked(final int callingUid) {
- if (mRunningJob == null || callingUid != mRunningJob.getUid()) {
+ private boolean verifyCallerLocked(JobCallback cb) {
+ if (mRunningCallback != cb) {
if (DEBUG) {
Slog.d(TAG, "Stale callback received, ignoring.");
}
@@ -379,16 +411,15 @@
return true;
}
- private void assertCallingUidLocked(final int callingUid) {
- if (!verifyCallingUidLocked(callingUid)) {
+ private void assertCallerLocked(JobCallback cb) {
+ if (!verifyCallerLocked(cb)) {
StringBuilder sb = new StringBuilder(128);
- sb.append("Bad calling uid ");
- sb.append(callingUid);
- if (mStoppedReason != null) {
+ sb.append("Caller no longer running");
+ if (cb.mStoppedReason != null) {
sb.append(", last stopped ");
- TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mStoppedTime, sb);
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb);
sb.append(" because: ");
- sb.append(mStoppedReason);
+ sb.append(cb.mStoppedReason);
}
throw new SecurityException(sb.toString());
}
@@ -421,12 +452,11 @@
handleServiceBoundLocked();
}
- void doCallback(boolean reschedule, String reason) {
- final int callingUid = Binder.getCallingUid();
+ void doCallback(JobCallback cb, boolean reschedule, String reason) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- if (!verifyCallingUidLocked(callingUid)) {
+ if (!verifyCallerLocked(cb)) {
return;
}
doCallbackLocked(reschedule, reason);
@@ -559,7 +589,7 @@
* VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
* {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
* _STARTING -> Mark as cancelled and wait for
- * {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
+ * {@link JobServiceContext#doAcknowledgeStartMessage}
* _EXECUTING -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks
* in the message queue.
* _ENDING -> No point in doing anything here, so we ignore.
@@ -671,6 +701,7 @@
mContext.unbindService(JobServiceContext.this);
mWakeLock = null;
mRunningJob = null;
+ mRunningCallback = null;
mParams = null;
mVerb = VERB_FINISHED;
mCancelled = false;
@@ -684,6 +715,10 @@
if (reason != null && mStoppedReason == null) {
mStoppedReason = reason;
mStoppedTime = SystemClock.elapsedRealtime();
+ if (mRunningCallback != null) {
+ mRunningCallback.mStoppedReason = mStoppedReason;
+ mRunningCallback.mStoppedTime = mStoppedTime;
+ }
}
}
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 22eed3b..f0cd8a8 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -66,7 +66,7 @@
* and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
* object.
*/
-public class JobStore {
+public final class JobStore {
private static final String TAG = "JobStore";
private static final boolean DEBUG = JobSchedulerService.DEBUG;
@@ -263,7 +263,7 @@
* Runnable that writes {@link #mJobSet} out to xml.
* NOTE: This Runnable locks on mLock
*/
- private class WriteJobsMapToDiskRunnable implements Runnable {
+ private final class WriteJobsMapToDiskRunnable implements Runnable {
@Override
public void run() {
final long startElapsed = SystemClock.elapsedRealtime();
@@ -444,7 +444,7 @@
* Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
* need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
*/
- private class ReadJobMapFromDiskRunnable implements Runnable {
+ private final class ReadJobMapFromDiskRunnable implements Runnable {
private final JobSet jobSet;
/**
@@ -796,7 +796,7 @@
}
}
- static class JobSet {
+ static final class JobSet {
// Key is the getUid() originator of the jobs in each sheaf
private SparseArray<ArraySet<JobStatus>> mJobs;
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 68dd00f..39f2a96 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -33,7 +33,7 @@
* for a certain amount of time (maybe hours or days) are considered idle. When the app comes
* out of idle state, it will be allowed to run scheduled jobs.
*/
-public class AppIdleController extends StateController {
+public final class AppIdleController extends StateController {
private static final String LOG_TAG = "AppIdleController";
private static final boolean DEBUG = false;
@@ -171,7 +171,7 @@
}
}
- private class AppIdleStateChangeListener
+ private final class AppIdleStateChangeListener
extends UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override
public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index d275bd9..9111969 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -39,7 +39,7 @@
* be charging when it's been plugged in for more than two minutes, and the system has broadcast
* ACTION_BATTERY_OK.
*/
-public class BatteryController extends StateController {
+public final class BatteryController extends StateController {
private static final String TAG = "JobScheduler.Batt";
private static final Object sCreationLock = new Object();
@@ -121,7 +121,7 @@
}
}
- public class ChargingTracker extends BroadcastReceiver {
+ public final class ChargingTracker extends BroadcastReceiver {
/**
* Track whether we're "charging", where charging means that we're ready to commit to
* doing work.
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index f426818..17c8928 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -43,7 +43,7 @@
* status due to user-requested network policies, so we need to check
* constraints on a per-UID basis.
*/
-public class ConnectivityController extends StateController implements
+public final class ConnectivityController extends StateController implements
ConnectivityManager.OnNetworkActiveListener {
private static final String TAG = "JobScheduler.Conn";
private static final boolean DEBUG = false;
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index cfafc38..ff807ec 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -39,7 +39,7 @@
/**
* Controller for monitoring changes to content URIs through a ContentObserver.
*/
-public class ContentObserverController extends StateController {
+public final class ContentObserverController extends StateController {
private static final String TAG = "JobScheduler.Content";
private static final boolean DEBUG = false;
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 5ccf812..85993b9 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -37,7 +37,7 @@
* When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
* When device is not dozing, set constraint for all jobs as satisfied.
*/
-public class DeviceIdleJobsController extends StateController {
+public final class DeviceIdleJobsController extends StateController {
private static final String LOG_TAG = "DeviceIdleJobsController";
private static final boolean LOG_DEBUG = false;
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 7e92293..9eda046 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -33,7 +33,7 @@
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
-public class IdleController extends StateController {
+public final class IdleController extends StateController {
private static final String TAG = "IdleController";
// Policy: we decide that we're "idle" if the device has been unused /
@@ -107,7 +107,7 @@
mIdleTracker.startTracking();
}
- class IdlenessTracker extends BroadcastReceiver {
+ final class IdlenessTracker extends BroadcastReceiver {
private AlarmManager mAlarm;
private PendingIntent mIdleTriggerIntent;
boolean mIdle;
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 53bf402..446b0d6 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -698,7 +698,8 @@
static final int CONSTRAINTS_OF_INTEREST =
CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
CONSTRAINT_TIMING_DELAY |
- CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING |
+ CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
+ CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED |
CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
// Soft override covers all non-"functional" constraints
@@ -865,6 +866,9 @@
if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
pw.print(" NOT_ROAMING");
}
+ if ((constraints&CONSTRAINT_METERED) != 0) {
+ pw.print(" METERED");
+ }
if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
pw.print(" APP_NOT_IDLE");
}
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
index 4fe8eca..c24e563 100644
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -35,7 +35,7 @@
/**
* Simple controller that tracks the status of the device's storage.
*/
-public class StorageController extends StateController {
+public final class StorageController extends StateController {
private static final String TAG = "JobScheduler.Stor";
private static final Object sCreationLock = new Object();
@@ -112,7 +112,7 @@
}
}
- public class StorageTracker extends BroadcastReceiver {
+ public final class StorageTracker extends BroadcastReceiver {
/**
* Track whether storage is low.
*/
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index 01c841e..d90699a 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -38,7 +38,7 @@
* This class sets an alarm for the next expiring job, and determines whether a job's minimum
* delay has been satisfied.
*/
-public class TimeController extends StateController {
+public final class TimeController extends StateController {
private static final String TAG = "JobScheduler.Time";
/** Deadline alarm tag for logging purposes */
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 19d5f3c..bdea247 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -307,11 +307,15 @@
// used as a mutex for access to all active notifications & listeners
final Object mNotificationLock = new Object();
+ @GuardedBy("mNotificationLock")
final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
+ @GuardedBy("mNotificationLock")
final ArrayMap<String, NotificationRecord> mNotificationsByKey =
new ArrayMap<String, NotificationRecord>();
+ @GuardedBy("mNotificationLock")
final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>();
+ @GuardedBy("mNotificationLock")
final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
@@ -607,7 +611,7 @@
@Override
public void onPanelRevealed(boolean clearEffects, int items) {
MetricsLogger.visible(getContext(), MetricsEvent.NOTIFICATION_PANEL);
- MetricsLogger.histogram(getContext(), "notification_load", items);
+ MetricsLogger.histogram(getContext(), "note_load", items);
EventLogTags.writeNotificationPanelRevealed(items);
if (clearEffects) {
clearEffects();
@@ -2806,7 +2810,8 @@
// Clear summary.
final NotificationRecord removed = findNotificationByKeyLocked(summaries.remove(pkg));
if (removed != null) {
- cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED);
+ boolean wasPosted = removeFromNotificationListsLocked(removed);
+ cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED, wasPosted);
}
}
}
@@ -3420,7 +3425,8 @@
.setType(MetricsEvent.TYPE_CLOSE)
.addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
mSnoozeCriterionId == null ? 0 : 1));
- cancelNotificationLocked(r, false, REASON_SNOOZED);
+ boolean wasPosted = removeFromNotificationListsLocked(r);
+ cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted);
updateLightsLocked();
if (mSnoozeCriterionId != null) {
mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn, mSnoozeCriterionId);
@@ -4206,15 +4212,18 @@
manager.sendAccessibilityEvent(event);
}
+ /**
+ * Removes all NotificationsRecords with the same key as the given notification record
+ * from both lists. Do not call this method while iterating over either list.
+ */
@GuardedBy("mNotificationLock")
- private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) {
- final String canceledKey = r.getKey();
-
- // Remove from both lists, either list could have a separate Record for what is effectively
- // the same notification.
+ private boolean removeFromNotificationListsLocked(NotificationRecord r) {
+ // Remove from both lists, either list could have a separate Record for what is
+ // effectively the same notification.
boolean wasPosted = false;
NotificationRecord recordInList = null;
- if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey())) != null) {
+ if ((recordInList = findNotificationByListLocked(mNotificationList, r.getKey()))
+ != null) {
mNotificationList.remove(recordInList);
mNotificationsByKey.remove(recordInList.sbn.getKey());
wasPosted = true;
@@ -4223,6 +4232,13 @@
!= null) {
mEnqueuedNotifications.remove(recordInList);
}
+ return wasPosted;
+ }
+
+ @GuardedBy("mNotificationLock")
+ private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason,
+ boolean wasPosted) {
+ final String canceledKey = r.getKey();
// Record caller.
recordCallerLocked(r);
@@ -4363,7 +4379,8 @@
}
// Cancel the notification.
- cancelNotificationLocked(r, sendDelete, reason);
+ boolean wasPosted = removeFromNotificationListsLocked(r);
+ cancelNotificationLocked(r, sendDelete, reason, wasPosted);
cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName,
sendDelete);
updateLightsLocked();
@@ -4440,11 +4457,11 @@
cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
- listenerName);
+ listenerName, true /* wasPosted */);
cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
flagChecker, false /*includeCurrentProfiles*/, userId,
- false /*sendDelete*/, reason, listenerName);
+ false /*sendDelete*/, reason, listenerName, false /* wasPosted */);
mSnoozeHelper.cancel(userId, pkg);
}
}
@@ -4460,7 +4477,7 @@
private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
- boolean sendDelete, int reason, String listenerName) {
+ boolean sendDelete, int reason, String listenerName, boolean wasPosted) {
ArrayList<NotificationRecord> canceledNotifications = null;
for (int i = notificationList.size() - 1; i >= 0; --i) {
NotificationRecord r = notificationList.get(i);
@@ -4488,8 +4505,9 @@
if (canceledNotifications == null) {
canceledNotifications = new ArrayList<>();
}
+ notificationList.remove(i);
canceledNotifications.add(r);
- cancelNotificationLocked(r, sendDelete, reason);
+ cancelNotificationLocked(r, sendDelete, reason, wasPosted);
}
if (canceledNotifications != null) {
final int M = canceledNotifications.size();
@@ -4548,11 +4566,11 @@
cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
includeCurrentProfiles, userId, true /*sendDelete*/, reason,
- listenerName);
+ listenerName, true);
cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
- reason, listenerName);
+ reason, listenerName, false);
mSnoozeHelper.cancel(userId, includeCurrentProfiles);
}
}
@@ -4569,7 +4587,6 @@
}
String pkg = r.sbn.getPackageName();
- int userId = r.getUserId();
if (pkg == null) {
if (DBG) Log.e(TAG, "No package for group summary: " + r.getKey());
@@ -4577,15 +4594,15 @@
}
cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
- sendDelete);
+ sendDelete, true);
cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
- listenerName, sendDelete);
+ listenerName, sendDelete, false);
}
@GuardedBy("mNotificationLock")
private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
NotificationRecord parentNotification, int callingUid, int callingPid,
- String listenerName, boolean sendDelete) {
+ String listenerName, boolean sendDelete, boolean wasPosted) {
final String pkg = parentNotification.sbn.getPackageName();
final int userId = parentNotification.getUserId();
final int reason = REASON_GROUP_SUMMARY_CANCELED;
@@ -4597,7 +4614,8 @@
&& (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
childSbn.getTag(), userId, 0, 0, reason, listenerName);
- cancelNotificationLocked(childR, sendDelete, reason);
+ notificationList.remove(i);
+ cancelNotificationLocked(childR, sendDelete, reason, wasPosted);
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 8952870..6953ffd 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,6 +25,7 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -170,6 +171,11 @@
private Uri calculateSound() {
final Notification n = sbn.getNotification();
+ // No notification sounds on tv
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+ return null;
+ }
+
Uri sound = mChannel.getSound();
if (mPreChannelsNotification && (getChannel().getUserLockedFields()
& NotificationChannel.USER_LOCKED_SOUND) == 0) {
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 2c0cc95..d7b36aa 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -53,6 +53,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -524,12 +525,11 @@
if (r == null) {
throw new IllegalArgumentException("Invalid package");
}
- LogMaker lm = new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
- .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
- group.getId())
- .setPackageName(pkg);
- MetricsLogger.action(lm);
+ final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
+ if (!group.equals(oldGroup)) {
+ // will log for new entries as well as name changes
+ MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
+ }
r.groups.put(group.getId(), group);
updateConfig();
}
@@ -557,13 +557,16 @@
if (existing != null && fromTargetApp) {
if (existing.isDeleted()) {
existing.setDeleted(false);
+
+ // log a resurrected channel as if it's new again
+ MetricsLogger.action(getChannelLog(channel, pkg).setType(
+ MetricsProto.MetricsEvent.TYPE_OPEN));
}
existing.setName(channel.getName().toString());
existing.setDescription(channel.getDescription());
existing.setBlockableSystem(channel.isBlockableSystem());
- MetricsLogger.action(getChannelLog(channel, pkg));
updateConfig();
return;
}
@@ -621,7 +624,10 @@
r.showBadge = updatedChannel.canShowBadge();
}
- MetricsLogger.action(getChannelLog(updatedChannel, pkg));
+ if (!channel.equals(updatedChannel)) {
+ // only log if there are real changes
+ MetricsLogger.action(getChannelLog(updatedChannel, pkg));
+ }
updateConfig();
}
@@ -1140,6 +1146,14 @@
channel.getImportance());
}
+ private LogMaker getChannelGroupLog(String groupId, String pkg) {
+ return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
+ .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
+ .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
+ groupId)
+ .setPackageName(pkg);
+ }
+
public void updateBadgingEnabled() {
if (mBadgingEnabled == null) {
mBadgingEnabled = new SparseBooleanArray();
@@ -1186,6 +1200,6 @@
boolean showBadge = DEFAULT_SHOW_BADGE;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
- ArrayMap<String, NotificationChannelGroup> groups = new ArrayMap<>();
+ Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index ef3e7bc..2940a6e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -714,11 +714,17 @@
final Map<String, List<String>> pendingChanges = new ArrayMap<>(targetPackageNames.size());
synchronized (mLock) {
+ final List<String> frameworkOverlays =
+ mImpl.getEnabledOverlayPackageNames("android", userId);
final int N = targetPackageNames.size();
for (int i = 0; i < N; i++) {
final String targetPackageName = targetPackageNames.get(i);
- pendingChanges.put(targetPackageName,
- mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
+ List<String> list = new ArrayList<>();
+ if (!"android".equals(targetPackageName)) {
+ list.addAll(frameworkOverlays);
+ }
+ list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
+ pendingChanges.put(targetPackageName, list);
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 261bcc5..db6e974 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -170,6 +170,7 @@
final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
updateAllOverlaysForTarget(packageName, userId, targetPackage);
+ mListener.onOverlaysChanged(packageName, userId);
}
void onTargetPackageChanged(@NonNull final String packageName, final int userId) {
@@ -178,7 +179,9 @@
}
final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- updateAllOverlaysForTarget(packageName, userId, targetPackage);
+ if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ mListener.onOverlaysChanged(packageName, userId);
+ }
}
void onTargetPackageUpgrading(@NonNull final String packageName, final int userId) {
@@ -186,7 +189,9 @@
Slog.d(TAG, "onTargetPackageUpgrading packageName=" + packageName + " userId=" + userId);
}
- updateAllOverlaysForTarget(packageName, userId, null);
+ if (updateAllOverlaysForTarget(packageName, userId, null)) {
+ mListener.onOverlaysChanged(packageName, userId);
+ }
}
void onTargetPackageUpgraded(@NonNull final String packageName, final int userId) {
@@ -195,7 +200,9 @@
}
final PackageInfo targetPackage = mPackageManager.getPackageInfo(packageName, userId);
- updateAllOverlaysForTarget(packageName, userId, targetPackage);
+ if (updateAllOverlaysForTarget(packageName, userId, targetPackage)) {
+ mListener.onOverlaysChanged(packageName, userId);
+ }
}
void onTargetPackageRemoved(@NonNull final String packageName, final int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4540d2d..f111db1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -856,8 +856,15 @@
mResolvedInstructionSets.add(archSubDir.getName());
List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
- if (!oatFiles.isEmpty()) {
- mResolvedInheritedFiles.addAll(oatFiles);
+
+ // Only add compiled files associated with the base.
+ // Once b/62269291 is resolved, we can add all compiled files again.
+ for (File oatFile : oatFiles) {
+ if (oatFile.getName().equals("base.art")
+ || oatFile.getName().equals("base.odex")
+ || oatFile.getName().equals("base.vdex")) {
+ mResolvedInheritedFiles.add(oatFile);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e62b107..6c59505 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -48,6 +48,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
@@ -675,13 +676,6 @@
@GuardedBy("mPackages")
final SparseIntArray mIsolatedOwners = new SparseIntArray();
- // List of APK paths to load for each user and package. This data is never
- // persisted by the package manager. Instead, the overlay manager will
- // ensure the data is up-to-date in runtime.
- @GuardedBy("mPackages")
- final SparseArray<ArrayMap<String, ArrayList<String>>> mEnabledOverlayPaths =
- new SparseArray<ArrayMap<String, ArrayList<String>>>();
-
/**
* Tracks new system packages [received in an OTA] that we expect to
* find updated user-installed versions. Keys are package name, values
@@ -3583,8 +3577,6 @@
return null;
}
- rebaseEnabledOverlays(packageInfo.applicationInfo, userId);
-
packageInfo.packageName = packageInfo.applicationInfo.packageName =
resolveExternalPackageNameLPr(p);
@@ -4096,7 +4088,6 @@
ApplicationInfo ai = PackageParser.generateApplicationInfo(ps.pkg, flags,
ps.readUserState(userId), userId);
if (ai != null) {
- rebaseEnabledOverlays(ai, userId);
ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
}
return ai;
@@ -4145,7 +4136,6 @@
ApplicationInfo ai = PackageParser.generateApplicationInfo(
p, flags, ps.readUserState(userId), userId);
if (ai != null) {
- rebaseEnabledOverlays(ai, userId);
ai.packageName = resolveExternalPackageNameLPr(p);
}
return ai;
@@ -4162,26 +4152,6 @@
return null;
}
- private void rebaseEnabledOverlays(@NonNull ApplicationInfo ai, int userId) {
- List<String> paths = new ArrayList<>();
- ArrayMap<String, ArrayList<String>> userSpecificOverlays =
- mEnabledOverlayPaths.get(userId);
- if (userSpecificOverlays != null) {
- if (!"android".equals(ai.packageName)) {
- ArrayList<String> frameworkOverlays = userSpecificOverlays.get("android");
- if (frameworkOverlays != null) {
- paths.addAll(frameworkOverlays);
- }
- }
-
- ArrayList<String> appOverlays = userSpecificOverlays.get(ai.packageName);
- if (appOverlays != null) {
- paths.addAll(appOverlays);
- }
- }
- ai.resourceDirs = paths.size() > 0 ? paths.toArray(new String[paths.size()]) : null;
- }
-
private String normalizePackageNameLPr(String packageName) {
String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
return normalizedPackageName != null ? normalizedPackageName : packageName;
@@ -4566,24 +4536,6 @@
return updateFlagsForComponent(flags, userId, intent /*cookie*/);
}
- private ActivityInfo generateActivityInfo(ActivityInfo ai, int flags, PackageUserState state,
- int userId) {
- ActivityInfo ret = PackageParser.generateActivityInfo(ai, flags, state, userId);
- if (ret != null) {
- rebaseEnabledOverlays(ret.applicationInfo, userId);
- }
- return ret;
- }
-
- private ActivityInfo generateActivityInfo(PackageParser.Activity a, int flags,
- PackageUserState state, int userId) {
- ActivityInfo ai = PackageParser.generateActivityInfo(a, flags, state, userId);
- if (ai != null) {
- rebaseEnabledOverlays(ai.applicationInfo, userId);
- }
- return ai;
- }
-
@Override
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
@@ -4611,11 +4563,12 @@
if (filterAppAccessLPr(ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
return null;
}
- return generateActivityInfo(a, flags, ps.readUserState(userId), userId);
+ return PackageParser.generateActivityInfo(
+ a, flags, ps.readUserState(userId), userId);
}
if (mResolveComponentName.equals(component)) {
- return generateActivityInfo(mResolveActivity, flags, new PackageUserState(),
- userId);
+ return PackageParser.generateActivityInfo(
+ mResolveActivity, flags, new PackageUserState(), userId);
}
}
return null;
@@ -4669,7 +4622,8 @@
if (filterAppAccessLPr(ps, callingUid, component, TYPE_RECEIVER, userId)) {
return null;
}
- return generateActivityInfo(a, flags, ps.readUserState(userId), userId);
+ return PackageParser.generateActivityInfo(
+ a, flags, ps.readUserState(userId), userId);
}
}
return null;
@@ -4805,12 +4759,8 @@
if (filterAppAccessLPr(ps, callingUid, component, TYPE_SERVICE, userId)) {
return null;
}
- ServiceInfo si = PackageParser.generateServiceInfo(s, flags,
- ps.readUserState(userId), userId);
- if (si != null) {
- rebaseEnabledOverlays(si.applicationInfo, userId);
- }
- return si;
+ return PackageParser.generateServiceInfo(
+ s, flags, ps.readUserState(userId), userId);
}
}
return null;
@@ -4833,12 +4783,8 @@
if (filterAppAccessLPr(ps, callingUid, component, TYPE_PROVIDER, userId)) {
return null;
}
- ProviderInfo pi = PackageParser.generateProviderInfo(p, flags,
- ps.readUserState(userId), userId);
- if (pi != null) {
- rebaseEnabledOverlays(pi.applicationInfo, userId);
- }
- return pi;
+ return PackageParser.generateProviderInfo(
+ p, flags, ps.readUserState(userId), userId);
}
}
return null;
@@ -8264,7 +8210,6 @@
ai = PackageParser.generateApplicationInfo(ps.pkg, effectiveFlags,
ps.readUserState(userId), userId);
if (ai != null) {
- rebaseEnabledOverlays(ai, userId);
ai.packageName = resolveExternalPackageNameLPr(ps.pkg);
}
} else {
@@ -8291,7 +8236,6 @@
ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
ps.readUserState(userId), userId);
if (ai != null) {
- rebaseEnabledOverlays(ai, userId);
ai.packageName = resolveExternalPackageNameLPr(p);
list.add(ai);
}
@@ -8445,7 +8389,6 @@
ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
ps.readUserState(userId), userId);
if (ai != null) {
- rebaseEnabledOverlays(ai, userId);
finalList.add(ai);
}
}
@@ -13308,7 +13251,8 @@
return null;
}
final PackageUserState userState = ps.readUserState(userId);
- ActivityInfo ai = generateActivityInfo(activity, mFlags, userState, userId);
+ ActivityInfo ai =
+ PackageParser.generateActivityInfo(activity, mFlags, userState, userId);
if (ai == null) {
return null;
}
@@ -17837,16 +17781,17 @@
// Instant apps must have target SDK >= O and have targetSanboxVersion >= 2
if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
- Slog.w(TAG, "Instant app package " + pkg.packageName
- + " does not target O, this will be a fatal error.");
- // STOPSHIP: Make this a fatal error
- pkg.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+ Slog.w(TAG, "Instant app package " + pkg.packageName + " does not target O");
+ res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
+ "Instant app package must target O");
+ return;
}
if (instantApp && pkg.applicationInfo.targetSandboxVersion != 2) {
Slog.w(TAG, "Instant app package " + pkg.packageName
- + " does not target targetSandboxVersion 2, this will be a fatal error.");
- // STOPSHIP: Make this a fatal error
- pkg.applicationInfo.targetSandboxVersion = 2;
+ + " does not target targetSandboxVersion 2");
+ res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
+ "Instant app package must use targetSanboxVersion 2");
+ return;
}
if (pkg.applicationInfo.isStaticSharedLibrary()) {
@@ -18160,8 +18105,10 @@
// step during installation. Instead, we'll take extra time the first time the
// instant app starts. It's preferred to do it this way to provide continuous
// progress to the user instead of mysteriously blocking somewhere in the
- // middle of running an instant app.
- if (!instantApp) {
+ // middle of running an instant app. The default behaviour can be overridden
+ // via gservices.
+ if (!instantApp || Global.getInt(
+ mContext.getContentResolver(), Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` may not be in `mPackages` yet.
@@ -19364,7 +19311,7 @@
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps == null || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
- return true;
+ return false;
}
return mSettings.getBlockUninstallLPr(userId, packageName);
}
@@ -21640,8 +21587,7 @@
public static final int DUMP_FROZEN = 1 << 19;
public static final int DUMP_DEXOPT = 1 << 20;
public static final int DUMP_COMPILER_STATS = 1 << 21;
- public static final int DUMP_ENABLED_OVERLAYS = 1 << 22;
- public static final int DUMP_CHANGES = 1 << 23;
+ public static final int DUMP_CHANGES = 1 << 22;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
@@ -21885,8 +21831,6 @@
dumpState.setDump(DumpState.DUMP_DEXOPT);
} else if ("compiler-stats".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_COMPILER_STATS);
- } else if ("enabled-overlays".equals(cmd)) {
- dumpState.setDump(DumpState.DUMP_ENABLED_OVERLAYS);
} else if ("changes".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_CHANGES);
} else if ("write".equals(cmd)) {
@@ -22277,11 +22221,6 @@
dumpCompilerStatsLPr(pw, packageName);
}
- if (!checkin && dumpState.isDumping(DumpState.DUMP_ENABLED_OVERLAYS)) {
- if (dumpState.onTitlePrinted()) pw.println();
- dumpEnabledOverlaysLPr(pw);
- }
-
if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
if (dumpState.onTitlePrinted()) pw.println();
mSettings.dumpReadMessagesLPr(pw, dumpState);
@@ -22478,23 +22417,6 @@
}
}
- private void dumpEnabledOverlaysLPr(PrintWriter pw) {
- pw.println("Enabled overlay paths:");
- final int N = mEnabledOverlayPaths.size();
- for (int i = 0; i < N; i++) {
- final int userId = mEnabledOverlayPaths.keyAt(i);
- pw.println(String.format(" User %d:", userId));
- final ArrayMap<String, ArrayList<String>> userSpecificOverlays =
- mEnabledOverlayPaths.valueAt(i);
- final int M = userSpecificOverlays.size();
- for (int j = 0; j < M; j++) {
- final String targetPackageName = userSpecificOverlays.keyAt(j);
- final ArrayList<String> overlayPackagePaths = userSpecificOverlays.valueAt(j);
- pw.println(String.format(" %s: %s", targetPackageName, overlayPackagePaths));
- }
- }
- }
-
private String dumpDomainString(String packageName) {
List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
.getList();
@@ -24669,11 +24591,10 @@
Slog.e(TAG, "failed to find package " + targetPackageName);
return false;
}
-
- ArrayList<String> paths = null;
- if (overlayPackageNames != null) {
+ ArrayList<String> overlayPaths = null;
+ if (overlayPackageNames != null && overlayPackageNames.size() > 0) {
final int N = overlayPackageNames.size();
- paths = new ArrayList<>(N);
+ overlayPaths = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final String packageName = overlayPackageNames.get(i);
final PackageParser.Package pkg = mPackages.get(packageName);
@@ -24681,22 +24602,12 @@
Slog.e(TAG, "failed to find package " + packageName);
return false;
}
- paths.add(pkg.baseCodePath);
+ overlayPaths.add(pkg.baseCodePath);
}
}
- ArrayMap<String, ArrayList<String>> userSpecificOverlays =
- mEnabledOverlayPaths.get(userId);
- if (userSpecificOverlays == null) {
- userSpecificOverlays = new ArrayMap<>();
- mEnabledOverlayPaths.put(userId, userSpecificOverlays);
- }
-
- if (paths != null && paths.size() > 0) {
- userSpecificOverlays.put(targetPackageName, paths);
- } else {
- userSpecificOverlays.remove(targetPackageName);
- }
+ final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
+ ps.setOverlayPaths(overlayPaths, userId);
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 14f65eb..f685127 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -31,6 +31,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.google.android.collect.Lists;
import java.io.File;
import java.util.ArrayList;
@@ -329,6 +330,15 @@
modifyUserState(userId).installReason = installReason;
}
+ void setOverlayPaths(List<String> overlayPaths, int userId) {
+ modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
+ overlayPaths.toArray(new String[overlayPaths.size()]);
+ }
+
+ String[] getOverlayPaths(int userId) {
+ return readUserState(userId).overlayPaths;
+ }
+
/** Only use for testing. Do NOT use in production code. */
@VisibleForTesting
SparseArray<PackageUserState> getUserState() {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 24cbdbf..45d0c58 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4858,6 +4858,15 @@
pw.print(ps.getEnabled(user.id));
pw.print(" instant=");
pw.println(ps.getInstantApp(user.id));
+
+ String[] overlayPaths = ps.getOverlayPaths(user.id);
+ if (overlayPaths != null && overlayPaths.length > 0) {
+ pw.print(prefix); pw.println(" overlay paths:");
+ for (String path : overlayPaths) {
+ pw.print(prefix); pw.print(" "); pw.println(path);
+ }
+ }
+
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
if (lastDisabledAppCaller != null) {
pw.print(prefix); pw.print(" lastDisabledCaller: ");
diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java
index 7a28081..b179235 100644
--- a/services/core/java/com/android/server/policy/BarController.java
+++ b/services/core/java/com/android/server/policy/BarController.java
@@ -166,8 +166,14 @@
return change || stateChanged;
}
- void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener) {
+ void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener,
+ boolean invokeWithState) {
mVisibilityChangeListener = listener;
+ if (invokeWithState) {
+ // Optionally report the initial window state for initialization purposes
+ mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED,
+ (mState == StatusBarManager.WINDOW_STATE_SHOWING) ? 1 : 0, 0).sendToTarget();
+ }
}
protected boolean skipAnimation() {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 01eabd8..8b28df1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1045,7 +1045,7 @@
new BarController.OnBarVisibilityChangedListener() {
@Override
public void onBarVisibilityChanged(boolean visible) {
- mAccessibilityManager.notifyAccessibilityButtonAvailabilityChanged(visible);
+ mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
}
};
@@ -3037,7 +3037,7 @@
mNavigationBar = win;
mNavigationBarController.setWindow(win);
mNavigationBarController.setOnBarVisibilityChangedListener(
- mNavBarVisibilityListener);
+ mNavBarVisibilityListener, true);
if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
break;
case TYPE_NAVIGATION_BAR_PANEL:
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 28ffc94..02f2afc 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -391,7 +391,8 @@
// First send the high-level shut down broadcast.
mActionDone = false;
Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mContext.sendOrderedBroadcastAsUser(intent,
UserHandle.ALL, null, br, mHandler, 0, null, null);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c1c72ca..b1ed358 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -2044,7 +2044,10 @@
if (forceOverride || isKeyguardTransit(transit) || !isTransitionSet()
|| mNextAppTransition == TRANSIT_NONE) {
setAppTransition(transit, flags);
- } else if (!alwaysKeepCurrent) {
+ }
+ // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic
+ // relies on the fact that we always execute a Keyguard transition after preparing one.
+ else if (!alwaysKeepCurrent && !isKeyguardTransit(transit)) {
if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) {
// Opening a new task always supersedes a close for the anim.
setAppTransition(transit, flags);
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 8cfbf68..fe74947 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -38,8 +38,11 @@
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Trace;
import android.util.Slog;
+import android.view.DisplayInfo;
import android.view.IApplicationToken;
import android.view.WindowManagerPolicy.StartingSurface;
@@ -61,23 +64,38 @@
private final IApplicationToken mToken;
private final Handler mHandler;
- private final Runnable mOnStartingWindowDrawn = () -> {
- if (mListener == null) {
- return;
- }
- if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
- + AppWindowContainerController.this.mToken);
- mListener.onStartingWindowDrawn();
- };
+ private final class H extends Handler {
+ public static final int NOTIFY_WINDOWS_DRAWN = 1;
+ public static final int NOTIFY_STARTING_WINDOW_DRAWN = 2;
- private final Runnable mOnWindowsDrawn = () -> {
- if (mListener == null) {
- return;
+ public H(Looper looper) {
+ super(looper);
}
- if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
- + AppWindowContainerController.this.mToken);
- mListener.onWindowsDrawn();
- };
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case NOTIFY_WINDOWS_DRAWN:
+ if (mListener == null) {
+ return;
+ }
+ if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+ + AppWindowContainerController.this.mToken);
+ mListener.onWindowsDrawn(msg.getWhen());
+ break;
+ case NOTIFY_STARTING_WINDOW_DRAWN:
+ if (mListener == null) {
+ return;
+ }
+ if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in "
+ + AppWindowContainerController.this.mToken);
+ mListener.onStartingWindowDrawn(msg.getWhen());
+ break;
+ default:
+ break;
+ }
+ }
+ }
private final Runnable mOnWindowsVisible = () -> {
if (mListener == null) {
@@ -212,7 +230,7 @@
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
WindowManagerService service, Configuration overrideConfig, Rect bounds) {
super(listener, service);
- mHandler = new Handler(service.mH.getLooper());
+ mHandler = new H(service.mH.getLooper());
mToken = token;
synchronized(mWindowMap) {
AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
@@ -481,7 +499,7 @@
public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
- boolean allowTaskSnapshot, boolean activityCreated) {
+ boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
synchronized(mWindowMap) {
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
+ " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
@@ -510,11 +528,14 @@
return false;
}
+ final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
+ mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
+ false /* restoreFromDisk */, false /* reducedResolution */);
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
- allowTaskSnapshot, activityCreated);
+ allowTaskSnapshot, activityCreated, fromRecents, snapshot);
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
- return createSnapshot();
+ return createSnapshot(snapshot);
}
// If this is a translucent window, then don't show a starting window -- the current
@@ -582,7 +603,8 @@
}
private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
- boolean allowTaskSnapshot, boolean activityCreated) {
+ boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
+ TaskSnapshot snapshot) {
if (mService.mAppTransition.getAppTransition() == TRANSIT_DOCK_TASK_FROM_RECENTS) {
// TODO(b/34099271): Remove this statement to add back the starting window and figure
// out why it causes flickering, the starting window appears over the thumbnail while
@@ -591,7 +613,9 @@
} else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
- return STARTING_WINDOW_TYPE_SNAPSHOT;
+ return snapshot == null ? STARTING_WINDOW_TYPE_NONE
+ : snapshotFillsWidth(snapshot) || fromRecents ? STARTING_WINDOW_TYPE_SNAPSHOT
+ : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else {
return STARTING_WINDOW_TYPE_NONE;
}
@@ -605,11 +629,7 @@
mService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
}
- private boolean createSnapshot() {
- final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
- mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
- false /* restoreFromDisk */, false /* reducedResolution */);
-
+ private boolean createSnapshot(TaskSnapshot snapshot) {
if (snapshot == null) {
return false;
}
@@ -620,6 +640,24 @@
return true;
}
+ private boolean snapshotFillsWidth(TaskSnapshot snapshot) {
+ if (snapshot == null) {
+ return false;
+ }
+ final Rect rect = new Rect(0, 0, snapshot.getSnapshot().getWidth(),
+ snapshot.getSnapshot().getHeight());
+ rect.inset(snapshot.getContentInsets());
+ final Rect taskBoundsWithoutInsets = new Rect();
+ mContainer.getTask().getBounds(taskBoundsWithoutInsets);
+ final DisplayInfo di = mContainer.getDisplayContent().getDisplayInfo();
+ final Rect displayBounds = new Rect(0, 0, di.logicalWidth, di.logicalHeight);
+ final Rect stableInsets = new Rect();
+ mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ stableInsets);
+ displayBounds.inset(stableInsets);
+ return rect.width() >= displayBounds.width();
+ }
+
public void removeStartingWindow() {
synchronized (mWindowMap) {
if (mHandler.hasCallbacks(mRemoveStartingWindow)) {
@@ -740,11 +778,11 @@
}
void reportStartingWindowDrawn() {
- mHandler.post(mOnStartingWindowDrawn);
+ mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_STARTING_WINDOW_DRAWN));
}
void reportWindowsDrawn() {
- mHandler.post(mOnWindowsDrawn);
+ mHandler.sendMessage(mHandler.obtainMessage(H.NOTIFY_WINDOWS_DRAWN));
}
void reportWindowsVisible() {
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerListener.java b/services/core/java/com/android/server/wm/AppWindowContainerListener.java
index 26537f2..8a39a74 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerListener.java
@@ -19,7 +19,7 @@
/** Interface used by the creator of the controller to listen to changes with the container. */
public interface AppWindowContainerListener extends WindowContainerListener {
/** Called when the windows associated app window container are drawn. */
- void onWindowsDrawn();
+ void onWindowsDrawn(long timestamp);
/** Called when the windows associated app window container are visible. */
void onWindowsVisible();
/** Called when the windows associated app window container are no longer visible. */
@@ -28,7 +28,7 @@
/**
* Called when the starting window for this container is drawn.
*/
- void onStartingWindowDrawn();
+ void onStartingWindowDrawn(long timestamp);
/**
* Called when the key dispatching to a window associated with the app window container
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 71ecaf6..bd37934 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -394,6 +394,10 @@
startingWindow.mPolicyVisibility = false;
startingWindow.mPolicyVisibilityAfterAnim = false;
}
+
+ // We are becoming visible, so better freeze the screen with the windows that are
+ // getting visible so we also wait for them.
+ forAllWindows(mService::makeWindowFreezingScreenIfNeededLocked, true);
}
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "setVisibility: " + this
@@ -891,8 +895,24 @@
return mPendingRelaunchCount > 0;
}
+ boolean shouldFreezeBounds() {
+ final Task task = getTask();
+
+ // For freeform windows, we can't freeze the bounds at the moment because this would make
+ // the resizing unresponsive.
+ if (task == null || task.inFreeformWorkspace()) {
+ return false;
+ }
+
+ // We freeze the bounds while drag resizing to deal with the time between
+ // the divider/drag handle being released, and the handling it's new
+ // configuration. If we are relaunched outside of the drag resizing state,
+ // we need to be careful not to do this.
+ return getTask().isDragResizing();
+ }
+
void startRelaunching() {
- if (canFreezeBounds()) {
+ if (shouldFreezeBounds()) {
freezeBounds();
}
@@ -909,9 +929,8 @@
}
void finishRelaunching() {
- if (canFreezeBounds()) {
- unfreezeBounds();
- }
+ unfreezeBounds();
+
if (mPendingRelaunchCount > 0) {
mPendingRelaunchCount--;
} else {
@@ -926,9 +945,7 @@
if (mPendingRelaunchCount == 0) {
return;
}
- if (canFreezeBounds()) {
- unfreezeBounds();
- }
+ unfreezeBounds();
mPendingRelaunchCount = 0;
}
@@ -1032,14 +1049,6 @@
}
}
- private boolean canFreezeBounds() {
- final Task task = getTask();
-
- // For freeform windows, we can't freeze the bounds at the moment because this would make
- // the resizing unresponsive.
- return task != null && !task.inFreeformWorkspace();
- }
-
/**
* Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
* freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
@@ -1064,9 +1073,10 @@
* Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
*/
private void unfreezeBounds() {
- if (!mFrozenBounds.isEmpty()) {
- mFrozenBounds.remove();
+ if (mFrozenBounds.isEmpty()) {
+ return;
}
+ mFrozenBounds.remove();
if (!mFrozenMergedConfig.isEmpty()) {
mFrozenMergedConfig.remove();
}
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 7d13889..cff2fad 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -206,6 +206,10 @@
mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
mFrom.top + mFrozenTaskHeight);
+ // Boost the thread priority of the animation thread while the bounds animation is
+ // running
+ updateBooster();
+
// Ensure that we have prepared the target for animation before
// we trigger any size changes, so it can swap surfaces
// in to appropriate modes, or do as it wishes otherwise.
@@ -316,6 +320,9 @@
removeListener(this);
removeUpdateListener(this);
mRunningAnimations.remove(mTarget);
+
+ // Reset the thread priority of the animation thread after the bounds animation is done
+ updateBooster();
}
@Override
@@ -446,4 +453,9 @@
b.resume();
}
}
+
+ private void updateBooster() {
+ WindowManagerService.sThreadPriorityBooster.setBoundsAnimationRunning(
+ !mRunningAnimations.isEmpty());
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ccc8f63..54983c8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -314,6 +314,9 @@
// the display's direct children should be allowed.
private boolean mRemovingDisplay = false;
+ // {@code false} if this display is in the processing of being created.
+ private boolean mDisplayReady = false;
+
private final WindowLayersController mLayersController;
WallpaperController mWallpaperController;
int mInputMethodAnimLayerAdjustment;
@@ -720,7 +723,6 @@
*/
DisplayContent(Display display, WindowManagerService service,
WindowLayersController layersController, WallpaperController wallpaperController) {
-
if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+ " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -748,6 +750,15 @@
// Add itself as a child to the root container.
mService.mRoot.addChild(this, null);
+
+ // TODO(b/62541591): evaluate whether this is the best spot to declare the
+ // {@link DisplayContent} ready for use.
+ mDisplayReady = true;
+ }
+
+ boolean isReady() {
+ // The display is ready when the system and the individual display are both ready.
+ return mService.mDisplayReady && mDisplayReady;
}
int getDisplayId() {
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 9a9e29a..6d33ce2 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -155,6 +155,10 @@
mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
reloadResources();
+ // Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
+ // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
+ // triggers a configuration change and the resources to be reloaded.
+ mAspectRatio = mDefaultAspectRatio;
}
void onConfigurationChanged() {
@@ -171,7 +175,6 @@
mCurrentMinSize = mDefaultMinSize;
mDefaultAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
- mAspectRatio = mDefaultAspectRatio;
final String screenEdgeInsetsDpString = res.getString(
com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 551e3bf..a96d224 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -125,6 +125,7 @@
private final Paint mBackgroundPaint = new Paint();
private final int mStatusBarColor;
@VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
+ private final int mOrientationOnCreation;
static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
TaskSnapshot snapshot) {
@@ -146,6 +147,7 @@
final int sysUiVis;
final int windowFlags;
final int windowPrivateFlags;
+ final int currentOrientation;
synchronized (service.mWindowMap) {
final WindowState mainWindow = token.findMainWindow();
if (mainWindow == null) {
@@ -183,6 +185,7 @@
} else {
taskBounds = null;
}
+ currentOrientation = mainWindow.getConfiguration().orientation;
}
try {
final int res = session.addToDisplay(window, window.mSeq, layoutParams,
@@ -197,7 +200,8 @@
}
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
surface, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor,
- navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds);
+ navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds,
+ currentOrientation);
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
@@ -215,7 +219,7 @@
TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor,
int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags,
- Rect taskBounds) {
+ Rect taskBounds, int currentOrientation) {
mService = service;
mHandler = new Handler(mService.mH.getLooper());
mSession = WindowManagerGlobal.getWindowSession();
@@ -228,6 +232,7 @@
mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
windowPrivateFlags, sysUiVis, statusBarColor, navigationBarColor);
mStatusBarColor = statusBarColor;
+ mOrientationOnCreation = currentOrientation;
}
@Override
@@ -394,6 +399,7 @@
static class Window extends BaseIWindow {
private TaskSnapshotSurface mOuter;
+
public void setOuter(TaskSnapshotSurface outer) {
mOuter = outer;
}
@@ -403,6 +409,15 @@
Rect stableInsets, Rect outsets, boolean reportDraw,
MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
boolean alwaysConsumeNavBar, int displayId) {
+ if (mergedConfiguration != null && mOuter != null
+ && mOuter.mOrientationOnCreation
+ != mergedConfiguration.getMergedConfiguration().orientation) {
+
+ // The orientation of the screen is changing. We better remove the snapshot ASAP as
+ // we are going to wait on the new window in any case to unfreeze the screen, and
+ // the starting window is not needed anymore.
+ sHandler.post(mOuter::remove);
+ }
if (reportDraw) {
sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget();
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 03b5b827..fe5b7f2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -108,7 +108,8 @@
}
void addDisplayLocked(final int displayId) {
- // Create the DisplayContentsAnimator object by retrieving it.
+ // Create the DisplayContentsAnimator object by retrieving it if the associated
+ // {@link DisplayContent} exists.
getDisplayContentsAnimatorLocked(displayId);
if (displayId == DEFAULT_DISPLAY) {
mInitialized = true;
@@ -227,7 +228,10 @@
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
if (transactionOpen) {
- mService.closeSurfaceTransaction();
+
+ // Do not hold window manager lock while closing the transaction, as this might be
+ // blocking until the next frame, which can lead to total lock starvation.
+ mService.closeSurfaceTransaction(false /* withLockHeld */);
if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
}
}
@@ -356,8 +360,16 @@
}
private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
+ if (displayId < 0) {
+ return null;
+ }
+
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
- if (displayAnimator == null) {
+
+ // It is possible that this underlying {@link DisplayContent} has been removed. In this
+ // case, we do not want to create an animator associated with it as {link #animate} will
+ // fail.
+ if (displayAnimator == null && mService.mRoot.getDisplayContent(displayId) != null) {
displayAnimator = new DisplayContentsAnimator();
mDisplayContentsAnimators.put(displayId, displayAnimator);
}
@@ -365,8 +377,10 @@
}
void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) {
- if (displayId >= 0) {
- getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation = animation;
+ final DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
+
+ if (animator != null) {
+ animator.mScreenRotationAnimation = animation;
}
}
@@ -374,7 +388,9 @@
if (displayId < 0) {
return null;
}
- return getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation;
+
+ DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
+ return animator != null? animator.mScreenRotationAnimation : null;
}
void requestRemovalOfReplacedWindows(WindowState win) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 77e2f71..ec7ab23 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -893,10 +893,26 @@
}
void closeSurfaceTransaction() {
+ closeSurfaceTransaction(true /* withLockHeld */);
+ }
+
+ /**
+ * Closes a surface transaction.
+ *
+ * @param withLockHeld Whether to acquire the window manager while doing so. In some cases
+ * holding the lock my lead to starvation in WM in case closeTransaction
+ * blocks and we call it repeatedly, like we do for animations.
+ */
+ void closeSurfaceTransaction(boolean withLockHeld) {
synchronized (mWindowMap) {
if (mRoot.mSurfaceTraceEnabled) {
mRoot.mRemoteEventTrace.closeSurfaceTransaction();
}
+ if (withLockHeld) {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ if (!withLockHeld) {
SurfaceControl.closeTransaction();
}
}
@@ -1270,14 +1286,6 @@
+ token + ". Aborting.");
return WindowManagerGlobal.ADD_APP_EXITING;
}
- if (rootType == TYPE_APPLICATION_STARTING
- && (attrs.privateFlags & PRIVATE_FLAG_TASK_SNAPSHOT) == 0
- && atoken.firstWindowDrawn) {
- // No need for this guy!
- if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
- TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
- return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
- }
} else if (rootType == TYPE_INPUT_METHOD) {
if (token.windowType != TYPE_INPUT_METHOD) {
Slog.w(TAG_WM, "Attempted to add input method window with bad token "
@@ -5150,7 +5158,8 @@
}
break;
case NOTIFY_APP_TRANSITION_STARTING: {
- mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj);
+ mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj,
+ msg.getWhen());
}
break;
case NOTIFY_APP_TRANSITION_CANCELLED: {
@@ -5890,8 +5899,8 @@
return;
}
- if (!mDisplayReady || !mPolicy.isScreenOn()) {
- // No need to freeze the screen before the system is ready or if
+ if (!displayContent.isReady() || !mPolicy.isScreenOn()) {
+ // No need to freeze the screen before the display is ready, system is ready, or if
// the screen is off.
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
index 6a244a2..1b2eb46 100644
--- a/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
+++ b/services/core/java/com/android/server/wm/WindowManagerThreadPriorityBooster.java
@@ -23,6 +23,7 @@
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.AnimationThread;
import com.android.server.ThreadPriorityBooster;
@@ -32,12 +33,18 @@
*/
class WindowManagerThreadPriorityBooster extends ThreadPriorityBooster {
- private final AnimationThread mAnimationThread;
+ private final Object mLock = new Object();
+
+ private final int mAnimationThreadId;
+
+ @GuardedBy("mLock")
private boolean mAppTransitionRunning;
+ @GuardedBy("mLock")
+ private boolean mBoundsAnimationRunning;
WindowManagerThreadPriorityBooster() {
super(THREAD_PRIORITY_DISPLAY, INDEX_WINDOW);
- mAnimationThread = AnimationThread.get();
+ mAnimationThreadId = AnimationThread.get().getThreadId();
}
@Override
@@ -45,7 +52,7 @@
// Do not boost the animation thread. As the animation thread is changing priorities,
// boosting it might mess up the priority because we reset it the the previous priority.
- if (myTid() == mAnimationThread.getThreadId()) {
+ if (myTid() == mAnimationThreadId) {
return;
}
super.boost();
@@ -55,24 +62,35 @@
public void reset() {
// See comment in boost().
- if (myTid() == mAnimationThread.getThreadId()) {
+ if (myTid() == mAnimationThreadId) {
return;
}
super.reset();
}
void setAppTransitionRunning(boolean running) {
- if (mAppTransitionRunning == running) {
- return;
+ synchronized (mLock) {
+ if (mAppTransitionRunning != running) {
+ mAppTransitionRunning = running;
+ updatePriorityLocked();
+ }
}
-
- final int priority = calculatePriority(running);
- setBoostToPriority(priority);
- setThreadPriority(mAnimationThread.getThreadId(), priority);
- mAppTransitionRunning = running;
}
- private int calculatePriority(boolean appTransitionRunning) {
- return appTransitionRunning ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
+ void setBoundsAnimationRunning(boolean running) {
+ synchronized (mLock) {
+ if (mBoundsAnimationRunning != running) {
+ mBoundsAnimationRunning = running;
+ updatePriorityLocked();
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void updatePriorityLocked() {
+ int priority = (mAppTransitionRunning || mBoundsAnimationRunning)
+ ? TOP_APP_PRIORITY_BOOST : THREAD_PRIORITY_DISPLAY;
+ setBoostToPriority(priority);
+ setThreadPriority(mAnimationThreadId, priority);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 73f8d27..33cb908 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1524,7 +1524,10 @@
void prepareSurfaceLocked(final boolean recoveringMemory) {
final WindowState w = mWin;
if (!hasSurface()) {
- if (w.mOrientationChanging) {
+
+ // There is no need to wait for an animation change if our window is gone for layout
+ // already as we'll never be visible.
+ if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
if (DEBUG_ORIENTATION) {
Slog.v(TAG, "Orientation change skips hidden " + w);
}
@@ -1557,13 +1560,11 @@
hide("prepareSurfaceLocked");
mWallpaperControllerLocked.hideWallpapers(w);
- // If we are waiting for this window to handle an
- // orientation change, well, it is hidden, so
- // doesn't really matter. Note that this does
- // introduce a potential glitch if the window
- // becomes unhidden before it has drawn for the
- // new orientation.
- if (w.mOrientationChanging) {
+ // If we are waiting for this window to handle an orientation change. If this window is
+ // really hidden (gone for layout), there is no point in still waiting for it.
+ // Note that this does introduce a potential glitch if the window becomes unhidden
+ // before it has drawn for the new orientation.
+ if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
w.mOrientationChanging = false;
if (DEBUG_ORIENTATION) Slog.v(TAG,
"Orientation change skips hidden " + w);
@@ -1630,18 +1631,19 @@
displayed = true;
}
- if (displayed) {
- if (w.mOrientationChanging) {
- if (!w.isDrawnLw()) {
- mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
- mAnimator.mLastWindowFreezeSource = w;
- if (DEBUG_ORIENTATION) Slog.v(TAG,
- "Orientation continue waiting for draw in " + w);
- } else {
- w.mOrientationChanging = false;
- if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
- }
+ if (w.mOrientationChanging) {
+ if (!w.isDrawnLw()) {
+ mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
+ mAnimator.mLastWindowFreezeSource = w;
+ if (DEBUG_ORIENTATION) Slog.v(TAG,
+ "Orientation continue waiting for draw in " + w);
+ } else {
+ w.mOrientationChanging = false;
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
}
+ }
+
+ if (displayed) {
w.mToken.hasVisible = true;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 4442bb8..82c862f 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -25,6 +25,7 @@
import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_WALLPAPER_OPEN;
+import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -239,7 +240,7 @@
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
int transit = mService.mAppTransition.getAppTransition();
- if (mService.mSkipAppTransitionAnimation) {
+ if (mService.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) {
transit = AppTransition.TRANSIT_UNSET;
}
mService.mSkipAppTransitionAnimation = false;
@@ -598,42 +599,47 @@
+ ", openingApps=" + openingApps
+ ", closingApps=" + closingApps);
mService.mAnimateWallpaperWithTarget = false;
- if (closingAppHasWallpaper && openingAppHasWallpaper) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
- switch (transit) {
- case TRANSIT_ACTIVITY_OPEN:
- case TRANSIT_TASK_OPEN:
- case TRANSIT_TASK_TO_FRONT:
- transit = TRANSIT_WALLPAPER_INTRA_OPEN;
- break;
- case TRANSIT_ACTIVITY_CLOSE:
- case TRANSIT_TASK_CLOSE:
- case TRANSIT_TASK_TO_BACK:
- transit = TRANSIT_WALLPAPER_INTRA_CLOSE;
- break;
- }
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "New transit: " + AppTransition.appTransitionToString(transit));
- } else if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+ if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) {
transit = TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit: " + AppTransition.appTransitionToString(transit));
- } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
- && !openingApps.contains(oldWallpaper.mAppToken)
- && closingApps.contains(oldWallpaper.mAppToken)) {
- // We are transitioning from an activity with a wallpaper to one without.
- transit = TRANSIT_WALLPAPER_CLOSE;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
- + AppTransition.appTransitionToString(transit));
- } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
- openingApps.contains(wallpaperTarget.mAppToken)) {
- // We are transitioning from an activity without
- // a wallpaper to now showing the wallpaper
- transit = TRANSIT_WALLPAPER_OPEN;
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: "
- + AppTransition.appTransitionToString(transit));
- } else {
- mService.mAnimateWallpaperWithTarget = true;
+ }
+ // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic
+ // relies on the fact that we always execute a Keyguard transition after preparing one.
+ else if (!isKeyguardGoingAwayTransit(transit)) {
+ if (closingAppHasWallpaper && openingAppHasWallpaper) {
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
+ switch (transit) {
+ case TRANSIT_ACTIVITY_OPEN:
+ case TRANSIT_TASK_OPEN:
+ case TRANSIT_TASK_TO_FRONT:
+ transit = TRANSIT_WALLPAPER_INTRA_OPEN;
+ break;
+ case TRANSIT_ACTIVITY_CLOSE:
+ case TRANSIT_TASK_CLOSE:
+ case TRANSIT_TASK_TO_BACK:
+ transit = TRANSIT_WALLPAPER_INTRA_CLOSE;
+ break;
+ }
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+ "New transit: " + AppTransition.appTransitionToString(transit));
+ } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
+ && !openingApps.contains(oldWallpaper.mAppToken)
+ && closingApps.contains(oldWallpaper.mAppToken)) {
+ // We are transitioning from an activity with a wallpaper to one without.
+ transit = TRANSIT_WALLPAPER_CLOSE;
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
+ + AppTransition.appTransitionToString(transit));
+ } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw() &&
+ openingApps.contains(wallpaperTarget.mAppToken)) {
+ // We are transitioning from an activity without
+ // a wallpaper to now showing the wallpaper
+ transit = TRANSIT_WALLPAPER_OPEN;
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: "
+ + AppTransition.appTransitionToString(transit));
+ } else {
+ mService.mAnimateWallpaperWithTarget = true;
+ }
}
return transit;
}
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 96c2d7e..470cc57 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -39,7 +39,7 @@
}
-static void android_server_SystemServer_startHidlServices(JNIEnv* /* env */, jobject /* clazz */) {
+static void android_server_SystemServer_startHidlServices(JNIEnv* env, jobject /* clazz */) {
using ::android::frameworks::schedulerservice::V1_0::ISchedulingPolicyService;
using ::android::frameworks::schedulerservice::V1_0::implementation::SchedulingPolicyService;
using ::android::frameworks::sensorservice::V1_0::ISensorManager;
@@ -50,7 +50,10 @@
configureRpcThreadpool(5, false /* callerWillJoin */);
- sp<ISensorManager> sensorService = new SensorManager();
+ JavaVM *vm;
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Cannot get Java VM");
+
+ sp<ISensorManager> sensorService = new SensorManager(vm);
err = sensorService->registerAsService();
ALOGE_IF(err != OK, "Cannot register %s: %d", ISensorManager::descriptor, err);
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index ec08874..ae98274 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -20,40 +20,9 @@
import static android.app.Notification.GROUP_ALERT_SUMMARY;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
-import com.android.server.lights.Light;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.Notification.Builder;
-import android.app.NotificationManager;
-import android.app.NotificationChannel;
-import android.graphics.Color;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.os.VibrationEffect;
-import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
@@ -61,11 +30,43 @@
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.lights.Light;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BuzzBeepBlinkTest extends NotificationTestCase {
@@ -163,6 +164,11 @@
true /* noisy */, false /* buzzy*/, false /* lights */);
}
+ private NotificationRecord getInsistentBeepyLeanbackNotification() {
+ return getLeanbackNotificationRecord(mId, true /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */);
+ }
+
private NotificationRecord getBuzzyNotification() {
return getNotificationRecord(mId, false /* insistent */, false /* once */,
false /* noisy */, true /* buzzy*/, false /* lights */);
@@ -192,23 +198,30 @@
return getNotificationRecord(mId, false /* insistent */, true /* once */,
false /* noisy */, true /* buzzy*/, true /* lights */,
true /* defaultVibration */, true /* defaultSound */, false /* defaultLights */,
- null, Notification.GROUP_ALERT_ALL);
+ null, Notification.GROUP_ALERT_ALL, false);
}
private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
boolean noisy, boolean buzzy, boolean lights) {
return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
- null, Notification.GROUP_ALERT_ALL);
+ null, Notification.GROUP_ALERT_ALL, false);
+ }
+
+ private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, boolean once,
+ boolean noisy, boolean buzzy, boolean lights) {
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
+ null, Notification.GROUP_ALERT_ALL, true);
}
private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) {
return getNotificationRecord(mId, false, false, true, false, false, true, true, true,
- groupKey, groupAlertBehavior);
+ groupKey, groupAlertBehavior, false);
}
private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
- boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior) {
+ boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+ boolean isLeanback) {
NotificationChannel channel =
new NotificationChannel("test", "test", IMPORTANCE_HIGH);
final Builder builder = new Builder(getContext())
@@ -257,9 +270,15 @@
n.flags |= Notification.FLAG_INSISTENT;
}
+ Context context = spy(getContext());
+ PackageManager packageManager = spy(context.getPackageManager());
+ when(context.getPackageManager()).thenReturn(packageManager);
+ when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+ .thenReturn(isLeanback);
+
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+ NotificationRecord r = new NotificationRecord(context, sbn, channel);
mService.addNotification(r);
return r;
}
@@ -367,6 +386,15 @@
}
@Test
+ public void testNoLeanbackBeep() throws Exception {
+ NotificationRecord r = getInsistentBeepyLeanbackNotification();
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verifyNeverBeep();
+ }
+
+ @Test
public void testNoInterruptionForMin() throws Exception {
NotificationRecord r = getBeepyNotification();
r.setImportance(NotificationManager.IMPORTANCE_MIN, "foo");
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index bd6e379..46c536c 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -357,6 +357,43 @@
}
@Test
+ public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception {
+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+ for (int i = 0; i < 10; i++) {
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ }
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
+ waitForIdle();
+ }
+
+ @Test
+ public void testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash() throws Exception {
+ final NotificationRecord parent = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group1", true);
+ final NotificationRecord parentAsChild = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group1", false);
+ final NotificationRecord child = generateNotificationRecord(
+ mTestNotificationChannel, 2, "group1", false);
+
+ // fully post parent notification
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
+ waitForIdle();
+
+ // enqueue the child several times
+ for (int i = 0; i < 10; i++) {
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
+ }
+ // make the parent a child, which will cancel the child notification
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+ parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(),
+ parentAsChild.sbn.getUserId());
+ waitForIdle();
+ }
+
+ @Test
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 65a5632..6060881 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -107,7 +107,7 @@
createAppWindowController();
controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false);
+ false, false);
waitUntilHandlersIdle();
final AppWindowToken atoken = controller.getAppWindowToken(mDisplayContent);
assertHasStartingWindow(atoken);
@@ -125,7 +125,7 @@
createAppWindowController();
controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false);
+ false, false);
controller.removeStartingWindow();
waitUntilHandlersIdle();
assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent));
@@ -140,11 +140,11 @@
createAppWindowController();
controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false);
+ false, false);
waitUntilHandlersIdle();
controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
- true, true, false, true, false);
+ true, true, false, true, false, false);
waitUntilHandlersIdle();
assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent));
assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent));
@@ -161,11 +161,11 @@
// Surprise, ...! Transfer window in the middle of the creation flow.
controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(),
- true, true, false, true, false);
+ true, true, false, true, false, false);
});
controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(),
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
- false);
+ false, false);
waitUntilHandlersIdle();
assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent));
assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent));
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index e2868d7..4288eac 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -62,7 +62,8 @@
final TaskSnapshot snapshot = new TaskSnapshot(buffer,
ORIENTATION_PORTRAIT, contentInsets, false, 1.0f);
mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test",
- Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds);
+ Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds,
+ ORIENTATION_PORTRAIT);
}
private void setupSurface(int width, int height) {
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 8a5fd4b6..4d2f97f 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -111,7 +111,6 @@
private Map<String, Intent> mNameToIntent;
- private Map<String, String> mNameToProcess;
private List<LaunchOrder> mLaunchOrderList = new ArrayList<LaunchOrder>();
private Map<String, String> mNameToResultKey;
private Map<String, List<Long>> mNameToLaunchTime;
@@ -431,7 +430,6 @@
private void createMappings() {
mNameToIntent = new LinkedHashMap<String, Intent>();
- mNameToProcess = new LinkedHashMap<String, String>();
PackageManager pm = getInstrumentation().getContext()
.getPackageManager();
@@ -459,8 +457,9 @@
ri.activityInfo.name);
String appName = ri.loadLabel(pm).toString();
if (appName != null) {
+ // Support launching intent using package name or app name
+ mNameToIntent.put(ri.activityInfo.packageName, startIntent);
mNameToIntent.put(appName, startIntent);
- mNameToProcess.put(appName, ri.activityInfo.processName);
}
}
}
@@ -665,7 +664,8 @@
if (lineCount == 2 && line.contains(SUCCESS_MESSAGE)) {
launchSuccess = true;
}
- if (launchSuccess && lineCount == 4) {
+ // Parse TotalTime which is the launch time
+ if (launchSuccess && lineCount == 5) {
String launchSplit[] = line.split(":");
launchTime = launchSplit[1].trim();
}
diff --git a/tests/JobSchedulerTestApp/res/layout/activity_main.xml b/tests/JobSchedulerTestApp/res/layout/activity_main.xml
index 96e1641..41f9777 100644
--- a/tests/JobSchedulerTestApp/res/layout/activity_main.xml
+++ b/tests/JobSchedulerTestApp/res/layout/activity_main.xml
@@ -73,10 +73,18 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
+ <RadioButton android:id="@+id/checkbox_none"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/none"/>
<RadioButton android:id="@+id/checkbox_any"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/any"/>
+ <RadioButton android:id="@+id/checkbox_metered"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/metered"/>
<RadioButton android:id="@+id/checkbox_unmetered"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/tests/JobSchedulerTestApp/res/values/strings.xml b/tests/JobSchedulerTestApp/res/values/strings.xml
index 90dd2b6..866b61e 100644
--- a/tests/JobSchedulerTestApp/res/values/strings.xml
+++ b/tests/JobSchedulerTestApp/res/values/strings.xml
@@ -30,7 +30,9 @@
<string name="persisted_caption">Persisted:</string>
<string name="constraints">Constraints</string>
<string name="connectivity">Connectivity:</string>
+ <string name="none">None</string>
<string name="any">Any</string>
+ <string name="metered">Metered</string>
<string name="unmetered">WiFi</string>
<string name="timing">Timing:</string>
<string name="delay">Delay:</string>
diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
index 51cdbb5..3dfdba7 100644
--- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
+++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/MainActivity.java
@@ -63,6 +63,7 @@
mDeadlineEditText = findViewById(R.id.deadline_time);
mWiFiConnectivityRadioButton = findViewById(R.id.checkbox_unmetered);
mAnyConnectivityRadioButton = findViewById(R.id.checkbox_any);
+ mCellConnectivityRadioButton = findViewById(R.id.checkbox_metered);
mRequiresChargingCheckBox = findViewById(R.id.checkbox_charging);
mRequiresIdleCheckbox = findViewById(R.id.checkbox_idle);
mIsPersistedCheckbox = findViewById(R.id.checkbox_persisted);
@@ -85,6 +86,7 @@
EditText mDeadlineEditText;
RadioButton mWiFiConnectivityRadioButton;
RadioButton mAnyConnectivityRadioButton;
+ RadioButton mCellConnectivityRadioButton;
CheckBox mRequiresChargingCheckBox;
CheckBox mRequiresIdleCheckbox;
CheckBox mIsPersistedCheckbox;
@@ -141,9 +143,12 @@
builder.setOverrideDeadline(Long.parseLong(deadline) * 1000);
}
boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
+ boolean requiresMetered = mCellConnectivityRadioButton.isChecked();
boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
if (requiresUnmetered) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+ } else if (requiresMetered) {
+ builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
} else if (requiresAnyConnectivity) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
}
diff --git a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
index 9df11fe..b698a3a 100644
--- a/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
+++ b/tests/JobSchedulerTestApp/src/com/android/demo/jobSchedulerApp/service/TestJobService.java
@@ -81,7 +81,8 @@
@Override
public boolean onStartJob(JobParameters params) {
- Log.i(TAG, "on start job: " + params.getJobId());
+ Log.i(TAG, "on start job: " + params.getJobId()
+ + " deadline?=" + params.isOverrideDeadlineExpired());
currentId++;
jobParamsMap.put(currentId, params);
final int currId = this.currentId;