Merge "AAPT2: Ignore namespaced elements in AndroidManifest.xml" into oc-dev
diff --git a/Android.mk b/Android.mk
index e58f306..8f99bc0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -322,7 +322,6 @@
core/java/android/service/chooser/IChooserTargetResult.aidl \
core/java/android/service/resolver/IResolverRankerService.aidl \
core/java/android/service/resolver/IResolverRankerResult.aidl \
- core/java/android/text/ITextClassificationService.aidl \
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
core/java/android/view/accessibility/IAccessibilityManager.aidl \
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index a5b37f3..8fd8043 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -370,15 +370,6 @@
"android.accounts.action.VISIBLE_ACCOUNTS_CHANGED";
/**
- * Key to set default visibility for applications targeting API level
- * {@link android.os.Build.VERSION_CODES#O} or above and don't have the same signature as
- * authenticator See {@link #getAccountVisibility}. If the value was not set by authenticator
- * {@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE} is used.
- */
- public static final String PACKAGE_NAME_KEY_LEGACY_VISIBLE =
- "android:accounts:key_legacy_visible";
-
- /**
* Key to set visibility for applications which satisfy one of the following conditions:
* <ul>
* <li>Target API level below {@link android.os.Build.VERSION_CODES#O} and have
@@ -394,6 +385,14 @@
* See {@link #getAccountVisibility}. If the value was not set by authenticator
* {@link #VISIBILITY_USER_MANAGED_VISIBLE} is used.
*/
+ public static final String PACKAGE_NAME_KEY_LEGACY_VISIBLE =
+ "android:accounts:key_legacy_visible";
+
+ /**
+ * Key to set default visibility for applications which don't satisfy conditions in
+ * {@link PACKAGE_NAME_KEY_LEGACY_VISIBLE}. If the value was not set by authenticator
+ * {@link #VISIBILITY_USER_MANAGED_NOT_VISIBLE} is used.
+ */
public static final String PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE =
"android:accounts:key_legacy_not_visible";
@@ -608,14 +607,17 @@
}
/**
- * Returns the accounts visible to the specified package, in an environment where some apps are
+ * Returns the accounts visible to the specified package in an environment where some apps are
* not authorized to view all accounts. This method can only be called by system apps and
- * authenticators managing the type
+ * authenticators managing the type.
+ * Beginning API level {@link android.os.Build.VERSION_CODES#O} it also return accounts
+ * which user can make visible to the application (see {@link VISIBILITY_USER_MANAGED_VISIBLE}).
*
* @param type The type of accounts to return, null to retrieve all accounts
* @param packageName The package name of the app for which the accounts are to be returned
* @return An array of {@link Account}, one per matching account. Empty (never null) if no
- * accounts of the specified type have been added.
+ * accounts of the specified type can be accessed by the package.
+ *
*/
@NonNull
public Account[] getAccountsByTypeForPackage(String type, String packageName) {
@@ -644,7 +646,10 @@
*
* <p>
* Caller targeting API level {@link android.os.Build.VERSION_CODES#O} and above, will get list
- * of accounts made visible to it by user or AbstractAcccountAuthenticator and
+ * of accounts made visible to it by user
+ * (see {@link #newChooseAccountIntent(Account, List, String[], String,
+ * String, String[], Bundle)}) or AbstractAcccountAuthenticator
+ * using {@link setAccountVisibility}.
* {@link android.Manifest.permission#GET_ACCOUNTS} permission is not used.
*
* <p>
@@ -787,7 +792,10 @@
*
* <p>
* Caller targeting API level {@link android.os.Build.VERSION_CODES#O} and above, will get list
- * of accounts made visible to it by user or AbstractAcccountAuthenticator and
+ * of accounts made visible to it by user
+ * (see {@link #newChooseAccountIntent(Account, List, String[], String,
+ * String, String[], Bundle)}) or AbstractAcccountAuthenticator
+ * using {@link setAccountVisibility}.
* {@link android.Manifest.permission#GET_ACCOUNTS} permission is not used.
*
* <p>
@@ -869,7 +877,7 @@
}
/**
- * Adds an account directly to the AccountManager. Additionally it specifies Account visiblity
+ * Adds an account directly to the AccountManager. Additionally it specifies Account visibility
* for given list of packages.
* <p>
* Normally used by sign-up wizards associated with authenticators, not directly by
@@ -2663,8 +2671,8 @@
*
* <p>
* This method gets a list of the accounts matching specific type and feature set which are
- * visible to the caller or for which user can grant access (see {@link #getAccountsByType} for
- * details); if there is exactly one already visible account, it is used; if there are some
+ * visible to the caller (see {@link #getAccountsByType} for details);
+ * if there is exactly one already visible account, it is used; if there are some
* accounts for which user grant visibility, the user is prompted to pick one; if there are
* none, the user is prompted to add one. Finally, an auth token is acquired for the chosen
* account.
@@ -2735,6 +2743,9 @@
* <p>
* On success the activity returns a Bundle with the account name and type specified using
* keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
+ * Chosen account is marked as {@link #VISIBILITY_USER_MANAGED_VISIBLE} to the caller
+ * (see {@link setAccountVisibility}) and will be returned to it in consequent
+ * {@link #getAccountsByType}) calls.
* <p>
* The most common case is to call this with one account type, e.g.:
* <p>
@@ -2787,6 +2798,9 @@
* <p>
* On success the activity returns a Bundle with the account name and type specified using
* keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
+ * Chosen account is marked as {@link #VISIBILITY_USER_MANAGED_VISIBLE} to the caller
+ * (see {@link setAccountVisibility}) and will be returned to it in consequent
+ * {@link #getAccountsByType}) calls.
* <p>
* The most common case is to call this with one account type, e.g.:
* <p>
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index abe5dc3..37c287e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -25,6 +25,7 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.policy.DecorView;
import com.android.internal.policy.PhoneWindow;
import android.annotation.CallSuper;
@@ -3147,19 +3148,6 @@
}
/**
- * Called before {@link #onAttachedToWindow}.
- *
- * @hide
- */
- @CallSuper
- public void onBeforeAttachedToWindow() {
- if (mAutoFillResetNeeded) {
- getAutofillManager().onAttachedToWindow(
- getWindow().getDecorView().getRootView().getWindowToken());
- }
- }
-
- /**
* Called when the main window associated with the activity has been
* attached to the window manager.
* See {@link View#onAttachedToWindow() View.onAttachedToWindow()}
@@ -7471,45 +7459,62 @@
}
/** @hide */
+ @NonNull public View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds) {
+ final View[] views = new View[viewIds.length];
+ final ArrayList<ViewRootImpl> roots =
+ WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
+
+ for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+ final View rootView = roots.get(rootNum).getView();
+
+ if (rootView != null) {
+ for (int viewNum = 0; viewNum < viewIds.length; viewNum++) {
+ if (views[viewNum] == null) {
+ views[viewNum] = rootView.findViewByAccessibilityIdTraversal(
+ viewIds[viewNum]);
+ }
+ }
+ }
+ }
+
+ return views;
+ }
+
+ /** @hide */
@Override
- public boolean getViewVisibility(int viewId) {
- Window window = getWindow();
- if (window == null) {
- Log.i(TAG, "no window");
- return false;
- }
+ @NonNull public boolean[] getViewVisibility(@NonNull int[] viewIds) {
+ final boolean[] isVisible = new boolean[viewIds.length];
+ final View views[] = findViewsByAccessibilityIdTraversal(viewIds);
- View decorView = window.peekDecorView();
- if (decorView == null) {
- Log.i(TAG, "no decorView");
- return false;
- }
-
- View view = decorView.findViewByAccessibilityIdTraversal(viewId);
- if (view == null) {
- Log.i(TAG, "cannot find view");
- return false;
- }
-
- // Check if the view is visible by checking all parents
- while (view != null) {
- if (view == decorView) {
- break;
+ for (int i = 0; i < viewIds.length; i++) {
+ View view = views[i];
+ if (view == null) {
+ isVisible[i] = false;
+ continue;
}
- if (view.getVisibility() != View.VISIBLE) {
- Log.i(TAG, view + " is not visible");
- return false;
- }
+ isVisible[i] = true;
- if (view.getParent() instanceof View) {
- view = (View) view.getParent();
- } else {
- break;
+ // Check if the view is visible by checking all parents
+ while (true) {
+ if (view instanceof DecorView && view.getViewRootImpl() == view.getParent()) {
+ break;
+ }
+
+ if (view.getVisibility() != View.VISIBLE) {
+ isVisible[i] = false;
+ break;
+ }
+
+ if (view.getParent() instanceof View) {
+ view = (View) view.getParent();
+ } else {
+ break;
+ }
}
}
- return true;
+ return isVisible;
}
/** @hide */
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 21a7ca7..7d4d70d 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -570,8 +570,10 @@
// Find the location in the view's parent
ViewGroup parent = (ViewGroup) view.getParent();
Matrix matrix = new Matrix();
- parent.transformMatrixToLocal(matrix);
- matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
+ if (parent != null) {
+ parent.transformMatrixToLocal(matrix);
+ matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
+ }
mSharedElementParentMatrices.add(matrix);
}
}
@@ -861,15 +863,17 @@
Matrix tempMatrix = new Matrix();
for (int i = 0; i < numSharedElements; i++) {
View view = mSharedElements.get(i);
- tempMatrix.reset();
- mSharedElementParentMatrices.get(i).invert(tempMatrix);
- GhostView.addGhost(view, decor, tempMatrix);
- ViewGroup parent = (ViewGroup) view.getParent();
- if (moveWithParent && !isInTransitionGroup(parent, decor)) {
- GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
- parent.getViewTreeObserver().addOnPreDrawListener(listener);
- parent.addOnAttachStateChangeListener(listener);
- mGhostViewListeners.add(listener);
+ if (view.isAttachedToWindow()) {
+ tempMatrix.reset();
+ mSharedElementParentMatrices.get(i).invert(tempMatrix);
+ GhostView.addGhost(view, decor, tempMatrix);
+ ViewGroup parent = (ViewGroup) view.getParent();
+ if (moveWithParent && !isInTransitionGroup(parent, decor)) {
+ GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
+ parent.getViewTreeObserver().addOnPreDrawListener(listener);
+ parent.addOnAttachStateChangeListener(listener);
+ mGhostViewListeners.add(listener);
+ }
}
}
}
@@ -1065,7 +1069,7 @@
@Override
public boolean onPreDraw() {
GhostView ghostView = GhostView.getGhost(mView);
- if (ghostView == null) {
+ if (ghostView == null || !mView.isAttachedToWindow()) {
removeListener();
} else {
GhostView.calculateMatrix(mView, mDecor, mMatrix);
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index 28704e6..995d98a 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -71,11 +71,12 @@
public static final int TYPE_INSTALL_WINDOWED = 2;
/**
- * Incoming system updates (except for security updates) will be blocked for 30 days, after
- * which the policy will no longer be effective and the system will revert back to its normal
- * behavior as if no policy were set.
- * <b>Note:</b> security updates (e.g. monthly security patches) will <i>not</i> be affected by
- * this policy.
+ * Incoming system updates (except for security updates) will be blocked for a maximum of 30
+ * days, after which the policy will no longer be effective and the system will revert back to
+ * its normal behavior as if no policy were set.
+ *
+ * <p><b>Note:</b> security updates (e.g. monthly security patches) may <i>not</i> be affected
+ * by this policy, depending on the policy set by the device manufacturer and carrier.
*
* <p>After this policy expires, resetting it to any policy other than
* {@link #TYPE_INSTALL_AUTOMATIC} will produce no effect, as the 30-day maximum delay has
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index bf7af20..10594af 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2891,6 +2891,7 @@
CLIPBOARD_SERVICE,
INPUT_METHOD_SERVICE,
TEXT_SERVICES_MANAGER_SERVICE,
+ TEXT_CLASSIFICATION_SERVICE,
APPWIDGET_SERVICE,
//@hide: VOICE_INTERACTION_MANAGER_SERVICE,
//@hide: BACKUP_SERVICE,
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 5264cd7..e127ca3 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -952,4 +952,12 @@
public Handler getMainThreadHandler() {
return mBase.getMainThreadHandler();
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getNextAccessibilityId() {
+ return mBase.getNextAccessibilityId();
+ }
}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 211d54d..63eedf5 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -107,7 +107,8 @@
/**
* Create a request suitable for zero shutter lag still capture. This means
* means maximizing image quality without compromising preview frame rate.
- * AE/AWB/AF should be on auto mode.
+ * AE/AWB/AF should be on auto mode. This is intended for application-operated ZSL. For
+ * device-operated ZSL, use {@link CaptureRequest#CONTROL_ENABLE_ZSL} if available.
* This template is guaranteed to be supported on camera devices that support the
* {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING PRIVATE_REPROCESSING}
* capability or the
@@ -115,6 +116,7 @@
* capability.
*
* @see #createCaptureRequest
+ * @see CaptureRequest#CONTROL_ENABLE_ZSL
*/
public static final int TEMPLATE_ZERO_SHUTTER_LAG = 5;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 279d73d..c41fc02 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1669,6 +1669,7 @@
* <code>false</code> if present.</p>
* <p>For applications targeting SDK versions older than O, the value of enableZsl in all
* capture templates is always <code>false</code> if present.</p>
+ * <p>For application-operated ZSL, use CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG template.</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index aedfc4b..6d80c20 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2174,6 +2174,7 @@
* <code>false</code> if present.</p>
* <p>For applications targeting SDK versions older than O, the value of enableZsl in all
* capture templates is always <code>false</code> if present.</p>
+ * <p>For application-operated ZSL, use CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG template.</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 37c153f..b5fd116 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -521,6 +521,7 @@
public abstract Timer getForegroundActivityTimer();
public abstract Timer getBluetoothScanTimer();
public abstract Timer getBluetoothScanBackgroundTimer();
+ public abstract Counter getBluetoothScanResultCounter();
// Note: the following times are disjoint. They can be added together to find the
// total time a uid has had any processes running at all.
@@ -3370,8 +3371,10 @@
final long actualTime = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
final long actualTimeBg = bleTimerBg != null ?
bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+ final int resultCount = u.getBluetoothScanResultCounter() != null ?
+ u.getBluetoothScanResultCounter().getCountLocked(which) : 0;
dumpLine(pw, uid, category, BLUETOOTH_MISC_DATA, totalTime, count,
- countBg, actualTime, actualTimeBg);
+ countBg, actualTime, actualTimeBg, resultCount);
}
}
@@ -4523,6 +4526,8 @@
final long actualTimeMs = bleTimer.getTotalDurationMsLocked(rawRealtimeMs);
final long actualTimeMsBg = bleTimerBg != null ?
bleTimerBg.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+ final int resultCount = u.getBluetoothScanResultCounter() != null ?
+ u.getBluetoothScanResultCounter().getCountLocked(which) : 0;
sb.setLength(0);
sb.append(prefix);
@@ -4547,6 +4552,8 @@
sb.append(countBg);
sb.append(" times)");
}
+ sb.append("; Results count ");
+ sb.append(resultCount);
pw.println(sb.toString());
uidActivity = true;
}
diff --git a/core/java/android/text/ITextClassificationService.aidl b/core/java/android/text/ITextClassificationService.aidl
deleted file mode 100644
index a73dbf0..0000000
--- a/core/java/android/text/ITextClassificationService.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.os.ParcelFileDescriptor;
-
-/**
- * Interface to the text classification service, which grants access to the text classification
- * LSTM model file.
- * {@hide}
- */
-interface ITextClassificationService {
-
- /**
- * Request a file descriptor with read-only access to the LSTM model file.
- * This file descriptor should be closed after the client is done with it.
- */
- ParcelFileDescriptor getModelFileFd();
-}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f539752..2ade9b5 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -678,6 +678,7 @@
mIsCreating = false;
if (mSurfaceControl != null && !mSurfaceCreated) {
mSurfaceControl.destroy();
+ mSurface.release();
mSurfaceControl = null;
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0af49ed..6ee6d63 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7304,7 +7304,7 @@
* fills in all data that can be inferred from the view itself.
*/
public void onProvideStructure(ViewStructure structure) {
- onProvideStructureForAssistOrAutofill(structure, false);
+ onProvideStructureForAssistOrAutofill(structure, false, 0);
}
/**
@@ -7318,6 +7318,9 @@
* <li>It must set fields such {@link ViewStructure#setText(CharSequence)},
* {@link ViewStructure#setAutofillOptions(CharSequence[])},
* or {@link ViewStructure#setWebDomain(String)}.
+ * <li> The {@code left} and {@code top} values set in
+ * {@link ViewStructure#setDimens(int, int, int, int, int, int)} need to be relative to the next
+ * {@link ViewGroup#isImportantForAutofill() included} parent in the structure.
* </ul>
*
* @param structure Fill in with structured view data. The default implementation
@@ -7326,12 +7329,12 @@
*
* @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
*/
- public void onProvideAutofillStructure(ViewStructure structure, int flags) {
- onProvideStructureForAssistOrAutofill(structure, true);
+ public void onProvideAutofillStructure(ViewStructure structure, @AutofillFlags int flags) {
+ onProvideStructureForAssistOrAutofill(structure, true, flags);
}
private void onProvideStructureForAssistOrAutofill(ViewStructure structure,
- boolean forAutofill) {
+ boolean forAutofill, @AutofillFlags int flags) {
final int id = mID;
if (id != NO_ID && !isViewIdGenerated(id)) {
String pkg, type, entry;
@@ -7359,7 +7362,31 @@
}
}
- structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight - mLeft, mBottom - mTop);
+ int ignoredParentLeft = 0;
+ int ignoredParentTop = 0;
+ if (forAutofill && (flags & AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
+ View parentGroup = null;
+
+ ViewParent viewParent = getParent();
+ if (viewParent instanceof View) {
+ parentGroup = (View) viewParent;
+ }
+
+ while (parentGroup != null && !parentGroup.isImportantForAutofill()) {
+ ignoredParentLeft += parentGroup.mLeft;
+ ignoredParentTop += parentGroup.mTop;
+
+ viewParent = parentGroup.getParent();
+ if (viewParent instanceof View) {
+ parentGroup = (View) viewParent;
+ } else {
+ break;
+ }
+ }
+ }
+
+ structure.setDimens(ignoredParentLeft + mLeft, ignoredParentTop + mTop, mScrollX, mScrollY,
+ mRight - mLeft, mBottom - mTop);
if (!forAutofill) {
if (!hasIdentityMatrix()) {
structure.setTransformation(getMatrix());
@@ -7445,10 +7472,15 @@
* <li>Call {@link AutofillManager#cancel()} ()} when the autofill context
* of the view structure changed and you want the current autofill interaction if such
* to be cancelled.
+ * <li> The {@code left} and {@code top} values set in
+ * {@link ViewStructure#setDimens(int, int, int, int, int, int)} need to be relative to the next
+ * {@link ViewGroup#isImportantForAutofill() included} parent in the structure.
* </ol>
*
* @param structure Fill in with structured view data.
- * @param flags optional flags (currently {@code 0}).
+ * @param flags optional flags.
+ *
+ * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
*/
public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
}
@@ -7706,7 +7738,8 @@
}
private boolean isAutofillable() {
- return getAutofillType() != AUTOFILL_TYPE_NONE && isImportantForAutofill();
+ return getAutofillType() != AUTOFILL_TYPE_NONE && isImportantForAutofill()
+ && getAccessibilityViewId() > LAST_APP_ACCESSIBILITY_ID;
}
private void populateVirtualStructure(ViewStructure structure,
@@ -7771,7 +7804,7 @@
* {@link #onProvideVirtualStructure}.
*/
public void dispatchProvideStructure(ViewStructure structure) {
- dispatchProvideStructureForAssistOrAutofill(structure, false);
+ dispatchProvideStructureForAssistOrAutofill(structure, false, 0);
}
/**
@@ -7804,16 +7837,15 @@
*/
public void dispatchProvideAutofillStructure(@NonNull ViewStructure structure,
@AutofillFlags int flags) {
- dispatchProvideStructureForAssistOrAutofill(structure, true);
+ dispatchProvideStructureForAssistOrAutofill(structure, true, flags);
}
private void dispatchProvideStructureForAssistOrAutofill(ViewStructure structure,
- boolean forAutofill) {
+ boolean forAutofill, @AutofillFlags int flags) {
if (forAutofill) {
structure.setAutofillId(getAutofillId());
- // NOTE: flags are not currently supported, hence 0
- onProvideAutofillStructure(structure, 0);
- onProvideAutofillVirtualStructure(structure, 0);
+ onProvideAutofillStructure(structure, flags);
+ onProvideAutofillVirtualStructure(structure, flags);
} else if (!isAssistBlocked()) {
onProvideStructure(structure);
onProvideVirtualStructure(structure);
@@ -20430,9 +20462,10 @@
@Nullable private Drawable getAutofilledDrawable() {
// Lazily load the isAutofilled drawable.
if (mAttachInfo.mAutofilledDrawable == null) {
- TypedArray a = mContext.getTheme().obtainStyledAttributes(AUTOFILL_HIGHLIGHT_ATTR);
+ Context rootContext = getRootView().getContext();
+ TypedArray a = rootContext.getTheme().obtainStyledAttributes(AUTOFILL_HIGHLIGHT_ATTR);
int attributeResourceId = a.getResourceId(0, 0);
- mAttachInfo.mAutofilledDrawable = mContext.getDrawable(attributeResourceId);
+ mAttachInfo.mAutofilledDrawable = rootContext.getDrawable(attributeResourceId);
a.recycle();
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index a432d30..6dd8ecf 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -480,13 +480,6 @@
public void onWindowFocusChanged(boolean hasFocus);
/**
- * @hide
- */
- default void onBeforeAttachedToWindow() {
- // empty
- }
-
- /**
* Called when the window has been attached to the window manager.
* See {@link View#onAttachedToWindow() View.onAttachedToWindow()}
* for more information.
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 7018529..02c8945 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -109,11 +109,6 @@
}
@Override
- public void onBeforeAttachedToWindow() {
- mWrapped.onBeforeAttachedToWindow();
- }
-
- @Override
public void onAttachedToWindow() {
mWrapped.onAttachedToWindow();
}
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index 143c49a..604e985 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -309,6 +309,13 @@
final int oldChildCount = oldInfo.getChildCount();
for (int i = 0; i < oldChildCount; i++) {
+ if (nodes.get(sourceId) == null) {
+ // We've removed (and thus recycled) this node because it was its own
+ // ancestor (the app gave us bad data), we can't continue using it.
+ // Clear the cache for this window and give up on adding the node.
+ clearNodesForWindowLocked(windowId);
+ return;
+ }
final long oldChildId = oldInfo.getChildId(i);
// If the child is no longer present, remove the sub-tree.
if (newChildrenIds == null || newChildrenIds.indexOf(oldChildId) < 0) {
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f8a13a3..79c81b2 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -630,7 +630,8 @@
public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;
/**
- * Represents the event of scrolling a view.
+ * Represents the event of scrolling a view. This event type is generally not sent directly.
+ * @see View#onScrollChanged(int, int, int, int)
*/
public static final int TYPE_VIEW_SCROLLED = 0x00001000;
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index d0133ed..c7151db 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -28,7 +28,6 @@
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
@@ -38,7 +37,6 @@
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
-import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
@@ -47,6 +45,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -188,11 +187,11 @@
boolean autofillCallbackRequestHideFillUi();
/**
- * Checks if the view is currently attached and visible.
+ * Checks if views are currently attached and visible.
*
- * @return {@code true} iff the view is attached or visible
+ * @return And array with {@code true} iff the view is attached or visible
*/
- boolean getViewVisibility(int viewId);
+ @NonNull boolean[] getViewVisibility(@NonNull int[] viewId);
/**
* Checks is the client is currently visible as understood by autofill.
@@ -200,6 +199,15 @@
* @return {@code true} if the client is currently visible
*/
boolean isVisibleForAutofill();
+
+ /**
+ * Find views by traversing the hierarchies of the client.
+ *
+ * @param viewIds The accessibility ids of the views to find
+ *
+ * @return And array containing the views, or {@code null} if not found
+ */
+ @NonNull View[] findViewsByAccessibilityIdTraversal(@NonNull int[] viewIds);
}
/**
@@ -259,30 +267,6 @@
}
/**
- * Set window future popup windows should be attached to.
- *
- * @param windowToken The window the popup windows should be attached to
- *
- * {@hide}
- */
- public void onAttachedToWindow(@NonNull IBinder windowToken) {
- if (!hasAutofillFeature()) {
- return;
- }
- synchronized (mLock) {
- if (mSessionId == NO_SESSION) {
- return;
- }
-
- try {
- mService.setWindow(mSessionId, windowToken);
- } catch (RemoteException e) {
- Log.e(TAG, "Could not attach window to session " + mSessionId);
- }
- }
- }
-
- /**
* Called once the client becomes visible.
*
* @see AutofillClient#isVisibleForAutofill()
@@ -292,7 +276,7 @@
public void onVisibleForAutofill() {
synchronized (mLock) {
if (mEnabled && mSessionId != NO_SESSION && mTrackedViews != null) {
- mTrackedViews.onVisibleForAutofill();
+ mTrackedViews.onVisibleForAutofillLocked();
}
}
}
@@ -406,7 +390,7 @@
if (mSessionId == NO_SESSION) {
// Starts new session.
- startSessionLocked(id, view.getWindowToken(), null, value, flags);
+ startSessionLocked(id, null, value, flags);
} else {
// Update focus on existing session.
updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
@@ -484,7 +468,7 @@
if (mSessionId == NO_SESSION) {
// Starts new session.
- startSessionLocked(id, view.getWindowToken(), bounds, null, flags);
+ startSessionLocked(id, bounds, null, flags);
} else {
// Update focus on existing session.
updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
@@ -725,15 +709,15 @@
return new AutofillId(parent.getAccessibilityViewId(), childId);
}
- private void startSessionLocked(@NonNull AutofillId id, @NonNull IBinder windowToken,
- @NonNull Rect bounds, @NonNull AutofillValue value, int flags) {
+ private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
+ @NonNull AutofillValue value, int flags) {
if (sVerbose) {
Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
+ ", flags=" + flags);
}
try {
- mSessionId = mService.startSession(mContext.getActivityToken(), windowToken,
+ mSessionId = mService.startSession(mContext.getActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, mContext.getOpPackageName());
final AutofillClient client = getClientLocked();
@@ -855,9 +839,9 @@
}
}
- private void requestShowFillUi(int sessionId, IBinder windowToken, AutofillId id, int width,
- int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
- final View anchor = findAchorView(windowToken, id);
+ private void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+ Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ final View anchor = findView(id);
if (anchor == null) {
return;
}
@@ -930,27 +914,27 @@
}
}
- private void autofill(int sessionId, IBinder windowToken, List<AutofillId> ids,
- List<AutofillValue> values) {
+ private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
synchronized (mLock) {
if (sessionId != mSessionId) {
return;
}
- final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
- if (root == null) {
+ final AutofillClient client = getClientLocked();
+ if (client == null) {
return;
}
final int itemCount = ids.size();
int numApplied = 0;
ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
+ final View[] views = client.findViewsByAccessibilityIdTraversal(getViewIds(ids));
for (int i = 0; i < itemCount; i++) {
final AutofillId id = ids.get(i);
final AutofillValue value = values.get(i);
final int viewId = id.getViewId();
- final View view = root.findViewByAccessibilityIdTraversal(viewId);
+ final View view = views[i];
if (view == null) {
Log.w(TAG, "autofill(): no View with id " + viewId);
continue;
@@ -1022,8 +1006,11 @@
}
}
- private void requestHideFillUi(int sessionId, IBinder windowToken, AutofillId id) {
- final View anchor = findAchorView(windowToken, id);
+ private void requestHideFillUi(int sessionId, AutofillId id) {
+ final View anchor = findView(id);
+ if (anchor == null) {
+ return;
+ }
AutofillCallback callback = null;
synchronized (mLock) {
@@ -1049,8 +1036,11 @@
}
}
- private void notifyNoFillUi(int sessionId, IBinder windowToken, AutofillId id) {
- final View anchor = findAchorView(windowToken, id);
+ private void notifyNoFillUi(int sessionId, AutofillId id) {
+ final View anchor = findView(id);
+ if (anchor == null) {
+ return;
+ }
AutofillCallback callback = null;
synchronized (mLock) {
@@ -1070,18 +1060,38 @@
}
}
- private View findAchorView(IBinder windowToken, AutofillId id) {
- final View root = WindowManagerGlobal.getInstance().getWindowView(windowToken);
- if (root == null) {
- Log.w(TAG, "no window with token " + windowToken);
+ /**
+ * Get an array of viewIds from a List of {@link AutofillId}.
+ *
+ * @param autofillIds The autofill ids to convert
+ *
+ * @return The array of viewIds.
+ */
+ @NonNull private int[] getViewIds(@NonNull List<AutofillId> autofillIds) {
+ final int numIds = autofillIds.size();
+ final int[] viewIds = new int[numIds];
+ for (int i = 0; i < numIds; i++) {
+ viewIds[i] = autofillIds.get(i).getViewId();
+ }
+
+ return viewIds;
+ }
+
+ /**
+ * Find a single view by its id.
+ *
+ * @param autofillId The autofill id of the view
+ *
+ * @return The view or {@code null} if view was not found
+ */
+ private View findView(@NonNull AutofillId autofillId) {
+ final AutofillClient client = getClientLocked();
+
+ if (client == null) {
return null;
}
- final View view = root.findViewByAccessibilityIdTraversal(id.getViewId());
- if (view == null) {
- Log.w(TAG, "no view with id " + id);
- return null;
- }
- return view;
+
+ return client.findViewsByAccessibilityIdTraversal(new int[]{autofillId.getViewId()})[0];
}
/** @hide */
@@ -1160,22 +1170,26 @@
*
* @param trackedIds The views to be tracked
*/
- TrackedViews(@NonNull List<AutofillId> trackedIds) {
+ TrackedViews(List<AutofillId> trackedIds) {
mVisibleTrackedIds = null;
mInvisibleTrackedIds = null;
AutofillClient client = getClientLocked();
- if (trackedIds != null) {
- int numIds = trackedIds.size();
+ if (trackedIds != null && client != null) {
+ final boolean[] isVisible;
+
+ if (client.isVisibleForAutofill()) {
+ isVisible = client.getViewVisibility(getViewIds(trackedIds));
+ } else {
+ // All false
+ isVisible = new boolean[trackedIds.size()];
+ }
+
+ final int numIds = trackedIds.size();
for (int i = 0; i < numIds; i++) {
- AutofillId id = trackedIds.get(i);
+ final AutofillId id = trackedIds.get(i);
- boolean isVisible = true;
- if (client != null && client.isVisibleForAutofill()) {
- isVisible = client.getViewVisibility(id.getViewId());
- }
-
- if (isVisible) {
+ if (isVisible[i]) {
mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
} else {
mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
@@ -1233,16 +1247,23 @@
*
* @see AutofillClient#isVisibleForAutofill()
*/
- void onVisibleForAutofill() {
- // The visibility of the views might have changed while the client was not started,
+ void onVisibleForAutofillLocked() {
+ // The visibility of the views might have changed while the client was not be visible,
// hence update the visibility state for all views.
AutofillClient client = getClientLocked();
ArraySet<AutofillId> updatedVisibleTrackedIds = null;
ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
if (client != null) {
if (mInvisibleTrackedIds != null) {
- for (AutofillId id : mInvisibleTrackedIds) {
- if (client.getViewVisibility(id.getViewId())) {
+ final ArrayList<AutofillId> orderedInvisibleIds =
+ new ArrayList<>(mInvisibleTrackedIds);
+ final boolean[] isVisible = client.getViewVisibility(
+ getViewIds(orderedInvisibleIds));
+
+ final int numInvisibleTrackedIds = orderedInvisibleIds.size();
+ for (int i = 0; i < numInvisibleTrackedIds; i++) {
+ final AutofillId id = orderedInvisibleIds.get(i);
+ if (isVisible[i]) {
updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
if (sDebug) {
@@ -1255,8 +1276,16 @@
}
if (mVisibleTrackedIds != null) {
- for (AutofillId id : mVisibleTrackedIds) {
- if (client.getViewVisibility(id.getViewId())) {
+ final ArrayList<AutofillId> orderedVisibleIds =
+ new ArrayList<>(mVisibleTrackedIds);
+ final boolean[] isVisible = client.getViewVisibility(
+ getViewIds(orderedVisibleIds));
+
+ final int numVisibleTrackedIds = orderedVisibleIds.size();
+ for (int i = 0; i < numVisibleTrackedIds; i++) {
+ final AutofillId id = orderedVisibleIds.get(i);
+
+ if (isVisible[i]) {
updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
} else {
updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
@@ -1355,12 +1384,11 @@
}
@Override
- public void autofill(int sessionId, IBinder windowToken, List<AutofillId> ids,
- List<AutofillValue> values) {
+ public void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
afm.mContext.getMainThreadHandler().post(
- () -> afm.autofill(sessionId, windowToken, ids, values));
+ () -> afm.autofill(sessionId, ids, values));
}
}
@@ -1374,31 +1402,30 @@
}
@Override
- public void requestShowFillUi(int sessionId, IBinder windowToken, AutofillId id,
- int width, int height, Rect anchorBounds, IAutofillWindowPresenter presenter) {
+ public void requestShowFillUi(int sessionId, AutofillId id, int width, int height,
+ Rect anchorBounds, IAutofillWindowPresenter presenter) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
afm.mContext.getMainThreadHandler().post(
- () -> afm.requestShowFillUi(sessionId, windowToken, id, width, height,
- anchorBounds, presenter));
+ () -> afm.requestShowFillUi(sessionId, id, width, height, anchorBounds,
+ presenter));
}
}
@Override
- public void requestHideFillUi(int sessionId, IBinder windowToken, AutofillId id) {
+ public void requestHideFillUi(int sessionId, AutofillId id) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
afm.mContext.getMainThreadHandler().post(
- () -> afm.requestHideFillUi(sessionId, windowToken, id));
+ () -> afm.requestHideFillUi(sessionId, id));
}
}
@Override
- public void notifyNoFillUi(int sessionId, IBinder windowToken, AutofillId id) {
+ public void notifyNoFillUi(int sessionId, AutofillId id) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.mContext.getMainThreadHandler().post(
- () -> afm.notifyNoFillUi(sessionId, windowToken, id));
+ afm.mContext.getMainThreadHandler().post(() -> afm.notifyNoFillUi(sessionId, id));
}
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index f28d8ba..4193a3c 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -32,12 +32,11 @@
interface IAutoFillManager {
// Returns flags: FLAG_ADD_CLIENT_ENABLED | FLAG_ADD_CLIENT_DEBUG | FLAG_ADD_CLIENT_VERBOSE
int addClient(in IAutoFillManagerClient client, int userId);
- int startSession(IBinder activityToken, IBinder windowToken, in IBinder appCallback,
- in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId,
- boolean hasCallback, int flags, String packageName);
+ int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
+ in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
+ String packageName);
FillEventHistory getFillEventHistory();
boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
- void setWindow(int sessionId, in IBinder windowToken);
void updateSession(int sessionId, in AutofillId id, in Rect bounds,
in AutofillValue value, int action, int flags, int userId);
void finishSession(int sessionId, int userId);
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index aef98b7..825d311 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -40,8 +40,7 @@
/**
* Autofills the activity with the contents of a dataset.
*/
- void autofill(int sessionId, in IBinder windowToken, in List<AutofillId> ids,
- in List<AutofillValue> values);
+ void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values);
/**
* Authenticates a fill response or a data set.
@@ -58,18 +57,18 @@
/**
* Requests showing the fill UI.
*/
- void requestShowFillUi(int sessionId, in IBinder windowToken, in AutofillId id, int width,
- int height, in Rect anchorBounds, in IAutofillWindowPresenter presenter);
+ void requestShowFillUi(int sessionId, in AutofillId id, int width, int height,
+ in Rect anchorBounds, in IAutofillWindowPresenter presenter);
/**
* Requests hiding the fill UI.
*/
- void requestHideFillUi(int sessionId, in IBinder windowToken, in AutofillId id);
+ void requestHideFillUi(int sessionId, in AutofillId id);
/**
* Notifies no fill UI will be shown.
*/
- void notifyNoFillUi(int sessionId, in IBinder windowToken, in AutofillId id);
+ void notifyNoFillUi(int sessionId, in AutofillId id);
/**
* Starts the provided intent sender
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 7bdd6da..d456989 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -737,6 +737,7 @@
mInputText.setFilters(new InputFilter[] {
new InputTextFilter()
});
+ mInputText.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);
@@ -770,6 +771,12 @@
if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
+
+ // Should be focusable by default, as the text view whose visibility changes is focusable
+ if (getFocusable() == View.FOCUSABLE_AUTO) {
+ setFocusable(View.FOCUSABLE);
+ setFocusableInTouchMode(true);
+ }
}
@Override
@@ -856,7 +863,7 @@
switch (action) {
case MotionEvent.ACTION_DOWN: {
removeAllCallbacks();
- mInputText.setVisibility(View.INVISIBLE);
+ hideSoftInput();
mLastDownOrMoveEventY = mLastDownEventY = event.getY();
mLastDownEventTime = event.getEventTime();
mIgnoreMoveEvents = false;
@@ -883,11 +890,9 @@
mFlingScroller.forceFinished(true);
mAdjustScroller.forceFinished(true);
} else if (mLastDownEventY < mTopSelectionDividerTop) {
- hideSoftInput();
postChangeCurrentByOneFromLongPress(
false, ViewConfiguration.getLongPressTimeout());
} else if (mLastDownEventY > mBottomSelectionDividerBottom) {
- hideSoftInput();
postChangeCurrentByOneFromLongPress(
true, ViewConfiguration.getLongPressTimeout());
} else {
@@ -1120,6 +1125,7 @@
@Override
public void scrollBy(int x, int y) {
int[] selectorIndices = mSelectorIndices;
+ int startScrollOffset = mCurrentScrollOffset;
if (!mWrapSelectorWheel && y > 0
&& selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
mCurrentScrollOffset = mInitialScrollOffset;
@@ -1147,6 +1153,9 @@
mCurrentScrollOffset = mInitialScrollOffset;
}
}
+ if (startScrollOffset != mCurrentScrollOffset) {
+ onScrollChanged(0, mCurrentScrollOffset, 0, startScrollOffset);
+ }
}
@Override
@@ -1735,7 +1744,10 @@
}
int previous = mValue;
mValue = current;
- updateInputTextView();
+ // If we're flinging, we'll update the text view at the end when it becomes visible
+ if (mScrollState != OnScrollListener.SCROLL_STATE_FLING) {
+ updateInputTextView();
+ }
if (notifyChange) {
notifyChange(previous, current);
}
@@ -1752,7 +1764,7 @@
*/
private void changeValueByOne(boolean increment) {
if (mHasSelectorWheel) {
- mInputText.setVisibility(View.INVISIBLE);
+ hideSoftInput();
if (!moveToFinalScrollerPosition(mFlingScroller)) {
moveToFinalScrollerPosition(mAdjustScroller);
}
@@ -1799,9 +1811,8 @@
*/
private void onScrollerFinished(Scroller scroller) {
if (scroller == mFlingScroller) {
- if (!ensureScrollWheelAdjusted()) {
- updateInputTextView();
- }
+ ensureScrollWheelAdjusted();
+ updateInputTextView();
onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
} else {
if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
@@ -1937,9 +1948,25 @@
*/
String text = (mDisplayedValues == null) ? formatNumber(mValue)
: mDisplayedValues[mValue - mMinValue];
- if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) {
- mInputText.setText(text);
- return true;
+ if (!TextUtils.isEmpty(text)) {
+ CharSequence beforeText = mInputText.getText();
+ if (!text.equals(beforeText.toString())) {
+ mInputText.setText(text);
+ if (mAccessibilityNodeProvider != null) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ mInputText.onInitializeAccessibilityEvent(event);
+ mInputText.onPopulateAccessibilityEvent(event);
+ event.setFromIndex(0);
+ event.setRemovedCount(beforeText.length());
+ event.setAddedCount(text.length());
+ event.setBeforeText(beforeText);
+ event.setSource(NumberPicker.this,
+ AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT);
+ requestSendAccessibilityEvent(NumberPicker.this, event);
+ }
+ return true;
+ }
}
return false;
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 99a25fd..373f4bb 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -132,6 +132,7 @@
void noteBleScanStarted(in WorkSource ws);
void noteBleScanStopped(in WorkSource ws);
void noteResetBleScan();
+ void noteBleScanResult(in WorkSource ws);
HealthStatsParceler takeUidSnapshot(int uid);
HealthStatsParceler[] takeUidSnapshots(in int[] uid);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index a582c2c..748272600 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -114,7 +114,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 155 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 156 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -4704,6 +4704,14 @@
}
}
+ public void noteBluetoothScanResultFromSourceLocked(WorkSource ws) {
+ final int N = ws.size();
+ for (int i = 0; i < N; i++) {
+ int uid = mapUid(ws.get(i));
+ getUidStatsLocked(uid).noteBluetoothScanResultLocked();
+ }
+ }
+
private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis,
final long uptimeMillis, int uid) {
uid = mapUid(uid);
@@ -5421,6 +5429,7 @@
StopwatchTimer mCameraTurnedOnTimer;
StopwatchTimer mForegroundActivityTimer;
DualTimer mBluetoothScanTimer;
+ Counter mBluetoothScanResultCounter;
int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
StopwatchTimer[] mProcessStateTimer;
@@ -5864,6 +5873,17 @@
}
}
+ public Counter createBluetoothScanResultCounterLocked() {
+ if (mBluetoothScanResultCounter == null) {
+ mBluetoothScanResultCounter = new Counter(mBsi.mOnBatteryTimeBase);
+ }
+ return mBluetoothScanResultCounter;
+ }
+
+ public void noteBluetoothScanResultLocked() {
+ createBluetoothScanResultCounterLocked().stepAtomic();
+ }
+
@Override
public void noteActivityResumedLocked(long elapsedRealtimeMs) {
// We always start, since we want multiple foreground PIDs to nest
@@ -6017,6 +6037,11 @@
return mBluetoothScanTimer.getSubTimer();
}
+ @Override
+ public Counter getBluetoothScanResultCounter() {
+ return mBluetoothScanResultCounter;
+ }
+
void makeProcessState(int i, Parcel in) {
if (i < 0 || i >= NUM_PROCESS_STATE) return;
@@ -6266,6 +6291,9 @@
active |= !resetTimerIfNotNull(mCameraTurnedOnTimer, false);
active |= !resetTimerIfNotNull(mForegroundActivityTimer, false);
active |= !resetTimerIfNotNull(mBluetoothScanTimer, false);
+ if (mBluetoothScanResultCounter != null) {
+ mBluetoothScanResultCounter.reset(false);
+ }
if (mProcessStateTimer != null) {
for (int i = 0; i < NUM_PROCESS_STATE; i++) {
@@ -6450,6 +6478,10 @@
mBluetoothScanTimer.detach();
mBluetoothScanTimer = null;
}
+ if (mBluetoothScanResultCounter != null) {
+ mBluetoothScanResultCounter.detach();
+ mBluetoothScanResultCounter = null;
+ }
if (mUserActivityCounters != null) {
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
mUserActivityCounters[i].detach();
@@ -6620,6 +6652,12 @@
} else {
out.writeInt(0);
}
+ if (mBluetoothScanResultCounter != null) {
+ out.writeInt(1);
+ mBluetoothScanResultCounter.writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (mProcessStateTimer[i] != null) {
out.writeInt(1);
@@ -6850,6 +6888,11 @@
} else {
mBluetoothScanTimer = null;
}
+ if (in.readInt() != 0) {
+ mBluetoothScanResultCounter = new Counter(mBsi.mOnBatteryTimeBase, in);
+ } else {
+ mBluetoothScanResultCounter = null;
+ }
mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (in.readInt() != 0) {
@@ -10998,6 +11041,9 @@
if (in.readInt() != 0) {
u.createBluetoothScanTimerLocked().readSummaryFromParcelLocked(in);
}
+ if (in.readInt() != 0) {
+ u.createBluetoothScanResultCounterLocked().readSummaryFromParcelLocked(in);
+ }
u.mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
if (in.readInt() != 0) {
@@ -11391,6 +11437,12 @@
} else {
out.writeInt(0);
}
+ if (u.mBluetoothScanResultCounter != null) {
+ out.writeInt(1);
+ u.mBluetoothScanResultCounter.writeSummaryFromParcelLocked(out);
+ } else {
+ out.writeInt(0);
+ }
for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
if (u.mProcessStateTimer[i] != null) {
out.writeInt(1);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 80b6b08..1b83708 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1498,7 +1498,6 @@
final Window.Callback cb = mWindow.getCallback();
if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
- cb.onBeforeAttachedToWindow();
cb.onAttachedToWindow();
}
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index fc90fb3..9ad57b1 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -41,9 +41,6 @@
namespace android {
-// Must be same with Java constant in Typeface.Builder. See Typeface.java
-constexpr jint RESOLVE_BY_FONT_TABLE = -1;
-
struct NativeFamilyBuilder {
NativeFamilyBuilder(uint32_t langId, int variant)
: langId(langId), variant(variant), allowUnsupportedFont(false) {}
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 86c97a1..eb2ca5d 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -30,14 +30,14 @@
static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) {
Typeface* family = reinterpret_cast<Typeface*>(familyHandle);
- Typeface* face = Typeface::createFromTypeface(family, (SkTypeface::Style)style);
+ Typeface* face = Typeface::createRelative(family, (SkTypeface::Style)style);
// TODO: the following logic shouldn't be necessary, the above should always succeed.
// Try to find the closest matching font, using the standard heuristic
if (NULL == face) {
- face = Typeface::createFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
+ face = Typeface::createRelative(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
}
for (int i = 0; NULL == face && i < 4; i++) {
- face = Typeface::createFromTypeface(family, (SkTypeface::Style)i);
+ face = Typeface::createRelative(family, (SkTypeface::Style)i);
}
return reinterpret_cast<jlong>(face);
}
@@ -45,8 +45,7 @@
static jlong Typeface_createFromTypefaceWithExactStyle(JNIEnv* env, jobject, jlong nativeInstance,
jint weight, jboolean italic) {
Typeface* baseTypeface = reinterpret_cast<Typeface*>(nativeInstance);
- return reinterpret_cast<jlong>(
- Typeface::createFromTypefaceWithStyle(baseTypeface, weight, italic));
+ return reinterpret_cast<jlong>(Typeface::createAbsolute(baseTypeface, weight, italic));
}
static jlong Typeface_createFromTypefaceWithVariation(JNIEnv* env, jobject, jlong familyHandle,
@@ -68,7 +67,7 @@
static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {
Typeface* family = reinterpret_cast<Typeface*>(familyHandle);
- Typeface* face = Typeface::createWeightAlias(family, weight);
+ Typeface* face = Typeface::createWithDifferentBaseWeight(family, weight);
return reinterpret_cast<jlong>(face);
}
@@ -82,9 +81,9 @@
return face->fSkiaStyle;
}
-static jint Typeface_getBaseWeight(JNIEnv* env, jobject obj, jlong faceHandle) {
+static jint Typeface_getWeight(JNIEnv* env, jobject obj, jlong faceHandle) {
Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
- return face->fBaseWeight;
+ return face->fStyle.getWeight() * 100;
}
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
@@ -134,7 +133,7 @@
{ "nativeCreateWeightAlias", "(JI)J", (void*)Typeface_createWeightAlias },
{ "nativeUnref", "(J)V", (void*)Typeface_unref },
{ "nativeGetStyle", "(J)I", (void*)Typeface_getStyle },
- { "nativeGetBaseWeight", "(J)I", (void*)Typeface_getBaseWeight },
+ { "nativeGetWeight", "(J)I", (void*)Typeface_getWeight },
{ "nativeCreateFromArray", "([JII)J",
(void*)Typeface_createFromArray },
{ "nativeSetDefault", "(J)V", (void*)Typeface_setDefault },
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
new file mode 100644
index 0000000..1dced75
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.internal.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+
+import android.os.WorkSource;
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Test various BatteryStatsImpl noteStart methods.
+ */
+public class BatteryStatsNoteTest extends TestCase{
+ private static final int UID = 10500;
+ private static final WorkSource WS = new WorkSource(UID);
+
+ /** Test that BatteryStatsImpl.Uid.noteBluetoothScanResultLocked. */
+ @SmallTest
+ public void testNoteBluetoothScanResultLocked() throws Exception {
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClocks());
+ bi.updateTimeBasesLocked(true, true, 0, 0);
+
+ bi.noteBluetoothScanResultFromSourceLocked(WS);
+ bi.noteBluetoothScanResultFromSourceLocked(WS);
+ assertEquals(2,
+ bi.getUidStats().get(UID).getBluetoothScanResultCounter()
+ .getCountLocked(STATS_SINCE_CHARGED));
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 57d6934..3a16fcf 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -15,6 +15,7 @@
BatteryStatsUidTest.class,
BatteryStatsSensorTest.class,
BatteryStatsBackgroundStatsTest.class,
+ BatteryStatsNoteTest.class,
})
public class BatteryStatsTests {
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 79898bc..5a56f53 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -123,7 +123,7 @@
public static final int BOLD_ITALIC = 3;
private int mStyle = 0;
- private int mBaseWeight = 0;
+ private int mWeight = 0;
// Value for weight and italic. Indicates the value is resolved by font metadata.
// Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
@@ -544,7 +544,7 @@
return base;
}
- final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mBaseWeight : mWeight;
+ final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
final boolean italic =
(mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
final int key = weight << 1 | (italic ? 1 : 0);
@@ -882,7 +882,7 @@
native_instance = ni;
mStyle = nativeGetStyle(ni);
- mBaseWeight = nativeGetBaseWeight(ni);
+ mWeight = nativeGetWeight(ni);
}
private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
@@ -1068,7 +1068,7 @@
private static native long nativeCreateWeightAlias(long native_instance, int weight);
private static native void nativeUnref(long native_instance);
private static native int nativeGetStyle(long native_instance);
- private static native int nativeGetBaseWeight(long native_instance);
+ private static native int nativeGetWeight(long native_instance);
private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
private static native void nativeSetDefault(long native_instance);
private static native int[] nativeGetSupportedAxes(long native_instance);
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 41e5af1..c3ef450 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -483,10 +483,10 @@
final int sourceDensity = mVectorState.mDensity;
final int targetDensity = mTargetDensity;
if (targetDensity != sourceDensity) {
- mDpiScaledWidth = Drawable.scaleFromDensity(
- (int) mVectorState.mBaseWidth, sourceDensity, targetDensity, true);
- mDpiScaledHeight = Drawable.scaleFromDensity(
- (int) mVectorState.mBaseHeight,sourceDensity, targetDensity, true);
+ mDpiScaledWidth = Drawable.scaleFromDensity(mVectorState.mBaseWidth, sourceDensity,
+ targetDensity, true);
+ mDpiScaledHeight = Drawable.scaleFromDensity(mVectorState.mBaseHeight,sourceDensity,
+ targetDensity, true);
final int left = Drawable.scaleFromDensity(
opticalInsets.left, sourceDensity, targetDensity, false);
final int right = Drawable.scaleFromDensity(
@@ -497,8 +497,8 @@
opticalInsets.bottom, sourceDensity, targetDensity, false);
mDpiScaledInsets = Insets.of(left, top, right, bottom);
} else {
- mDpiScaledWidth = (int) mVectorState.mBaseWidth;
- mDpiScaledHeight = (int) mVectorState.mBaseHeight;
+ mDpiScaledWidth = mVectorState.mBaseWidth;
+ mDpiScaledHeight = mVectorState.mBaseHeight;
mDpiScaledInsets = opticalInsets;
}
@@ -675,9 +675,9 @@
"<vector> tag requires viewportHeight > 0");
}
- state.mBaseWidth = a.getDimension(
+ state.mBaseWidth = a.getDimensionPixelSize(
R.styleable.VectorDrawable_width, state.mBaseWidth);
- state.mBaseHeight = a.getDimension(
+ state.mBaseHeight = a.getDimensionPixelSize(
R.styleable.VectorDrawable_height, state.mBaseHeight);
if (state.mBaseWidth <= 0) {
@@ -819,8 +819,8 @@
Mode mTintMode = DEFAULT_TINT_MODE;
boolean mAutoMirrored;
- float mBaseWidth = 0;
- float mBaseHeight = 0;
+ int mBaseWidth = 0;
+ int mBaseHeight = 0;
float mViewportWidth = 0;
float mViewportHeight = 0;
Insets mOpticalInsets = Insets.NONE;
@@ -1004,8 +1004,9 @@
}
private void applyDensityScaling(int sourceDensity, int targetDensity) {
- mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity);
- mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity);
+ mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity, true);
+ mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity,
+ true);
final int insetLeft = Drawable.scaleFromDensity(
mOpticalInsets.left, sourceDensity, targetDensity, false);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 5ef49dc..030e845 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -319,6 +319,7 @@
tests/unit/TestUtilsTests.cpp \
tests/unit/TextDropShadowCacheTests.cpp \
tests/unit/TextureCacheTests.cpp \
+ tests/unit/TypefaceTests.cpp \
tests/unit/VectorDrawableTests.cpp \
include $(LOCAL_PATH)/hwui_static_deps.mk
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 4fb4b533..f66bb04 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -36,28 +36,33 @@
#include <minikin/FontFamily.h>
#include <minikin/Layout.h>
#include <utils/Log.h>
+#include <utils/MathUtils.h>
namespace android {
-// This indicates that the passed information should be resolved by OS/2 table.
-// This value must be the same as the android.graphics.Typeface$Builder.RESOLVE_BY_FONT_TABLE.
-constexpr int RESOLVE_BY_FONT_TABLE = -1;
+static SkTypeface::Style computeSkiaStyle(int weight, bool italic) {
+ // This bold detection comes from SkTypeface.h
+ if (weight >= SkFontStyle::kSemiBold_Weight) {
+ return italic ? SkTypeface::kBoldItalic : SkTypeface::kBold;
+ } else {
+ return italic ? SkTypeface::kItalic : SkTypeface::kNormal;
+ }
+}
-// Resolve the 1..10 weight based on base weight and bold flag
-static void resolveStyle(Typeface* typeface) {
+static minikin::FontStyle computeMinikinStyle(int weight, bool italic) {
// TODO: Better to use raw base weight value for font selection instead of dividing by 100.
- int weight = (typeface->fBaseWeight + 50) / 100;
- if (typeface->fSkiaStyle & SkTypeface::kBold) {
- weight += 3;
+ const int minikinWeight = uirenderer::MathUtils::clamp((weight + 50) / 100, 1, 10);
+ return minikin::FontStyle(minikinWeight, italic);
+}
+
+// Resolve the relative weight from the baseWeight and target style.
+static minikin::FontStyle computeRelativeStyle(int baseWeight, SkTypeface::Style relativeStyle) {
+ int weight = baseWeight;
+ if ((relativeStyle & SkTypeface::kBold) != 0) {
+ weight += 300;
}
- if (weight > 10) {
- weight = 10;
- }
- if (weight < 1) {
- weight = 1;
- }
- bool italic = (typeface->fSkiaStyle & SkTypeface::kItalic) != 0;
- typeface->fStyle = minikin::FontStyle(weight, italic);
+ bool italic = (relativeStyle & SkTypeface::kItalic) != 0;
+ return computeMinikinStyle(weight, italic);
}
Typeface* gDefaultTypeface = NULL;
@@ -67,26 +72,26 @@
return src == nullptr ? gDefaultTypeface : src;
}
-Typeface* Typeface::createFromTypeface(Typeface* src, SkTypeface::Style style) {
+Typeface* Typeface::createRelative(Typeface* src, SkTypeface::Style style) {
Typeface* resolvedFace = Typeface::resolveDefault(src);
Typeface* result = new Typeface;
if (result != nullptr) {
result->fFontCollection = resolvedFace->fFontCollection;
- result->fSkiaStyle = style;
result->fBaseWeight = resolvedFace->fBaseWeight;
- resolveStyle(result);
+ result->fSkiaStyle = style;
+ result->fStyle = computeRelativeStyle(result->fBaseWeight, style);
}
return result;
}
-Typeface* Typeface::createFromTypefaceWithStyle(Typeface* base, int weight, bool italic) {
+Typeface* Typeface::createAbsolute(Typeface* base, int weight, bool italic) {
Typeface* resolvedFace = Typeface::resolveDefault(base);
Typeface* result = new Typeface();
if (result != nullptr) {
result->fFontCollection = resolvedFace->fFontCollection;
- result->fBaseWeight = weight;
- result->fStyle = minikin::FontStyle(weight / 100, italic);
- result->fSkiaStyle = resolvedFace->fSkiaStyle;
+ result->fBaseWeight = resolvedFace->fBaseWeight;
+ result->fSkiaStyle = computeSkiaStyle(weight, italic);
+ result->fStyle = computeMinikinStyle(weight, italic);
}
return result;
}
@@ -103,21 +108,23 @@
// So we will reuse the same collection with incrementing reference count.
result->fFontCollection = resolvedFace->fFontCollection;
}
- result->fSkiaStyle = resolvedFace->fSkiaStyle;
+ // Do not update styles.
+ // TODO: We may want to update base weight if the 'wght' is specified.
result->fBaseWeight = resolvedFace->fBaseWeight;
- resolveStyle(result);
+ result->fSkiaStyle = resolvedFace->fSkiaStyle;
+ result->fStyle = resolvedFace->fStyle;
}
return result;
}
-Typeface* Typeface::createWeightAlias(Typeface* src, int weight) {
+Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) {
Typeface* resolvedFace = Typeface::resolveDefault(src);
Typeface* result = new Typeface;
if (result != nullptr) {
result->fFontCollection = resolvedFace->fFontCollection;
- result->fSkiaStyle = resolvedFace->fSkiaStyle;
result->fBaseWeight = weight;
- resolveStyle(result);
+ result->fSkiaStyle = resolvedFace->fSkiaStyle;
+ result->fStyle = computeRelativeStyle(weight, result->fSkiaStyle);
}
return result;
}
@@ -160,14 +167,8 @@
}
result->fBaseWeight = weight;
- // This bold detection comes from SkTypefae.h
- const bool isBold = weight >= SkFontStyle::kSemiBold_Weight;
- const bool isItalic = italic == 1;
- // TODO: remove fSkiaStyle
- result->fSkiaStyle = isBold ?
- (isItalic ? SkTypeface::kBoldItalic : SkTypeface::kBold) :
- (isItalic ? SkTypeface::kItalic : SkTypeface::kNormal);
- resolveStyle(result);
+ result->fSkiaStyle = computeSkiaStyle(weight, italic);
+ result->fStyle = computeMinikinStyle(weight, italic);
return result;
}
@@ -197,7 +198,7 @@
Typeface* hwTypeface = new Typeface();
hwTypeface->fFontCollection = collection;
hwTypeface->fSkiaStyle = SkTypeface::kNormal;
- hwTypeface->fBaseWeight = SkFontStyle::kSemiBold_Weight;
+ hwTypeface->fBaseWeight = SkFontStyle::kNormal_Weight;
hwTypeface->fStyle = minikin::FontStyle(4 /* weight */, false /* italic */);
Typeface::setDefault(hwTypeface);
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index e35a7b4..db0b2cd 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -27,28 +27,53 @@
namespace android {
-struct ANDROID_API Typeface {
- std::shared_ptr<minikin::FontCollection> fFontCollection;
+// This indicates that the weight or italic information should be resolved by OS/2 table.
+// This value must be the same as the android.graphics.Typeface$Builder.RESOLVE_BY_FONT_TABLE.
+constexpr int RESOLVE_BY_FONT_TABLE = -1;
- // style used for constructing and querying Typeface objects
- SkTypeface::Style fSkiaStyle;
- // base weight in CSS-style units, 100..900
- int fBaseWeight;
+struct ANDROID_API Typeface {
+ public:
+ std::shared_ptr<minikin::FontCollection> fFontCollection;
// resolved style actually used for rendering
minikin::FontStyle fStyle;
+ // style used for constructing and querying Typeface objects
+ SkTypeface::Style fSkiaStyle;
+
static Typeface* resolveDefault(Typeface* src);
- static Typeface* createFromTypeface(Typeface* src, SkTypeface::Style style);
-
- static Typeface* createFromTypefaceWithStyle(Typeface* base, int weight, bool italic);
+ // The following three functions create new Typeface from an existing Typeface with a different
+ // style. There is a base weight concept which is used for calculating relative style from an
+ // existing Typeface.
+ // The createRelative method creates a new Typeface with a style relative to the base Typeface.
+ // For example, if the base Typeface has a base weight of 400 and the desired style is bold, the
+ // resulting Typeface renders the text with a weight of 700. This function doesn't change the
+ // base weight, so even if you create a new Typeface from the bold Typeface specifying bold on
+ // it again, the text is still rendered with a weight of 700.
+ // You can create another base weight Typeface from an existing Typeface with
+ // createWithDifferentBaseWeight. The Typeface created with this function renders the text with
+ // a specified base weight.
+ // The createAbsolute method creates a new Typeface ignoring the base weight.
+ // Here is an example:
+ // Typeface* base = resolveDefault(nullptr); // Usually this has a weight of 400.
+ // Typeface* bold = createRelative(base, Bold); // Rendered with a weight of 700.
+ // Typeface* bold2 = createRelative(bold, Bold); // Rendered with a weight of 700.
+ //
+ // Typeface* boldBase = createWithDifferentBaseWeight(base, 700); // With a weight of 700.
+ // Typeface* boldBold = createRelative(boldBase, Bold); // Rendered with a weight of 1000.
+ //
+ // Typeface* lightBase = createWithDifferentBaseWeight(base, 300); // With a weight of 300.
+ // Typeface* lightBold = createRelative(lightBase, Bold); // Rendered with a weight of 600.
+ //
+ // Typeface* black = createAbsolute(base, 900, false); // Rendered with a weight of 900.
+ static Typeface* createWithDifferentBaseWeight(Typeface* src, int baseweight);
+ static Typeface* createRelative(Typeface* src, SkTypeface::Style desiredStyle);
+ static Typeface* createAbsolute(Typeface* base, int weight, bool italic);
static Typeface* createFromTypefaceWithVariation(Typeface* src,
const std::vector<minikin::FontVariation>& variations);
- static Typeface* createWeightAlias(Typeface* src, int baseweight);
-
static Typeface* createFromFamilies(
std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
int weight, int italic);
@@ -57,6 +82,10 @@
// Sets roboto font as the default typeface for testing purpose.
static void setRobotoTypefaceForTest();
+ private:
+ // base weight in CSS-style units, 1..1000
+ int fBaseWeight;
+
};
}
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
new file mode 100644
index 0000000..c90b6f0
--- /dev/null
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -0,0 +1,418 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "SkFontMgr.h"
+#include "SkStream.h"
+
+#include "hwui/MinikinSkia.h"
+#include "hwui/Typeface.h"
+
+using namespace android;
+
+namespace {
+
+constexpr char kRobotoRegular[] = "/system/fonts/Roboto-Regular.ttf";
+constexpr char kRobotoBold[] = "/system/fonts/Roboto-Bold.ttf";
+constexpr char kRobotoItalic[] = "/system/fonts/Roboto-Italic.ttf";
+constexpr char kRobotoBoldItalic[] = "/system/fonts/Roboto-BoldItalic.ttf";
+
+void unmap(const void* ptr, void* context) {
+ void* p = const_cast<void*>(ptr);
+ size_t len = reinterpret_cast<size_t>(context);
+ munmap(p, len);
+}
+
+std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
+ int fd = open(fileName, O_RDONLY);
+ LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName);
+ struct stat st = {};
+ LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName);
+ void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ sk_sp<SkData> skData =
+ SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
+ std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
+ sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+ sk_sp<SkTypeface> typeface(fm->createFromStream(fontData.release()));
+ LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
+ std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
+ std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>());
+ return std::make_shared<minikin::FontFamily>(
+ std::vector<minikin::Font>({ minikin::Font(std::move(font), minikin::FontStyle()) }));
+}
+
+std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const char* fileName) {
+ return std::vector<std::shared_ptr<minikin::FontFamily>>({ buildFamily(fileName) });
+}
+
+TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoRegular),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get()));
+
+ Typeface* old = Typeface::resolveDefault(nullptr); // Keep the original to restore it later.
+ ASSERT_NE(nullptr, old);
+
+ Typeface::setDefault(regular.get());
+ EXPECT_EQ(regular.get(), Typeface::resolveDefault(nullptr));
+
+ Typeface::setDefault(old); // Restore to the original.
+}
+
+TEST(TypefaceTest, createWithDifferentBaseWeight) {
+ std::unique_ptr<Typeface> bold(Typeface::createWithDifferentBaseWeight(nullptr, 700));
+ EXPECT_EQ(7, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, bold->fSkiaStyle);
+
+ std::unique_ptr<Typeface> light(Typeface::createWithDifferentBaseWeight(nullptr, 300));
+ EXPECT_EQ(3, light->fStyle.getWeight());
+ EXPECT_FALSE(light->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, light->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createRelativeTest_fromRegular) {
+ // In Java, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
+ std::unique_ptr<Typeface> normal(Typeface::createRelative(nullptr, SkTypeface::kNormal));
+ EXPECT_EQ(4, normal->fStyle.getWeight());
+ EXPECT_FALSE(normal->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
+ std::unique_ptr<Typeface> bold(Typeface::createRelative(nullptr, SkTypeface::kBold));
+ EXPECT_EQ(7, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.DEFAULT, Typeface.ITALIC);
+ std::unique_ptr<Typeface> italic(Typeface::createRelative(nullptr, SkTypeface::kItalic));
+ EXPECT_EQ(4, italic->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC);
+ std::unique_ptr<Typeface> boldItalic(
+ Typeface::createRelative(nullptr, SkTypeface::kBoldItalic));
+ EXPECT_EQ(7, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createRelativeTest_BoldBase) {
+ std::unique_ptr<Typeface> base(Typeface::createWithDifferentBaseWeight(nullptr, 700));
+
+ // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.NORMAL);
+ std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+ EXPECT_EQ(7, normal->fStyle.getWeight());
+ EXPECT_FALSE(normal->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.BOLD);
+ std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+ EXPECT_EQ(10, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.ITALIC);
+ std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+ EXPECT_EQ(7, italic->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.BOLD_ITALIC);
+ std::unique_ptr<Typeface>
+ boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+ EXPECT_EQ(10, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createRelativeTest_LightBase) {
+ std::unique_ptr<Typeface> base(Typeface::createWithDifferentBaseWeight(nullptr, 300));
+
+ // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.NORMAL);
+ std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+ EXPECT_EQ(3, normal->fStyle.getWeight());
+ EXPECT_FALSE(normal->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.BOLD);
+ std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+ EXPECT_EQ(6, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.ITLIC);
+ std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+ EXPECT_EQ(3, italic->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.BOLD_ITALIC);
+ std::unique_ptr<Typeface>
+ boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+ EXPECT_EQ(6, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createRelativeTest_fromBoldStyled) {
+ std::unique_ptr<Typeface> base(Typeface::createRelative(nullptr, SkTypeface::kBold));
+
+ // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.NORMAL);
+ std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+ EXPECT_EQ(4, normal->fStyle.getWeight());
+ EXPECT_FALSE(normal->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+
+ // In Java Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.BOLD);
+ std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+ EXPECT_EQ(7, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.ITALIC);
+ std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+ EXPECT_EQ(4, normal->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java,
+ // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.BOLD_ITALIC);
+ std::unique_ptr<Typeface>
+ boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+ EXPECT_EQ(7, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createRelativeTest_fromItalicStyled) {
+ std::unique_ptr<Typeface> base(Typeface::createRelative(nullptr, SkTypeface::kItalic));
+
+ // In Java,
+ // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.NORMAL);
+ std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+ EXPECT_EQ(4, normal->fStyle.getWeight());
+ EXPECT_FALSE(normal->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+
+ // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.BOLD);
+ std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+ EXPECT_EQ(7, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java,
+ // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.ITALIC);
+ std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+ EXPECT_EQ(4, italic->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java,
+ // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.BOLD_ITALIC);
+ std::unique_ptr<Typeface>
+ boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+ EXPECT_EQ(7, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createRelativeTest_fromSpecifiedStyled) {
+ std::unique_ptr<Typeface> base(Typeface::createAbsolute(nullptr, 400, false));
+
+ // In Java,
+ // Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
+ // .setWeight(700).setItalic(false).build();
+ // Typeface.create(typeface, Typeface.NORMAL);
+ std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+ EXPECT_EQ(4, normal->fStyle.getWeight());
+ EXPECT_FALSE(normal->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+
+ // In Java,
+ // Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
+ // .setWeight(700).setItalic(false).build();
+ // Typeface.create(typeface, Typeface.BOLD);
+ std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+ EXPECT_EQ(7, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java,
+ // Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
+ // .setWeight(700).setItalic(false).build();
+ // Typeface.create(typeface, Typeface.ITALIC);
+ std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+ EXPECT_EQ(4, italic->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java,
+ // Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
+ // .setWeight(700).setItalic(false).build();
+ // Typeface.create(typeface, Typeface.BOLD_ITALIC);
+ std::unique_ptr<Typeface>
+ boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+ EXPECT_EQ(7, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createAbsolute) {
+ // In Java,
+ // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(false)
+ // .build();
+ std::unique_ptr<Typeface> regular(Typeface::createAbsolute(nullptr, 400, false));
+ EXPECT_EQ(4, regular->fStyle.getWeight());
+ EXPECT_FALSE(regular->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, regular->fSkiaStyle);
+
+ // In Java,
+ // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(false)
+ // .build();
+ std::unique_ptr<Typeface> bold(Typeface::createAbsolute(nullptr, 700, false));
+ EXPECT_EQ(7, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java,
+ // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(true)
+ // .build();
+ std::unique_ptr<Typeface> italic(Typeface::createAbsolute(nullptr, 400, true));
+ EXPECT_EQ(4, italic->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java,
+ // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(true)
+ // .build();
+ std::unique_ptr<Typeface> boldItalic(Typeface::createAbsolute(nullptr, 700, true));
+ EXPECT_EQ(7, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+
+ // In Java,
+ // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(1100).setItalic(true)
+ // .build();
+ std::unique_ptr<Typeface> over1000(Typeface::createAbsolute(nullptr, 1100, false));
+ EXPECT_EQ(10, over1000->fStyle.getWeight());
+ EXPECT_FALSE(over1000->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, over1000->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createFromFamilies_Single) {
+ // In Java, new Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoRegular), 400, false));
+ EXPECT_EQ(4, regular->fStyle.getWeight());
+ EXPECT_FALSE(regular->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, regular->fSkiaStyle);
+
+ // In Java, new Typeface.Builder("Roboto-Bold.ttf").setWeight(700).setItalic(false).build();
+ std::unique_ptr<Typeface> bold(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 700, false));
+ EXPECT_EQ(7, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java, new Typeface.Builder("Roboto-Italic.ttf").setWeight(400).setItalic(true).build();
+ std::unique_ptr<Typeface> italic(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoItalic), 400, true));
+ EXPECT_EQ(4, italic->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java,
+ // new Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(700).setItalic(true).build();
+ std::unique_ptr<Typeface> boldItalic(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic), 700, true));
+ EXPECT_EQ(7, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java,
+ // new Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(1100).setItalic(false).build();
+ std::unique_ptr<Typeface> over1000(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 1100, false));
+ EXPECT_EQ(10, over1000->fStyle.getWeight());
+ EXPECT_FALSE(over1000->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, over1000->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
+ // In Java, new Typeface.Builder("Roboto-Regular.ttf").build();
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoRegular),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ EXPECT_EQ(4, regular->fStyle.getWeight());
+ EXPECT_FALSE(regular->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kNormal, regular->fSkiaStyle);
+
+ // In Java, new Typeface.Builder("Roboto-Bold.ttf").build();
+ std::unique_ptr<Typeface> bold(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ EXPECT_EQ(7, bold->fStyle.getWeight());
+ EXPECT_FALSE(bold->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+
+ // In Java, new Typeface.Builder("Roboto-Italic.ttf").build();
+ std::unique_ptr<Typeface> italic(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoItalic),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ EXPECT_EQ(4, italic->fStyle.getWeight());
+ EXPECT_TRUE(italic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+
+ // In Java, new Typeface.Builder("Roboto-BoldItalic.ttf").build();
+ std::unique_ptr<Typeface> boldItalic(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ EXPECT_EQ(7, boldItalic->fStyle.getWeight());
+ EXPECT_TRUE(boldItalic->fStyle.getItalic());
+ EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+}
+
+TEST(TypefaceTest, createFromFamilies_Family) {
+ std::vector<std::shared_ptr<minikin::FontFamily>> families = {
+ buildFamily(kRobotoRegular), buildFamily(kRobotoBold), buildFamily(kRobotoItalic),
+ buildFamily(kRobotoBoldItalic)
+ };
+ std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(std::move(families),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ EXPECT_EQ(4, typeface->fStyle.getWeight());
+ EXPECT_FALSE(typeface->fStyle.getItalic());
+}
+
+TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
+ std::vector<std::shared_ptr<minikin::FontFamily>> families = {
+ buildFamily(kRobotoBold), buildFamily(kRobotoItalic), buildFamily(kRobotoBoldItalic)
+ };
+ std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(std::move(families),
+ RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ EXPECT_EQ(7, typeface->fStyle.getWeight());
+ EXPECT_FALSE(typeface->fStyle.getItalic());
+}
+
+} // namespace
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index d82e549..e7da20b 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1453,7 +1453,8 @@
/**
* The release date of this TV program.
*
- * <p>The value should be in the form of either "yyyy-MM-dd" or "yyyy".
+ * <p>The value should be in one of the following formats:
+ * "yyyy", "yyyy-MM-dd", and "yyyy-MM-ddTHH:mm:ssZ" (UTC in ISO 8601).
*
* <p>Type: TEXT
*/
diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/res/layout/preference_two_target.xml
index 5446ace..9fb956e 100644
--- a/packages/SettingsLib/res/layout/preference_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target.xml
@@ -37,7 +37,7 @@
android:id="@+id/icon_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="60dp"
+ android:minWidth="56dp"
android:orientation="horizontal"
android:paddingEnd="12dp"
android:paddingTop="4dp"
diff --git a/packages/SystemUI/res/values/arrays_tv.xml b/packages/SystemUI/res/values/arrays_tv.xml
index e52c5db..7541b0e 100644
--- a/packages/SystemUI/res/values/arrays_tv.xml
+++ b/packages/SystemUI/res/values/arrays_tv.xml
@@ -31,5 +31,6 @@
<item>com.google.android.katniss.setting/.SpeechSettingsActivity</item>
<item>com.google.android.katniss.setting/.SearchSettingsActivity</item>
<item>com.google.android.gsf.notouch/.UsageDiagnosticsSettingActivity</item>
+ <item>com.google.android.tvlauncher/.notifications.NotificationsSidePanelActivity</item>
</string-array>
</resources>
diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml
index 40e3b12..ffd58dc 100644
--- a/packages/SystemUI/res/values/config_tv.xml
+++ b/packages/SystemUI/res/values/config_tv.xml
@@ -17,17 +17,9 @@
<resources>
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
when the PIP menu is shown with settings. -->
- <string translatable="false" name="pip_settings_bounds">"662 54 1142 324"</string>
+ <string translatable="false" name="pip_settings_bounds">"662 756 1142 1026"</string>
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
when the PIP menu is shown in center. -->
<string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
-
- <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
- when the PIP is shown in Recents without focus. -->
- <string translatable="false" name="pip_recents_bounds">"800 54 1120 234"</string>
-
- <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
- when the PIP is shown in Recents with focus. -->
- <string translatable="false" name="pip_recents_focused_bounds">"775 54 1145 262"</string>
</resources>
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index e578068..a9bdb71 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -17,6 +17,14 @@
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Picture-in-Picture (PIP) notification -->
+ <!-- Title for the notification channel for TV PIP controls. [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_tv_pip">Picture-in-Picture</string>
+ <!-- Title of the picture-in-picture (PIP) notification title
+ when the media doesn't have title [CHAR LIMIT=NONE] -->
+ <string name="pip_notification_unknown_title">(No title program)</string>
+
<!-- Picture-in-Picture (PIP) menu -->
<eat-comment />
<!-- Button to close picture-in-picture (PIP) in PIP menu [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 9735bfc..6667b71 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -61,7 +61,8 @@
*/
public class PipManager implements BasePipManager {
private static final String TAG = "PipManager";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
private static final String SETTINGS_PACKAGE_AND_CLASS_DELIMITER = "/";
private static PipManager sPipManager;
@@ -122,6 +123,7 @@
private ComponentName mPipComponentName;
private MediaController mPipMediaController;
private String[] mLastPackagesResourceGranted;
+ private PipNotification mPipNotification;
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
@@ -246,6 +248,8 @@
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned stack listener", e);
}
+
+ mPipNotification = new PipNotification(context);
}
private void loadConfigurationsAndApply() {
@@ -267,6 +271,7 @@
*/
public void onConfigurationChanged() {
loadConfigurationsAndApply();
+ mPipNotification.onConfigurationChanged(mContext);
}
/**
@@ -345,7 +350,7 @@
* @param state In Pip state also used to determine the new size for the Pip.
*/
void resizePinnedStack(int state) {
- if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
+ if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state, new Exception());
boolean wasStateNoPip = (mState == STATE_NO_PIP);
mResumeResizePinnedStackRunnable = state;
for (int i = mListeners.size() - 1; i >= 0; --i) {
@@ -511,8 +516,8 @@
/**
* Returns the PIPed activity's playback state.
- * This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED},
- * or {@link PLAYBACK_STATE_UNAVAILABLE}.
+ * This returns one of {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED},
+ * or {@link #PLAYBACK_STATE_UNAVAILABLE}.
*/
int getPlaybackState() {
if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
new file mode 100644
index 0000000..727eb5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip.tv;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.PlaybackState;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.R;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+
+/**
+ * A notification that informs users that PIP is running and also provides PIP controls.
+ * <p>Once it's created, it will manage the PIP notification UI by itself except for handling
+ * configuration changes.
+ */
+public class PipNotification {
+ private static final String TAG = "PipNotification";
+ private static final boolean DEBUG = PipManager.DEBUG;
+
+ private static final String ACTION_MENU = "PipNotification.menu";
+ private static final String ACTION_CLOSE = "PipNotification.close";
+
+ private final PipManager mPipManager = PipManager.getInstance();
+
+ private final NotificationManager mNotificationManager;
+ private final Notification.Builder mNotificationBuilder;
+
+ private MediaController mMediaController;
+ private String mDefaultTitle;
+ private Icon mDefaultIcon;
+
+ private boolean mNotified;
+ private String mTitle;
+ private Bitmap mArt;
+
+ private PipManager.Listener mPipListener = new PipManager.Listener() {
+ @Override
+ public void onPipEntered() {
+ updateMediaControllerMetadata();
+ notifyPipNotification();
+ }
+
+ @Override
+ public void onPipActivityClosed() {
+ dismissPipNotification();
+ }
+
+ @Override
+ public void onShowPipMenu() {
+ // no-op.
+ }
+
+ @Override
+ public void onMoveToFullscreen() {
+ dismissPipNotification();
+ }
+
+ @Override
+ public void onPipResizeAboutToStart() {
+ // no-op.
+ }
+ };
+
+ private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ if (updateMediaControllerMetadata() && mNotified) {
+ // update notification
+ notifyPipNotification();
+ }
+ }
+ };
+
+ private final PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() {
+ @Override
+ public void onMediaControllerChanged() {
+ MediaController newController = mPipManager.getMediaController();
+ if (mMediaController == newController) {
+ return;
+ }
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mMediaControllerCallback);
+ }
+ mMediaController = newController;
+ if (mMediaController != null) {
+ mMediaController.registerCallback(mMediaControllerCallback);
+ }
+ if (updateMediaControllerMetadata() && mNotified) {
+ // update notification
+ notifyPipNotification();
+ }
+ }
+ };
+
+ private final BroadcastReceiver mEventReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Log.d(TAG, "Received " + intent.getAction() + " from the notification UI");
+ }
+ switch (intent.getAction()) {
+ case ACTION_MENU:
+ mPipManager.showPictureInPictureMenu();
+ break;
+ case ACTION_CLOSE:
+ mPipManager.closePip();
+ break;
+ }
+ }
+ };
+
+ public PipNotification(Context context) {
+ mNotificationManager = (NotificationManager) context.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+
+ mNotificationBuilder = new Notification.Builder(context, NotificationChannels.TVPIP)
+ .setLocalOnly(true)
+ .setOngoing(false)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .extend(new Notification.TvExtender()
+ .setContentIntent(createPendingIntent(context, ACTION_MENU))
+ .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE)));
+
+ mPipManager.addListener(mPipListener);
+ mPipManager.addMediaListener(mPipMediaListener);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_MENU);
+ intentFilter.addAction(ACTION_CLOSE);
+ context.registerReceiver(mEventReceiver, intentFilter);
+
+ onConfigurationChanged(context);
+ }
+
+ /**
+ * Called by {@link PipManager} when the configuration is changed.
+ */
+ void onConfigurationChanged(Context context) {
+ Resources res = context.getResources();
+ mDefaultTitle = res.getString(R.string.pip_notification_unknown_title);
+ mDefaultIcon = Icon.createWithResource(context,
+ res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR
+ ? R.drawable.pip_expand_ll : R.drawable.pip_expand_lr);
+ if (mNotified) {
+ // update notification
+ notifyPipNotification();
+ }
+ }
+
+ private void notifyPipNotification() {
+ mNotified = true;
+ mNotificationBuilder
+ .setShowWhen(true)
+ .setWhen(System.currentTimeMillis())
+ // TODO: Sending bitmap doesn't work in launcher side. Once launcher supports it,
+ // we can set icon.
+ //.setSmallIcon(mArt != null ? Icon.createWithBitmap(mArt) : mDefaultIcon)
+ .setSmallIcon(mDefaultIcon.getResId())
+ .setContentTitle(!TextUtils.isEmpty(mTitle) ? mTitle : mDefaultTitle);
+ mNotificationManager.notify(SystemMessage.NOTE_TV_PIP, mNotificationBuilder.build());
+ }
+
+ private void dismissPipNotification() {
+ mNotified = false;
+ mNotificationManager.cancel(SystemMessage.NOTE_TV_PIP);
+ }
+
+ private boolean updateMediaControllerMetadata() {
+ String title = null;
+ Bitmap art = null;
+ if (mPipManager.getMediaController() != null) {
+ MediaMetadata metadata = mPipManager.getMediaController().getMetadata();
+ if (metadata != null) {
+ title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
+ if (TextUtils.isEmpty(title)) {
+ title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ }
+ art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ if (art == null) {
+ art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+ }
+ }
+ }
+ if (!TextUtils.equals(title, mTitle) || art != mArt) {
+ mTitle = title;
+ mArt = art;
+ return true;
+ }
+ return false;
+ }
+
+ private static PendingIntent createPendingIntent(Context context, String action) {
+ return PendingIntent.getBroadcast(context, 0,
+ new Intent(action), PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5512993..6f28838 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -68,6 +68,7 @@
private final Callback mCallback = new Callback();
private final ActivityStarter mActivityStarter;
private Dialog mDialog;
+ private boolean mRegistered;
public CastTile(QSHost host) {
super(host);
@@ -146,7 +147,7 @@
mDialog = dialog;
}
mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
- mDialog.show();
+ mUiHandler.post(() -> mDialog.show());
registerReceiver();
mHost.collapsePanels();
});
@@ -155,7 +156,13 @@
private void registerReceiver() {
mContext.registerReceiverAsUser(mReceiver, UserHandle.CURRENT,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null, null);
- mDialog.setOnDismissListener(dialog -> mContext.unregisterReceiver(mReceiver));
+ mRegistered = true;
+ mDialog.setOnDismissListener(dialog -> {
+ if (mRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mRegistered = false;
+ }
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 1375310..23a67e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -95,14 +95,25 @@
/**
* Called when the guts view have been told to close, typically after an outside
- * interaction. Returning {@code true} here will prevent the guts view to close.
+ * interaction.
+ *
+ * @param save whether the state should be saved.
+ * @param force whether the guts view should be forced closed regardless of state.
+ * @return if closing the view has been handled.
*/
- public boolean handleCloseControls(boolean save);
+ public boolean handleCloseControls(boolean save, boolean force);
/**
* @return whether the notification associated with these guts is set to be removed.
*/
public boolean willBeRemoved();
+
+ /**
+ * @return whether these guts are a leavebehind (e.g. {@link NotificationSnooze}).
+ */
+ public default boolean isLeavebehind() {
+ return false;
+ }
}
public interface OnGutsClosedListener {
@@ -125,7 +136,7 @@
@Override
public void run() {
if (mNeedsFalsingProtection && mExposed) {
- closeControls(-1 /* x */, -1 /* y */, false /* save */);
+ closeControls(-1 /* x */, -1 /* y */, false /* save */, false /* force */);
}
}
};
@@ -144,6 +155,10 @@
addView(mGutsContent.getContentView());
}
+ public GutsContent getGutsContent() {
+ return mGutsContent;
+ }
+
public void resetFalsingCheck() {
mHandler.removeCallbacks(mFalsingCheck);
if (mNeedsFalsingProtection && mExposed) {
@@ -197,19 +212,30 @@
}
}
- public void closeControls(int x, int y, boolean save) {
+ public void closeControls(boolean leavebehinds, boolean controls, int x, int y, boolean force) {
+ if (mGutsContent != null) {
+ if (mGutsContent.isLeavebehind() && leavebehinds) {
+ closeControls(x, y, true /* save */, force);
+ } else if (!mGutsContent.isLeavebehind() && controls) {
+ closeControls(x, y, true /* save */, force);
+ }
+ }
+ }
+
+ public void closeControls(int x, int y, boolean save, boolean force) {
if (getWindowToken() == null) {
if (mClosedListener != null) {
mClosedListener.onGutsClosed(this);
}
return;
}
- if (mGutsContent == null || !mGutsContent.handleCloseControls(save)) {
+
+ if (mGutsContent == null || !mGutsContent.handleCloseControls(save, force)) {
animateClose(x, y);
- }
- setExposed(false, mNeedsFalsingProtection);
- if (mClosedListener != null) {
- mClosedListener.onGutsClosed(this);
+ setExposed(false, mNeedsFalsingProtection);
+ if (mClosedListener != null) {
+ mClosedListener.onGutsClosed(this);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 2cf06c2..0c2c5bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -393,7 +393,7 @@
}
@Override
- public boolean handleCloseControls(boolean save) {
+ public boolean handleCloseControls(boolean save, boolean force) {
if (save && hasImportanceChanged()) {
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(() -> { saveImportance(); });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 8b3d6d9..8830c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -103,6 +103,18 @@
createOptionViews();
}
+ public boolean isExpanded() {
+ return mExpanded;
+ }
+
+ public void setSnoozeListener(NotificationSwipeActionHelper listener) {
+ mSnoozeListener = listener;
+ }
+
+ public void setStatusBarNotification(StatusBarNotification sbn) {
+ mSbn = sbn;
+ }
+
private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
ArrayList<SnoozeOption> options = new ArrayList<>();
options.add(createOption(R.string.snooze_option_15_min, 15));
@@ -203,7 +215,7 @@
final int x = targetLoc[0] - parentLoc[0] + centerX;
final int y = targetLoc[1] - parentLoc[1] + centerY;
showSnoozeOptions(false);
- mGutsContainer.closeControls(x, y, false /* save */);
+ mGutsContainer.closeControls(x, y, false /* save */, false /* force */);
}
}
@@ -224,29 +236,31 @@
return this;
}
- public void setStatusBarNotification(StatusBarNotification sbn) {
- mSbn = sbn;
- }
-
@Override
public void setGutsParent(NotificationGuts guts) {
mGutsContainer = guts;
}
- public void setSnoozeListener(NotificationSwipeActionHelper listener) {
- mSnoozeListener = listener;
- }
-
@Override
- public boolean handleCloseControls(boolean save) {
- // When snooze is closed (i.e. there was interaction outside of the notification)
- // then we commit the snooze action.
- if (mSnoozeListener != null && mSelectedOption != null) {
+ public boolean handleCloseControls(boolean save, boolean force) {
+ if (mExpanded && !force) {
+ // Collapse expanded state on outside touch
+ showSnoozeOptions(false);
+ return true;
+ } else if (mSnoozeListener != null && mSelectedOption != null) {
+ // Snooze option selected so commit it
mSnoozing = true;
mSnoozeListener.snooze(mSbn, mSelectedOption);
return true;
+ } else {
+ // The view should actually be closed
+ setSelected(mSnoozeOptions.get(0));
+ return false; // Return false here so that guts handles closing the view
}
- // The view should be closed
- return false;
+ }
+
+ @Override
+ public boolean isLeavebehind() {
+ return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 1a9a40b..674a61c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -168,7 +168,8 @@
distance = mTranslationOnDown + distance;
distance = Math.max(0, distance);
}
- setTranslation(distance, false /* isReset */, false /* animateReset */);
+ setTranslation(distance, false /* isReset */, false /* animateReset */,
+ false /* force */);
}
break;
@@ -373,11 +374,12 @@
targetView.finishAnimation(velocity, mAnimationEndRunnable);
}
- private void setTranslation(float translation, boolean isReset, boolean animateReset) {
+ private void setTranslation(float translation, boolean isReset, boolean animateReset,
+ boolean force) {
translation = rightSwipePossible() ? translation : Math.max(0, translation);
translation = leftSwipePossible() ? translation : Math.min(0, translation);
float absTranslation = Math.abs(translation);
- if (translation != mTranslation || isReset) {
+ if (translation != mTranslation || isReset || force) {
KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
float alpha = absTranslation / getMinTranslationAmount();
@@ -392,15 +394,15 @@
boolean slowAnimation = isReset && isBelowFalsingThreshold();
if (!isReset) {
updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
- false, false, false, false);
+ false, false, force, false);
} else {
updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
- animateIcons, slowAnimation, false, forceNoCircleAnimation);
+ animateIcons, slowAnimation, force, forceNoCircleAnimation);
}
updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
- animateIcons, slowAnimation, false, forceNoCircleAnimation);
+ animateIcons, slowAnimation, force, forceNoCircleAnimation);
updateIcon(mCenterIcon, 0.0f, fadeOutAlpha * mCenterIcon.getRestingAlpha(),
- animateIcons, slowAnimation, false, forceNoCircleAnimation);
+ animateIcons, slowAnimation, force, forceNoCircleAnimation);
mTranslation = translation;
}
@@ -508,8 +510,12 @@
}
public void reset(boolean animate) {
+ reset(animate, false /* force */);
+ }
+
+ public void reset(boolean animate, boolean force) {
cancelAnimation();
- setTranslation(0.0f, true, animate);
+ setTranslation(0.0f, true, animate, force);
mMotionCancelled = true;
if (mSwipingInProgress) {
mCallback.onSwipingAborted();
@@ -517,6 +523,10 @@
}
}
+ public void resetImmediately() {
+ reset(false /* animate */, true /* force */);
+ }
+
public boolean isSwipingInProgress() {
return mSwipingInProgress;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 8a97be5..f7480bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -46,7 +46,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.DejankUtils;
-import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
@@ -524,7 +523,8 @@
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
}
closeQs();
- mStatusBar.dismissPopups();
+ mStatusBar.closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
true /* cancelAnimators */);
mNotificationStackScroller.resetScrollPosition();
@@ -1015,6 +1015,7 @@
float height = mQsExpansionHeight - overscrollAmount;
setQsExpansion(height);
requestPanelHeightUpdate();
+ mNotificationStackScroller.checkSnoozeLeavebehind();
}
private void setQsExpanded(boolean expanded) {
@@ -1381,6 +1382,7 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ mNotificationStackScroller.resetCheckSnoozeLeavebehind();
mQsExpansionAnimator = null;
if (onFinishRunnable != null) {
onFinishRunnable.run();
@@ -2463,6 +2465,14 @@
}
};
+ @Override
+ public void setTouchDisabled(boolean disabled) {
+ super.setTouchDisabled(disabled);
+ if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
+ mAffordanceHelper.resetImmediately();
+ }
+ }
+
public void setDark(boolean dark) {
mDark = dark;
mKeyguardStatusView.setDark(dark);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index e86fd48..d342635 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -221,8 +221,11 @@
public void setTouchDisabled(boolean disabled) {
mTouchDisabled = disabled;
- if (mTouchDisabled && mTracking) {
- onTrackingStopped(true /* expanded */);
+ if (mTouchDisabled) {
+ cancelHeightAnimator();
+ if (mTracking) {
+ onTrackingStopped(true /* expanded */);
+ }
}
}
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 4610bc8..b33d509 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2996,8 +2996,9 @@
mStatusBarWindowManager.setPanelVisible(false);
mStatusBarWindowManager.setForceStatusBarVisible(false);
- // Close any "App info" popups that might have snuck on-screen
- dismissPopups();
+ // Close any guts that might be visible
+ closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */,
+ -1 /* x */, -1 /* y */, true /* resetMenu */);
runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
@@ -4056,13 +4057,6 @@
setBarState(StatusBarState.KEYGUARD);
}
updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */);
- if (!mDeviceInteractive) {
-
- // If the screen is off already, we need to disable touch events because these might
- // collapse the panel after we expanded it, and thus we would end up with a blank
- // Keyguard.
- mNotificationPanel.setTouchDisabled(true);
- }
if (mState == StatusBarState.KEYGUARD) {
instantExpandNotificationsPanel();
} else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
@@ -4617,6 +4611,7 @@
public void onDragDownReset() {
mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
mStackScroller.resetScrollPosition();
+ mStackScroller.resetCheckSnoozeLeavebehind();
}
@Override
@@ -4627,6 +4622,7 @@
@Override
public void onTouchSlopExceeded() {
mStackScroller.removeLongPressCallback();
+ mStackScroller.checkSnoozeLeavebehind();
}
@Override
@@ -4861,6 +4857,12 @@
mStackScroller.setAnimationsEnabled(false);
mVisualStabilityManager.setScreenOn(false);
updateVisibleToUser();
+
+ // We need to disable touch events because these might
+ // collapse the panel after we expanded it, and thus we would end up with a blank
+ // Keyguard.
+ mNotificationPanel.setTouchDisabled(true);
+ mStatusBarWindow.cancelCurrentTouch();
if (mLaunchCameraOnFinishedGoingToSleep) {
mLaunchCameraOnFinishedGoingToSleep = false;
@@ -5800,8 +5802,10 @@
if (!g.willBeRemoved() && !row.isRemoved()) {
mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
}
- mNotificationGutsExposed = null;
- mGutsMenuItem = null;
+ if (mNotificationGutsExposed == g) {
+ mNotificationGutsExposed = null;
+ mGutsMenuItem = null;
+ }
});
View gutsView = item.getGutsView();
@@ -5897,7 +5901,8 @@
final int centerY = done.getHeight() / 2;
final int x = doneLocation[0] - rowLocation[0] + centerX;
final int y = doneLocation[1] - rowLocation[1] + centerY;
- dismissPopups(x, y);
+ closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, x, y, true /* resetMenu */);
}
protected SwipeHelper.LongPressListener getNotificationLongClicker() {
@@ -5917,6 +5922,12 @@
if (row.isDark()) {
return false;
}
+ if (row.areGutsExposed()) {
+ closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */,
+ true /* resetMenu */);
+ return false;
+ }
bindGuts(row, item);
NotificationGuts guts = row.getGuts();
@@ -5926,12 +5937,6 @@
return false;
}
- // Already showing?
- if (guts.getVisibility() == View.VISIBLE) {
- dismissPopups(x, y);
- return false;
- }
-
mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS);
// ensure that it's laid but not visible until actually laid out
@@ -5945,8 +5950,9 @@
+ "window");
return;
}
- dismissPopups(-1 /* x */, -1 /* y */, false /* resetMenu */,
- false /* animate */);
+ closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
guts.setVisibility(View.VISIBLE);
final double horz = Math.max(guts.getWidth() - x, x);
final double vert = Math.max(guts.getHeight() - y, y);
@@ -5986,20 +5992,23 @@
return mNotificationGutsExposed;
}
- public void dismissPopups() {
- dismissPopups(-1 /* x */, -1 /* y */, true /* resetMenu */, false /* animate */);
- }
-
- private void dismissPopups(int x, int y) {
- dismissPopups(x, y, true /* resetMenu */, false /* animate */);
- }
-
- public void dismissPopups(int x, int y, boolean resetMenu, boolean animate) {
+ /**
+ * Closes guts or notification menus that might be visible and saves any changes.
+ *
+ * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
+ * @param force true if guts should be closed regardless of state (used for snooze only).
+ * @param removeControls true if controls (e.g. info) should be closed.
+ * @param x if closed based on touch location, this is the x touch location.
+ * @param y if closed based on touch location, this is the y touch location.
+ * @param resetMenu if any notification menus that might be revealed should be closed.
+ */
+ public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
+ int x, int y, boolean resetMenu) {
if (mNotificationGutsExposed != null) {
- mNotificationGutsExposed.closeControls(x, y, true /* save */);
+ mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
}
if (resetMenu) {
- mStackScroller.resetExposedMenuView(animate, true /* force */);
+ mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
}
}
@@ -6528,7 +6537,8 @@
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
- dismissPopups();
+ closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
}
}
updateVisibleToUser();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 1a09d75..26e007c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -37,6 +37,7 @@
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.ActionMode;
+import android.view.InputDevice;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -84,6 +85,8 @@
private ActionMode mFloatingActionMode;
private FloatingToolbar mFloatingToolbar;
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
+ private boolean mTouchCancelled;
+ private boolean mTouchActive;
public StatusBarWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -239,10 +242,20 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN
- && mNotificationPanel.isFullyCollapsed()) {
+ boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
+ if (isDown && mNotificationPanel.isFullyCollapsed()) {
mNotificationPanel.startExpandLatencyTracking();
}
+ if (isDown) {
+ mTouchActive = true;
+ mTouchCancelled = false;
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_UP
+ || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ mTouchActive = false;
+ }
+ if (mTouchCancelled) {
+ return false;
+ }
mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == VISIBLE) {
// Disallow new pointers while the brightness mirror is visible. This is so that you
@@ -252,7 +265,7 @@
return false;
}
}
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ if (isDown) {
mStackScrollLayout.closeControlsIfOutsideTouch(ev);
}
if (mService.isDozing()) {
@@ -349,6 +362,18 @@
}
}
+ public void cancelCurrentTouch() {
+ if (mTouchActive) {
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ dispatchTouchEvent(event);
+ event.recycle();
+ mTouchCancelled = true;
+ }
+ }
+
public class LayoutParams extends FrameLayout.LayoutParams {
public boolean ignoreRightInset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 15fcb38..5f83e3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -75,6 +75,7 @@
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.FakeShadowView;
@@ -234,6 +235,7 @@
private NotificationMenuRowPlugin mCurrMenuRow;
private View mTranslatingParentView;
private View mMenuExposedView;
+ boolean mCheckForLeavebehind;
/**
* Should in this touch motion only be scrolling allowed? It's true when the scroller was
@@ -1310,6 +1312,22 @@
&& !mDisallowDismissInThisMotion) {
horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
}
+
+ // Check if we need to clear any snooze leavebehinds
+ NotificationGuts guts = mStatusBar.getExposedGuts();
+ if (guts != null && !isTouchInView(ev, guts)
+ && guts.getGutsContent() instanceof NotificationSnooze) {
+ NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
+ if ((ns.isExpanded() && isCancelOrUp)
+ || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
+ // If the leavebehind is expanded we clear it on the next up event, otherwise we
+ // clear it on the next non-horizontal swipe or expand event.
+ checkSnoozeLeavebehind();
+ }
+ }
+ if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
+ mCheckForLeavebehind = true;
+ }
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
}
@@ -1429,6 +1447,8 @@
// existing overScroll, we have to scroll the view
customOverScrollBy((int) scrollAmount, mOwnScrollY,
range, getHeight() / 2);
+ // If we're scrolling, leavebehinds should be dismissed
+ checkSnoozeLeavebehind();
}
}
break;
@@ -2458,6 +2478,18 @@
&& !mDisallowDismissInThisMotion) {
swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
}
+ // Check if we need to clear any snooze leavebehinds
+ boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
+ NotificationGuts guts = mStatusBar.getExposedGuts();
+ if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt
+ && !scrollWantsIt) {
+ mCheckForLeavebehind = false;
+ mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
+ false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ }
+ if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
+ mCheckForLeavebehind = true;
+ }
return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
}
@@ -3287,13 +3319,27 @@
return Math.max(mMaxLayoutHeight - mContentHeight, 0);
}
+ public void checkSnoozeLeavebehind() {
+ if (mCheckForLeavebehind) {
+ mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
+ false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mCheckForLeavebehind = false;
+ }
+ }
+
+ public void resetCheckSnoozeLeavebehind() {
+ mCheckForLeavebehind = true;
+ }
+
public void onExpansionStarted() {
mIsExpansionChanging = true;
mAmbientState.setExpansionChanging(true);
+ checkSnoozeLeavebehind();
}
public void onExpansionStopped() {
mIsExpansionChanging = false;
+ resetCheckSnoozeLeavebehind();
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
setOwnScrollY(0);
@@ -4292,6 +4338,8 @@
// of the panel early.
handleChildDismissed(view);
}
+ mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
+ false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
handleMenuCoveredOrDismissed();
}
@@ -4374,29 +4422,19 @@
public void closeControlsIfOutsideTouch(MotionEvent ev) {
NotificationGuts guts = mStatusBar.getExposedGuts();
View view = null;
- int height = 0;
- if (guts != null) {
- // Checking guts
+ if (guts != null && !guts.getGutsContent().isLeavebehind()) {
+ // Only close visible guts if they're not a leavebehind.
view = guts;
- height = guts.getActualHeight();
} else if (mCurrMenuRow != null && mCurrMenuRow.isMenuVisible()
&& mTranslatingParentView != null) {
// Checking menu
view = mTranslatingParentView;
- height = ((ExpandableView) mTranslatingParentView).getActualHeight();
}
- if (view != null) {
- final int rx = (int) ev.getRawX();
- final int ry = (int) ev.getRawY();
-
- view.getLocationOnScreen(mTempInt2);
- final int x = mTempInt2[0];
- final int y = mTempInt2[1];
- Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
- if (!rect.contains(rx, ry)) {
- // Touch was outside visible guts / meny notification, close what's visible
- mStatusBar.dismissPopups(-1, -1, true /* resetMenu */, true /* animate */);
- }
+ if (view != null && !isTouchInView(ev, view)) {
+ // Touch was outside visible guts / menu notification, close what's visible
+ mStatusBar.closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ resetExposedMenuView(true /* animate */, true /* force */);
}
}
@@ -4420,6 +4458,23 @@
}
}
+ private boolean isTouchInView(MotionEvent ev, View view) {
+ if (view == null) {
+ return false;
+ }
+ final int height = (view instanceof ExpandableView)
+ ? ((ExpandableView) view).getActualHeight()
+ : view.getHeight();
+ final int rx = (int) ev.getRawX();
+ final int ry = (int) ev.getRawY();
+ view.getLocationOnScreen(mTempInt2);
+ final int x = mTempInt2[0];
+ final int y = mTempInt2[1];
+ Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
+ boolean ret = rect.contains(rx, ry);
+ return ret;
+ }
+
private void updateContinuousShadowDrawing() {
boolean continuousShadowUpdate = mAnimationRunning
|| !mAmbientState.getDraggedViews().isEmpty();
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index cd85a76..ae8afe4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -30,6 +30,7 @@
public static String SCREENSHOTS = "SCN";
public static String GENERAL = "GEN";
public static String STORAGE = "DSK";
+ public static String TVPIP = "TPP";
@VisibleForTesting
static void createAll(Context context) {
@@ -55,6 +56,15 @@
? NotificationManager.IMPORTANCE_DEFAULT
: NotificationManager.IMPORTANCE_LOW)
));
+ if (isTv(context)) {
+ // TV specific notification channel for TV PIP controls.
+ // Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest
+ // priority, so it can be shown in all times.
+ nm.createNotificationChannel(new NotificationChannel(
+ TVPIP,
+ context.getString(R.string.notification_channel_tv_pip),
+ NotificationManager.IMPORTANCE_MAX));
+ }
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 2b14b31..0531ec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -497,7 +497,7 @@
mNotificationChannel.getImportance(), mSbn, null, null, null,
null, null);
- mNotificationInfo.handleCloseControls(true);
+ mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -511,7 +511,7 @@
mNotificationChannel.getImportance(), mSbn, null, null, null,
null, null);
- mNotificationInfo.handleCloseControls(true);
+ mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -571,7 +571,7 @@
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
mNotificationChannel.getImportance(), mSbn, null, null, null,
null, Collections.singleton(TEST_PACKAGE_NAME));
- mNotificationInfo.handleCloseControls(true);
+ mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -586,7 +586,7 @@
Switch enabledSwitch = mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
- mNotificationInfo.handleCloseControls(true);
+ mNotificationInfo.handleCloseControls(true, false);
ArgumentCaptor<NotificationChannel> updated =
ArgumentCaptor.forClass(NotificationChannel.class);
@@ -606,7 +606,7 @@
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
- mNotificationInfo.handleCloseControls(false);
+ mNotificationInfo.handleCloseControls(false, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
@@ -623,7 +623,7 @@
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
- mNotificationInfo.handleCloseControls(true);
+ mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
@@ -641,7 +641,7 @@
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
- mNotificationInfo.handleCloseControls(true);
+ mNotificationInfo.handleCloseControls(true, false);
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 53b3fe9..2f6b7e6 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -212,6 +212,10 @@
// Package: com.android.systemui
NOTE_LOGOUT_USER = 1011;
+ // Notify the user that a TV PIP is running.
+ // Package: com.android.systemui
+ NOTE_TV_PIP = 1100;
+
// Communicate to the user about remote bugreports.
// Package: android
NOTE_REMOTE_BUGREPORT = 678432343;
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index 05ad161..238bf0f 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -2895,6 +2895,7 @@
mAllocationArray[0] = createTyped(rs, t, usage);
if ((usage & USAGE_IO_INPUT) != 0) {
if (numAlloc > MAX_NUMBER_IO_INPUT_ALLOC) {
+ mAllocationArray[0].destroy();
throw new RSIllegalArgumentException("Exceeds the max number of Allocations allowed: " +
MAX_NUMBER_IO_INPUT_ALLOC);
}
diff --git a/rs/java/android/renderscript/ScriptIntrinsicLUT.java b/rs/java/android/renderscript/ScriptIntrinsicLUT.java
index 69ff64a..e90462d 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicLUT.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicLUT.java
@@ -56,6 +56,10 @@
}
+ public void destroy() {
+ mTables.destroy();
+ super.destroy();
+ }
private void validate(int index, int value) {
if (index < 0 || index > 255) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 9081746..e3398c9 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -118,7 +118,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- mUi.hideAll();
+ mUi.hideAll(null);
}
}
};
@@ -470,9 +470,9 @@
}
@Override
- public int startSession(IBinder activityToken, IBinder windowToken, IBinder appCallback,
- AutofillId autofillId, Rect bounds, AutofillValue value, int userId,
- boolean hasCallback, int flags, String packageName) {
+ public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
+ Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
+ String packageName) {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
@@ -489,8 +489,8 @@
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
- return service.startSessionLocked(activityToken, getCallingUid(), windowToken,
- appCallback, autofillId, bounds, value, hasCallback, flags, packageName);
+ return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
+ autofillId, bounds, value, hasCallback, flags, packageName);
}
}
@@ -528,19 +528,6 @@
}
@Override
- public void setWindow(int sessionId, IBinder windowToken) throws RemoteException {
- windowToken = Preconditions.checkNotNull(windowToken, "windowToken");
-
- synchronized (mLock) {
- final AutofillManagerServiceImpl service = mServicesCache.get(
- UserHandle.getCallingUserId());
- if (service != null) {
- service.setWindow(sessionId, getCallingUid(), windowToken);
- }
- }
- }
-
- @Override
public void updateSession(int sessionId, AutofillId id, Rect bounds,
AutofillValue value, int action, int flags, int userId) {
synchronized (mLock) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 3ab8b8d..e315f9d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -263,7 +263,7 @@
}
}
- int startSessionLocked(@NonNull IBinder activityToken, int uid, @Nullable IBinder windowToken,
+ int startSessionLocked(@NonNull IBinder activityToken, int uid,
@NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
@NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
int flags, @NonNull String packageName) {
@@ -274,8 +274,8 @@
// Occasionally clean up abandoned sessions
pruneAbandonedSessionsLocked();
- final Session newSession = createSessionByTokenLocked(activityToken, uid, windowToken,
- appCallbackToken, hasCallback, flags, packageName);
+ final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
+ hasCallback, flags, packageName);
if (newSession == null) {
return NO_SESSION;
}
@@ -359,8 +359,8 @@
}
private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
- @Nullable IBinder windowToken, @NonNull IBinder appCallbackToken, boolean hasCallback,
- int flags, @NonNull String packageName) {
+ @NonNull IBinder appCallbackToken, boolean hasCallback, int flags,
+ @NonNull String packageName) {
// use random ids so that one app cannot know that another app creates sessions
int sessionId;
int tries = 0;
@@ -375,7 +375,7 @@
} while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
- sessionId, uid, activityToken, windowToken, appCallbackToken, hasCallback,
+ sessionId, uid, activityToken, appCallbackToken, hasCallback,
mInfo.getServiceInfo().getComponentName(), packageName);
mSessions.put(newSession.id, newSession);
@@ -402,24 +402,6 @@
}
}
- /**
- * Set the window the UI should get attached to
- *
- * @param sessionId The id of the session to restore
- * @param uid UID of the process that tries to restore the session
- * @param windowToken The window the activity is now in
- */
- boolean setWindow(int sessionId, int uid, @NonNull IBinder windowToken) {
- final Session session = mSessions.get(sessionId);
-
- if (session == null || uid != session.uid) {
- return false;
- } else {
- session.switchWindow(windowToken);
- return true;
- }
- }
-
void updateSessionLocked(int sessionId, int uid, AutofillId autofillId, Rect virtualBounds,
AutofillValue value, int action, int flags) {
final Session session = mSessions.get(sessionId);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a98821d..3f78fb8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -119,9 +119,6 @@
@GuardedBy("mLock")
@NonNull private IBinder mActivityToken;
- @GuardedBy("mLock")
- @NonNull private IBinder mWindowToken;
-
/** Package name of the app that is auto-filled */
@NonNull private final String mPackageName;
@@ -341,7 +338,7 @@
Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
@NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
@NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
- @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback,
+ @NonNull IBinder client, boolean hasCallback,
@NonNull ComponentName componentName, @NonNull String packageName) {
id = sessionId;
this.uid = uid;
@@ -351,7 +348,6 @@
mHandlerCaller = handlerCaller;
mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
mActivityToken = activityToken;
- mWindowToken = windowToken;
mHasCallback = hasCallback;
mPackageName = packageName;
mClient = IAutoFillManagerClient.Stub.asInterface(client);
@@ -369,23 +365,6 @@
}
/**
- * Sets new window for this session.
- *
- * @param newWindow The window the Ui should be attached to. Can be {@code null} if no
- * further UI is needed.
- */
- void switchWindow(@NonNull IBinder newWindow) {
- synchronized (mLock) {
- if (mDestroyed) {
- Slog.w(TAG, "Call to Session#switchWindow() rejected - session: "
- + id + " destroyed");
- return;
- }
- mWindowToken = newWindow;
- }
- }
-
- /**
* Sets new activity and client for this session.
*
* @param newActivity The token of the new activity
@@ -419,7 +398,7 @@
}
if (response == null) {
if ((requestFlags & FLAG_MANUAL_REQUEST) != 0) {
- getUiForShowing().showError(R.string.autofill_error_cannot_autofill);
+ getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
}
// Nothing to be done, but need to notify client.
notifyUnavailableToClient();
@@ -469,7 +448,7 @@
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
mMetricsLogger.write(log);
- getUiForShowing().showError(message);
+ getUiForShowing().showError(message, this);
removeSelf();
}
@@ -516,7 +495,7 @@
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
mMetricsLogger.write(log);
- getUiForShowing().showError(message);
+ getUiForShowing().showError(message, this);
removeSelf();
}
@@ -627,8 +606,8 @@
if (id.equals(mCurrentViewId)) {
try {
final ViewState view = mViewStates.get(id);
- mClient.requestShowFillUi(this.id, mWindowToken, id, width, height,
- view.getVirtualBounds(), presenter);
+ mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(),
+ presenter);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting to show fill UI", e);
}
@@ -648,7 +627,7 @@
// NOTE: We allow this call in a destroyed state as the UI is
// asked to go away after we get destroyed, so let it do that.
try {
- mClient.requestHideFillUi(this.id, mWindowToken, id);
+ mClient.requestHideFillUi(this.id, id);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting to hide fill UI", e);
}
@@ -706,7 +685,7 @@
// refactor auth to support partitions; if it doesn't, we need to
// investigate it further (it can be reproduced by running
// LoginActivityTest.testFillResponseAuthServiceHasNoData())
- mUi.hideAll();
+ mUi.hideAll(this);
}
processResponseLocked(response);
} else {
@@ -851,7 +830,8 @@
}
if (atLeastOneChanged) {
mService.setSaveShown();
- getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName);
+ getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName,
+ this);
mIsSaving = true;
return false;
@@ -1062,9 +1042,9 @@
//..and the UI
if (value.isText()) {
- getUiForShowing().filterFillUi(value.getTextValue().toString());
+ getUiForShowing().filterFillUi(value.getTextValue().toString(), this);
} else {
- getUiForShowing().filterFillUi(null);
+ getUiForShowing().filterFillUi(null, this);
}
}
break;
@@ -1076,7 +1056,7 @@
// Remove the UI if the ViewState has changed.
if (mCurrentViewId != viewState.id) {
- mUi.hideFillUi(mCurrentViewId != null ? mCurrentViewId : null);
+ mUi.hideFillUi(this);
mCurrentViewId = viewState.id;
}
@@ -1086,7 +1066,7 @@
case ACTION_VIEW_EXITED:
if (mCurrentViewId == viewState.id) {
if (sVerbose) Slog.d(TAG, "Exiting view " + id);
- mUi.hideFillUi(viewState.id);
+ mUi.hideFillUi(this);
mCurrentViewId = null;
}
break;
@@ -1123,7 +1103,7 @@
filterText = value.getTextValue().toString();
}
- getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
+ getUiForShowing().showFillUi(filledId, response, filterText, mPackageName, this);
}
boolean isDestroyed() {
@@ -1142,10 +1122,9 @@
synchronized (mLock) {
if (!mHasCallback) return;
try {
- mClient.notifyNoFillUi(id, mWindowToken, mCurrentViewId);
+ mClient.notifyNoFillUi(id, mCurrentViewId);
} catch (RemoteException e) {
- Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken
- + " id=" + mCurrentViewId, e);
+ Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
}
}
}
@@ -1413,8 +1392,7 @@
}
try {
if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
- mClient.autofill(id, mWindowToken, dataset.getFieldIds(),
- dataset.getFieldValues());
+ mClient.autofill(id, dataset.getFieldIds(), dataset.getFieldValues());
setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED);
} catch (RemoteException e) {
Slog.w(TAG, "Error autofilling activity: " + e);
@@ -1434,8 +1412,8 @@
return;
}
mRemoteFillService.destroy();
- mUi.hideAll();
- mUi.setCallback(null);
+ mUi.hideAll(this);
+ mUi.clearCallback(this);
mDestroyed = true;
mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 7428460..9eaabfe2 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -74,31 +74,43 @@
mContext = context;
}
- public void setCallback(@Nullable AutoFillUiCallback callback) {
+ public void setCallback(@NonNull AutoFillUiCallback callback) {
mHandler.post(() -> {
if (mCallback != callback) {
- hideAllUiThread();
+ if (mCallback != null) {
+ hideAllUiThread(mCallback);
+ }
+
mCallback = callback;
}
});
}
+ public void clearCallback(@NonNull AutoFillUiCallback callback) {
+ mHandler.post(() -> {
+ if (mCallback == callback) {
+ hideAllUiThread(callback);
+ mCallback = null;
+ }
+ });
+ }
+
/**
* Displays an error message to the user.
*/
- public void showError(int resId) {
- showError(mContext.getString(resId));
+ public void showError(int resId, @NonNull AutoFillUiCallback callback) {
+ showError(mContext.getString(resId), callback);
}
/**
* Displays an error message to the user.
*/
- public void showError(@Nullable CharSequence message) {
+ public void showError(@Nullable CharSequence message, @NonNull AutoFillUiCallback callback) {
mHandler.post(() -> {
- if (!hasCallback()) {
+ if (mCallback != callback) {
return;
}
- hideAllUiThread();
+ hideAllUiThread(callback);
if (!TextUtils.isEmpty(message)) {
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
}
@@ -108,8 +120,8 @@
/**
* Hides the fill UI.
*/
- public void hideFillUi(AutofillId id) {
- mHandler.post(this::hideFillUiUiThread);
+ public void hideFillUi(@NonNull AutoFillUiCallback callback) {
+ mHandler.post(() -> hideFillUiUiThread(callback));
}
/**
@@ -117,12 +129,12 @@
*
* @param filterText The filter prefix.
*/
- public void filterFillUi(@Nullable String filterText) {
+ public void filterFillUi(@Nullable String filterText, @NonNull AutoFillUiCallback callback) {
mHandler.post(() -> {
- if (!hasCallback()) {
+ if (callback != mCallback) {
return;
}
- hideSaveUiUiThread();
+ hideSaveUiUiThread(callback);
if (mFillUi != null) {
mFillUi.setFilterText(filterText);
}
@@ -136,9 +148,11 @@
* @param response the current fill response
* @param filterText text of the view to be filled
* @param packageName package name of the activity that is filled
+ * @param callback Identifier for the caller
*/
public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
- @Nullable String filterText, @NonNull String packageName) {
+ @Nullable String filterText, @NonNull String packageName,
+ @NonNull AutoFillUiCallback callback) {
if (sDebug) {
Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + filterText);
}
@@ -150,16 +164,16 @@
response.getDatasets() == null ? 0 : response.getDatasets().size());
mHandler.post(() -> {
- if (!hasCallback()) {
+ if (callback != mCallback) {
return;
}
- hideAllUiThread();
+ hideAllUiThread(callback);
mFillUi = new FillUi(mContext, response, focusedId,
filterText, new FillUi.Callback() {
@Override
public void onResponsePicked(FillResponse response) {
log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL);
- hideFillUiUiThread();
+ hideFillUiUiThread(callback);
if (mCallback != null) {
mCallback.authenticate(response.getRequestId(),
response.getAuthentication(), response.getClientState());
@@ -169,7 +183,7 @@
@Override
public void onDatasetPicked(Dataset dataset) {
log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
- hideFillUiUiThread();
+ hideFillUiUiThread(callback);
if (mCallback != null) {
mCallback.fill(response.getRequestId(), dataset);
}
@@ -178,7 +192,7 @@
@Override
public void onCanceled() {
log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
- hideFillUiUiThread();
+ hideFillUiUiThread(callback);
}
@Override
@@ -218,7 +232,7 @@
* Shows the UI asking the user to save for autofill.
*/
public void showSaveUi(@NonNull CharSequence providerLabel, @NonNull SaveInfo info,
- @NonNull String packageName) {
+ @NonNull String packageName, @NonNull AutoFillUiCallback callback) {
int numIds = 0;
numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
@@ -228,16 +242,16 @@
MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
mHandler.post(() -> {
- if (!hasCallback()) {
+ if (callback != mCallback) {
return;
}
- hideAllUiThread();
+ hideAllUiThread(callback);
mSaveUi = new SaveUi(mContext, providerLabel, info,
new SaveUi.OnSaveListener() {
@Override
public void onSave() {
log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
- hideSaveUiUiThread();
+ hideSaveUiUiThread(callback);
if (mCallback != null) {
mCallback.save();
}
@@ -246,7 +260,7 @@
@Override
public void onCancel(IntentSender listener) {
log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
- hideSaveUiUiThread();
+ hideSaveUiUiThread(callback);
if (listener != null) {
try {
listener.sendIntent(mContext, 0, null, null, null);
@@ -278,8 +292,8 @@
/**
* Hides all UI affordances.
*/
- public void hideAll() {
- mHandler.post(this::hideAllUiThread);
+ public void hideAll(@Nullable AutoFillUiCallback callback) {
+ mHandler.post(() -> hideAllUiThread(callback));
}
public void dump(PrintWriter pw) {
@@ -301,28 +315,24 @@
}
@android.annotation.UiThread
- private void hideFillUiUiThread() {
- if (mFillUi != null) {
+ private void hideFillUiUiThread(@Nullable AutoFillUiCallback callback) {
+ if (mFillUi != null && (callback == null || callback == mCallback)) {
mFillUi.destroy();
mFillUi = null;
}
}
@android.annotation.UiThread
- private void hideSaveUiUiThread() {
- if (mSaveUi != null) {
+ private void hideSaveUiUiThread(@Nullable AutoFillUiCallback callback) {
+ if (mSaveUi != null && (callback == null || callback == mCallback)) {
mSaveUi.destroy();
mSaveUi = null;
}
}
@android.annotation.UiThread
- private void hideAllUiThread() {
- hideFillUiUiThread();
- hideSaveUiUiThread();
- }
-
- private boolean hasCallback() {
- return mCallback != null;
+ private void hideAllUiThread(@Nullable AutoFillUiCallback callback) {
+ hideFillUiUiThread(callback);
+ hideSaveUiUiThread(callback);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 268adc5..b5f8a09 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6945,7 +6945,7 @@
}
// Check whether the next backup agent is in this process...
- if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.uid) {
+ if (!badApp && mBackupTarget != null && mBackupTarget.app == app) {
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
"New app is backup target, launching agent for " + app);
notifyPackageUse(mBackupTarget.appInfo.packageName,
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index ba2c0cd..01fbdd4 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -249,6 +249,8 @@
// Last configuration reported to the activity in the client process.
private MergedConfiguration mLastReportedConfiguration;
private int mLastReportedDisplayId;
+ private boolean mLastReportedMultiWindowMode;
+ private boolean mLastReportedPictureInPictureMode;
CompatibilityInfo compat;// last used compatibility mode
ActivityRecord resultTo; // who started this entry, so will get our reply
final String resultWho; // additional identifier for use by resultTo.
@@ -289,10 +291,6 @@
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
boolean immersive; // immersive mode (don't interrupt if possible)
boolean forceNewConfig; // force re-create with new config next time
- private boolean mInMultiWindowMode; // whether or not this activity is currently in multi-window
- // mode (default false)
- private boolean mInPictureInPictureMode; // whether or not this activity is currently in
- // picture-in-picture mode (default false)
boolean supportsPictureInPictureWhilePausing; // This flag is set by the system to indicate
// that the activity can enter picture in picture while pausing (ie. only when another
// task is brought to front or started)
@@ -535,6 +533,8 @@
}
if (info != null) {
pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
+ pw.println(prefix + "mLastReportedMultiWindowMode=" + mLastReportedMultiWindowMode
+ + " mLastReportedPictureInPictureMode=" + mLastReportedPictureInPictureMode);
if (info.supportsPictureInPicture()) {
pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
pw.println(prefix + "supportsPictureInPictureWhilePausing: "
@@ -637,15 +637,15 @@
// An activity is considered to be in multi-window mode if its task isn't fullscreen.
final boolean inMultiWindowMode = !task.mFullscreen;
- if (inMultiWindowMode != mInMultiWindowMode) {
- mInMultiWindowMode = inMultiWindowMode;
+ if (inMultiWindowMode != mLastReportedMultiWindowMode) {
+ mLastReportedMultiWindowMode = inMultiWindowMode;
scheduleMultiWindowModeChanged(getConfiguration());
}
}
private void scheduleMultiWindowModeChanged(Configuration overrideConfig) {
try {
- app.thread.scheduleMultiWindowModeChanged(appToken, mInMultiWindowMode,
+ app.thread.scheduleMultiWindowModeChanged(appToken, mLastReportedMultiWindowMode,
overrideConfig);
} catch (Exception e) {
// If process died, I don't care.
@@ -659,11 +659,11 @@
final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) &&
(targetStackBounds != null);
- if (inPictureInPictureMode != mInPictureInPictureMode) {
+ if (inPictureInPictureMode != mLastReportedPictureInPictureMode) {
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order
- mInPictureInPictureMode = inPictureInPictureMode;
- mInMultiWindowMode = inPictureInPictureMode;
+ mLastReportedPictureInPictureMode = inPictureInPictureMode;
+ mLastReportedMultiWindowMode = inPictureInPictureMode;
final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
targetStackBounds, null);
schedulePictureInPictureModeChanged(newConfig);
@@ -673,7 +673,8 @@
private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
try {
- app.thread.schedulePictureInPictureModeChanged(appToken, mInPictureInPictureMode,
+ app.thread.schedulePictureInPictureModeChanged(appToken,
+ mLastReportedPictureInPictureMode,
overrideConfig);
} catch (Exception e) {
// If process died, no one cares.
@@ -940,6 +941,12 @@
task.addActivityToTop(this);
+ // When an activity is started directly into a split-screen fullscreen stack, we need to
+ // update the initial multi-window modes so that the callbacks are scheduled correctly when
+ // the user leaves that mode.
+ mLastReportedMultiWindowMode = !task.mFullscreen;
+ mLastReportedPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID);
+
onOverrideConfigurationSent();
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 983c975..7a46248 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -966,6 +966,14 @@
}
@Override
+ public void noteBleScanResult(WorkSource ws) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteBluetoothScanResultFromSourceLocked(ws);
+ }
+ }
+
+ @Override
public void noteWifiControllerActivity(WifiActivityEnergyInfo info) {
enforceCallingPermission();
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 9b984c1..6f5b028 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -16,21 +16,20 @@
package com.android.server.fingerprint;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
-import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.SynchronousUserSwitchObserver;
-import android.content.ComponentName;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -82,10 +81,10 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.LinkedList;
/**
* A service to manage multiple clients that want to access the fingerprint HAL API.
@@ -400,6 +399,10 @@
ClientMonitor client = mCurrentClient;
if (client != null && client.onRemoved(fingerId, groupId, remaining)) {
removeClient(client);
+ // When the last fingerprint of a group is removed, update the authenticator id
+ if (!hasEnrolledFingerprints(groupId)) {
+ updateActiveGroup(groupId, null);
+ }
}
if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
cleanupUnknownFingerprints();
@@ -446,6 +449,9 @@
ClientMonitor client = mCurrentClient;
if (client != null && client.onEnrollResult(fingerId, groupId, remaining)) {
removeClient(client);
+ // When enrollment finishes, update this group's authenticator id, as the HAL has
+ // already generated a new authenticator id when the new fingerprint is enrolled.
+ updateActiveGroup(groupId, null);
}
}
@@ -1382,7 +1388,8 @@
daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
mCurrentUserId = userId;
}
- mAuthenticatorIds.put(userId, daemon.getAuthenticatorId());
+ mAuthenticatorIds.put(userId,
+ hasEnrolledFingerprints(userId) ? daemon.getAuthenticatorId() : 0L);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to setActiveGroup():", e);
}
@@ -1436,7 +1443,6 @@
*/
public long getAuthenticatorId(String opPackageName) {
final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
- Long authenticatorId = mAuthenticatorIds.get(userId);
- return authenticatorId != null ? authenticatorId : 0;
+ return mAuthenticatorIds.getOrDefault(userId, 0L);
}
}
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
index 09e9433..aa780cc 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -80,19 +80,23 @@
}
public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[],
- String token) {
+ String token) throws ConnectionException {
throwIfCalledOnMainThread();
IInstantAppResolver target = null;
try {
- target = getRemoteInstanceLazy(token);
- return mGetEphemeralResolveInfoCaller
- .getEphemeralResolveInfoList(target, hashPrefix, token);
- } catch (RemoteException e) {
- } catch (InterruptedException | TimeoutException e) {
- if (target == null) {
- Slog.w(TAG, "[" + token + "] Timeout! Phase1 binding to instant app resolver");
- } else {
- Slog.w(TAG, "[" + token + "] Timeout! Phase1 resolving instant app");
+ try {
+ target = getRemoteInstanceLazy(token);
+ } catch (TimeoutException e) {
+ throw new ConnectionException(ConnectionException.FAILURE_BIND);
+ } catch (InterruptedException e) {
+ throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED);
+ }
+ try {
+ return mGetEphemeralResolveInfoCaller
+ .getEphemeralResolveInfoList(target, hashPrefix, token);
+ } catch (TimeoutException e) {
+ throw new ConnectionException(ConnectionException.FAILURE_BIND);
+ } catch (RemoteException ignore) {
}
} finally {
synchronized (mLock) {
@@ -104,7 +108,7 @@
public final void getInstantAppIntentFilterList(int hashPrefix[], String token,
String hostName, PhaseTwoCallback callback, Handler callbackHandler,
- final long startTime) {
+ final long startTime) throws ConnectionException {
final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) throws RemoteException {
@@ -122,14 +126,16 @@
try {
getRemoteInstanceLazy(token)
.getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback);
- } catch (RemoteException e) {
- } catch (InterruptedException | TimeoutException e) {
- Slog.w(TAG, "[" + token + "] Timeout! Phase2 binding to instant app resolver");
+ } catch (TimeoutException e) {
+ throw new ConnectionException(ConnectionException.FAILURE_BIND);
+ } catch (InterruptedException e) {
+ throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED);
+ } catch (RemoteException ignore) {
}
}
private IInstantAppResolver getRemoteInstanceLazy(String token)
- throws TimeoutException, InterruptedException {
+ throws ConnectionException, TimeoutException, InterruptedException {
synchronized (mLock) {
if (mRemoteInstance != null) {
return mRemoteInstance;
@@ -139,7 +145,7 @@
}
}
- private void waitForBind(String token) throws TimeoutException, InterruptedException {
+ private void waitForBindLocked(String token) throws TimeoutException, InterruptedException {
final long startMillis = SystemClock.uptimeMillis();
while (mIsBinding) {
if (mRemoteInstance != null) {
@@ -154,12 +160,13 @@
}
}
- private void bindLocked(String token) throws TimeoutException, InterruptedException {
+ private void bindLocked(String token)
+ throws ConnectionException, TimeoutException, InterruptedException {
if (DEBUG_EPHEMERAL && mIsBinding && mRemoteInstance == null) {
Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection");
}
try {
- waitForBind(token);
+ waitForBindLocked(token);
} catch (TimeoutException e) {
if (DEBUG_EPHEMERAL) {
Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding");
@@ -179,9 +186,10 @@
wasBound = mContext
.bindServiceAsUser(mIntent, mServiceConnection, flags, UserHandle.SYSTEM);
if (wasBound) {
- waitForBind(token);
+ waitForBindLocked(token);
} else {
Slog.w(TAG, "[" + token + "] Failed to bind to: " + mIntent);
+ throw new ConnectionException(ConnectionException.FAILURE_BIND);
}
} finally {
mIsBinding = wasBound && mRemoteInstance == null;
@@ -222,6 +230,17 @@
List<InstantAppResolveInfo> instantAppResolveInfoList, long startTime);
}
+ public static class ConnectionException extends Exception {
+ public static final int FAILURE_BIND = 1;
+ public static final int FAILURE_CALL = 2;
+ public static final int FAILURE_INTERRUPTED = 3;
+
+ public final int failure;
+ public ConnectionException(int _failure) {
+ failure = _failure;
+ }
+ }
+
private final class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index f3c9cbb..85be4a2 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -22,6 +22,7 @@
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_INSTANT_APP_RESOLUTION_STATUS;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -50,20 +51,37 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.server.pm.EphemeralResolverConnection.ConnectionException;
import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
+import java.util.concurrent.TimeoutException;
/** @hide */
public abstract class InstantAppResolver {
private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
private static final String TAG = "PackageManager";
- private static int RESOLUTION_SUCCESS = 0;
- private static int RESOLUTION_FAILURE = 1;
+ private static final int RESOLUTION_SUCCESS = 0;
+ private static final int RESOLUTION_FAILURE = 1;
+ /** Binding to the external service timed out */
+ private static final int RESOLUTION_BIND_TIMEOUT = 2;
+ /** The call to retrieve an instant application response timed out */
+ private static final int RESOLUTION_CALL_TIMEOUT = 3;
+
+ @IntDef(flag = true, prefix = { "RESOLUTION_" }, value = {
+ RESOLUTION_SUCCESS,
+ RESOLUTION_FAILURE,
+ RESOLUTION_BIND_TIMEOUT,
+ RESOLUTION_CALL_TIMEOUT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResolutionStatus {}
private static MetricsLogger sMetricsLogger;
private static MetricsLogger getLogger() {
@@ -78,29 +96,43 @@
final long startTime = System.currentTimeMillis();
final String token = UUID.randomUUID().toString();
if (DEBUG_EPHEMERAL) {
- Log.d(TAG, "[" + token + "] Resolving phase 1");
+ Log.d(TAG, "[" + token + "] Phase1; resolving");
}
final Intent intent = requestObj.origIntent;
final InstantAppDigest digest =
new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/);
final int[] shaPrefix = digest.getDigestPrefix();
- final List<InstantAppResolveInfo> instantAppResolveInfoList =
- connection.getInstantAppResolveInfoList(shaPrefix, token);
-
- if (instantAppResolveInfoList == null || instantAppResolveInfoList.size() == 0) {
- // No hash prefix match; there are no instant apps for this domain.
- if (DEBUG_EPHEMERAL) {
- Log.d(TAG, "[" + token + "] No results returned");
+ AuxiliaryResolveInfo resolveInfo = null;
+ @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
+ try {
+ final List<InstantAppResolveInfo> instantAppResolveInfoList =
+ connection.getInstantAppResolveInfoList(shaPrefix, token);
+ if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
+ resolveInfo = InstantAppResolver.filterInstantAppIntent(
+ instantAppResolveInfoList, intent, requestObj.resolvedType,
+ requestObj.userId, intent.getPackage(), digest, token);
}
- return null;
+ } catch (ConnectionException e) {
+ if (e.failure == ConnectionException.FAILURE_BIND) {
+ resolutionStatus = RESOLUTION_BIND_TIMEOUT;
+ } else if (e.failure == ConnectionException.FAILURE_CALL) {
+ resolutionStatus = RESOLUTION_CALL_TIMEOUT;
+ } else {
+ resolutionStatus = RESOLUTION_FAILURE;
+ }
}
- final AuxiliaryResolveInfo resolveInfo = InstantAppResolver.filterInstantAppIntent(
- instantAppResolveInfoList, intent, requestObj.resolvedType, requestObj.userId,
- intent.getPackage(), digest, token);
logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token,
- RESOLUTION_SUCCESS);
+ resolutionStatus);
if (DEBUG_EPHEMERAL && resolveInfo == null) {
- Log.d(TAG, "[" + token + "] No results matched");
+ if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) {
+ Log.d(TAG, "[" + token + "] Phase1; bind timed out");
+ } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) {
+ Log.d(TAG, "[" + token + "] Phase1; call timed out");
+ } else if (resolutionStatus != RESOLUTION_SUCCESS) {
+ Log.d(TAG, "[" + token + "] Phase1; service connection error");
+ } else {
+ Log.d(TAG, "[" + token + "] Phase1; No results matched");
+ }
}
return resolveInfo;
}
@@ -111,7 +143,7 @@
final long startTime = System.currentTimeMillis();
final String token = requestObj.responseObj.token;
if (DEBUG_EPHEMERAL) {
- Log.d(TAG, "[" + token + "] Resolving phase 2");
+ Log.d(TAG, "[" + token + "] Phase2; resolving");
}
final Intent intent = requestObj.origIntent;
final String hostName = intent.getData().getHost();
@@ -170,8 +202,24 @@
context.startActivity(installerIntent);
}
};
- connection.getInstantAppIntentFilterList(
- shaPrefix, token, hostName, callback, callbackHandler, startTime);
+ try {
+ connection.getInstantAppIntentFilterList(
+ shaPrefix, token, hostName, callback, callbackHandler, startTime);
+ } catch (ConnectionException e) {
+ @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
+ if (e.failure == ConnectionException.FAILURE_BIND) {
+ resolutionStatus = RESOLUTION_BIND_TIMEOUT;
+ }
+ logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token,
+ resolutionStatus);
+ if (DEBUG_EPHEMERAL) {
+ if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) {
+ Log.d(TAG, "[" + token + "] Phase2; bind timed out");
+ } else {
+ Log.d(TAG, "[" + token + "] Phase2; service connection error");
+ }
+ }
+ }
}
/**
@@ -322,7 +370,8 @@
return null;
}
- private static void logMetrics(int action, long startTime, String token, int status) {
+ private static void logMetrics(int action, long startTime, String token,
+ @ResolutionStatus int status) {
final LogMaker logMaker = new LogMaker(action)
.setType(MetricsProto.MetricsEvent.TYPE_ACTION)
.addTaggedData(FIELD_INSTANT_APP_RESOLUTION_DELAY_MS,
diff --git a/services/core/java/com/android/server/text/TextClassificationService.java b/services/core/java/com/android/server/text/TextClassificationService.java
deleted file mode 100644
index 9358238..0000000
--- a/services/core/java/com/android/server/text/TextClassificationService.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.text;
-
-import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.text.ITextClassificationService;
-import android.util.Slog;
-
-import com.android.server.SystemService;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-/**
- * Text classification service.
- * This is used to provide access to the text classification LSTM model file.
- */
-public class TextClassificationService extends ITextClassificationService.Stub {
-
- private static final String LOG_TAG = "TextClassificationService";
-
- public static final class Lifecycle extends SystemService {
-
- private TextClassificationService mService;
-
- public Lifecycle(Context context) {
- super(context);
- mService = new TextClassificationService();
- }
-
- @Override
- public void onStart() {
- try {
- publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mService);
- } catch (Throwable t) {
- // Starting this service is not critical to the running of this device and should
- // therefore not crash the device. If it fails, log the error and continue.
- Slog.e(LOG_TAG, "Could not start the TextClassificationService.", t);
- }
- }
- }
-
- @Override
- public synchronized ParcelFileDescriptor getModelFileFd() throws RemoteException {
- try {
- return ParcelFileDescriptor.open(
- new File("/etc/assistant/smart-selection.model"),
- ParcelFileDescriptor.MODE_READ_ONLY);
- } catch (Throwable t) {
- Slog.e(LOG_TAG, "Error retrieving an fd to the text classification model file.", t);
- throw new RemoteException(t.getMessage());
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 92d26cb..257f285 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -759,16 +759,25 @@
return token.asAppWindowToken();
}
- void addWindowToken(IBinder binder, WindowToken token) {
+ private void addWindowToken(IBinder binder, WindowToken token) {
final DisplayContent dc = mService.mRoot.getWindowTokenDisplay(token);
if (dc != null) {
// We currently don't support adding a window token to the display if the display
// already has the binder mapped to another token. If there is a use case for supporting
// this moving forward we will either need to merge the WindowTokens some how or have
// the binder map to a list of window tokens.
- throw new IllegalArgumentException("Can't map token=" + token + " to display=" + this
- + " already mapped to display=" + dc + " tokens=" + dc.mTokenMap);
+ throw new IllegalArgumentException("Can't map token=" + token + " to display="
+ + getName() + " already mapped to display=" + dc + " tokens=" + dc.mTokenMap);
}
+ if (binder == null) {
+ throw new IllegalArgumentException("Can't map token=" + token + " to display="
+ + getName() + " binder is null");
+ }
+ if (token == null) {
+ throw new IllegalArgumentException("Can't map null token to display="
+ + getName() + " binder=" + binder);
+ }
+
mTokenMap.put(binder, token);
if (token.asAppWindowToken() == null) {
@@ -1503,16 +1512,8 @@
return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom);
}
- /**
- * Returns the orientation that this display should be in factoring in its children containers.
- *
- * @param includeAppContainers True if then app containers (stacks, tasks, ...) should be
- * factored in when determining the orientation. If false only
- * non-app/system containers will be used to determine the returned
- * orientation.
- * @return The orientation the display should be in.
- */
- int getOrientation(boolean includeAppContainers) {
+ @Override
+ int getOrientation() {
final WindowManagerPolicy policy = mService.mPolicy;
if (mService.mDisplayFrozen) {
@@ -1541,14 +1542,8 @@
}
}
- // Top system windows are not requesting an orientation. Get orientation from app containers
- // if allowed. Otherwise, return the last orientation.
- return includeAppContainers ? mTaskStackContainers.getOrientation() : mLastOrientation;
- }
-
- @Override
- int getOrientation() {
- return getOrientation(true /* includeAppContainers */);
+ // Top system windows are not requesting an orientation. Start searching from apps.
+ return mTaskStackContainers.getOrientation();
}
void updateDisplayInfo() {
@@ -2499,7 +2494,8 @@
void startKeyguardExitOnNonAppWindows(boolean onWallpaper, boolean goingToShade) {
final WindowManagerPolicy policy = mService.mPolicy;
forAllWindows(w -> {
- if (w.mAppToken == null && policy.canBeHiddenByKeyguardLw(w)) {
+ if (w.mAppToken == null && policy.canBeHiddenByKeyguardLw(w)
+ && w.wouldBeVisibleIfPolicyIgnored() && !w.isVisible()) {
w.mWinAnimator.setAnimation(
policy.createHiddenByKeyguardExit(onWallpaper, goingToShade));
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a48397b..63cc9bf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1239,7 +1239,8 @@
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
- token = new WindowToken(this, attrs.token, type, false, displayContent,
+ final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
+ token = new WindowToken(this, binder, type, false, displayContent,
session.mCanAddInternalSystemWindow);
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
atoken = token.asAppWindowToken();
@@ -1310,7 +1311,7 @@
// It is not valid to use an app token with other system types; we will
// instead make a new token for it (as if null had been passed in for the token).
attrs.token = null;
- token = new WindowToken(this, null, type, false, displayContent,
+ token = new WindowToken(this, client.asBinder(), type, false, displayContent,
session.mCanAddInternalSystemWindow);
}
@@ -2373,7 +2374,7 @@
try {
synchronized(mWindowMap) {
config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded,
- displayId, true /* includeAppContainers */);
+ displayId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2383,13 +2384,13 @@
}
private Configuration updateOrientationFromAppTokensLocked(Configuration currentConfig,
- IBinder freezeThisOneIfNeeded, int displayId, boolean includeAppContainers) {
+ IBinder freezeThisOneIfNeeded, int displayId) {
if (!mDisplayReady) {
return null;
}
Configuration config = null;
- if (updateOrientationFromAppTokensLocked(false, displayId, includeAppContainers)) {
+ if (updateOrientationFromAppTokensLocked(false, displayId)) {
// If we changed the orientation but mOrientationChangeComplete is already true,
// we used seamless rotation, and we don't need to freeze the screen.
if (freezeThisOneIfNeeded != null && !mRoot.mOrientationChangeComplete) {
@@ -2427,11 +2428,6 @@
return config;
}
- boolean updateOrientationFromAppTokensLocked(boolean inTransaction, int displayId) {
- return updateOrientationFromAppTokensLocked(inTransaction, displayId,
- false /* includeAppContainers */);
- }
-
/**
* Determine the new desired orientation of the display, returning a non-null new Configuration
* if it has changed from the current orientation. IF TRUE IS RETURNED SOMEONE MUST CALL
@@ -2442,25 +2438,13 @@
* The orientation is computed from non-application windows first. If none of the
* non-application windows specify orientation, the orientation is computed from application
* tokens.
- *
- * @param inTransaction True if we are currently in a surface transaction.
- * @param displayId Id of the display to update orientation for.
- * @param includeAppContainers True if then app containers (stacks, tasks, ...) should be
- * factored in when determining the orientation. If false only
- * non-app/system containers will be used to determine the returned
- * orientation.
- * NOTE: Only call originating from activity manager are expected to
- * set this to true as it needs to synchronize several app states
- * like visibility with the update of display orientation.
- * @return True if the display orientation was updated.
* @see android.view.IWindowManager#updateOrientationFromAppTokens(Configuration, IBinder, int)
*/
- private boolean updateOrientationFromAppTokensLocked(boolean inTransaction, int displayId,
- boolean includeAppContainers) {
- final long ident = Binder.clearCallingIdentity();
+ boolean updateOrientationFromAppTokensLocked(boolean inTransaction, int displayId) {
+ long ident = Binder.clearCallingIdentity();
try {
final DisplayContent dc = mRoot.getDisplayContent(displayId);
- final int req = dc.getOrientation(includeAppContainers);
+ final int req = dc.getOrientation();
if (req != dc.getLastOrientation()) {
dc.setLastOrientation(req);
//send a message to Policy indicating orientation change to take
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 67516c1..25b6561 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1292,36 +1292,14 @@
@Override
boolean isVisible() {
- // TODO: The check for hiddenRequested is commented out below, because the window can still
- // be visible on screen when the flag is true. We would like the isVisible() method to
- // return an answer closer to if the window is truly visible (can't be an exact answer
- // without checking the surface state), so comment out the check for now so we can test to
- // see what problem it causes.
- // If it doesn't cause any issues, then we can remove just before we lock down the current
- // release (O) and also consolidate this method with #isVisibleUnchecked() and possibly
- // other methods like isVisibleNow().
- // If it does cause problems, then we can look if there are other ways to solve the problem.
- // If there isn't then uncomment and document here why it is needed.
- if (/*(mAppToken == null || !mAppToken.hiddenRequested) && */isVisibleUnchecked()
- // TODO: The window isn't considered visible when the token is hidden, however
- // uncommenting the check below breaks the visual transition from an app to the launcher
- // if the home buttons is pressed. Need to investigate an fix that issue before
- // uncommenting.
- /* && !mToken.hidden*/) {
- // Is this window visible? It is not visible if there is no surface, or we are in the
- // process of running an exit animation that will remove the surface, or its app token
- // has been hidden.
- return true;
- }
- return false;
+ return wouldBeVisibleIfPolicyIgnored() && mPolicyVisibility;
}
/**
- * Does the minimal check for visibility. Callers generally want to use one of the public
- * methods as they perform additional checks on the app token.
- * TODO: See if there are other places we can use this check below instead of duplicating...
+ * @return True if the window would be visible if we'd ignore policy visibility, false
+ * otherwise.
*/
- private boolean isVisibleUnchecked() {
+ boolean wouldBeVisibleIfPolicyIgnored() {
return mHasSurface && mPolicyVisibility && !isParentWindowHidden()
&& !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
}
@@ -1338,7 +1316,7 @@
// TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
boolean isWinVisibleLw() {
return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
- && isVisibleUnchecked();
+ && isVisible();
}
/**
@@ -1347,7 +1325,7 @@
*/
boolean isVisibleNow() {
return (!mToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
- && isVisibleUnchecked();
+ && isVisible();
}
/**
@@ -2063,7 +2041,7 @@
// If app died visible, apply a dim over the window to indicate that it's inactive
dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
} else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
- && dc != null && !mAnimatingExit && isVisibleUnchecked()) {
+ && dc != null && !mAnimatingExit && isVisible()) {
dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 978803d..0965f03 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -104,7 +104,6 @@
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
-import com.android.server.text.TextClassificationService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.tv.TvRemoteService;
@@ -1042,12 +1041,6 @@
traceEnd();
}
- if (!disableNonCoreServices) {
- traceBeginAndSlog("StartTextClassificationService");
- mSystemServiceManager.startService(TextClassificationService.Lifecycle.class);
- traceEnd();
- }
-
if (!disableNetwork) {
traceBeginAndSlog("StartNetworkScoreService");
try {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
index ace65a6..02f645a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
@@ -54,6 +54,7 @@
private static int OTHER_VIEW_ID = 0xCAB2;
private static int PARENT_VIEW_ID = 0xFED4;
private static int CHILD_VIEW_ID = 0xFEED;
+ private static int OTHER_CHILD_VIEW_ID = 0xACE2;
private static int MOCK_CONNECTION_ID = 1;
AccessibilityCache mAccessibilityCache;
@@ -482,6 +483,30 @@
}
@Test
+ public void addNode_whenNodeBeingReplacedIsOwnGrandparent_doesntCrash() {
+ AccessibilityNodeInfo parentNodeInfo =
+ getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
+ parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(CHILD_VIEW_ID, WINDOW_ID_1));
+ parentNodeInfo.addChild(getMockViewWithA11yAndWindowIds(OTHER_CHILD_VIEW_ID, WINDOW_ID_1));
+ AccessibilityNodeInfo childNodeInfo =
+ getNodeWithA11yAndWindowId(CHILD_VIEW_ID, WINDOW_ID_1);
+ childNodeInfo.setParent(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
+ childNodeInfo.addChild(getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1));
+
+ AccessibilityNodeInfo replacementParentNodeInfo =
+ getNodeWithA11yAndWindowId(PARENT_VIEW_ID, WINDOW_ID_1);
+ try {
+ mAccessibilityCache.add(parentNodeInfo);
+ mAccessibilityCache.add(childNodeInfo);
+ mAccessibilityCache.add(replacementParentNodeInfo);
+ } finally {
+ parentNodeInfo.recycle();
+ childNodeInfo.recycle();
+ replacementParentNodeInfo.recycle();
+ }
+ }
+
+ @Test
public void testCacheCriticalEventList_doesntLackEvents() {
for (int i = 0; i < 32; i++) {
int eventType = 1 << i;
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
index 47ced99..d9349ed 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
@@ -66,8 +66,7 @@
/** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
public static Task createTaskInStack(WindowManagerService service, TaskStack stack,
int userId) {
- final Task newTask = new Task(WindowTestUtils.sNextTaskId++, stack, userId, service, null,
- EMPTY, 0, false,
+ final Task newTask = new Task(sNextTaskId++, stack, userId, service, null, EMPTY, 0, false,
false, new ActivityManager.TaskDescription(), null);
stack.addTask(newTask, POSITION_TOP);
return newTask;
@@ -92,7 +91,7 @@
public static class TestAppWindowToken extends AppWindowToken {
TestAppWindowToken(DisplayContent dc) {
- super(dc.mService, null, false, dc, true /* fillsParent */,
+ super(dc.mService, new IApplicationToken.Stub() {}, false, dc, true /* fillsParent */,
null /* overrideConfig */, null /* bounds */);
}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/tests/net/java/android/net/NetworkStatsHistoryTest.java
similarity index 99%
rename from core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
rename to tests/net/java/android/net/NetworkStatsHistoryTest.java
index 9a08f41..e7b91b5 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
+++ b/tests/net/java/android/net/NetworkStatsHistoryTest.java
@@ -38,7 +38,7 @@
import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
-import com.android.frameworks.coretests.R;
+import com.android.frameworks.tests.net.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
similarity index 100%
rename from core/tests/coretests/src/android/net/NetworkStatsTest.java
rename to tests/net/java/android/net/NetworkStatsTest.java
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
similarity index 99%
rename from core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
rename to tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
index 7f13abc..978e5f5 100644
--- a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -30,7 +30,7 @@
import android.net.TrafficStats;
import android.test.AndroidTestCase;
-import com.android.frameworks.coretests.R;
+import com.android.frameworks.tests.net.R;
import java.io.File;
import java.io.FileOutputStream;
diff --git a/core/tests/coretests/res/raw/history_v1 b/tests/net/res/raw/history_v1
similarity index 100%
rename from core/tests/coretests/res/raw/history_v1
rename to tests/net/res/raw/history_v1
Binary files differ
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical b/tests/net/res/raw/xt_qtaguid_iface_fmt_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_iface_fmt_typical
rename to tests/net/res/raw/xt_qtaguid_iface_fmt_typical
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_iface_typical b/tests/net/res/raw/xt_qtaguid_iface_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_iface_typical
rename to tests/net/res/raw/xt_qtaguid_iface_typical
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_typical b/tests/net/res/raw/xt_qtaguid_typical
similarity index 100%
rename from core/tests/coretests/res/raw/xt_qtaguid_typical
rename to tests/net/res/raw/xt_qtaguid_typical