Merge "Also log when mayWait=false"
diff --git a/api/current.txt b/api/current.txt
index 2512b17..e6b44f1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -102,7 +102,7 @@
field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
- field public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+ field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
@@ -15389,8 +15389,8 @@
method public boolean setState(@NonNull int[]);
method public void setTint(@ColorInt int);
method public void setTintList(@Nullable android.content.res.ColorStateList);
- method @Deprecated public void setTintMode(@NonNull android.graphics.PorterDuff.Mode);
- method public void setTintMode(@NonNull android.graphics.BlendMode);
+ method @Deprecated public void setTintMode(@Nullable android.graphics.PorterDuff.Mode);
+ method public void setTintMode(@Nullable android.graphics.BlendMode);
method public boolean setVisible(boolean, boolean);
method public void unscheduleSelf(@NonNull Runnable);
}
@@ -17058,6 +17058,7 @@
public class CaptureFailure {
method public long getFrameNumber();
+ method @Nullable public String getPhysicalCameraId();
method public int getReason();
method @NonNull public android.hardware.camera2.CaptureRequest getRequest();
method public int getSequenceId();
@@ -42534,6 +42535,7 @@
method public static int getpid();
method public static int getppid();
method public static java.net.SocketAddress getsockname(java.io.FileDescriptor) throws android.system.ErrnoException;
+ method @NonNull public static android.system.StructTimeval getsockoptTimeval(@NonNull java.io.FileDescriptor, int, int) throws android.system.ErrnoException;
method public static int gettid();
method public static int getuid();
method public static byte[] getxattr(String, String) throws android.system.ErrnoException;
@@ -42584,6 +42586,7 @@
method @Deprecated public static void setgid(int) throws android.system.ErrnoException;
method public static int setsid() throws android.system.ErrnoException;
method public static void setsockoptInt(java.io.FileDescriptor, int, int, int) throws android.system.ErrnoException;
+ method public static void setsockoptTimeval(@NonNull java.io.FileDescriptor, int, int, @NonNull android.system.StructTimeval) throws android.system.ErrnoException;
method @Deprecated public static void setuid(int) throws android.system.ErrnoException;
method public static void setxattr(String, String, byte[], int) throws android.system.ErrnoException;
method public static void shutdown(java.io.FileDescriptor, int) throws android.system.ErrnoException;
@@ -42789,6 +42792,10 @@
field public static final int F_SETOWN;
field public static final int F_UNLCK;
field public static final int F_WRLCK;
+ field public static final int ICMP6_ECHO_REPLY;
+ field public static final int ICMP6_ECHO_REQUEST;
+ field public static final int ICMP_ECHO;
+ field public static final int ICMP_ECHOREPLY;
field public static final int IFA_F_DADFAILED;
field public static final int IFA_F_DEPRECATED;
field public static final int IFA_F_HOMEADDRESS;
@@ -43161,6 +43168,13 @@
field public final long tv_sec;
}
+ public final class StructTimeval {
+ method @NonNull public static android.system.StructTimeval fromMillis(long);
+ method public long toMillis();
+ field public final long tv_sec;
+ field public final long tv_usec;
+ }
+
public final class StructUtsname {
ctor public StructUtsname(String, String, String, String, String);
field public final String machine;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4b0c05f..ddae34c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5440,13 +5440,14 @@
}
/**
- * Called by a device or profile owner to set whether auto time is required. If auto time is
- * required, no user will be able set the date and time and network date and time will be used.
+ * Called by a device owner, or alternatively a profile owner from Android 8.0 (API level 26) or
+ * higher, to set whether auto time is required. If auto time is required, no user will be able
+ * set the date and time and network date and time will be used.
* <p>
* Note: if auto time is required the user can still manually set the time zone.
* <p>
- * The calling device admin must be a device or profile owner. If it is not, a security
- * exception will be thrown.
+ * The calling device admin must be a device owner, or alternatively a profile owner from
+ * Android 8.0 (API level 26) or higher. If it is not, a security exception will be thrown.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param required Whether auto time is set required or not.
@@ -10789,8 +10790,8 @@
}
/**
- * Returns whether the device is being used as a managed kiosk, as defined in the CDD. As of
- * this release, these requirements are as follows:
+ * Returns whether the device is being used as a managed kiosk. These requirements are as
+ * follows:
* <ul>
* <li>The device is in Lock Task (therefore there is also a Device Owner app on the
* device)</li>
@@ -10829,11 +10830,11 @@
}
/**
- * Returns whether the device is being used as an unattended managed kiosk, as defined in the
- * CDD. As of this release, these requirements are as follows:
+ * Returns whether the device is being used as an unattended managed kiosk. These requirements
+ * are as follows:
* <ul>
- * <li>The device is being used as a managed kiosk, as defined in the CDD and verified at
- * {@link #isManagedKiosk()}</li>
+ * <li>The device is being used as a managed kiosk, as defined at {@link
+ * #isManagedKiosk()}</li>
* <li>The device has not received user input for at least 30 minutes</li>
* </ul>
*
diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java
index cd2bc5f..20ca4a3 100644
--- a/core/java/android/hardware/camera2/CaptureFailure.java
+++ b/core/java/android/hardware/camera2/CaptureFailure.java
@@ -17,6 +17,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -61,17 +62,19 @@
private final boolean mDropped;
private final int mSequenceId;
private final long mFrameNumber;
+ private final String mErrorPhysicalCameraId;
/**
* @hide
*/
public CaptureFailure(CaptureRequest request, int reason,
- boolean dropped, int sequenceId, long frameNumber) {
+ boolean dropped, int sequenceId, long frameNumber, String errorPhysicalCameraId) {
mRequest = request;
mReason = reason;
mDropped = dropped;
mSequenceId = sequenceId;
mFrameNumber = frameNumber;
+ mErrorPhysicalCameraId = errorPhysicalCameraId;
}
/**
@@ -155,4 +158,17 @@
public int getSequenceId() {
return mSequenceId;
}
+
+ /**
+ * The physical camera device ID in case the capture failure comes from a {@link CaptureRequest}
+ * with configured physical camera streams for a logical camera.
+ *
+ * @return String The physical camera device ID of the respective failing output.
+ * {@code null} in case the capture request has no associated physical camera device.
+ * @see CaptureRequest.Builder#setPhysicalCameraKey
+ * @see android.hardware.camera2.params.OutputConfiguration#setPhysicalCameraId
+ */
+ public @Nullable String getPhysicalCameraId() {
+ return mErrorPhysicalCameraId;
+ }
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 57b608f..fc12b09 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -2232,6 +2232,7 @@
final int requestId = resultExtras.getRequestId();
final int subsequenceId = resultExtras.getSubsequenceId();
final long frameNumber = resultExtras.getFrameNumber();
+ final String errorPhysicalCameraId = resultExtras.getErrorPhysicalCameraId();
final CaptureCallbackHolder holder =
CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
@@ -2287,7 +2288,8 @@
reason,
/*dropped*/ mayHaveBuffers,
requestId,
- frameNumber);
+ frameNumber,
+ errorPhysicalCameraId);
failureDispatch = new Runnable() {
@Override
diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
index edc3d91..1ff5bd5 100644
--- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
+++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java
@@ -29,6 +29,7 @@
private long frameNumber;
private int partialResultCount;
private int errorStreamId;
+ private String errorPhysicalCameraId;
public static final @android.annotation.NonNull Parcelable.Creator<CaptureResultExtras> CREATOR =
new Parcelable.Creator<CaptureResultExtras>() {
@@ -49,7 +50,8 @@
public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId,
int precaptureTriggerId, long frameNumber,
- int partialResultCount, int errorStreamId) {
+ int partialResultCount, int errorStreamId,
+ String errorPhysicalCameraId) {
this.requestId = requestId;
this.subsequenceId = subsequenceId;
this.afTriggerId = afTriggerId;
@@ -57,6 +59,7 @@
this.frameNumber = frameNumber;
this.partialResultCount = partialResultCount;
this.errorStreamId = errorStreamId;
+ this.errorPhysicalCameraId = errorPhysicalCameraId;
}
@Override
@@ -73,6 +76,12 @@
dest.writeLong(frameNumber);
dest.writeInt(partialResultCount);
dest.writeInt(errorStreamId);
+ if ((errorPhysicalCameraId != null) && !errorPhysicalCameraId.isEmpty()) {
+ dest.writeBoolean(true);
+ dest.writeString(errorPhysicalCameraId);
+ } else {
+ dest.writeBoolean(false);
+ }
}
public void readFromParcel(Parcel in) {
@@ -83,6 +92,14 @@
frameNumber = in.readLong();
partialResultCount = in.readInt();
errorStreamId = in.readInt();
+ boolean errorPhysicalCameraIdPresent = in.readBoolean();
+ if (errorPhysicalCameraIdPresent) {
+ errorPhysicalCameraId = in.readString();
+ }
+ }
+
+ public String getErrorPhysicalCameraId() {
+ return errorPhysicalCameraId;
}
public int getRequestId() {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index aff09f2..908ed09 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -109,11 +109,11 @@
}
if (holder == null) {
return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
- ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE);
+ ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null);
}
return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
/*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(),
- /*partialResultCount*/1, errorStreamId);
+ /*partialResultCount*/1, errorStreamId, null);
}
/**
diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
index a7b280b..2fab404 100644
--- a/core/java/android/speech/tts/TtsEngines.java
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -15,8 +15,10 @@
*/
package android.speech.tts;
-import org.xmlpull.v1.XmlPullParserException;
+import static android.provider.Settings.Secure.getString;
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -27,10 +29,6 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-
-import static android.provider.Settings.Secure.getString;
-
-import android.annotation.UnsupportedAppUsage;
import android.provider.Settings;
import android.speech.tts.TextToSpeech.Engine;
import android.speech.tts.TextToSpeech.EngineInfo;
@@ -39,6 +37,8 @@
import android.util.Log;
import android.util.Xml;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -522,7 +522,8 @@
* read back, will evaluate to {@link Locale#getDefault()}.
*/
@UnsupportedAppUsage
- public synchronized void updateLocalePrefForEngine(String engineName, Locale newLocale) {
+ public synchronized void updateLocalePrefForEngine(
+ @NonNull String engineName, Locale newLocale) {
final String prefList = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.TTS_DEFAULT_LOCALE);
if (DBG) {
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index c794a69..8fbbcf4 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -16,11 +16,20 @@
package android.view;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
+
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
+import android.os.SystemClock;
+import android.util.StatsLog;
/**
* Detects various gestures and events using the supplied {@link MotionEvent}s.
@@ -251,8 +260,12 @@
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
private boolean mIgnoreNextUpEvent;
+ // Whether a classification has been recorded by statsd for the current event stream. Reset on
+ // ACTION_DOWN.
+ private boolean mHasRecordedClassification;
private MotionEvent mCurrentDownEvent;
+ private MotionEvent mCurrentMotionEvent;
private MotionEvent mPreviousUpEvent;
/**
@@ -297,6 +310,7 @@
break;
case LONG_PRESS:
+ recordGestureClassification(msg.arg1);
dispatchLongPress();
break;
@@ -304,6 +318,8 @@
// If the user's finger is still down, do not count it as a tap
if (mDoubleTapListener != null) {
if (!mStillDown) {
+ recordGestureClassification(
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else {
mDeferConfirmSingleTap = true;
@@ -501,6 +517,11 @@
final int action = ev.getAction();
+ if (mCurrentMotionEvent != null) {
+ mCurrentMotionEvent.recycle();
+ }
+ mCurrentMotionEvent = MotionEvent.obtain(ev);
+
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
@@ -569,6 +590,8 @@
&& isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
+ recordGestureClassification(
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
// Give a callback with the first tap of the double-tap
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
// Give a callback with down event of the double-tap
@@ -590,11 +613,17 @@
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
+ mHasRecordedClassification = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
- mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
- + ViewConfiguration.getLongPressTimeout());
+ mHandler.sendMessageAtTime(
+ mHandler.obtainMessage(
+ LONG_PRESS,
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
+ 0 /* arg2 */),
+ mCurrentDownEvent.getDownTime()
+ + ViewConfiguration.getLongPressTimeout());
}
mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
@@ -613,6 +642,8 @@
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
+ recordGestureClassification(
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
final int deltaX = (int) (focusX - mDownFocusX);
@@ -635,8 +666,12 @@
// reschedule long press with a modified timeout.
mHandler.removeMessages(LONG_PRESS);
final long longPressTimeout = ViewConfiguration.getLongPressTimeout();
- mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime()
- + (long) (longPressTimeout * multiplier));
+ mHandler.sendMessageAtTime(
+ mHandler.obtainMessage(
+ LONG_PRESS,
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
+ 0 /* arg2 */),
+ ev.getDownTime() + (long) (longPressTimeout * multiplier));
}
// Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
// until the gesture is resolved.
@@ -646,6 +681,8 @@
}
if (distance > slopSquare) {
+ recordGestureClassification(
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
@@ -659,6 +696,7 @@
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
+ recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
@@ -667,7 +705,11 @@
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPress) {
mHandler.removeMessages(LONG_PRESS);
- mHandler.sendEmptyMessage(LONG_PRESS);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(
+ LONG_PRESS,
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS,
+ 0 /* arg2 */));
}
break;
@@ -676,11 +718,15 @@
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
// Finally, give the up event of the double-tap
+ recordGestureClassification(
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mInLongPress) {
mHandler.removeMessages(TAP);
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
+ recordGestureClassification(
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
handled = mListener.onSingleTapUp(ev);
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
mDoubleTapListener.onSingleTapConfirmed(ev);
@@ -821,4 +867,26 @@
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
+
+ private void recordGestureClassification(int classification) {
+ if (mHasRecordedClassification
+ || classification
+ == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
+ // Only record the first classification for an event stream.
+ return;
+ }
+ if (mCurrentDownEvent == null || mCurrentMotionEvent == null) {
+ // If the complete event stream wasn't seen, don't record anything.
+ mHasRecordedClassification = true;
+ return;
+ }
+ StatsLog.write(
+ StatsLog.TOUCH_GESTURE_CLASSIFIED,
+ getClass().getName(),
+ classification,
+ (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()),
+ (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(),
+ mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY()));
+ mHasRecordedClassification = true;
+ }
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 13b0cc0..b76f2a1 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -17,7 +17,10 @@
package android.view;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.SIZE;
+import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.indexOf;
import android.annotation.IntDef;
@@ -55,6 +58,12 @@
TYPE_SIDE_BAR_1,
TYPE_SIDE_BAR_2,
TYPE_SIDE_BAR_3,
+ TYPE_TOP_GESTURES,
+ TYPE_BOTTOM_GESTURES,
+ TYPE_LEFT_GESTURES,
+ TYPE_RIGHT_GESTURES,
+ TYPE_TOP_TAPPABLE_ELEMENT,
+ TYPE_BOTTOM_TAPPABLE_ELEMENT,
TYPE_IME
})
public @interface InternalInsetType {}
@@ -73,8 +82,16 @@
public static final int TYPE_SIDE_BAR_2 = 2;
public static final int TYPE_SIDE_BAR_3 = 3;
+ public static final int TYPE_TOP_GESTURES = 4;
+ public static final int TYPE_BOTTOM_GESTURES = 5;
+ public static final int TYPE_LEFT_GESTURES = 6;
+ public static final int TYPE_RIGHT_GESTURES = 7;
+ public static final int TYPE_TOP_TAPPABLE_ELEMENT = 8;
+ public static final int TYPE_BOTTOM_TAPPABLE_ELEMENT = 9;
+
/** Input method window. */
- public static final int TYPE_IME = 4;
+ public static final int TYPE_IME = 10;
+
static final int LAST_TYPE = TYPE_IME;
// Derived types
@@ -137,17 +154,6 @@
&& legacyContentInsets != null && legacyStableInsets != null) {
WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
-
- // TODO: set system gesture insets based on actual system gesture area.
- typeInsetsMap[Type.indexOf(Type.systemGestures())] = Insets.of(legacyContentInsets);
- typeInsetsMap[Type.indexOf(Type.mandatorySystemGestures())] =
- Insets.of(legacyContentInsets);
- typeInsetsMap[Type.indexOf(Type.tappableElement())] = Insets.of(legacyContentInsets);
-
- typeMaxInsetsMap[Type.indexOf(Type.systemGestures())] = Insets.of(legacyStableInsets);
- typeMaxInsetsMap[Type.indexOf(Type.mandatorySystemGestures())] =
- Insets.of(legacyStableInsets);
- typeMaxInsetsMap[Type.indexOf(Type.tappableElement())] = Insets.of(legacyStableInsets);
}
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources.get(type);
@@ -159,7 +165,9 @@
&& (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR);
boolean skipIme = source.getType() == TYPE_IME
&& (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0;
- if (skipSystemBars || skipIme) {
+ boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+ && (toPublicType(type) & Type.compatSystemInsets()) != 0;
+ if (skipSystemBars || skipIme || skipLegacyTypes) {
typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
continue;
}
@@ -183,7 +191,25 @@
@Nullable boolean[] typeVisibilityMap) {
Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
- int index = indexOf(toPublicType(source.getType()));
+ int type = toPublicType(source.getType());
+ processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+ insets, type);
+
+ if (type == MANDATORY_SYSTEM_GESTURES) {
+ // Mandatory system gestures are also system gestures.
+ // TODO: find a way to express this more generally. One option would be to define
+ // Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
+ // ability to set systemGestureInsets() independently from
+ // mandatorySystemGestureInsets() in the Builder.
+ processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+ insets, SYSTEM_GESTURES);
+ }
+ }
+
+ private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
+ @InsetSide @Nullable SparseIntArray typeSideMap,
+ @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
+ int index = indexOf(type);
Insets existing = typeInsetsMap[index];
if (existing == null) {
typeInsetsMap[index] = insets;
@@ -300,6 +326,15 @@
return Type.SIDE_BARS;
case TYPE_IME:
return Type.IME;
+ case TYPE_TOP_GESTURES:
+ case TYPE_BOTTOM_GESTURES:
+ return Type.MANDATORY_SYSTEM_GESTURES;
+ case TYPE_LEFT_GESTURES:
+ case TYPE_RIGHT_GESTURES:
+ return Type.SYSTEM_GESTURES;
+ case TYPE_TOP_TAPPABLE_ELEMENT:
+ case TYPE_BOTTOM_TAPPABLE_ELEMENT:
+ return Type.TAPPABLE_ELEMENT;
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
@@ -336,10 +371,20 @@
return "TYPE_SIDE_BAR_2";
case TYPE_SIDE_BAR_3:
return "TYPE_SIDE_BAR_3";
- case TYPE_IME:
- return "TYPE_IME";
+ case TYPE_TOP_GESTURES:
+ return "TYPE_TOP_GESTURES";
+ case TYPE_BOTTOM_GESTURES:
+ return "TYPE_BOTTOM_GESTURES";
+ case TYPE_LEFT_GESTURES:
+ return "TYPE_LEFT_GESTURES";
+ case TYPE_RIGHT_GESTURES:
+ return "TYPE_RIGHT_GESTURES";
+ case TYPE_TOP_TAPPABLE_ELEMENT:
+ return "TYPE_TOP_TAPPABLE_ELEMENT";
+ case TYPE_BOTTOM_TAPPABLE_ELEMENT:
+ return "TYPE_BOTTOM_TAPPABLE_ELEMENT";
default:
- return "TYPE_UNKNOWN";
+ return "TYPE_UNKNOWN_" + type;
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 24f4c14..65fe87f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,10 @@
package android.view;
import static android.content.res.Resources.ID_NULL;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
@@ -96,6 +100,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.StateSet;
+import android.util.StatsLog;
import android.util.SuperNotCalledException;
import android.util.TypedValue;
import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
@@ -4062,11 +4067,11 @@
}, formatToHexString = true)
/* @hide */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769414)
public int mPrivateFlags;
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768943)
int mPrivateFlags2;
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 129147060)
int mPrivateFlags3;
private int mPrivateFlags4;
@@ -14571,7 +14576,12 @@
if (clickable) {
setPressed(true, x, y);
}
- checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+ checkForLongClick(
+ ViewConfiguration.getLongPressTimeout(),
+ x,
+ y,
+ // This is not a touch gesture -- do not classify it as one.
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION);
return true;
}
}
@@ -15312,7 +15322,11 @@
mHasPerformedLongPress = false;
if (!clickable) {
- checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+ checkForLongClick(
+ ViewConfiguration.getLongPressTimeout(),
+ x,
+ y,
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
@@ -15336,7 +15350,11 @@
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
- checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+ checkForLongClick(
+ ViewConfiguration.getLongPressTimeout(),
+ x,
+ y,
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
@@ -15373,7 +15391,11 @@
* ambiguousMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
- checkForLongClick(delay, x, y);
+ checkForLongClick(
+ delay,
+ x,
+ y,
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= ambiguousMultiplier;
}
@@ -15395,7 +15417,11 @@
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
- checkForLongClick(0 /* send immediately */, x, y);
+ checkForLongClick(
+ 0 /* send immediately */,
+ x,
+ y,
+ TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
@@ -26143,7 +26169,7 @@
}
}
- private void checkForLongClick(long delay, float x, float y) {
+ private void checkForLongClick(long delay, float x, float y, int classification) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
@@ -26153,6 +26179,7 @@
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
+ mPendingCheckForLongPress.setClassification(classification);
postDelayed(mPendingCheckForLongPress, delay);
}
}
@@ -27710,11 +27737,17 @@
private float mX;
private float mY;
private boolean mOriginalPressedState;
+ /**
+ * The classification of the long click being checked: one of the
+ * StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants.
+ */
+ private int mClassification;
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
+ recordGestureClassification(mClassification);
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
@@ -27733,6 +27766,10 @@
public void rememberPressedState() {
mOriginalPressedState = isPressed();
}
+
+ public void setClassification(int classification) {
+ mClassification = classification;
+ }
}
private final class CheckForTap implements Runnable {
@@ -27745,17 +27782,28 @@
setPressed(true, x, y);
final long delay =
ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
- checkForLongClick(delay, x, y);
+ checkForLongClick(delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
}
private final class PerformClick implements Runnable {
@Override
public void run() {
+ recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
performClickInternal();
}
}
+ /** Records a classification for the current event stream. */
+ private void recordGestureClassification(int classification) {
+ if (classification == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
+ return;
+ }
+ // To avoid negatively impacting View performance, the latency and displacement metrics
+ // are omitted.
+ StatsLog.write(StatsLog.TOUCH_GESTURE_CLASSIFIED, getClass().getName(), classification);
+ }
+
/**
* This method returns a ViewPropertyAnimator object, which can be used to animate
* specific properties on this View.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 49166ad..ca64969 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1916,16 +1916,10 @@
}
contentInsets = ensureInsetsNonNegative(contentInsets, "content");
stableInsets = ensureInsetsNonNegative(stableInsets, "stable");
- if (sNewInsetsMode != NEW_INSETS_MODE_NONE) {
- mLastWindowInsets = mInsetsController.calculateInsets(
- mContext.getResources().getConfiguration().isScreenRound(),
- mAttachInfo.mAlwaysConsumeSystemBars, displayCutout,
- contentInsets, stableInsets, mWindowAttributes.softInputMode);
- } else {
- mLastWindowInsets = new WindowInsets(contentInsets, stableInsets,
- mContext.getResources().getConfiguration().isScreenRound(),
- mAttachInfo.mAlwaysConsumeSystemBars, displayCutout);
- }
+ mLastWindowInsets = mInsetsController.calculateInsets(
+ mContext.getResources().getConfiguration().isScreenRound(),
+ mAttachInfo.mAlwaysConsumeSystemBars, displayCutout,
+ contentInsets, stableInsets, mWindowAttributes.softInputMode);
}
return mLastWindowInsets;
}
@@ -3986,7 +3980,7 @@
void systemGestureExclusionChanged() {
final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects();
- if (rectsForWindowManager != null) {
+ if (rectsForWindowManager != null && mView != null) {
try {
mWindowSession.reportSystemGestureExclusionChanged(mWindow, rectsForWindowManager);
} catch (RemoteException e) {
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index ffa769a..2d292ef 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -29,9 +29,6 @@
import static android.view.WindowInsets.Type.all;
import static android.view.WindowInsets.Type.compatSystemInsets;
import static android.view.WindowInsets.Type.indexOf;
-import static android.view.WindowInsets.Type.mandatorySystemGestures;
-import static android.view.WindowInsets.Type.systemGestures;
-import static android.view.WindowInsets.Type.tappableElement;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -225,10 +222,6 @@
}
Insets[] typeInsetMap = new Insets[SIZE];
assignCompatInsets(typeInsetMap, insets);
- // TODO: set system gesture insets based on actual system gesture area.
- typeInsetMap[indexOf(systemGestures())] = Insets.of(insets);
- typeInsetMap[indexOf(mandatorySystemGestures())] = Insets.of(insets);
- typeInsetMap[indexOf(tappableElement())] = Insets.of(insets);
return typeInsetMap;
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 4413585..678a252 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -321,45 +321,6 @@
}
}
- /**
- * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the
- * required values from the donor package. If the ApplicationInfo is for a full WebView,
- * leave it alone. Throws MissingWebViewPackageException if the donor is missing.
- */
- private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm)
- throws MissingWebViewPackageException {
- String donorPackageName = null;
- if (ai.metaData != null) {
- donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage");
- }
- if (donorPackageName != null) {
- PackageInfo donorPackage;
- try {
- donorPackage = pm.getPackageInfo(
- donorPackageName,
- PackageManager.GET_SHARED_LIBRARY_FILES
- | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
- | PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_FACTORY_ONLY);
- } catch (PackageManager.NameNotFoundException e) {
- throw new MissingWebViewPackageException("Failed to find donor package: " +
- donorPackageName);
- }
- ApplicationInfo donorInfo = donorPackage.applicationInfo;
-
- // Replace the stub's code locations with the donor's.
- ai.sourceDir = donorInfo.sourceDir;
- ai.splitSourceDirs = donorInfo.splitSourceDirs;
- ai.nativeLibraryDir = donorInfo.nativeLibraryDir;
- ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir;
-
- // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code
- // and so they are unset.
- ai.primaryCpuAbi = donorInfo.primaryCpuAbi;
- ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi;
- }
- }
-
@UnsupportedAppUsage
private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException {
Application initialApplication = AppGlobals.getInitialApplication();
@@ -411,7 +372,6 @@
verifyPackageInfo(response.packageInfo, newPackageInfo);
ApplicationInfo ai = newPackageInfo.applicationInfo;
- fixupStubApplicationInfo(ai, pm);
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
"initialApplication.createApplicationContext");
@@ -494,18 +454,14 @@
*/
public static int onWebViewProviderChanged(PackageInfo packageInfo) {
int startedRelroProcesses = 0;
- ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
try {
- fixupStubApplicationInfo(packageInfo.applicationInfo,
- AppGlobals.getInitialApplication().getPackageManager());
-
startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the system server.
Log.e(LOGTAG, "error preparing webview native library", t);
}
- WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
+ WebViewZygote.onWebViewProviderChanged(packageInfo);
return startedRelroProcesses;
}
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 09aa066..62f54b9 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -16,8 +16,6 @@
package android.webkit;
-import android.app.LoadedApk;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.AsyncTask;
import android.os.Build;
@@ -29,10 +27,6 @@
import com.android.internal.annotations.GuardedBy;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
/** @hide */
public class WebViewZygote {
private static final String LOGTAG = "WebViewZygote";
@@ -56,13 +50,6 @@
private static PackageInfo sPackage;
/**
- * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from
- * #onWebViewProviderChanged().
- */
- @GuardedBy("sLock")
- private static ApplicationInfo sPackageOriginalAppInfo;
-
- /**
* Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
* will not be started.
*/
@@ -110,11 +97,9 @@
}
}
- public static void onWebViewProviderChanged(PackageInfo packageInfo,
- ApplicationInfo originalAppInfo) {
+ static void onWebViewProviderChanged(PackageInfo packageInfo) {
synchronized (sLock) {
sPackage = packageInfo;
- sPackageOriginalAppInfo = originalAppInfo;
// If multi-process is not enabled, then do not start the zygote service.
if (!sMultiprocessEnabled) {
@@ -165,34 +150,7 @@
Process.FIRST_ISOLATED_UID,
Integer.MAX_VALUE); // TODO(b/123615476) deal with user-id ranges properly
ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
-
- if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) {
- // No stub WebView is involved here, so we can preload the package the "clean" way
- // using the ApplicationInfo.
- sZygote.preloadApp(sPackage.applicationInfo, abi);
- } else {
- // Legacy path to support the stub WebView.
- // Reuse the logic from LoadedApk to determine the correct paths and pass them to
- // the zygote as strings.
- final List<String> zipPaths = new ArrayList<>(10);
- final List<String> libPaths = new ArrayList<>(10);
- LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
- final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
- final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
- TextUtils.join(File.pathSeparator, zipPaths);
-
- String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
-
- // Use the original ApplicationInfo to determine what the original classpath would
- // have been to use as a cache key.
- LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
- final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
- TextUtils.join(File.pathSeparator, zipPaths);
-
- Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
- sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
- Build.SUPPORTED_ABIS[0]);
- }
+ sZygote.preloadApp(sPackage.applicationInfo, abi);
} catch (Exception e) {
Log.e(LOGTAG, "Error connecting to webview zygote", e);
stopZygoteLocked();
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f14b50d..07d28eb 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -24,7 +24,6 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.prediction.AppPredictionContext;
@@ -197,6 +196,11 @@
private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 3;
private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
+ private static final int LIST_VIEW_UPDATE_MESSAGE = 5;
+
+ private static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
+
+ private boolean mListViewDataChanged = false;
@Retention(SOURCE)
@IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
@@ -213,10 +217,13 @@
private final Handler mChooserHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
+ if (mChooserListAdapter == null || isDestroyed()) {
+ return;
+ }
+
switch (msg.what) {
case CHOOSER_TARGET_SERVICE_RESULT:
if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
- if (isDestroyed()) break;
final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
if (!mServiceConnections.contains(sri.connection)) {
Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
@@ -240,17 +247,22 @@
if (DEBUG) {
Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
}
- if (mChooserListAdapter == null || isDestroyed()) {
- break;
- }
+
unbindRemainingServices();
sendVoiceChoicesIfNeeded();
mChooserListAdapter.completeServiceTargetLoading();
break;
+ case LIST_VIEW_UPDATE_MESSAGE:
+ if (DEBUG) {
+ Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
+ }
+
+ mChooserListAdapter.refreshListView();
+ break;
+
case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
- if (isDestroyed()) break;
final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
if (resultInfo.resultTargets != null) {
mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
@@ -829,6 +841,7 @@
mRefinementResultReceiver = null;
}
unbindRemainingServices();
+ mChooserHandler.removeMessages(LIST_VIEW_UPDATE_MESSAGE);
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
@@ -1872,6 +1885,23 @@
}
}
+ @Override
+ public void notifyDataSetChanged() {
+ if (!mListViewDataChanged) {
+ mChooserHandler.sendEmptyMessageDelayed(LIST_VIEW_UPDATE_MESSAGE,
+ LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ mListViewDataChanged = true;
+ }
+ }
+
+ private void refreshListView() {
+ if (mListViewDataChanged) {
+ super.notifyDataSetChanged();
+ }
+ mListViewDataChanged = false;
+ }
+
+
private void createPlaceHolders() {
mServiceTargets.clear();
for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
@@ -1893,7 +1923,7 @@
}
if (mServiceTargets != null) {
- if (getDisplayInfoCount() == 0) {
+ if (getDisplayResolveInfoCount() == 0) {
// b/109676071: When packages change, onListRebuilt() is called before
// ResolverActivity.mDisplayList is re-populated; pruning now would cause the
// list to disappear briefly, so instead we detect this case (the
@@ -1906,12 +1936,14 @@
if (DEBUG) {
Log.d(TAG, "querying direct share targets from ShortcutManager");
}
+
queryDirectShareTargets(this);
}
if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
if (DEBUG) {
Log.d(TAG, "List built querying services");
}
+
queryTargetServices(this);
}
}
@@ -2007,7 +2039,7 @@
offset += callerTargetCount;
return filtered ? super.getItem(position - offset)
- : getDisplayInfoAt(position - offset);
+ : getDisplayResolveInfo(position - offset);
}
public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 9f9e083..f47469a 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1230,7 +1230,7 @@
final ImageView iconView = findViewById(R.id.icon);
final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
if (iconView != null && iconInfo != null) {
- new LoadIconIntoViewTask(iconInfo, iconView).execute();
+ new LoadIconTask(iconInfo, iconView).execute();
}
}
@@ -1871,14 +1871,6 @@
return mDisplayList.size();
}
- public int getDisplayInfoCount() {
- return mDisplayList.size();
- }
-
- public DisplayResolveInfo getDisplayInfoAt(int index) {
- return mDisplayList.get(index);
- }
-
@Nullable
public TargetInfo getItem(int position) {
if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
@@ -1966,9 +1958,10 @@
if (info instanceof DisplayResolveInfo
&& !((DisplayResolveInfo) info).hasDisplayIcon()) {
- new LoadAdapterIconTask((DisplayResolveInfo) info).execute();
+ new LoadIconTask((DisplayResolveInfo) info, holder.icon).execute();
+ } else {
+ holder.icon.setImageDrawable(info.getDisplayIcon());
}
- holder.icon.setImageDrawable(info.getDisplayIcon());
}
}
@@ -2087,13 +2080,15 @@
}
- abstract class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
+ class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
protected final DisplayResolveInfo mDisplayResolveInfo;
private final ResolveInfo mResolveInfo;
+ private final ImageView mTargetView;
- public LoadIconTask(DisplayResolveInfo dri) {
+ LoadIconTask(DisplayResolveInfo dri, ImageView target) {
mDisplayResolveInfo = dri;
mResolveInfo = dri.getResolveInfo();
+ mTargetView = target;
}
@Override
@@ -2103,37 +2098,12 @@
@Override
protected void onPostExecute(Drawable d) {
- mDisplayResolveInfo.setDisplayIcon(d);
- }
- }
-
- class LoadAdapterIconTask extends LoadIconTask {
- public LoadAdapterIconTask(DisplayResolveInfo dri) {
- super(dri);
- }
-
- @Override
- protected void onPostExecute(Drawable d) {
- super.onPostExecute(d);
if (mProfileView != null && mAdapter.getOtherProfile() == mDisplayResolveInfo) {
bindProfileView();
+ } else {
+ mDisplayResolveInfo.setDisplayIcon(d);
+ mTargetView.setImageDrawable(d);
}
- mAdapter.notifyDataSetChanged();
- }
- }
-
- class LoadIconIntoViewTask extends LoadIconTask {
- private final ImageView mTargetView;
-
- public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) {
- super(dri);
- mTargetView = target;
- }
-
- @Override
- protected void onPostExecute(Drawable d) {
- super.onPostExecute(d);
- mTargetView.setImageDrawable(d);
}
}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index dfb6c08..9a9c9d1 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -48,6 +48,7 @@
import "frameworks/base/core/proto/android/service/package.proto";
import "frameworks/base/core/proto/android/service/print.proto";
import "frameworks/base/core/proto/android/service/procstats.proto";
+import "frameworks/base/core/proto/android/service/restricted_image.proto";
import "frameworks/base/core/proto/android/service/usb.proto";
import "frameworks/base/core/proto/android/util/event_log_tags.proto";
import "frameworks/base/core/proto/android/util/log.proto";
@@ -314,6 +315,12 @@
(section).args = "role --proto"
];
+ optional android.service.restricted_image.RestrictedImagesDumpProto restricted_images = 3025 [
+ (section).type = SECTION_DUMPSYS,
+ (section).userdebug_and_eng_only = true,
+ (section).args = "incidentcompanion --restricted_image"
+ ];
+
// Reserved for OEMs.
extensions 50000 to 100000;
}
diff --git a/core/proto/android/service/restricted_image.proto b/core/proto/android/service/restricted_image.proto
new file mode 100644
index 0000000..4a33d47
--- /dev/null
+++ b/core/proto/android/service/restricted_image.proto
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.service.restricted_image;
+
+option java_multiple_files = true;
+option java_outer_classname = "RestrictedImage";
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+// Restricted Image proto is for collecting images from the user with their
+// permission for the purpose of debugging photos.
+message RestrictedImagesDumpProto {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ repeated RestrictedImageSetProto sets = 1;
+}
+
+message RestrictedImageSetProto {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ // Name of the service producing the data.
+ optional string category = 1;
+
+ // The images
+ repeated RestrictedImageProto images = 2;
+
+ // Additional metadata
+ optional bytes metadata = 3;
+}
+
+message RestrictedImageProto {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ // Type of image data
+ optional string mime_type = 1;
+
+ // The image data
+ optional bytes image_data = 2;
+
+ // Metadata about the image. Typically this has another proto schema,
+ // but it is undefined exactly what that is in AOSP code.
+ optional bytes metadata = 3;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ba7a93f..ab86c42 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1014,6 +1014,9 @@
call with the option to redirect the call to a different number or
abort the call altogether.
<p>Protection level: dangerous
+
+ @deprecated Applications should use {@link android.telecom.CallRedirectionService} instead
+ of the {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL} broadcast.
-->
<permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
android:permissionGroup="android.permission-group.UNDEFINED"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2139453..5cbe003 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3241,6 +3241,12 @@
Only applies if the device display is not square. -->
<bool name="config_navBarCanMove">true</bool>
+ <!-- Controls whether the navigation bar lets through taps. -->
+ <bool name="config_navBarTapThrough">false</bool>
+
+ <!-- Controls the size of the back gesture inset. -->
+ <dimen name="config_backGestureInset">0dp</dimen>
+
<!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
These values are in DPs and will be converted to pixel sizes internally. -->
<string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string>
@@ -4006,4 +4012,6 @@
<item>system_server_wtf</item>
</string-array>
+ <!-- Which binder services to include in incident reports containing restricted images. -->
+ <string-array name="config_restrictedImagesServices" translatable="false"/>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 874bde1..a29a4f8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2845,6 +2845,8 @@
<java-symbol type="integer" name="config_navBarOpacityMode" />
<java-symbol type="integer" name="config_navBarInteractionMode" />
<java-symbol type="bool" name="config_navBarCanMove" />
+ <java-symbol type="bool" name="config_navBarTapThrough" />
+ <java-symbol type="dimen" name="config_backGestureInset" />
<java-symbol type="color" name="system_bar_background_semi_transparent" />
<!-- EditText suggestion popup. -->
@@ -3688,6 +3690,7 @@
<java-symbol type="integer" name="config_attentionApiTimeout" />
<java-symbol type="string" name="config_incidentReportApproverPackage" />
+ <java-symbol type="array" name="config_restrictedImagesServices" />
<!-- Display White-Balance -->
<java-symbol type="integer" name="config_displayWhiteBalanceBrightnessSensorRate" />
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index e4142a9..adc04fb0 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -688,20 +688,20 @@
* {@link #setColorFilter(int, PorterDuff.Mode)} overrides tint.
* </p>
*
- * @param tintMode A Porter-Duff blending mode
+ * @param tintMode A Porter-Duff blending mode to apply to the drawable, a value of null sets
+ * the default Porter-Diff blending mode value
+ * of {@link PorterDuff.Mode#SRC_IN}
* @see #setTint(int)
* @see #setTintList(ColorStateList)
*
* @deprecated use {@link #setTintMode(BlendMode)} instead
*/
@Deprecated
- public void setTintMode(@NonNull PorterDuff.Mode tintMode) {
+ public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
if (!mSetTintModeInvoked) {
mSetTintModeInvoked = true;
- BlendMode mode = BlendMode.fromValue(tintMode.nativeInt);
- if (mode != null) {
- setTintMode(mode);
- }
+ BlendMode mode = tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null;
+ setTintMode(mode != null ? mode : Drawable.DEFAULT_BLEND_MODE);
mSetTintModeInvoked = false;
}
}
@@ -716,17 +716,16 @@
* {@link #setColorFilter(ColorFilter)}
* </p>
*
- * @param blendMode
+ * @param blendMode BlendMode to apply to the drawable, a value of null sets the default
+ * blend mode value of {@link BlendMode#SRC_IN}
* @see #setTint(int)
* @see #setTintList(ColorStateList)
*/
- public void setTintMode(@NonNull BlendMode blendMode) {
+ public void setTintMode(@Nullable BlendMode blendMode) {
if (!mSetBlendModeInvoked) {
mSetBlendModeInvoked = true;
PorterDuff.Mode mode = BlendMode.blendModeToPorterDuffMode(blendMode);
- if (mode != null) {
- setTintMode(mode);
- }
+ setTintMode(mode != null ? mode : Drawable.DEFAULT_TINT_MODE);
mSetBlendModeInvoked = false;
}
}
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 6cce319..b76e49c 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -209,8 +209,8 @@
mVkManager->initialize();
GrContextOptions options;
initGrContextOptions(options);
- // TODO: get a string describing the SPIR-V compiler version and use it here
- cacheManager().configureContext(&options, nullptr, 0);
+ auto vkDriverVersion = mVkManager->getDriverVersion();
+ cacheManager().configureContext(&options, &vkDriverVersion, sizeof(vkDriverVersion));
sk_sp<GrContext> grContext = mVkManager->createContext(options);
LOG_ALWAYS_FATAL_IF(!grContext.get());
setGrContext(grContext);
@@ -408,12 +408,13 @@
}
void RenderThread::preload() {
- std::thread eglInitThread([]() {
- //TODO: don't load EGL drivers for Vulkan, when HW bitmap uploader is refactored.
- eglGetDisplay(EGL_DEFAULT_DISPLAY);
- });
- eglInitThread.detach();
- if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+ // EGL driver is always preloaded only if HWUI renders with GL.
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
+ std::thread eglInitThread([]() {
+ eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ });
+ eglInitThread.detach();
+ } else {
requireVkContext();
}
HardwareBitmapUploader::initialize();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index c929098..ac62ff4 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -170,6 +170,7 @@
VkPhysicalDeviceProperties physDeviceProperties;
mGetPhysicalDeviceProperties(mPhysicalDevice, &physDeviceProperties);
LOG_ALWAYS_FATAL_IF(physDeviceProperties.apiVersion < VK_MAKE_VERSION(1, 1, 0));
+ mDriverVersion = physDeviceProperties.driverVersion;
// query to get the initial queue props size
uint32_t queueCount;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index c2d1802..54333f3 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -82,6 +82,8 @@
sk_sp<GrContext> createContext(const GrContextOptions& options);
+ uint32_t getDriverVersion() const { return mDriverVersion; }
+
private:
friend class VulkanSurface;
// Sets up the VkInstance and VkDevice objects. Also fills out the passed in
@@ -178,6 +180,7 @@
};
SwapBehavior mSwapBehavior = SwapBehavior::Discard;
GrVkExtensions mExtensions;
+ uint32_t mDriverVersion = 0;
};
} /* namespace renderthread */
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index dd5c9f5..b4588e0 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -25,6 +25,10 @@
<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+ <!-- Permissions must be defined here, and not in the base manifest, as the network stack
+ running in the system server process does not need any permission, and having privileged
+ permissions added would cause crashes on startup unless they are also added to the
+ privileged permissions whitelist for that package. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index 4d4ceed..b4eeefd 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -72,6 +72,10 @@
public static final String COLNAME_ASSIGNEDV4ADDRESS = "assignedV4Address";
public static final String COLTYPE_ASSIGNEDV4ADDRESS = "INTEGER";
+ public static final String COLNAME_ASSIGNEDV4ADDRESSEXPIRY = "assignedV4AddressExpiry";
+ // The lease expiry timestamp in uint of milliseconds
+ public static final String COLTYPE_ASSIGNEDV4ADDRESSEXPIRY = "BIGINT";
+
// Please note that the group hint is only a *hint*, hence its name. The client can offer
// this information to nudge the grouping in the decision it thinks is right, but it can't
// decide for the memory store what is the same L3 network.
@@ -86,13 +90,14 @@
public static final String COLTYPE_MTU = "INTEGER DEFAULT -1";
public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
- + TABLENAME + " ("
- + COLNAME_L2KEY + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
- + COLNAME_EXPIRYDATE + " " + COLTYPE_EXPIRYDATE + ", "
- + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", "
- + COLNAME_GROUPHINT + " " + COLTYPE_GROUPHINT + ", "
- + COLNAME_DNSADDRESSES + " " + COLTYPE_DNSADDRESSES + ", "
- + COLNAME_MTU + " " + COLTYPE_MTU + ")";
+ + TABLENAME + " ("
+ + COLNAME_L2KEY + " " + COLTYPE_L2KEY + " PRIMARY KEY NOT NULL, "
+ + COLNAME_EXPIRYDATE + " " + COLTYPE_EXPIRYDATE + ", "
+ + COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", "
+ + COLNAME_ASSIGNEDV4ADDRESSEXPIRY + " " + COLTYPE_ASSIGNEDV4ADDRESSEXPIRY + ", "
+ + COLNAME_GROUPHINT + " " + COLTYPE_GROUPHINT + ", "
+ + COLNAME_DNSADDRESSES + " " + COLTYPE_DNSADDRESSES + ", "
+ + COLNAME_MTU + " " + COLTYPE_MTU + ")";
public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
}
@@ -134,7 +139,7 @@
/** The SQLite DB helper */
public static class DbHelper extends SQLiteOpenHelper {
// Update this whenever changing the schema.
- private static final int SCHEMA_VERSION = 2;
+ private static final int SCHEMA_VERSION = 3;
private static final String DATABASE_FILENAME = "IpMemoryStore.db";
public DbHelper(@NonNull final Context context) {
@@ -153,10 +158,27 @@
@Override
public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion,
final int newVersion) {
- // No upgrade supported yet.
- db.execSQL(NetworkAttributesContract.DROP_TABLE);
- db.execSQL(PrivateDataContract.DROP_TABLE);
- onCreate(db);
+ try {
+ if (oldVersion < 2) {
+ // upgrade from version 1 to version 2
+ // since we starts from version 2, do nothing here
+ }
+
+ if (oldVersion < 3) {
+ // upgrade from version 2 to version 3
+ final String sqlUpgradeAddressExpiry = "alter table"
+ + " " + NetworkAttributesContract.TABLENAME + " ADD"
+ + " " + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY
+ + " " + NetworkAttributesContract.COLTYPE_ASSIGNEDV4ADDRESSEXPIRY;
+ db.execSQL(sqlUpgradeAddressExpiry);
+ }
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Could not upgrade to the new version", e);
+ // create database with new version
+ db.execSQL(NetworkAttributesContract.DROP_TABLE);
+ db.execSQL(PrivateDataContract.DROP_TABLE);
+ onCreate(db);
+ }
}
/** Called when the database is downgraded */
@@ -204,6 +226,10 @@
values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
inet4AddressToIntHTH(attributes.assignedV4Address));
}
+ if (null != attributes.assignedV4AddressExpiry) {
+ values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY,
+ attributes.assignedV4AddressExpiry);
+ }
if (null != attributes.groupHint) {
values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
}
@@ -251,6 +277,8 @@
final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
final int assignedV4AddressInt = getInt(cursor,
NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+ final long assignedV4AddressExpiry = getLong(cursor,
+ NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY, 0);
final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
final byte[] dnsAddressesBlob =
getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
@@ -258,6 +286,9 @@
if (0 != assignedV4AddressInt) {
builder.setAssignedV4Address(intToInet4AddressHTH(assignedV4AddressInt));
}
+ if (0 != assignedV4AddressExpiry) {
+ builder.setAssignedV4AddressExpiry(assignedV4AddressExpiry);
+ }
builder.setGroupHint(groupHint);
if (null != dnsAddressesBlob) {
builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index d0e58b8..071ff26 100644
--- a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -228,6 +228,7 @@
public void testNetworkAttributes() throws UnknownHostException {
final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ na.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
na.setGroupHint("hint1");
na.setMtu(219);
final String l2Key = FAKE_KEYS[0];
@@ -257,6 +258,8 @@
+ status.resultCode, status.isSuccess());
assertEquals(l2Key, key);
assertEquals(attributes.assignedV4Address, attr.assignedV4Address);
+ assertEquals(attributes.assignedV4AddressExpiry,
+ attr.assignedV4AddressExpiry);
assertEquals(attributes.groupHint, attr.groupHint);
assertEquals(attributes.mtu, attr.mtu);
assertEquals(attributes2.dnsAddresses, attr.dnsAddresses);
@@ -278,7 +281,7 @@
// Verify that this test does not miss any new field added later.
// If any field is added to NetworkAttributes it must be tested here for storing
// and retrieving.
- assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+ assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
.filter(f -> !Modifier.isStatic(f.getModifiers())).count());
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 3acbcd3..8a88a4c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -53,6 +53,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.NonNull;
@@ -65,8 +66,10 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -197,6 +200,9 @@
private final Context mContext;
+ private WifiManager mWifiManager;
+ private WifiManager.ActionListener mConnectListener;
+
private String ssid;
private String bssid;
private int security;
@@ -1068,8 +1074,10 @@
/**
* Starts the OSU Provisioning flow.
*/
- public void startOsuProvisioning() {
- mContext.getSystemService(WifiManager.class).startSubscriptionProvisioning(
+ public void startOsuProvisioning(@Nullable WifiManager.ActionListener connectListener) {
+ mConnectListener = connectListener;
+
+ getWifiManager().startSubscriptionProvisioning(
mOsuProvider,
mContext.getMainExecutor(),
new AccessPointProvisioningCallback()
@@ -1539,12 +1547,20 @@
return string;
}
+ private WifiManager getWifiManager() {
+ if (mWifiManager == null) {
+ mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ }
+ return mWifiManager;
+ }
+
/**
* Callbacks relaying changes to the AccessPoint representation.
*
* <p>All methods are invoked on the Main Thread.
*/
public interface AccessPointListener {
+
/**
* Indicates a change to the externally visible state of the AccessPoint trigger by an
* update of ScanResults, saved configuration state, connection state, or score
@@ -1561,7 +1577,6 @@
* changed
*/
@MainThread void onAccessPointChanged(AccessPoint accessPoint);
-
/**
* Indicates the "wifi pie signal level" has changed, retrieved via calls to
* {@link AccessPoint#getLevel()}.
@@ -1643,11 +1658,46 @@
mOsuProvisioningComplete = true;
mOsuFailure = null;
mOsuStatus = null;
+
ThreadUtils.postOnMainThread(() -> {
if (mAccessPointListener != null) {
mAccessPointListener.onAccessPointChanged(AccessPoint.this);
}
});
+
+ // Connect to the freshly provisioned network.
+ WifiManager wifiManager = getWifiManager();
+
+ PasspointConfiguration passpointConfig = wifiManager
+ .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(mOsuProvider))
+ .get(mOsuProvider);
+ if (passpointConfig == null) {
+ Log.e(TAG, "Missing PasspointConfiguration for newly provisioned network!");
+ if (mConnectListener != null) {
+ mConnectListener.onFailure(0);
+ }
+ return;
+ }
+
+ String fqdn = passpointConfig.getHomeSp().getFqdn();
+ for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing :
+ wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) {
+ WifiConfiguration config = pairing.first;
+ if (TextUtils.equals(config.FQDN, fqdn)) {
+ List<ScanResult> homeScans =
+ pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK);
+ List<ScanResult> roamingScans =
+ pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK);
+
+ AccessPoint connectionAp =
+ new AccessPoint(mContext, config, homeScans, roamingScans);
+ wifiManager.connect(connectionAp.getConfig(), mConnectListener);
+ return;
+ }
+ }
+ if (mConnectListener != null) {
+ mConnectListener.onFailure(0);
+ }
}
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 9c8e3f4..8e40271 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -41,6 +41,7 @@
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiSsid;
import android.net.wifi.hotspot2.OsuProvider;
@@ -53,6 +54,7 @@
import android.text.SpannableString;
import android.text.format.DateUtils;
import android.util.ArraySet;
+import android.util.Pair;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -72,6 +74,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -95,9 +98,12 @@
private Context mContext;
private WifiInfo mWifiInfo;
+ @Mock private Context mMockContext;
+ @Mock private WifiManager mMockWifiManager;
@Mock private RssiCurve mockBadgeCurve;
@Mock private WifiNetworkScoreCache mockWifiNetworkScoreCache;
@Mock private AccessPoint.AccessPointListener mMockAccessPointListener;
+ @Mock private WifiManager.ActionListener mMockConnectListener;
private static final int NETWORK_ID = 123;
private static final int DEFAULT_RSSI = -55;
@@ -1360,6 +1366,9 @@
.isEqualTo(mContext.getString(R.string.tap_to_sign_up));
}
+ /**
+ * Verifies that the summary of an OSU entry updates based on provisioning status.
+ */
@Test
public void testOsuAccessPointSummary_showsProvisioningUpdates() {
AccessPoint osuAccessPoint = new AccessPoint(mContext, createOsuProvider(),
@@ -1411,4 +1420,82 @@
assertThat(osuAccessPoint.getSummary())
.isEqualTo(mContext.getString(R.string.osu_sign_up_complete));
}
+
+ /**
+ * Verifies that after provisioning through an OSU provider, we connect to the freshly
+ * provisioned network.
+ */
+ @Test
+ public void testOsuAccessPoint_connectsAfterProvisioning() {
+ // Set up mock for WifiManager.getAllMatchingWifiConfigs
+ WifiConfiguration config = new WifiConfiguration();
+ config.FQDN = "fqdn";
+ Map<Integer, List<ScanResult>> scanMapping = new HashMap<>();
+ scanMapping.put(WifiManager.PASSPOINT_HOME_NETWORK, mScanResults);
+ Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> configMapPair =
+ new Pair<>(config, scanMapping);
+ List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingWifiConfig =
+ new ArrayList<>();
+ matchingWifiConfig.add(configMapPair);
+ when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(matchingWifiConfig);
+
+ // Set up mock for WifiManager.getMatchingPasspointConfigsForOsuProviders
+ OsuProvider provider = createOsuProvider();
+ PasspointConfiguration passpointConfig = new PasspointConfiguration();
+ HomeSp homeSp = new HomeSp();
+ homeSp.setFqdn("fqdn");
+ homeSp.setFriendlyName("Test Provider");
+ passpointConfig.setHomeSp(homeSp);
+ Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>();
+ osuProviderConfigMap.put(provider, passpointConfig);
+ when(mMockWifiManager
+ .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(provider)))
+ .thenReturn(osuProviderConfigMap);
+
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+
+ AccessPoint osuAccessPoint = new AccessPoint(mMockContext, provider, mScanResults);
+ osuAccessPoint.setListener(mMockAccessPointListener);
+
+ AccessPoint.AccessPointProvisioningCallback provisioningCallback =
+ osuAccessPoint.new AccessPointProvisioningCallback();
+ provisioningCallback.onProvisioningComplete();
+
+ verify(mMockWifiManager).connect(any(), any());
+ }
+
+ /**
+ * Verifies that after provisioning through an OSU provider, we call the connect listener's
+ * onFailure() method if we cannot find the network we just provisioned.
+ */
+ @Test
+ public void testOsuAccessPoint_noMatchingConfigsAfterProvisioning_callsOnFailure() {
+ // Set up mock for WifiManager.getAllMatchingWifiConfigs
+ when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(new ArrayList<>());
+
+ // Set up mock for WifiManager.getMatchingPasspointConfigsForOsuProviders
+ OsuProvider provider = createOsuProvider();
+ PasspointConfiguration passpointConfig = new PasspointConfiguration();
+ HomeSp homeSp = new HomeSp();
+ homeSp.setFqdn("fqdn");
+ homeSp.setFriendlyName("Test Provider");
+ passpointConfig.setHomeSp(homeSp);
+ Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>();
+ osuProviderConfigMap.put(provider, passpointConfig);
+ when(mMockWifiManager
+ .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(provider)))
+ .thenReturn(osuProviderConfigMap);
+
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+
+ AccessPoint osuAccessPoint = new AccessPoint(mMockContext, provider, mScanResults);
+ osuAccessPoint.setListener(mMockAccessPointListener);
+ osuAccessPoint.startOsuProvisioning(mMockConnectListener);
+
+ AccessPoint.AccessPointProvisioningCallback provisioningCallback =
+ osuAccessPoint.new AccessPointProvisioningCallback();
+ provisioningCallback.onProvisioningComplete();
+
+ verify(mMockConnectListener).onFailure(anyInt());
+ }
}
diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml
new file mode 100644
index 0000000..b330875
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_camera.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="17dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFF"
+ android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_camera.xml b/packages/SystemUI/res/drawable/stat_sys_camera.xml
index eb3e963..c914262 100644
--- a/packages/SystemUI/res/drawable/stat_sys_camera.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_camera.xml
@@ -18,14 +18,5 @@
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="3dp"
- android:insetRight="3dp">
- <vector
- android:width="17dp"
- android:height="17dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFF"
- android:pathData="M20,5h-3.17L15,3H9L7.17,5H4C2.9,5 2,5.9 2,7v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V7C22,5.9 21.1,5 20,5zM20,19H4V7h16V19zM12,9c-2.21,0 -4,1.79 -4,4c0,2.21 1.79,4 4,4s4,-1.79 4,-4C16,10.79 14.21,9 12,9z"/>
- </vector>
-</inset>
+ android:insetRight="3dp"
+ android:drawable="@drawable/ic_camera" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
index d49aff9..3786812 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
@@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
- android:textColor="@color/ksh_keyword_color"
+ android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:maxLines="5"
android:singleLine="false"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b82c250..290d75b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -129,7 +129,6 @@
<!-- Keyboard shortcuts colors -->
<color name="ksh_application_group_color">#fff44336</color>
- <color name="ksh_keyword_color">#d9000000</color>
<color name="ksh_key_item_color">@color/material_grey_600</color>
<color name="ksh_key_item_background">@color/material_grey_100</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 116ccd2..6eb279a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -23,8 +23,6 @@
<dimen name="navigation_bar_size">@*android:dimen/navigation_bar_height</dimen>
<!-- Minimum swipe distance to catch the swipe gestures to invoke assist or switch tasks. -->
<dimen name="navigation_bar_min_swipe_distance">48dp</dimen>
- <!-- The default distance from a side of the device to start an edge swipe from -->
- <dimen name="navigation_bar_default_edge_width">48dp</dimen>
<dimen name="navigation_bar_default_edge_height">500dp</dimen>
<!-- thickness (height) of the dead zone at the top of the navigation bar,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 46f4c86..d0c17b7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -112,4 +112,22 @@
return context.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode);
}
+
+ /**
+ * @return {@code true} if the navbar can be clicked through
+ */
+ public static boolean isNavBarClickThrough(Context context) {
+ return context.getResources().getBoolean(
+ com.android.internal.R.bool.config_navBarTapThrough);
+ }
+
+ /**
+ * @return the edge sensitivity width in px
+ */
+ public static int getEdgeSensitivityWidth(Context context) {
+ return context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.config_backGestureInset);
+ }
+
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index e84c648..3aa9f73 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -293,6 +293,17 @@
}
/**
+ * Request the stack expand if needed, then select the specified Bubble as current.
+ *
+ * @param notificationKey the notification key for the bubble to be selected
+ */
+ public void expandStackAndSelectBubble(String notificationKey) {
+ if (mStackView != null && mBubbleData.getBubble(notificationKey) != null) {
+ mStackView.setExpandedBubble(notificationKey);
+ }
+ }
+
+ /**
* Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
*/
void dismissStack(@DismissReason int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 7f39e47..7e6ddcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -128,7 +128,8 @@
private KeyCharacterMap mBackupKeyCharacterMap;
private KeyboardShortcuts(Context context) {
- this.mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_Light);
+ this.mContext = new ContextThemeWrapper(
+ context, android.R.style.Theme_DeviceDefault_Settings);
this.mPackageManager = AppGlobals.getPackageManager();
loadResources(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index b788f53..fd2f720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -78,8 +78,7 @@
row.setJustClicked(true);
DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
- // If it was a bubble we should close it
- if (row.getEntry().isBubble()) {
+ if (!row.getEntry().isBubble()) {
mBubbleController.collapseStack();
}
@@ -95,7 +94,8 @@
*/
public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
Notification notification = sbn.getNotification();
- if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+ if (notification.contentIntent != null || notification.fullScreenIntent != null
+ || row.getEntry().isBubble()) {
row.setOnClickListener(this);
} else {
row.setOnClickListener(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index b91cdaf..d3e5af8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -220,8 +220,6 @@
StatusBarNotification sbn,
ExpandableNotificationRow row) {
row.setIsLowPriority(entry.ambient);
- // bind the click event to the content area
- checkNotNull(mNotificationClicker).register(row, sbn);
// Extract target SDK version.
try {
@@ -257,6 +255,9 @@
row.setNeedsRedaction(
Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
row.inflateViews();
+
+ // bind the click event to the content area
+ checkNotNull(mNotificationClicker).register(row, sbn);
}
private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index 7ea72c7..47a1054 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.phone;
import android.annotation.IntDef;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Point;
@@ -25,6 +27,8 @@
import android.os.Handler;
import android.provider.Settings;
+import com.android.systemui.shared.system.QuickStepContract;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -33,7 +37,7 @@
* prototypes to run in the system. The class will handle communication changes from the settings
* app and call back to listeners.
*/
-public class NavigationPrototypeController extends ContentObserver {
+public class NavigationPrototypeController extends ContentObserver implements ComponentCallbacks {
private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback";
private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome";
private static final String PROTOTYPE_ENABLED = "prototype_enabled";
@@ -85,9 +89,9 @@
registerObserver(HIDE_HOME_BUTTON_SETTING);
registerObserver(GESTURE_MATCH_SETTING);
registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING);
- registerObserver(EDGE_SENSITIVITY_WIDTH_SETTING);
registerObserver(SHOW_HOME_HANDLE_SETTING);
registerObserver(ENABLE_ASSISTANT_GESTURE);
+ mContext.registerComponentCallbacks(this);
}
/**
@@ -95,6 +99,7 @@
*/
public void unregister() {
mContext.getContentResolver().unregisterContentObserver(this);
+ mContext.unregisterComponentCallbacks(this);
}
@Override
@@ -115,9 +120,6 @@
} else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) {
mListener.onColorAdaptChanged(
NavBarTintController.isEnabled(mContext));
- } else if (path.endsWith(EDGE_SENSITIVITY_WIDTH_SETTING)) {
- mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(),
- getEdgeSensitivityHeight());
} else if (path.endsWith(SHOW_HOME_HANDLE_SETTING)) {
mListener.onHomeHandleVisiblilityChanged(showHomeHandle());
} else if (path.endsWith(ENABLE_ASSISTANT_GESTURE)) {
@@ -130,8 +132,7 @@
* @return the width for edge swipe
*/
public int getEdgeSensitivityWidth() {
- // TODO: Move into resource
- return convertDpToPixel(getGlobalInt(EDGE_SENSITIVITY_WIDTH_SETTING, 48));
+ return QuickStepContract.getEdgeSensitivityWidth(mContext);
}
/**
@@ -203,6 +204,18 @@
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (mListener != null) {
+ mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(),
+ getEdgeSensitivityHeight());
+ }
+ }
+
+ @Override
+ public void onLowMemory() {
+ }
+
public interface OnPrototypeChangedListener {
void onGestureRemap(@GestureAction int[] actions);
void onBackButtonVisibilityChanged(boolean visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index 25cb7d0..8053ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -29,7 +29,6 @@
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM;
-import static com.android.systemui.statusbar.phone.NavigationPrototypeController.EDGE_SENSITIVITY_WIDTH_SETTING;
import android.annotation.Nullable;
import android.content.Context;
@@ -264,10 +263,7 @@
mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
mAllowGestureDetection = true;
mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed();
- final int defaultRegionThreshold = mContext.getResources()
- .getDimensionPixelOffset(R.dimen.navigation_bar_default_edge_width);
- mGestureRegionThreshold = convertDpToPixel(getIntGlobalSetting(mContext,
- EDGE_SENSITIVITY_WIDTH_SETTING, defaultRegionThreshold));
+ mGestureRegionThreshold = QuickStepContract.getEdgeSensitivityWidth(mContext);
break;
}
case MotionEvent.ACTION_MOVE: {
@@ -357,7 +353,7 @@
if (mCurrentAction != null) {
mCurrentAction.endGesture();
}
- } else if (QuickStepContract.isGesturalMode(mContext)
+ } else if (QuickStepContract.isNavBarClickThrough(mContext)
&& !mClickThroughPressed) {
// Enable click through functionality where no gesture has been detected and
// not passed the drag slop so inject a touch event at the same location
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 db91d01..7e06232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -25,6 +25,7 @@
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static com.android.systemui.Dependency.BG_HANDLER;
import static com.android.systemui.Dependency.MAIN_HANDLER;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -1076,7 +1077,7 @@
mLockscreenUserManager, shadeController, mKeyguardMonitor,
mNotificationInterruptionStateProvider, mMetricsLogger,
new LockPatternUtils(mContext), Dependency.get(MAIN_HANDLER),
- mActivityIntentHelper);
+ Dependency.get(BG_HANDLER), mActivityIntentHelper, mBubbleController);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 215f5c4..e4af15c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -48,6 +48,7 @@
import com.android.systemui.EventLogTags;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -98,7 +99,9 @@
private final CommandQueue mCommandQueue;
private final IDreamManager mDreamManager;
private final Handler mMainThreadHandler;
+ private final Handler mBackgroundHandler;
private final ActivityIntentHelper mActivityIntentHelper;
+ private final BubbleController mBubbleController;
private boolean mIsCollapsingToShowActivityOverLockscreen;
@@ -125,7 +128,9 @@
MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils,
Handler mainThreadHandler,
- ActivityIntentHelper activityIntentHelper) {
+ Handler backgroundHandler,
+ ActivityIntentHelper activityIntentHelper,
+ BubbleController bubbleController) {
mContext = context;
mNotificationPanel = panel;
mPresenter = presenter;
@@ -147,6 +152,7 @@
mAssistManager = assistManager;
mGroupManager = groupManager;
mLockPatternUtils = lockPatternUtils;
+ mBackgroundHandler = backgroundHandler;
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
@@ -156,6 +162,7 @@
mStatusBarRemoteInputCallback = remoteInputCallback;
mMainThreadHandler = mainThreadHandler;
mActivityIntentHelper = activityIntentHelper;
+ mBubbleController = bubbleController;
}
/**
@@ -178,14 +185,24 @@
final PendingIntent intent = notification.contentIntent != null
? notification.contentIntent
: notification.fullScreenIntent;
+ final boolean isBubble = row.getEntry().isBubble();
+
+ // This code path is now executed for notification without a contentIntent.
+ // The only valid case is Bubble notifications. Guard against other cases
+ // entering here.
+ if (intent == null && !isBubble) {
+ Log.e(TAG, "onNotificationClicked called for non-clickable notification!");
+ return;
+ }
+
final String notificationKey = sbn.getKey();
- boolean isActivityIntent = intent.isActivity();
+ boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble;
final boolean afterKeyguardGone = isActivityIntent
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
final boolean wasOccluded = mShadeController.isOccluded();
- boolean showOverLockscreen = mKeyguardMonitor.isShowing()
+ boolean showOverLockscreen = mKeyguardMonitor.isShowing() && intent != null
&& mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
ActivityStarter.OnDismissAction postKeyguardAction =
@@ -244,9 +261,8 @@
mShadeController.addAfterKeyguardGoneRunnable(runnable);
mShadeController.collapsePanel();
} else {
- new Thread(runnable).start();
+ mBackgroundHandler.postAtFrontOfQueue(runnable);
}
-
return !mNotificationPanel.isFullyCollapsed();
}
@@ -287,6 +303,7 @@
}
Intent fillInIntent = null;
NotificationEntry entry = row.getEntry();
+ final boolean isBubble = entry.isBubble();
CharSequence remoteInputText = null;
if (!TextUtils.isEmpty(entry.remoteInputText)) {
remoteInputText = entry.remoteInputText;
@@ -295,8 +312,12 @@
fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
remoteInputText.toString());
}
- startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent);
- if (isActivityIntent) {
+ if (isBubble) {
+ expandBubbleStackOnMainThread(notificationKey);
+ } else {
+ startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent);
+ }
+ if (isActivityIntent || isBubble) {
mAssistManager.hideAssist();
}
if (shouldCollapse()) {
@@ -316,18 +337,29 @@
} catch (RemoteException ex) {
// system process is dead if we're here.
}
- if (parentToCancelFinal != null) {
- removeNotification(parentToCancelFinal);
- }
- if (shouldAutoCancel(sbn)
- || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
- notificationKey)) {
- // Automatically remove all notifications that we may have kept around longer
- removeNotification(sbn);
+ if (!isBubble) {
+ if (parentToCancelFinal != null) {
+ removeNotification(parentToCancelFinal);
+ }
+ if (shouldAutoCancel(sbn)
+ || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
+ notificationKey)) {
+ // Automatically remove all notifications that we may have kept around longer
+ removeNotification(sbn);
+ }
}
mIsCollapsingToShowActivityOverLockscreen = false;
}
+ private void expandBubbleStackOnMainThread(String notificationKey) {
+ if (Looper.getMainLooper().isCurrentThread()) {
+ mBubbleController.expandStackAndSelectBubble(notificationKey);
+ } else {
+ mMainThreadHandler.post(
+ () -> mBubbleController.expandStackAndSelectBubble(notificationKey));
+ }
+ }
+
private void startNotificationIntent(PendingIntent intent, Intent fillInIntent,
ExpandableNotificationRow row, boolean wasOccluded, boolean isActivityIntent) {
RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 14bc71b..9fa85d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -438,6 +438,22 @@
}
@Test
+ public void testExpandStackAndSelectBubble_removedFirst() {
+ final String key = mRow.getEntry().key;
+
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+
+ assertTrue(mRow.getEntry().isBubble());
+
+ // Simulate notification cancellation.
+ mEntryListener.onEntryRemoved(mRow.getEntry(), null /* notificationVisibility (unused) */,
+ false /* removedbyUser */);
+
+ mBubbleController.expandStackAndSelectBubble(key);
+ }
+
+ @Test
public void testMarkNewNotificationAsBubble() {
mEntryListener.onPendingEntryAdded(mRow.getEntry());
assertTrue(mRow.getEntry().isBubble());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 5ea4636..7e089a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -151,6 +151,16 @@
/**
* Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
+ */
+ public ExpandableNotificationRow createBubble()
+ throws Exception {
+ Notification n = createNotification(false /* isGroupSummary */,
+ null /* groupKey */, makeBubbleMetadata(null));
+ return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
+ }
+
+ /**
+ * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
*
* @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent}
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 20af1ac..41e82cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -24,7 +24,10 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
@@ -49,6 +52,7 @@
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -101,22 +105,23 @@
private KeyguardMonitor mKeyguardMonitor;
@Mock
private Handler mHandler;
+ @Mock
+ private BubbleController mBubbleController;
@Mock
private ActivityIntentHelper mActivityIntentHelper;
@Mock
private PendingIntent mContentIntent;
@Mock
+ private Intent mContentIntentInner;
+ @Mock
private NotificationData mNotificationData;
- @Mock
- private NotificationEntry mNotificationEntry;
- @Mock
- private NotificationEntry mBubbleEntry;
private NotificationActivityStarter mNotificationActivityStarter;
private NotificationTestHelper mNotificationTestHelper;
- ExpandableNotificationRow mNotificationRow;
+ private ExpandableNotificationRow mNotificationRow;
+ private ExpandableNotificationRow mBubbleNotificationRow;
private final Answer<Void> mCallOnDismiss = answerVoid(
(ActivityStarter.OnDismissAction dismissAction, Runnable cancel,
@@ -129,14 +134,32 @@
when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
- mActiveNotifications = new ArrayList<>();
- mActiveNotifications.add(mNotificationEntry);
- mActiveNotifications.add(mBubbleEntry);
- when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications);
- when(mNotificationEntry.getRow()).thenReturn(mNotificationRow);
+ when(mContentIntent.isActivity()).thenReturn(true);
+ when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
+ when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
mNotificationTestHelper = new NotificationTestHelper(mContext);
+
+ // Create standard notification with contentIntent
mNotificationRow = mNotificationTestHelper.createRow();
+ StatusBarNotification sbn = mNotificationRow.getStatusBarNotification();
+ sbn.getNotification().contentIntent = mContentIntent;
+ sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
+
+ // Create bubble notification row with contentIntent
+ mBubbleNotificationRow = mNotificationTestHelper.createBubble();
+ StatusBarNotification bubbleSbn = mBubbleNotificationRow.getStatusBarNotification();
+ bubbleSbn.getNotification().contentIntent = mContentIntent;
+ bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
+ // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does:
+ mBubbleNotificationRow.getEntry().setIsBubble(true);
+ mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true);
+
+ mActiveNotifications = new ArrayList<>();
+ mActiveNotifications.add(mNotificationRow.getEntry());
+ mActiveNotifications.add(mBubbleNotificationRow.getEntry());
+ when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
mNotificationActivityStarter = new StatusBarNotificationActivityStarter(getContext(),
mock(CommandQueue.class), mAssistManager, mock(NotificationPanelView.class),
@@ -147,16 +170,8 @@
mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class),
mock(NotificationLockscreenUserManager.class), mShadeController, mKeyguardMonitor,
mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
- mock(LockPatternUtils.class), mHandler, mActivityIntentHelper);
-
-
- when(mContentIntent.isActivity()).thenReturn(true);
- when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
-
- // SBNActivityStarter expects contentIntent or fullScreenIntent to be set
- mNotificationRow.getEntry().notification.getNotification().contentIntent = mContentIntent;
-
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+ mock(LockPatternUtils.class), mHandler, mHandler, mActivityIntentHelper,
+ mBubbleController);
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
doAnswer(mCallOnDismiss).when(mActivityStarter).dismissKeyguardThenExecute(
@@ -173,33 +188,26 @@
// set up Handler to synchronously invoke the Runnable arg
doAnswer(answerVoid(Runnable::run))
.when(mHandler).post(any(Runnable.class));
+
+ doAnswer(answerVoid(Runnable::run))
+ .when(mHandler).postAtFrontOfQueue(any(Runnable.class));
}
@Test
- public void testOnNotificationClicked_whileKeyguardVisible()
+ public void testOnNotificationClicked_keyGuardShowing()
throws PendingIntent.CanceledException, RemoteException {
// Given
+ StatusBarNotification sbn = mNotificationRow.getStatusBarNotification();
+ sbn.getNotification().contentIntent = mContentIntent;
+ sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
+
when(mKeyguardMonitor.isShowing()).thenReturn(true);
when(mShadeController.isOccluded()).thenReturn(true);
- when(mContentIntent.isActivity()).thenReturn(true);
- when(mActivityIntentHelper.wouldShowOverLockscreen(any(Intent.class), anyInt()))
- .thenReturn(false);
- when(mActivityIntentHelper.wouldLaunchResolverActivity(any(Intent.class), anyInt()))
- .thenReturn(false);
-
- StatusBarNotification statusBarNotification = mNotificationRow.getEntry().notification;
- statusBarNotification.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
// When
- mNotificationActivityStarter.onNotificationClicked(statusBarNotification,
- mNotificationRow);
+ mNotificationActivityStarter.onNotificationClicked(sbn, mNotificationRow);
// Then
- verify(mActivityStarter).dismissKeyguardThenExecute(
- any(ActivityStarter.OnDismissAction.class),
- any() /* cancel */,
- anyBoolean() /* afterKeyguardGone */);
-
verify(mShadeController, atLeastOnce()).collapsePanel();
verify(mContentIntent).sendAndReturnResult(
@@ -214,9 +222,100 @@
verify(mAssistManager).hideAssist();
verify(mStatusBarService).onNotificationClick(
- eq(mNotificationRow.getEntry().key), any(NotificationVisibility.class));
+ eq(sbn.getKey()), any(NotificationVisibility.class));
// Notification is removed due to FLAG_AUTO_CANCEL
- verify(mEntryManager).performRemoveNotification(eq(statusBarNotification));
+ verify(mEntryManager).performRemoveNotification(eq(sbn));
+ }
+
+ @Test
+ public void testOnNotificationClicked_bubble_noContentIntent_noKeyGuard()
+ throws RemoteException {
+ StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification();
+
+ // Given
+ sbn.getNotification().contentIntent = null;
+
+ // When
+ mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
+
+ // Then
+ verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+
+ // This is called regardless, and simply short circuits when there is nothing to do.
+ verify(mShadeController, atLeastOnce()).collapsePanel();
+
+ verify(mAssistManager).hideAssist();
+
+ verify(mStatusBarService).onNotificationClick(
+ eq(sbn.getKey()), any(NotificationVisibility.class));
+
+ // The content intent should NOT be sent on click.
+ verifyZeroInteractions(mContentIntent);
+
+ // Notification should not be cancelled.
+ verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
+ }
+
+ @Test
+ public void testOnNotificationClicked_bubble_noContentIntent_keyGuardShowing()
+ throws RemoteException {
+ StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification();
+
+ // Given
+ sbn.getNotification().contentIntent = null;
+ when(mKeyguardMonitor.isShowing()).thenReturn(true);
+ when(mShadeController.isOccluded()).thenReturn(true);
+
+ // When
+ mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
+
+ // Then
+ verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+
+ verify(mShadeController, atLeastOnce()).collapsePanel();
+
+ verify(mAssistManager).hideAssist();
+
+ verify(mStatusBarService).onNotificationClick(
+ eq(sbn.getKey()), any(NotificationVisibility.class));
+
+ // The content intent should NOT be sent on click.
+ verifyZeroInteractions(mContentIntent);
+
+ // Notification should not be cancelled.
+ verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
+ }
+
+ @Test
+ public void testOnNotificationClicked_bubble_withContentIntent_keyGuardShowing()
+ throws RemoteException {
+ StatusBarNotification sbn = mBubbleNotificationRow.getStatusBarNotification();
+
+ // Given
+ sbn.getNotification().contentIntent = mContentIntent;
+ when(mKeyguardMonitor.isShowing()).thenReturn(true);
+ when(mShadeController.isOccluded()).thenReturn(true);
+
+ // When
+ mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
+
+ // Then
+ verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+
+ verify(mShadeController, atLeastOnce()).collapsePanel();
+
+ verify(mAssistManager).hideAssist();
+
+ verify(mStatusBarService).onNotificationClick(
+ eq(sbn.getKey()), any(NotificationVisibility.class));
+
+ // The content intent should NOT be sent on click.
+ verify(mContentIntent).getIntent();
+ verify(mContentIntent).isActivity();
+ verifyNoMoreInteractions(mContentIntent);
+
+ // Notification should not be cancelled.
+ verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
}
}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
index 545a3cc..23d2e7b 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
@@ -26,4 +26,11 @@
<!-- Controls whether the nav bar can move from the bottom to the side in landscape.
Only applies if the device display is not square. -->
<bool name="config_navBarCanMove">false</bool>
-</resources>
\ No newline at end of file
+
+ <!-- Controls whether the navigation bar lets through taps. -->
+ <bool name="config_navBarTapThrough">true</bool>
+
+ <!-- Controls the size of the back gesture inset. -->
+ <dimen name="config_backGestureInset">48dp</dimen>
+
+</resources>
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index f50364d..164837a 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -39,7 +39,9 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -50,10 +52,22 @@
private static final String TAG = "ExplicitHealthCheckController";
private final Object mLock = new Object();
private final Context mContext;
- @GuardedBy("mLock") @Nullable private StateCallback mStateCallback;
+
+ // Called everytime the service is connected, so the watchdog can sync it's state with
+ // the health check service. In practice, should never be null after it has been #setEnabled.
+ @GuardedBy("mLock") @Nullable private Runnable mOnConnected;
+ // Called everytime a package passes the health check, so the watchdog is notified of the
+ // passing check. In practice, should never be null after it has been #setEnabled.
+ @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
+ // Actual binder object to the explicit health check service.
@GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
- @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
+ // Cache for packages supporting explicit health checks. This cache should not change while
+ // the health check service is running.
@GuardedBy("mLock") @Nullable private List<String> mSupportedPackages;
+ // Connection to the explicit health check service, necessary to unbind
+ @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
+ // Bind state of the explicit health check service.
+ @GuardedBy("mLock") private boolean mEnabled;
ExplicitHealthCheckController(Context context) {
mContext = context;
@@ -61,28 +75,40 @@
/**
* Requests an explicit health check for {@code packageName}.
- * After this request, the callback registered on {@link startService} can receive explicit
+ * After this request, the callback registered on {@link #setCallbacks} can receive explicit
* health check passed results.
*
* @throws IllegalStateException if the service is not started
*/
public void request(String packageName) throws RemoteException {
synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+
enforceServiceReadyLocked();
+
+ Slog.i(TAG, "Requesting health check for package " + packageName);
mRemoteService.request(packageName);
}
}
/**
* Cancels all explicit health checks for {@code packageName}.
- * After this request, the callback registered on {@link startService} can no longer receive
+ * After this request, the callback registered on {@link #setCallbacks} can no longer receive
* explicit health check passed results.
*
* @throws IllegalStateException if the service is not started
*/
public void cancel(String packageName) throws RemoteException {
synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+
enforceServiceReadyLocked();
+
+ Slog.i(TAG, "Cancelling health check for package " + packageName);
mRemoteService.cancel(packageName);
}
}
@@ -95,13 +121,21 @@
*/
public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
synchronized (mLock) {
+ if (!mEnabled) {
+ consumer.accept(Collections.emptyList());
+ return;
+ }
+
enforceServiceReadyLocked();
+
if (mSupportedPackages == null) {
+ Slog.d(TAG, "Getting health check supported packages");
mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
mSupportedPackages = result.getStringArrayList(EXTRA_SUPPORTED_PACKAGES);
consumer.accept(mSupportedPackages);
}));
} else {
+ Slog.d(TAG, "Getting cached health check supported packages");
consumer.accept(mSupportedPackages);
}
}
@@ -115,95 +149,113 @@
*/
public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
synchronized (mLock) {
+ if (!mEnabled) {
+ consumer.accept(Collections.emptyList());
+ return;
+ }
+
enforceServiceReadyLocked();
+
+ Slog.d(TAG, "Getting health check requested packages");
mRemoteService.getRequestedPackages(new RemoteCallback(
result -> consumer.accept(
result.getStringArrayList(EXTRA_REQUESTED_PACKAGES))));
}
}
+ /** Enables or disables explicit health checks. */
+ public void setEnabled(boolean enabled) {
+ synchronized (mLock) {
+ if (enabled == mEnabled) {
+ return;
+ }
+
+ Slog.i(TAG, "Setting explicit health checks enabled " + enabled);
+ mEnabled = enabled;
+ if (enabled) {
+ bindService();
+ } else {
+ unbindService();
+ }
+ }
+ }
+
/**
- * Starts the explicit health check service.
- *
- * @param stateCallback will receive important state changes changes
- * @param passedConsumer will accept packages that pass explicit health checks
- *
- * @throws IllegalStateException if the service is already started
+ * Sets callbacks to listen to important events from the controller.
+ * Should be called at initialization.
*/
- public void startService(StateCallback stateCallback, Consumer<String> passedConsumer) {
+ public void setCallbacks(Runnable onConnected, Consumer<String> passedConsumer) {
+ Preconditions.checkNotNull(onConnected);
+ Preconditions.checkNotNull(passedConsumer);
+ mOnConnected = onConnected;
+ mPassedConsumer = passedConsumer;
+ }
+
+ /** Binds to the explicit health check service. */
+ private void bindService() {
synchronized (mLock) {
if (mRemoteService != null) {
- throw new IllegalStateException("Explicit health check service already started.");
+ return;
}
- mStateCallback = stateCallback;
+ ComponentName component = getServiceComponentNameLocked();
+ if (component == null) {
+ Slog.wtf(TAG, "Explicit health check service not found");
+ return;
+ }
+
+ Intent intent = new Intent();
+ intent.setComponent(component);
+ // TODO: Fix potential race conditions during mConnection state transitions.
+ // E.g after #onServiceDisconected, the mRemoteService object is invalid until
+ // we get an #onServiceConnected.
mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mLock) {
- mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
- try {
- mRemoteService.setCallback(new RemoteCallback(result -> {
- String packageName =
- result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
- if (!TextUtils.isEmpty(packageName)) {
- passedConsumer.accept(packageName);
- } else {
- Slog.w(TAG, "Empty package passed explicit health check?");
- }
- }));
- mStateCallback.onStart();
- Slog.i(TAG, "Explicit health check service is connected " + name);
- } catch (RemoteException e) {
- Slog.wtf(TAG, "Coud not setCallback on explicit health check service");
- }
- }
+ initState(service);
+ Slog.i(TAG, "Explicit health check service is connected " + name);
}
@Override
@MainThread
public void onServiceDisconnected(ComponentName name) {
- resetState();
+ // Service crashed or process was killed, #onServiceConnected will be called.
+ // Don't need to re-bind.
Slog.i(TAG, "Explicit health check service is disconnected " + name);
}
@Override
public void onBindingDied(ComponentName name) {
- resetState();
+ // Application hosting service probably got updated
+ // Need to re-bind.
+ synchronized (mLock) {
+ if (mEnabled) {
+ unbindService();
+ bindService();
+ }
+ }
Slog.i(TAG, "Explicit health check service binding is dead " + name);
}
@Override
public void onNullBinding(ComponentName name) {
- resetState();
- Slog.i(TAG, "Explicit health check service binding is null " + name);
+ // Should never happen. Service returned null from #onBind.
+ Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
}
};
- ComponentName component = getServiceComponentNameLocked();
- if (component != null) {
- Intent intent = new Intent();
- intent.setComponent(component);
- mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
- UserHandle.of(UserHandle.USER_SYSTEM));
- }
+ Slog.i(TAG, "Binding to explicit health service");
+ mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.of(UserHandle.USER_SYSTEM));
}
}
- // TODO: Differentiate between expected vs unexpected stop?
- /** Callback to receive important {@link ExplicitHealthCheckController} state changes. */
- abstract static class StateCallback {
- /** The controller is ready and we can request explicit health checks for packages */
- public void onStart() {}
-
- /** The controller is not ready and we cannot request explicit health checks for packages */
- public void onStop() {}
- }
-
- /** Stops the explicit health check service. */
- public void stopService() {
+ /** Unbinds the explicit health check service. */
+ private void unbindService() {
synchronized (mLock) {
if (mRemoteService != null) {
+ Slog.i(TAG, "Unbinding from explicit health service");
mContext.unbindService(mConnection);
+ mRemoteService = null;
}
}
}
@@ -247,19 +299,41 @@
return name;
}
- private void resetState() {
+ private void initState(IBinder service) {
synchronized (mLock) {
- mStateCallback.onStop();
- mStateCallback = null;
mSupportedPackages = null;
- mRemoteService = null;
- mConnection = null;
+ mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
+ try {
+ mRemoteService.setCallback(new RemoteCallback(result -> {
+ String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
+ if (!TextUtils.isEmpty(packageName)) {
+ synchronized (mLock) {
+ if (mPassedConsumer == null) {
+ Slog.w(TAG, "Health check passed for package " + packageName
+ + "but no consumer registered.");
+ } else {
+ mPassedConsumer.accept(packageName);
+ }
+ }
+ } else {
+ Slog.w(TAG, "Empty package passed explicit health check?");
+ }
+ }));
+ if (mOnConnected == null) {
+ Slog.w(TAG, "Health check service connected but no runnable registered.");
+ } else {
+ mOnConnected.run();
+ }
+ } catch (RemoteException e) {
+ Slog.wtf(TAG, "Could not setCallback on explicit health check service");
+ }
}
}
@GuardedBy("mLock")
private void enforceServiceReadyLocked() {
if (mRemoteService == null) {
+ // TODO: Try to bind to service
throw new IllegalStateException("Explicit health check service not ready");
}
}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 660109c..2ba4d97 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -26,11 +26,12 @@
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.Log;
import android.util.Slog;
import android.util.Xml;
@@ -54,10 +55,12 @@
import java.lang.annotation.Retention;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Monitors the health of packages on the system and notifies interested observers when packages
@@ -84,10 +87,10 @@
private final Object mLock = new Object();
// System server context
private final Context mContext;
- // Handler to run package cleanup runnables
- private final Handler mTimerHandler;
- // Handler for processing IO and observer actions
- private final Handler mWorkerHandler;
+ // Handler to run short running tasks
+ private final Handler mShortTaskHandler;
+ // Handler for processing IO and long running tasks
+ private final Handler mLongTaskHandler;
// Contains (observer-name -> observer-handle) that have ever been registered from
// previous boots. Observers with all packages expired are periodically pruned.
// It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
@@ -97,6 +100,12 @@
private final AtomicFile mPolicyFile;
// Runnable to prune monitored packages that have expired
private final Runnable mPackageCleanup;
+ private final ExplicitHealthCheckController mHealthCheckController;
+ // Flag to control whether explicit health checks are supported or not
+ @GuardedBy("mLock")
+ private boolean mIsHealthCheckEnabled = true;
+ @GuardedBy("mLock")
+ private boolean mIsPackagesReady;
// Last SystemClock#uptimeMillis a package clean up was executed.
// 0 if mPackageCleanup not running.
private long mUptimeAtLastRescheduleMs;
@@ -104,32 +113,30 @@
// 0 if mPackageCleanup not running.
private long mDurationAtLastReschedule;
- // TODO(b/120598832): Remove redundant context param
private PackageWatchdog(Context context) {
- mContext = context;
- mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
- "package-watchdog.xml"));
- mTimerHandler = new Handler(Looper.myLooper());
- mWorkerHandler = BackgroundThread.getHandler();
- mPackageCleanup = this::rescheduleCleanup;
- loadFromFile();
+ // Needs to be constructed inline
+ this(context, new AtomicFile(
+ new File(new File(Environment.getDataDirectory(), "system"),
+ "package-watchdog.xml")),
+ new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
+ new ExplicitHealthCheckController(context));
}
/**
- * Creates a PackageWatchdog for testing that uses the same {@code looper} for all handlers
- * and creates package-watchdog.xml in an apps data directory.
+ * Creates a PackageWatchdog that allows injecting dependencies.
*/
@VisibleForTesting
- PackageWatchdog(Context context, Looper looper) {
+ PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
+ Handler longTaskHandler, ExplicitHealthCheckController controller) {
mContext = context;
- mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
- mTimerHandler = new Handler(looper);
- mWorkerHandler = mTimerHandler;
+ mPolicyFile = policyFile;
+ mShortTaskHandler = shortTaskHandler;
+ mLongTaskHandler = longTaskHandler;
mPackageCleanup = this::rescheduleCleanup;
+ mHealthCheckController = controller;
loadFromFile();
}
-
/** Creates or gets singleton instance of PackageWatchdog. */
public static PackageWatchdog getInstance(Context context) {
synchronized (PackageWatchdog.class) {
@@ -141,6 +148,20 @@
}
/**
+ * Called during boot to notify when packages are ready on the device so we can start
+ * binding.
+ */
+ public void onPackagesReady() {
+ synchronized (mLock) {
+ mIsPackagesReady = true;
+ mHealthCheckController.setCallbacks(this::updateHealthChecks,
+ packageName -> onHealthCheckPassed(packageName));
+ // Controller is disabled at creation until here where we may enable it
+ mHealthCheckController.setEnabled(mIsHealthCheckEnabled);
+ }
+ }
+
+ /**
* Registers {@code observer} to listen for package failures
*
* <p>Observers are expected to call this on boot. It does not specify any packages but
@@ -163,40 +184,63 @@
* Starts observing the health of the {@code packages} for {@code observer} and notifies
* {@code observer} of any package failures within the monitoring duration.
*
- * <p>If monitoring a package with {@code withExplicitHealthCheck}, at the end of the monitoring
- * duration if {@link #onExplicitHealthCheckPassed} was never called,
+ * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
+ * duration if {@link #onHealthCheckPassed} was never called,
* {@link PackageHealthObserver#execute} will be called as if the package failed.
*
* <p>If {@code observer} is already monitoring a package in {@code packageNames},
* the monitoring window of that package will be reset to {@code durationMs} and the health
- * check state will be reset to a default depending on {@code withExplictHealthCheck}.
+ * check state will be reset to a default depending on if the package is contained in
+ * {@link mPackagesWithExplicitHealthCheckEnabled}.
*
* @throws IllegalArgumentException if {@code packageNames} is empty
* or {@code durationMs} is less than 1
*/
- public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
- long durationMs, boolean withExplicitHealthCheck) {
- if (packageNames.isEmpty() || durationMs < 1) {
+ public void startObservingHealth(PackageHealthObserver observer, List<String> packages,
+ long durationMs) {
+ if (packages.isEmpty() || durationMs < 1) {
throw new IllegalArgumentException("Observation not started, no packages specified"
+ "or invalid duration");
}
+ if (!mIsPackagesReady) {
+ // TODO: Queue observation requests when packages are not ready
+ Slog.w(TAG, "Attempt to observe when packages not ready");
+ return;
+ }
+
+ try {
+ Slog.i(TAG, "Getting packages supporting explicit health check");
+ mHealthCheckController.getSupportedPackages(supportedPackages ->
+ startObservingInner(observer, packages, durationMs, supportedPackages));
+ } catch (RemoteException e) {
+ Slog.wtf(TAG, "Failed to fetch supported explicit health check packages");
+ }
+ }
+
+ private void startObservingInner(PackageHealthObserver observer,
+ List<String> packageNames, long durationMs,
+ List<String> healthCheckSupportedPackages) {
+ Slog.i(TAG, "Start observing packages " + packageNames
+ + ". Explicit health check supported packages " + healthCheckSupportedPackages);
List<MonitoredPackage> packages = new ArrayList<>();
for (int i = 0; i < packageNames.size(); i++) {
- // When observing packages withExplicitHealthCheck,
- // MonitoredPackage#mHasExplicitHealthCheckPassed will be false initially.
- packages.add(new MonitoredPackage(packageNames.get(i), durationMs,
- !withExplicitHealthCheck));
+ String packageName = packageNames.get(i);
+ boolean shouldEnableHealthCheck = healthCheckSupportedPackages.contains(packageName);
+ // If we should enable explicit health check for a package,
+ // MonitoredPackage#mHasHealthCheckPassed will be false
+ // until PackageWatchdog#onHealthCheckPassed
+ packages.add(new MonitoredPackage(packageName, durationMs, !shouldEnableHealthCheck));
}
synchronized (mLock) {
ObserverInternal oldObserver = mAllObservers.get(observer.getName());
if (oldObserver == null) {
- Slog.d(TAG, observer.getName() + " started monitoring health of packages "
- + packageNames);
+ Slog.d(TAG, observer.getName() + " started monitoring health "
+ + "of packages " + packageNames);
mAllObservers.put(observer.getName(),
new ObserverInternal(observer.getName(), packages));
} else {
- Slog.d(TAG, observer.getName() + " added the following packages to monitor "
- + packageNames);
+ Slog.d(TAG, observer.getName() + " added the following "
+ + "packages to monitor " + packageNames);
oldObserver.updatePackages(packages);
}
}
@@ -204,9 +248,97 @@
// Always reschedule because we may need to expire packages
// earlier than we are already scheduled for
rescheduleCleanup();
+ updateHealthChecks();
saveToFileAsync();
}
+ private void requestCheck(String packageName) {
+ try {
+ Slog.d(TAG, "Requesting explicit health check for " + packageName);
+ mHealthCheckController.request(packageName);
+ } catch (RemoteException e) {
+ Slog.wtf(TAG, "Failed to request explicit health check for " + packageName, e);
+ }
+ }
+
+ private void cancelCheck(String packageName) {
+ try {
+ Slog.d(TAG, "Cancelling explicit health check for " + packageName);
+ mHealthCheckController.cancel(packageName);
+ } catch (RemoteException e) {
+ Slog.wtf(TAG, "Failed to cancel explicit health check for " + packageName, e);
+ }
+ }
+
+ private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
+ Consumer<String> action) {
+ Iterator<String> iterator = collection1.iterator();
+ while (iterator.hasNext()) {
+ String packageName = iterator.next();
+ if (!collection2.contains(packageName)) {
+ action.accept(packageName);
+ }
+ }
+ }
+
+ private void updateChecksInner(List<String> supportedPackages,
+ List<String> previousRequestedPackages) {
+ boolean shouldUpdateFile = false;
+
+ synchronized (mLock) {
+ Slog.i(TAG, "Updating explicit health checks. Supported packages: " + supportedPackages
+ + ". Requested packages: " + previousRequestedPackages);
+ Set<String> newRequestedPackages = new ArraySet<>();
+ Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
+ while (oit.hasNext()) {
+ ObserverInternal observer = oit.next();
+ Iterator<MonitoredPackage> pit =
+ observer.mPackages.values().iterator();
+ while (pit.hasNext()) {
+ MonitoredPackage monitoredPackage = pit.next();
+ String packageName = monitoredPackage.mName;
+ if (!monitoredPackage.mHasPassedHealthCheck) {
+ if (supportedPackages.contains(packageName)) {
+ newRequestedPackages.add(packageName);
+ } else {
+ shouldUpdateFile = true;
+ monitoredPackage.mHasPassedHealthCheck = true;
+ }
+ }
+ }
+ }
+ // TODO: Support ending the binding if newRequestedPackages is empty.
+ // Will have to re-bind when we #startObservingHealth.
+
+ // Cancel packages no longer requested
+ actOnDifference(previousRequestedPackages, newRequestedPackages, p -> cancelCheck(p));
+ // Request packages not yet requested
+ actOnDifference(newRequestedPackages, previousRequestedPackages, p -> requestCheck(p));
+ }
+
+ if (shouldUpdateFile) {
+ saveToFileAsync();
+ }
+ }
+
+ private void updateHealthChecks() {
+ mShortTaskHandler.post(() -> {
+ try {
+ Slog.i(TAG, "Updating explicit health checks for all available packages");
+ mHealthCheckController.getSupportedPackages(supported -> {
+ try {
+ mHealthCheckController.getRequestedPackages(
+ requested -> updateChecksInner(supported, requested));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get requested health check packages", e);
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get supported health check package", e);
+ }
+ });
+ }
+
/**
* Unregisters {@code observer} from listening to package failure.
* Additionally, this stops observing any packages that may have previously been observed
@@ -250,7 +382,7 @@
* <p>This method could be called frequently if there is a severe problem on the device.
*/
public void onPackageFailure(List<VersionedPackage> packages) {
- mWorkerHandler.post(() -> {
+ mLongTaskHandler.post(() -> {
synchronized (mLock) {
if (mAllObservers.isEmpty()) {
return;
@@ -286,49 +418,32 @@
});
}
- /**
- * Updates the observers monitoring {@code packageName} that explicit health check has passed.
- *
- * <p> This update is strictly for registered observers at the time of the call
- * Observers that register after this signal will have no knowledge of prior signals and will
- * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
- *
- * <p> {@code packageName} can still be considered failed if reported by
- * {@link #onPackageFailure} before the package expires.
- *
- * <p> Triggered by components outside the system server when they are fully functional after an
- * update.
- */
- public void onExplicitHealthCheckPassed(String packageName) {
- Slog.i(TAG, "Health check passed for package: " + packageName);
- boolean shouldUpdateFile = false;
- synchronized (mLock) {
- for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
- ObserverInternal observer = mAllObservers.valueAt(observerIdx);
- MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
- if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) {
- monitoredPackage.mHasPassedHealthCheck = true;
- shouldUpdateFile = true;
- }
- }
- }
- if (shouldUpdateFile) {
- saveToFileAsync();
- }
- }
-
// TODO(b/120598832): Optimize write? Maybe only write a separate smaller file?
// This currently adds about 7ms extra to shutdown thread
/** Writes the package information to file during shutdown. */
public void writeNow() {
if (!mAllObservers.isEmpty()) {
- mWorkerHandler.removeCallbacks(this::saveToFile);
+ mLongTaskHandler.removeCallbacks(this::saveToFile);
pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
saveToFile();
Slog.i(TAG, "Last write to update package durations");
}
}
+ // TODO(b/120598832): Set depending on DeviceConfig flag
+ /**
+ * Enables or disables explicit health checks.
+ * <p> If explicit health checks are enabled, the health check service is started.
+ * <p> If explicit health checks are disabled, pending explicit health check requests are
+ * passed and the health check service is stopped.
+ */
+ public void setExplicitHealthCheckEnabled(boolean enabled) {
+ synchronized (mLock) {
+ mIsHealthCheckEnabled = enabled;
+ mHealthCheckController.setEnabled(enabled);
+ }
+ }
+
/** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
@Retention(SOURCE)
@IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
@@ -371,6 +486,37 @@
String getName();
}
+ /**
+ * Updates the observers monitoring {@code packageName} that explicit health check has passed.
+ *
+ * <p> This update is strictly for registered observers at the time of the call
+ * Observers that register after this signal will have no knowledge of prior signals and will
+ * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
+ *
+ * <p> {@code packageName} can still be considered failed if reported by
+ * {@link #onPackageFailure} before the package expires.
+ *
+ * <p> Triggered by components outside the system server when they are fully functional after an
+ * update.
+ */
+ private void onHealthCheckPassed(String packageName) {
+ Slog.i(TAG, "Health check passed for package: " + packageName);
+ boolean shouldUpdateFile = false;
+ synchronized (mLock) {
+ for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
+ ObserverInternal observer = mAllObservers.valueAt(observerIdx);
+ MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
+ if (monitoredPackage != null && !monitoredPackage.mHasPassedHealthCheck) {
+ monitoredPackage.mHasPassedHealthCheck = true;
+ shouldUpdateFile = true;
+ }
+ }
+ }
+ if (shouldUpdateFile) {
+ saveToFileAsync();
+ }
+ }
+
/** Reschedules handler to prune expired packages from observers. */
private void rescheduleCleanup() {
synchronized (mLock) {
@@ -393,8 +539,8 @@
|| nextDurationToScheduleMs < remainingDurationMs) {
// First schedule or an earlier reschedule
pruneObservers(elapsedDurationMs);
- mTimerHandler.removeCallbacks(mPackageCleanup);
- mTimerHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
+ mShortTaskHandler.removeCallbacks(mPackageCleanup);
+ mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
mDurationAtLastReschedule = nextDurationToScheduleMs;
mUptimeAtLastRescheduleMs = uptimeMs;
}
@@ -437,7 +583,7 @@
List<MonitoredPackage> failedPackages =
observer.updateMonitoringDurations(elapsedMs);
if (!failedPackages.isEmpty()) {
- onExplicitHealthCheckFailed(observer, failedPackages);
+ onHealthCheckFailed(observer, failedPackages);
}
if (observer.mPackages.isEmpty()) {
Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired");
@@ -445,12 +591,13 @@
}
}
}
+ updateHealthChecks();
saveToFileAsync();
}
- private void onExplicitHealthCheckFailed(ObserverInternal observer,
+ private void onHealthCheckFailed(ObserverInternal observer,
List<MonitoredPackage> failedPackages) {
- mWorkerHandler.post(() -> {
+ mLongTaskHandler.post(() -> {
synchronized (mLock) {
PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
if (registeredObserver != null) {
@@ -458,6 +605,7 @@
for (int i = 0; i < failedPackages.size(); i++) {
String packageName = failedPackages.get(i).mName;
long versionCode = 0;
+ Slog.i(TAG, "Explicit health check failed for package " + packageName);
try {
versionCode = pm.getPackageInfo(
packageName, 0 /* flags */).getLongVersionCode();
@@ -498,7 +646,7 @@
} catch (FileNotFoundException e) {
// Nothing to monitor
} catch (IOException | NumberFormatException | XmlPullParserException e) {
- Log.wtf(TAG, "Unable to read monitored packages, deleting file", e);
+ Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e);
mPolicyFile.delete();
} finally {
IoUtils.closeQuietly(infile);
@@ -542,8 +690,8 @@
}
private void saveToFileAsync() {
- mWorkerHandler.removeCallbacks(this::saveToFile);
- mWorkerHandler.post(this::saveToFile);
+ mLongTaskHandler.removeCallbacks(this::saveToFile);
+ mLongTaskHandler.post(this::saveToFile);
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f51589a..3eb7c03 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2135,6 +2135,8 @@
mService.mServices.systemServicesReady();
} else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
mService.startBroadcastObservers();
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mService.mPackageWatchdog.onPackagesReady();
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index cc90182..8a462da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
@@ -306,9 +305,7 @@
mSamplingInterval = 0;
mAutoStop = false;
mStreaming = false;
- mUserId = mInternal.mUserController.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), defUser, false, ALLOW_FULL_ONLY,
- "ActivityManagerShellCommand", null);
+ mUserId = defUser;
mDisplayId = INVALID_DISPLAY;
mWindowingMode = WINDOWING_MODE_UNDEFINED;
mActivityType = ACTIVITY_TYPE_UNDEFINED;
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index c385991..fe762c0 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -39,12 +39,16 @@
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
+import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SELinux;
import android.os.UserHandle;
import android.os.UserManager;
+import android.service.restricted_image.RestrictedImagesDumpProto;
+import android.service.restricted_image.RestrictedImageProto;
+import android.service.restricted_image.RestrictedImageSetProto;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -281,7 +285,9 @@
final long ident = Binder.clearCallingIdentity();
try {
- if (args.length > 0 && "--proto".equals(args[0])) {
+ if (args.length == 1 && "--restricted_image".equals(args[0])) {
+ dumpRestrictedImage(fd);
+ } else if (args.length > 0 && "--proto".equals(args[0])) {
dumpProto(fd);
} else {
dumpInternal(pw);
@@ -1063,4 +1069,74 @@
mPerformanceMap.clear();
mCryptoPerformanceMap.clear();
}
+
+ private void dumpRestrictedImage(FileDescriptor fd) {
+ // WARNING: CDD restricts image data from leaving TEE unencrypted on
+ // production devices:
+ // [C-1-10] MUST not allow unencrypted access to identifiable biometric
+ // data or any data derived from it (such as embeddings) to the
+ // Application Processor outside the context of the TEE.
+ // As such, this API should only be enabled for testing purposes on
+ // engineering and userdebug builds. All modules in the software stack
+ // MUST enforce final build products do NOT have this functionality.
+ // Additionally, the following check MUST NOT be removed.
+ if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+ return;
+ }
+
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ final long setToken = proto.start(RestrictedImagesDumpProto.SETS);
+
+ // Name of the service
+ proto.write(RestrictedImageSetProto.CATEGORY, "face");
+
+ // Individual images
+ for (int i = 0; i < 5; i++) {
+ final long imageToken = proto.start(RestrictedImageSetProto.IMAGES);
+ proto.write(RestrictedImageProto.MIME_TYPE, "image/png");
+ proto.write(RestrictedImageProto.IMAGE_DATA, new byte[] {
+ // png image data
+ -119, 80, 78, 71, 13, 10, 26, 10,
+ 0, 0, 0, 13, 73, 72, 68, 82,
+ 0, 0, 0, 100, 0, 0, 0, 100,
+ 1, 3, 0, 0, 0, 74, 44, 7,
+ 23, 0, 0, 0, 4, 103, 65, 77,
+ 65, 0, 0, -79, -113, 11, -4, 97,
+ 5, 0, 0, 0, 1, 115, 82, 71,
+ 66, 0, -82, -50, 28, -23, 0, 0,
+ 0, 6, 80, 76, 84, 69, -1, -1,
+ -1, 0, 0, 0, 85, -62, -45, 126,
+ 0, 0, 0, -115, 73, 68, 65, 84,
+ 56, -53, -19, -46, -79, 17, -128, 32,
+ 12, 5, -48, 120, 22, -106, -116, -32,
+ 40, -84, 101, -121, -93, 57, 10, 35,
+ 88, 82, 112, 126, 3, -60, 104, 6,
+ -112, 70, 127, -59, -69, -53, 29, 33,
+ -127, -24, 79, -49, -52, -15, 41, 36,
+ 34, -105, 85, 124, -14, 88, 27, 6,
+ 28, 68, 1, 82, 62, 22, -95, -108,
+ 55, -95, 40, -9, -110, -12, 98, -107,
+ 76, -41, -105, -62, -50, 111, -60, 46,
+ -14, -4, 24, -89, 42, -103, 16, 63,
+ -72, -11, -15, 48, -62, 102, -44, 102,
+ -73, -56, 56, -21, -128, 92, -70, -124,
+ 117, -46, -67, -77, 82, 80, 121, -44,
+ -56, 116, 93, -45, -90, -5, -29, -24,
+ -83, -75, 52, -34, 55, -22, 102, -21,
+ -105, -124, -23, 71, 87, -7, -25, -59,
+ -100, -73, -92, -122, -7, -109, -49, -80,
+ -89, 0, 0, 0, 0, 73, 69, 78,
+ 68, -82, 66, 96, -126
+ });
+ // proto.write(RestrictedImageProto.METADATA, flattened_protobuf);
+ proto.end(imageToken);
+ }
+
+ // Face service metadata
+ // proto.write(RestrictedImageSetProto.METADATA, flattened_protobuf);
+
+ proto.end(setToken);
+ proto.flush();
+ }
}
diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java
index 5c69c1d..9989c1a 100644
--- a/services/core/java/com/android/server/incident/IncidentCompanionService.java
+++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java
@@ -23,7 +23,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
import android.os.IIncidentAuthListener;
import android.os.IIncidentCompanion;
import android.os.IIncidentManager;
@@ -49,6 +52,12 @@
static final String TAG = "IncidentCompanionService";
/**
+ * Dump argument for proxying restricted image dumps to the services
+ * listed in the config.
+ */
+ private static String[] RESTRICTED_IMAGE_DUMP_ARGS = new String[] { "--restricted_image" };
+
+ /**
* The two permissions, for sendBroadcastAsUserMultiplePermissions.
*/
private static final String[] DUMP_AND_USAGE_STATS_PERMISSIONS = new String[] {
@@ -260,7 +269,42 @@
if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) {
return;
}
- mPendingReports.dump(fd, writer, args);
+
+ if (args.length == 1 && "--restricted_image".equals(args[0])) {
+ // Does NOT clearCallingIdentity
+ dumpRestrictedImages(fd);
+ } else {
+ // Regular dump
+ mPendingReports.dump(fd, writer, args);
+ }
+ }
+
+ /**
+ * Proxy for the restricted images section.
+ */
+ private void dumpRestrictedImages(FileDescriptor fd) {
+ // Only supported on eng or userdebug.
+ if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
+ return;
+ }
+
+ final Resources res = getContext().getResources();
+ final String[] services = res.getStringArray(
+ com.android.internal.R.array.config_restrictedImagesServices);
+ final int servicesCount = services.length;
+ for (int i = 0; i < servicesCount; i++) {
+ final String name = services[i];
+ Log.d(TAG, "Looking up service " + name);
+ final IBinder service = ServiceManager.getService(name);
+ if (service != null) {
+ Log.d(TAG, "Calling dump on service: " + name);
+ try {
+ service.dump(fd, RESTRICTED_IMAGE_DUMP_ARGS);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "dump --restricted_image of " + name + " threw", ex);
+ }
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 622c49e..5abc73e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1809,6 +1809,10 @@
}
// Native callback.
+ private void onPointerDownOutsideFocus(IBinder touchedToken) {
+ }
+
+ // Native callback.
private int getVirtualKeyQuietTimeMillis() {
return mContext.getResources().getInteger(
com.android.internal.R.integer.config_virtualKeyQuietTimeMillis);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 54ec4f2..bfab85b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,7 +17,11 @@
package com.android.server.notification;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
+import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
@@ -844,7 +848,7 @@
}
}
cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
- Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE,
+ FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE,
true, userId, REASON_CANCEL, nv.rank, nv.count,null);
nv.recycle();
}
@@ -2339,7 +2343,7 @@
// Don't allow client applications to cancel foreground service notis or autobundled
// summaries.
final int mustNotHaveFlags = isCallingUidSystem() ? 0 :
- (FLAG_FOREGROUND_SERVICE | Notification.FLAG_AUTOGROUP_SUMMARY);
+ (FLAG_FOREGROUND_SERVICE | FLAG_AUTOGROUP_SUMMARY);
cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,
mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null);
}
@@ -3143,7 +3147,7 @@
private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
- Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE,
+ FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE,
true,
userId, REASON_LISTENER_CANCEL, info);
}
@@ -4234,7 +4238,7 @@
.setGroupSummary(true)
.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
.setGroup(GroupHelper.AUTOGROUP_KEY)
- .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true)
+ .setFlag(FLAG_AUTOGROUP_SUMMARY, true)
.setFlag(Notification.FLAG_GROUP_SUMMARY, true)
.setColor(adjustedSbn.getNotification().color)
.setLocalOnly(true)
@@ -4763,9 +4767,9 @@
boolean canBubble = mPreferencesHelper.areBubblesAllowed(pkg, userId)
&& r.getChannel().canBubble();
if (notification.getBubbleMetadata() != null && canBubble) {
- notification.flags |= Notification.FLAG_BUBBLE;
+ notification.flags |= FLAG_BUBBLE;
} else {
- notification.flags &= ~Notification.FLAG_BUBBLE;
+ notification.flags &= ~FLAG_BUBBLE;
}
}
@@ -5228,8 +5232,8 @@
// Ensure if this is a foreground service that the proper additional
// flags are set.
if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
- notification.flags |= Notification.FLAG_ONGOING_EVENT
- | Notification.FLAG_NO_CLEAR;
+ notification.flags |= FLAG_ONGOING_EVENT
+ | FLAG_NO_CLEAR;
}
mRankingHelper.extractSignals(r);
@@ -6686,8 +6690,11 @@
null, userId, 0, 0, reason, listenerName);
FlagChecker flagChecker = (int flags) -> {
- if ((flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR))
- != 0) {
+ int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
+ if (REASON_LISTENER_CANCEL_ALL == reason) {
+ flagsToCheck |= FLAG_BUBBLE;
+ }
+ if ((flags & flagsToCheck) != 0) {
return false;
}
return true;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index c52e29b..e4cb283 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -498,10 +498,11 @@
}
}
- if (callingUid == Process.SYSTEM_UID) {
+ if (Build.IS_DEBUGGABLE || isDowngradeAllowedForCaller(callingUid)) {
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
+ params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
}
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
@@ -621,6 +622,11 @@
return sessionId;
}
+ private boolean isDowngradeAllowedForCaller(int callingUid) {
+ return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID
+ || callingUid == Process.SHELL_UID;
+ }
+
@Override
public void updateSessionAppIcon(int sessionId, Bitmap appIcon) {
synchronized (mSessions) {
@@ -813,7 +819,7 @@
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
statusReceiver, versionedPackage.getPackageName(),
- !canSilentlyInstallPackage, userId);
+ canSilentlyInstallPackage, userId);
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 9348806..d8f07fe 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -166,8 +166,7 @@
* This may cause {@code packages} to be rolled back if they crash too freqeuntly.
*/
public void startObservingHealth(List<String> packages, long durationMs) {
- PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs,
- false /* withExplicitHealthCheck */);
+ PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
}
/** Verifies the rollback state after a reboot. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c2a4339..d52ba16 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2671,7 +2671,10 @@
wallpaper.connection.mReply = null;
}
try {
- wallpaper.connection.mService.detach();
+ // It can be null if user switching happens before service connection.
+ if (wallpaper.connection.mService != null) {
+ wallpaper.connection.mService.detach();
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Failed detaching wallpaper service ", e);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a93bdba..91ec4a0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2730,30 +2730,40 @@
final int appHeight = resolvedAppBounds.height();
final int parentAppWidth = parentAppBounds.width();
final int parentAppHeight = parentAppBounds.height();
+ if (parentAppWidth == appWidth && parentAppHeight == appHeight) {
+ // Matched the parent bounds.
+ return false;
+ }
+ if (parentAppWidth > appWidth && parentAppHeight > appHeight) {
+ // Both sides are smaller than the parent.
+ return true;
+ }
if (parentAppWidth < appWidth || parentAppHeight < appHeight) {
// One side is larger than the parent.
return true;
}
- if (info.hasFixedAspectRatio()) {
+ // The rest of the condition is that only one side is smaller than the parent, but it still
+ // needs to exclude the cases where the size is limited by the fixed aspect ratio.
+ if (info.maxAspectRatio > 0) {
final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
/ Math.min(appWidth, appHeight);
- final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight))
- / Math.min(parentAppWidth, parentAppHeight);
- // Check if the parent still has available space in long side.
- if (aspectRatio < parentAspectRatio
- && (aspectRatio < info.maxAspectRatio || info.minAspectRatio > 0)) {
- return true;
+ if (aspectRatio >= info.maxAspectRatio) {
+ // The current size has reached the max aspect ratio.
+ return false;
}
}
-
- final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
- // If the width or height is the same as parent, it is already the best fit of the override
- // bounds, therefore this condition is considered as not size compatibility mode. Here uses
- // right and bottom as width and height of parent because the bounds may contain decor
- // insets which has been accounted in override bounds. See {@link #computeBounds}.
- return parentAppBounds.right != resolvedBounds.width()
- && parentAppBounds.bottom != resolvedBounds.height();
+ if (info.minAspectRatio > 0) {
+ // The activity should have at least the min aspect ratio, so this checks if the parent
+ // still has available space to provide larger aspect ratio.
+ final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight))
+ / Math.min(parentAppWidth, parentAppHeight);
+ if (parentAspectRatio <= info.minAspectRatio) {
+ // The long side has reached the parent.
+ return false;
+ }
+ }
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index 3bbe28d..90bb494 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -51,6 +51,7 @@
private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1;
protected final String mTag;
+ protected final int mDisplayId;
private final int mTransientFlag;
private final int mUnhideFlag;
private final int mTranslucentFlag;
@@ -74,9 +75,10 @@
private OnBarVisibilityChangedListener mVisibilityChangeListener;
- BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag,
+ BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag,
int statusBarManagerId, int translucentWmFlag, int transparentFlag) {
mTag = "BarController." + tag;
+ mDisplayId = displayId;
mTransientFlag = transientFlag;
mUnhideFlag = unhideFlag;
mTranslucentFlag = translucentFlag;
@@ -230,7 +232,7 @@
public void run() {
StatusBarManagerInternal statusbar = getStatusBarInternal();
if (statusbar != null) {
- statusbar.setWindowState(mWin.getDisplayId(), mStatusBarManagerId, state);
+ statusbar.setWindowState(mDisplayId, mStatusBarManagerId, state);
}
}
});
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index cb069fa..bbb857f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -26,6 +26,8 @@
import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.InsetsState.TYPE_TOP_BAR;
+import static android.view.InsetsState.TYPE_TOP_GESTURES;
+import static android.view.InsetsState.TYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
@@ -40,6 +42,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
@@ -152,6 +155,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ScreenShapeHelper;
import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.function.TriConsumer;
import com.android.internal.widget.PointerLocationView;
import com.android.server.LocalServices;
import com.android.server.UiThread;
@@ -215,6 +219,12 @@
private final Object mServiceAcquireLock = new Object();
private StatusBarManagerInternal mStatusBarManagerInternal;
+ @Px
+ private int mBottomGestureAdditionalInset;
+ @Px
+ private int mSideGestureInset;
+ private boolean mNavigationBarLetsThroughTaps;
+
private StatusBarManagerInternal getStatusBarManagerInternal() {
synchronized (mServiceAcquireLock) {
if (mStatusBarManagerInternal == null) {
@@ -261,15 +271,9 @@
/** Cached value of {@link ScreenShapeHelper#getWindowOutsetBottomPx} */
@Px private int mWindowOutsetBottom;
- private final StatusBarController mStatusBarController = new StatusBarController();
+ private final StatusBarController mStatusBarController;
- private final BarController mNavigationBarController = new BarController("NavigationBar",
- View.NAVIGATION_BAR_TRANSIENT,
- View.NAVIGATION_BAR_UNHIDE,
- View.NAVIGATION_BAR_TRANSLUCENT,
- StatusBarManager.WINDOW_NAVIGATION_BAR,
- FLAG_TRANSLUCENT_NAVIGATION,
- View.NAVIGATION_BAR_TRANSPARENT);
+ private final BarController mNavigationBarController;
private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
new BarController.OnBarVisibilityChangedListener() {
@@ -416,6 +420,17 @@
mDisplayContent = displayContent;
mLock = service.getWindowManagerLock();
+ final int displayId = displayContent.getDisplayId();
+ mStatusBarController = new StatusBarController(displayId);
+ mNavigationBarController = new BarController("NavigationBar",
+ displayId,
+ View.NAVIGATION_BAR_TRANSIENT,
+ View.NAVIGATION_BAR_UNHIDE,
+ View.NAVIGATION_BAR_TRANSLUCENT,
+ StatusBarManager.WINDOW_NAVIGATION_BAR,
+ FLAG_TRANSLUCENT_NAVIGATION,
+ View.NAVIGATION_BAR_TRANSPARENT);
+
final Resources r = mContext.getResources();
mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer);
@@ -527,7 +542,6 @@
if (mWindowSleepToken != null) {
return;
}
- final int displayId = displayContent.getDisplayId();
mWindowSleepToken = service.mAtmInternal.acquireSleepToken(
"WindowSleepTokenOnDisplay" + displayId, displayId);
};
@@ -857,11 +871,14 @@
if (mDisplayContent.isDefaultDisplay) {
mService.mPolicy.setKeyguardCandidateLw(win);
}
- mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win,
+ final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider =
(displayFrames, windowState, rect) -> {
rect.top = 0;
rect.bottom = getStatusBarHeight(displayFrames);
- });
+ };
+ mDisplayContent.setInsetProvider(TYPE_TOP_BAR, win, frameProvider);
+ mDisplayContent.setInsetProvider(TYPE_TOP_GESTURES, win, frameProvider);
+ mDisplayContent.setInsetProvider(TYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider);
break;
case TYPE_NAVIGATION_BAR:
mContext.enforceCallingOrSelfPermission(
@@ -878,6 +895,31 @@
mNavBarVisibilityListener, true);
mDisplayContent.setInsetProvider(InsetsState.TYPE_NAVIGATION_BAR,
win, null /* frameProvider */);
+ mDisplayContent.setInsetProvider(InsetsState.TYPE_BOTTOM_GESTURES, win,
+ (displayFrames, windowState, inOutFrame) -> {
+ inOutFrame.top -= mBottomGestureAdditionalInset;
+ });
+ mDisplayContent.setInsetProvider(InsetsState.TYPE_LEFT_GESTURES, win,
+ (displayFrames, windowState, inOutFrame) -> {
+ inOutFrame.left = 0;
+ inOutFrame.top = 0;
+ inOutFrame.bottom = displayFrames.mDisplayHeight;
+ inOutFrame.right = displayFrames.mUnrestricted.left + mSideGestureInset;
+ });
+ mDisplayContent.setInsetProvider(InsetsState.TYPE_RIGHT_GESTURES, win,
+ (displayFrames, windowState, inOutFrame) -> {
+ inOutFrame.left = displayFrames.mUnrestricted.right - mSideGestureInset;
+ inOutFrame.top = 0;
+ inOutFrame.bottom = displayFrames.mDisplayHeight;
+ inOutFrame.right = displayFrames.mDisplayWidth;
+ });
+ mDisplayContent.setInsetProvider(InsetsState.TYPE_BOTTOM_TAPPABLE_ELEMENT, win,
+ (displayFrames, windowState, inOutFrame) -> {
+ if ((windowState.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
+ || mNavigationBarLetsThroughTaps) {
+ inOutFrame.setEmpty();
+ }
+ });
if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
break;
case TYPE_NAVIGATION_BAR_PANEL:
@@ -2607,11 +2649,20 @@
}
mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
+ mSideGestureInset = res.getDimensionPixelSize(R.dimen.config_backGestureInset);
+ mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough);
// EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
mExperiments.onConfigurationChanged(uiContext);
// EXPERIMENT END
+ // EXPERIMENT: TODO(b/113952590): Replace with real code after experiment.
+ // This should calculate how much above the frame we accept gestures. Currently,
+ // we extend the frame to capture the gestures, so this is 0.
+ mBottomGestureAdditionalInset = mExperiments.getNavigationBarFrameHeight()
+ - mExperiments.getNavigationBarFrameHeight();
+ // EXPERIMENT END
+
updateConfigurationAndScreenSizeDependentBehaviors();
mWindowOutsetBottom = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
}
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
index 6db606d..f4260d3 100644
--- a/services/core/java/com/android/server/wm/StatusBarController.java
+++ b/services/core/java/com/android/server/wm/StatusBarController.java
@@ -36,22 +36,22 @@
private Runnable mAppTransitionPending = () -> {
StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null && mWin != null) {
- statusBar.appTransitionPending(mWin.getDisplayId());
+ if (statusBar != null) {
+ statusBar.appTransitionPending(mDisplayId);
}
};
private Runnable mAppTransitionCancelled = () -> {
StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null && mWin != null) {
- statusBar.appTransitionCancelled(mWin.getDisplayId());
+ if (statusBar != null) {
+ statusBar.appTransitionCancelled(mDisplayId);
}
};
private Runnable mAppTransitionFinished = () -> {
StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null && mWin != null) {
- statusBar.appTransitionFinished(mWin.getDisplayId());
+ if (statusBar != null) {
+ statusBar.appTransitionFinished(mDisplayId);
}
};
@@ -65,8 +65,8 @@
long statusBarAnimationStartTime, long statusBarAnimationDuration) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarInternal();
- if (statusBar != null && mWin != null) {
- statusBar.appTransitionStarting(mWin.getDisplayId(),
+ if (statusBar != null) {
+ statusBar.appTransitionStarting(mDisplayId,
statusBarAnimationStartTime, statusBarAnimationDuration);
}
});
@@ -84,8 +84,9 @@
}
};
- StatusBarController() {
+ StatusBarController(int displayId) {
super("StatusBar",
+ displayId,
View.STATUS_BAR_TRANSIENT,
View.STATUS_BAR_UNHIDE,
View.STATUS_BAR_TRANSLUCENT,
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3d84bd4..3d6c868 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -96,6 +96,7 @@
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
jmethodID checkInjectEventsPermission;
+ jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
jmethodID getInputPortAssociations;
@@ -259,6 +260,7 @@
virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
virtual bool checkInjectEventsPermissionNonReentrant(
int32_t injectorPid, int32_t injectorUid);
+ virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken);
/* --- PointerControllerPolicyInterface implementation --- */
@@ -1205,6 +1207,15 @@
return result;
}
+void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
+ ATRACE_CALL();
+ JNIEnv* env = jniEnv();
+
+ jobject touchedTokenObj = javaObjectForIBinder(env, touchedToken);
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDownOutsideFocus, touchedTokenObj);
+ checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus");
+}
+
void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
@@ -1809,6 +1820,9 @@
GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
"checkInjectEventsPermission", "(II)Z");
+ GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
+ "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
+
GET_METHOD_ID(gServiceClassInfo.getVirtualKeyQuietTimeMillis, clazz,
"getVirtualKeyQuietTimeMillis", "()I");
diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
index 6a9eae0..e769769 100644
--- a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
+++ b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -60,6 +60,13 @@
public final Inet4Address assignedV4Address;
private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
+ // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds.
+ @Nullable
+ public final Long assignedV4AddressExpiry;
+ // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the
+ // same L3 network".
+ private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f;
+
// Optionally supplied by the client if it has an opinion on L3 network. For example, this
// could be a hash of the SSID + security type on WiFi.
@Nullable
@@ -81,6 +88,7 @@
/** @hide */
@VisibleForTesting
public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
+ + WEIGHT_ASSIGNEDV4ADDREXPIRY
+ WEIGHT_GROUPHINT
+ WEIGHT_DNSADDRESSES
+ WEIGHT_MTU;
@@ -89,11 +97,16 @@
@VisibleForTesting
public NetworkAttributes(
@Nullable final Inet4Address assignedV4Address,
+ @Nullable final Long assignedV4AddressExpiry,
@Nullable final String groupHint,
@Nullable final List<InetAddress> dnsAddresses,
@Nullable final Integer mtu) {
if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+ if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) {
+ throw new IllegalArgumentException("lease expiry can't be negative or zero");
+ }
this.assignedV4Address = assignedV4Address;
+ this.assignedV4AddressExpiry = assignedV4AddressExpiry;
this.groupHint = groupHint;
this.dnsAddresses = null == dnsAddresses ? null :
Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
@@ -105,6 +118,8 @@
// The call to the other constructor must be the first statement of this constructor,
// so everything has to be inline
this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
+ parcelable.assignedV4AddressExpiry > 0
+ ? parcelable.assignedV4AddressExpiry : null,
parcelable.groupHint,
blobArrayToInetAddressList(parcelable.dnsAddresses),
parcelable.mtu >= 0 ? parcelable.mtu : null);
@@ -150,6 +165,8 @@
final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
parcelable.assignedV4Address =
(null == assignedV4Address) ? null : assignedV4Address.getAddress();
+ parcelable.assignedV4AddressExpiry =
+ (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry;
parcelable.groupHint = groupHint;
parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
parcelable.mtu = (null == mtu) ? -1 : mtu;
@@ -168,6 +185,8 @@
public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
final float samenessScore =
samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
+ + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry,
+ o.assignedV4AddressExpiry)
+ samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
+ samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
+ samenessContribution(WEIGHT_MTU, mtu, o.mtu);
@@ -189,6 +208,8 @@
@Nullable
private Inet4Address mAssignedAddress;
@Nullable
+ private Long mAssignedAddressExpiry;
+ @Nullable
private String mGroupHint;
@Nullable
private List<InetAddress> mDnsAddresses;
@@ -206,6 +227,20 @@
}
/**
+ * Set the lease expiry timestamp of assigned v4 address.
+ * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address.
+ * @return This builder.
+ */
+ public Builder setAssignedV4AddressExpiry(
+ @Nullable final Long assignedV4AddressExpiry) {
+ if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) {
+ throw new IllegalArgumentException("lease expiry can't be negative or zero");
+ }
+ mAssignedAddressExpiry = assignedV4AddressExpiry;
+ return this;
+ }
+
+ /**
* Set the group hint.
* @param groupHint The group hint.
* @return This builder.
@@ -248,14 +283,15 @@
* @return The built NetworkAttributes object.
*/
public NetworkAttributes build() {
- return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu);
+ return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry,
+ mGroupHint, mDnsAddresses, mMtu);
}
}
/** @hide */
public boolean isEmpty() {
- return (null == assignedV4Address) && (null == groupHint)
- && (null == dnsAddresses) && (null == mtu);
+ return (null == assignedV4Address) && (null == assignedV4AddressExpiry)
+ && (null == groupHint) && (null == dnsAddresses) && (null == mtu);
}
@Override
@@ -263,6 +299,7 @@
if (!(o instanceof NetworkAttributes)) return false;
final NetworkAttributes other = (NetworkAttributes) o;
return Objects.equals(assignedV4Address, other.assignedV4Address)
+ && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry)
&& Objects.equals(groupHint, other.groupHint)
&& Objects.equals(dnsAddresses, other.dnsAddresses)
&& Objects.equals(mtu, other.mtu);
@@ -270,7 +307,8 @@
@Override
public int hashCode() {
- return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
+ return Objects.hash(assignedV4Address, assignedV4AddressExpiry,
+ groupHint, dnsAddresses, mtu);
}
/** Pretty print */
@@ -286,6 +324,13 @@
nullFields.add("assignedV4Addr");
}
+ if (null != assignedV4AddressExpiry) {
+ resultJoiner.add("assignedV4AddressExpiry :");
+ resultJoiner.add(assignedV4AddressExpiry.toString());
+ } else {
+ nullFields.add("assignedV4AddressExpiry");
+ }
+
if (null != groupHint) {
resultJoiner.add("groupHint :");
resultJoiner.add(groupHint);
diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
index 0894d72..997eb2b 100644
--- a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
+++ b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -30,6 +30,7 @@
*/
parcelable NetworkAttributesParcelable {
byte[] assignedV4Address;
+ long assignedV4AddressExpiry;
String groupHint;
Blob[] dnsAddresses;
int mtu;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 4332fea..6f1bd87 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
@@ -123,6 +124,9 @@
import android.util.ArraySet;
import android.util.AtomicFile;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
@@ -133,7 +137,6 @@
import com.android.server.lights.LightsManager;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
import com.android.server.notification.NotificationManagerService.NotificationListeners;
-import com.android.server.pm.UserManagerService;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -159,9 +162,6 @@
import java.util.Set;
import java.util.function.Consumer;
-import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -4424,6 +4424,77 @@
sbn.getKey()).getNotification().isBubbleNotification());
}
+ @Test
+ public void testCancelAllNotifications_cancelsBubble() throws Exception {
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.sbn.getNotification().flags |= FLAG_BUBBLE;
+ mService.addNotification(nr);
+
+ mBinderService.cancelAllNotifications(PKG, nr.sbn.getUserId());
+ waitForIdle();
+
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(0, notifs.length);
+ assertEquals(0, mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testAppCancelNotifications_cancelsBubbles() throws Exception {
+ final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
+ nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+
+ // Post the notification
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+ nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
+ waitForIdle();
+
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+
+ mBinderService.cancelNotificationWithTag(PKG, null, nrBubble.sbn.getId(),
+ nrBubble.sbn.getUserId());
+ waitForIdle();
+
+ StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
+ assertEquals(0, notifs2.length);
+ assertEquals(0, mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception {
+ final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
+ final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
+ nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+
+ mService.addNotification(nrNormal);
+ mService.addNotification(nrBubble);
+
+ mService.getBinderService().cancelNotificationsFromListener(null, null);
+ waitForIdle();
+
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_ignoresBubbles() throws Exception {
+ final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
+ final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
+ nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
+
+ mService.addNotification(nrNormal);
+ mService.addNotification(nrBubble);
+
+ String[] keys = {nrNormal.sbn.getKey(), nrBubble.sbn.getKey()};
+ mService.getBinderService().cancelNotificationsFromListener(null, keys);
+ waitForIdle();
+
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
public void testGetAllowedAssistantCapabilities() throws Exception {
List<String> capabilities = mBinderService.getAllowedAssistantCapabilities(null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index d580557..32e96a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -438,8 +438,11 @@
doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
.when(mActivity.mAppWindowToken).getOrientationIgnoreVisibility();
mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
- mActivity.info.maxAspectRatio = 1;
+ mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
ensureActivityConfiguration();
+ // The parent configuration doesn't change since the first resolved configuration, so the
+ // activity shouldn't be in the size compatibility mode.
+ assertFalse(mActivity.inSizeCompatMode());
final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
// Ensure the app bounds keep the declared aspect ratio.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 14eac87..a933da7 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2587,8 +2587,7 @@
* {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED}.
* @param executor The executor of where the callback will execute.
* @param callback Callback will be triggered once it succeeds or failed.
- * See {@link TelephonyManager.SetOpportunisticSubscriptionResult}
- * for more details. Pass null if don't care about the result.
+ * Pass null if don't care about the result.
*
* @hide
*
@@ -2596,7 +2595,8 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void setPreferredDataSubscriptionId(int subId, boolean needValidation,
- @Nullable @CallbackExecutor Executor executor, @Nullable Consumer<Integer> callback) {
+ @Nullable @CallbackExecutor Executor executor, @Nullable
+ @TelephonyManager.SetOpportunisticSubscriptionResult Consumer<Integer> callback) {
if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId);
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index d0c2612..28af7ce 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -19,11 +19,16 @@
import static com.android.server.PackageWatchdog.TRIGGER_FAILURE_COUNT;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.content.pm.VersionedPackage;
+import android.os.Handler;
+import android.os.RemoteException;
import android.os.test.TestLooper;
+import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
@@ -36,11 +41,14 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
-// TODO(zezeozue): Write test without using PackageWatchdog#getPackages. Just rely on
+// TODO: Write test without using PackageWatchdog#getPackages. Just rely on
// behavior of observers receiving crash notifications or not to determine if it's registered
+// TODO: Use Truth in tests.
/**
* Test PackageWatchdog.
*/
@@ -77,12 +85,11 @@
TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
// Start observing for observer1 which will be unregistered
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Start observing for observer2 which will expire
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION,
- false);
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
// Start observing for observer3 which will have expiry duration reduced
- watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION, false);
+ watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
// Verify packages observed at start
// 1
@@ -145,9 +152,8 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false);
- watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION,
- false);
+ watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
// Verify 2 observers are registered and saved internally
// 1
@@ -193,8 +199,8 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false);
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A below the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT - 1; i++) {
@@ -220,8 +226,8 @@
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION, false);
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
// Then fail APP_C (not observed) above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -255,7 +261,7 @@
}
};
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION, false);
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A (different version) above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -288,13 +294,13 @@
// Start observing for all impact observers
watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
- SHORT_DURATION, false);
+ SHORT_DURATION);
watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
- SHORT_DURATION, false);
+ SHORT_DURATION);
watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
- SHORT_DURATION, false);
+ SHORT_DURATION);
watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
- SHORT_DURATION, false);
+ SHORT_DURATION);
// Then fail all apps above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -346,8 +352,8 @@
PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION, false);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION, false);
+ watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -424,8 +430,8 @@
PackageHealthObserverImpact.USER_IMPACT_HIGH);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION, false);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, false);
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
@@ -442,11 +448,12 @@
}
/**
- * Test explicit health check status determines package failure or success on expiry
+ * Test package passing explicit health checks does not fail and vice versa.
*/
@Test
- public void testPackageFailureExplicitHealthCheck() throws Exception {
- PackageWatchdog watchdog = createWatchdog();
+ public void testExplicitHealthChecks() throws Exception {
+ TestController controller = new TestController();
+ PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_HIGH);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
@@ -457,21 +464,36 @@
// Start observing with explicit health checks for APP_A and APP_B respectively
// with observer1 and observer2
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION, true);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION, true);
- // Explicit health check passed for APP_A (observer1 is aware)
- watchdog.onExplicitHealthCheckPassed(APP_A);
- // Start observing APP_A with explicit health checks for observer3.
+ controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+
+ // Run handler so requests are dispatched to the controller
+ mTestLooper.dispatchAll();
+
+ // Verify we requested health checks for APP_A and APP_B
+ List<String> requestedPackages = controller.getRequestedPackages();
+ assertEquals(2, requestedPackages.size());
+ assertEquals(APP_A, requestedPackages.get(0));
+ assertEquals(APP_B, requestedPackages.get(1));
+
+ // Then health check passed for APP_A (observer1 is aware)
+ controller.setPackagePassed(APP_A);
+
+ // Then start observing APP_A with explicit health checks for observer3.
// Observer3 didn't exist when we got the explicit health check above, so
// it starts out with a non-passing explicit health check and has to wait for a pass
// otherwise it would be notified of APP_A failure on expiry
- watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION, true);
+ watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
// Then expire observers
Thread.sleep(SHORT_DURATION);
// Run handler so package failures are dispatched to observers
mTestLooper.dispatchAll();
+ // Verify we cancelled all requests on expiry
+ assertEquals(0, controller.getRequestedPackages().size());
+
// Verify observer1 is not notified
assertEquals(0, observer1.mFailedPackages.size());
@@ -484,9 +506,96 @@
assertEquals(APP_A, observer3.mFailedPackages.get(0));
}
+ /**
+ * Test explicit health check state can be disabled and enabled correctly.
+ */
+ @Test
+ public void testExplicitHealthCheckStateChanges() throws Exception {
+ TestController controller = new TestController();
+ PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
+ TestObserver observer = new TestObserver(OBSERVER_NAME_1,
+ PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+
+ // Start observing with explicit health checks for APP_A and APP_B
+ controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+
+ // Run handler so requests are dispatched to the controller
+ mTestLooper.dispatchAll();
+
+ // Verify we requested health checks for APP_A and APP_B
+ List<String> requestedPackages = controller.getRequestedPackages();
+ assertEquals(2, requestedPackages.size());
+ assertEquals(APP_A, requestedPackages.get(0));
+ assertEquals(APP_B, requestedPackages.get(1));
+
+ // Disable explicit health checks (marks APP_A and APP_B as passed)
+ watchdog.setExplicitHealthCheckEnabled(false);
+
+ // Run handler so requests/cancellations are dispatched to the controller
+ mTestLooper.dispatchAll();
+
+ // Verify all checks are cancelled
+ assertEquals(0, controller.getRequestedPackages().size());
+
+ // Then expire APP_A
+ Thread.sleep(SHORT_DURATION);
+ mTestLooper.dispatchAll();
+
+ // Verify APP_A is not failed (APP_B) is not expired yet
+ assertEquals(0, observer.mFailedPackages.size());
+
+ // Re-enable explicit health checks
+ watchdog.setExplicitHealthCheckEnabled(true);
+
+ // Run handler so requests/cancellations are dispatched to the controller
+ mTestLooper.dispatchAll();
+
+ // Verify no requests are made cos APP_A is expired and APP_B was marked as passed
+ assertEquals(0, controller.getRequestedPackages().size());
+
+ // Then set new supported packages
+ controller.setSupportedPackages(Arrays.asList(APP_C));
+ // Start observing APP_A and APP_C; only APP_C has support for explicit health checks
+ watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
+
+ // Run handler so requests/cancellations are dispatched to the controller
+ mTestLooper.dispatchAll();
+
+ // Verify requests are only made for APP_C
+ requestedPackages = controller.getRequestedPackages();
+ assertEquals(1, requestedPackages.size());
+ assertEquals(APP_C, requestedPackages.get(0));
+
+ // Then expire APP_A and APP_C
+ Thread.sleep(SHORT_DURATION);
+ mTestLooper.dispatchAll();
+
+ // Verify only APP_C is failed because explicit health checks was not supported for APP_A
+ assertEquals(1, observer.mFailedPackages.size());
+ assertEquals(APP_C, observer.mFailedPackages.get(0));
+ }
+
private PackageWatchdog createWatchdog() {
- return new PackageWatchdog(InstrumentationRegistry.getContext(),
- mTestLooper.getLooper());
+ return createWatchdog(new TestController(), true /* withPackagesReady */);
+ }
+
+ private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) {
+ Context context = InstrumentationRegistry.getContext();
+ AtomicFile policyFile =
+ new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
+ Handler handler = new Handler(mTestLooper.getLooper());
+ PackageWatchdog watchdog =
+ new PackageWatchdog(context, policyFile, handler, handler, controller);
+ // Verify controller is not automatically started
+ assertFalse(controller.mIsEnabled);
+ if (withPackagesReady) {
+ watchdog.onPackagesReady();
+ // Verify controller by default is started when packages are ready
+ assertTrue(controller.mIsEnabled);
+ }
+ return watchdog;
}
private static class TestObserver implements PackageHealthObserver {
@@ -517,4 +626,69 @@
return mName;
}
}
+
+ private static class TestController extends ExplicitHealthCheckController {
+ TestController() {
+ super(null /* controller */);
+ }
+
+ private boolean mIsEnabled;
+ private List<String> mSupportedPackages = new ArrayList<>();
+ private List<String> mRequestedPackages = new ArrayList<>();
+ private Runnable mStateChangedRunnable;
+ private Consumer<String> mPassedConsumer;
+
+ @Override
+ public void request(String packageName) throws RemoteException {
+ if (!mRequestedPackages.contains(packageName)) {
+ mRequestedPackages.add(packageName);
+ }
+ }
+
+ @Override
+ public void cancel(String packageName) throws RemoteException {
+ mRequestedPackages.remove(packageName);
+ }
+
+ @Override
+ public void getSupportedPackages(Consumer<List<String>> consumer) throws RemoteException {
+ consumer.accept(mIsEnabled ? mSupportedPackages : Collections.emptyList());
+ }
+
+ @Override
+ public void getRequestedPackages(Consumer<List<String>> consumer) throws RemoteException {
+ // Pass copy to prevent ConcurrentModificationException during test
+ consumer.accept(
+ mIsEnabled ? new ArrayList<>(mRequestedPackages) : Collections.emptyList());
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mIsEnabled = enabled;
+ mStateChangedRunnable.run();
+ }
+
+ @Override
+ public void setCallbacks(Runnable stateChangedRunnable, Consumer<String> passedConsumer) {
+ mStateChangedRunnable = stateChangedRunnable;
+ mPassedConsumer = passedConsumer;
+ }
+
+ public void setSupportedPackages(List<String> packages) {
+ mSupportedPackages.clear();
+ mSupportedPackages.addAll(packages);
+ }
+
+ public void setPackagePassed(String packageName) {
+ mPassedConsumer.accept(packageName);
+ }
+
+ public List<String> getRequestedPackages() {
+ if (mIsEnabled) {
+ return mRequestedPackages;
+ } else {
+ return Collections.emptyList();
+ }
+ }
+ }
}
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
index 76cccc9..1a3ea609 100644
--- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -44,6 +44,8 @@
assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ // lease will expire in two hours
+ builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
// groupHint stays null this time around
builder.setDnsAddresses(Collections.emptyList());
builder.setMtu(18);
@@ -51,6 +53,7 @@
assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9"));
+ builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000);
builder.setGroupHint("groupHint");
builder.setDnsAddresses(Arrays.asList(
InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"),
@@ -66,7 +69,7 @@
// Verify that this test does not miss any new field added later.
// If any field is added to NetworkAttributes it must be tested here for parceling
// roundtrip.
- assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+ assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
.filter(f -> !Modifier.isStatic(f.getModifiers())).count());
}
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
index dc20185..fb84611 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -57,6 +57,7 @@
final NetworkAttributes na =
new NetworkAttributes(
(Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}),
+ System.currentTimeMillis() + 7_200_000,
"some hint",
Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}),
Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})),
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index fff9635..e2668bc 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -296,13 +296,13 @@
@Override
public void registerComponentCallbacks(ComponentCallbacks callback) {
if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
- super.registerComponentCallbacks(callback);
+ getBaseContext().registerComponentCallbacks(callback);
}
@Override
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
- super.unregisterComponentCallbacks(callback);
+ getBaseContext().unregisterComponentCallbacks(callback);
}
public TestablePermissions getTestablePermissions() {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 5336141..8577921 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -217,6 +217,29 @@
EXPECT_THAT(str->value->spans[1].last_char, Eq(13u));
}
+TEST_F(ResourceParserTest, ParseStringTranslatableAttribute) {
+ // If there is no translate attribute the default is 'true'
+ EXPECT_TRUE(TestParse(R"(<string name="foo1">Translate</string>)"));
+ String* str = test::GetValue<String>(&table_, "string/foo1");
+ ASSERT_THAT(str, NotNull());
+ ASSERT_TRUE(str->IsTranslatable());
+
+ // Explicit 'true' translate attribute
+ EXPECT_TRUE(TestParse(R"(<string name="foo2" translatable="true">Translate</string>)"));
+ str = test::GetValue<String>(&table_, "string/foo2");
+ ASSERT_THAT(str, NotNull());
+ ASSERT_TRUE(str->IsTranslatable());
+
+ // Explicit 'false' translate attribute
+ EXPECT_TRUE(TestParse(R"(<string name="foo3" translatable="false">Do not translate</string>)"));
+ str = test::GetValue<String>(&table_, "string/foo3");
+ ASSERT_THAT(str, NotNull());
+ ASSERT_FALSE(str->IsTranslatable());
+
+ // Invalid value for the translate attribute, should be boolean ('true' or 'false')
+ EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)"));
+}
+
TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
std::string input = R"(
<string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 2ec1ab3..9b81369f 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -143,6 +143,8 @@
const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
const std::string& output_path) {
TRACE_CALL();
+ // Filenames starting with "donottranslate" are not localizable
+ bool translatable_file = path_data.name.find("donottranslate") != 0;
ResourceTable table;
{
auto fin = file->OpenInputStream();
@@ -157,9 +159,7 @@
ResourceParserOptions parser_options;
parser_options.error_on_positional_arguments = !options.legacy_mode;
-
- // If the filename includes donottranslate, then the default translatable is false.
- parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos;
+ parser_options.translatable = translatable_file;
// If visibility was forced, we need to use it when creating a new resource and also error if
// we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
@@ -172,7 +172,7 @@
}
}
- if (options.pseudolocalize) {
+ if (options.pseudolocalize && translatable_file) {
// Generate pseudo-localized strings (en-XA and ar-XB).
// These are created as weak symbols, and are only generated from default
// configuration
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index c0c05cd..5f637bd 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -27,6 +27,8 @@
namespace aapt {
+using CompilerTest = CommandTestFixture;
+
std::string BuildPath(std::vector<std::string> args) {
std::string out;
if (args.empty()) {
@@ -51,7 +53,7 @@
return CompileCommand(&diag).Execute(args, &std::cerr);
}
-TEST(CompilerTest, MultiplePeriods) {
+TEST_F(CompilerTest, MultiplePeriods) {
StdErrDiagnostics diag;
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()),
@@ -108,7 +110,7 @@
ASSERT_EQ(::android::base::utf8::unlink(path5_out.c_str()), 0);
}
-TEST(CompilerTest, DirInput) {
+TEST_F(CompilerTest, DirInput) {
StdErrDiagnostics diag;
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()),
@@ -138,7 +140,7 @@
ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0);
}
-TEST(CompilerTest, ZipInput) {
+TEST_F(CompilerTest, ZipInput) {
StdErrDiagnostics diag;
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
const std::string kResZip =
@@ -169,4 +171,86 @@
ASSERT_EQ(::android::base::utf8::unlink(kOutputFlata.c_str()), 0);
}
-} // namespace aapt
\ No newline at end of file
+/*
+ * This tests the "protection" from pseudo-translation of
+ * non-translatable files (starting with 'donotranslate')
+ * and strings (with the translatable="false" attribute)
+ *
+ * We check 4 string files, 2 translatable, and 2 not (based on file name)
+ * Each file contains 2 strings, one translatable, one not (attribute based)
+ * Each of these files are compiled and linked into one .apk, then we load the
+ * strings from the apk and check if there are pseudo-translated strings.
+ */
+
+// Using 000 and 111 because they are not changed by pseudo-translation,
+// making our life easier.
+constexpr static const char sTranslatableXmlContent[] =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<resources>"
+ " <string name=\"normal\">000</string>"
+ " <string name=\"non_translatable\" translatable=\"false\">111</string>"
+ "</resources>";
+
+static void AssertTranslations(CommandTestFixture *ctf, std::string file_name,
+ std::vector<std::string> expected) {
+
+ StdErrDiagnostics diag;
+
+ const std::string source_file = ctf->GetTestPath("/res/values/" + file_name + ".xml");
+ const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name);
+ const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk");
+
+ CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent));
+ CHECK(file::mkdirs(compiled_files_dir.data()));
+
+ ASSERT_EQ(CompileCommand(&diag).Execute({
+ source_file,
+ "-o", compiled_files_dir,
+ "-v",
+ "--pseudo-localize"
+ }, &std::cerr), 0);
+
+ ASSERT_TRUE(ctf->Link({
+ "--manifest", ctf->GetDefaultManifest(),
+ "-o", out_apk
+ }, compiled_files_dir, &diag));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
+
+ ResourceTable* table = apk->GetResourceTable();
+ ASSERT_NE(table, nullptr);
+ table->string_pool.Sort();
+
+ const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings =
+ table->string_pool.strings();
+
+ // The actual / expected vectors have the same size
+ const size_t pool_size = pool_strings.size();
+ ASSERT_EQ(pool_size, expected.size());
+
+ for (size_t i = 0; i < pool_size; i++) {
+ std::string actual = pool_strings[i]->value;
+ ASSERT_EQ(actual, expected[i]);
+ }
+}
+
+TEST_F(CompilerTest, DoNotTranslateTest) {
+ // The first string (000) is translatable, the second is not
+ // ar-XB uses "\u200F\u202E...\u202C\u200F"
+ std::vector<std::string> expected_translatable = {
+ "000", "111", // default locale
+ "[000 one]", // en-XA
+ "\xE2\x80\x8F\xE2\x80\xAE" "000" "\xE2\x80\xAC\xE2\x80\x8F", // ar-XB
+ };
+ AssertTranslations(this, "foo", expected_translatable);
+ AssertTranslations(this, "foo_donottranslate", expected_translatable);
+
+ // No translatable strings because these are non-translatable files
+ std::vector<std::string> expected_not_translatable = {
+ "000", "111", // default locale
+ };
+ AssertTranslations(this, "donottranslate", expected_not_translatable);
+ AssertTranslations(this, "donottranslate_foo", expected_not_translatable);
+}
+
+} // namespace aapt