Merge changes I7c2c9411,Ibc72c84d,Ib3968644 into oc-dev
* changes:
No need to deal with windowTokens
Persistable accessibility ID from ContextWrappers
Check callbacks when operting on UI
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 f689d4a..37c287e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7431,7 +7431,7 @@
final int offsetX = (anchorBounds != null)
? anchorBounds.left - actualAnchorBounds.left : 0;
int offsetY = (anchorBounds != null)
- ? anchorBounds.top - actualAnchorBounds.top : 0;
+ ? anchorBounds.bottom - actualAnchorBounds.bottom : 0;
final boolean wasShowing;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2e56bcf..0041879 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2675,6 +2675,7 @@
private int mActionBarColor = COLOR_INVALID;
private int mBackgroundColor = COLOR_INVALID;
private int mForegroundColor = COLOR_INVALID;
+ private int mBackgroundColorHint = COLOR_INVALID;
/**
* Constructs a new Builder with the defaults:
@@ -3839,6 +3840,13 @@
backgroundColor);
mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(mContext,
backgroundColor);
+ if (backgroundColor != COLOR_DEFAULT
+ && (mBackgroundColorHint != COLOR_INVALID || isColorized())) {
+ mPrimaryTextColor = NotificationColorUtil.findAlphaToMeetContrast(
+ mPrimaryTextColor, backgroundColor, 4.5);
+ mSecondaryTextColor = NotificationColorUtil.findAlphaToMeetContrast(
+ mSecondaryTextColor, backgroundColor, 4.5);
+ }
} else {
double backLum = NotificationColorUtil.calculateLuminance(backgroundColor);
double textLum = NotificationColorUtil.calculateLuminance(mForegroundColor);
@@ -4662,10 +4670,26 @@
if (mCachedContrastColorIsFor == mN.color && mCachedContrastColor != COLOR_INVALID) {
return mCachedContrastColor;
}
- final int contrasted = NotificationColorUtil.resolveContrastColor(mContext, mN.color);
+ int color;
+ int background = mBackgroundColorHint;
+ if (mBackgroundColorHint == COLOR_INVALID) {
+ background = mContext.getColor(
+ com.android.internal.R.color.notification_material_background_color);
+ }
+ if (mN.color == COLOR_DEFAULT) {
+ ensureColors();
+ color = mSecondaryTextColor;
+ } else {
+ color = NotificationColorUtil.resolveContrastColor(mContext, mN.color,
+ background);
+ }
+ if (Color.alpha(color) < 255) {
+ // alpha doesn't go well for color filters, so let's blend it manually
+ color = NotificationColorUtil.compositeColors(color, background);
+ }
mCachedContrastColorIsFor = mN.color;
- return mCachedContrastColor = contrasted;
+ return mCachedContrastColor = color;
}
int resolveAmbientColor() {
@@ -4882,7 +4906,8 @@
if (isColorized()) {
return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : mN.color;
} else {
- return COLOR_DEFAULT;
+ return mBackgroundColorHint != COLOR_INVALID ? mBackgroundColorHint
+ : COLOR_DEFAULT;
}
}
@@ -4913,6 +4938,17 @@
mTextColorsAreForBackground = COLOR_INVALID;
ensureColors();
}
+
+ /**
+ * Sets the background color for this notification to be a different one then the default.
+ * This is mainly used to calculate contrast and won't necessarily be applied to the
+ * background.
+ *
+ * @hide
+ */
+ public void setBackgroundColorHint(int backgroundColor) {
+ mBackgroundColorHint = backgroundColor;
+ }
}
/**
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/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 7947620..d61fb97 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -42,8 +42,8 @@
import javax.crypto.Mac;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.USE_FINGERPRINT;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
+import static android.Manifest.permission.USE_FINGERPRINT;
/**
* A class that coordinates access to the fingerprint hardware.
@@ -910,6 +910,7 @@
} else if (mAuthenticationCallback != null) {
mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
getErrorString(errMsgId, vendorCode));
+ mAuthenticationCallback = null;
} else if (mRemovalCallback != null) {
mRemovalCallback.onRemovalError(mRemovalFingerprint, clientErrMsgId,
getErrorString(errMsgId, vendorCode));
@@ -930,12 +931,14 @@
final AuthenticationResult result =
new AuthenticationResult(mCryptoObject, fp, userId);
mAuthenticationCallback.onAuthenticationSucceeded(result);
+ mAuthenticationCallback = null;
}
}
private void sendAuthenticatedFailed() {
if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationFailed();
+ mAuthenticationCallback.onAuthenticationFailed();
+ mAuthenticationCallback = null;
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2e72939..6c74980 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6844,7 +6844,7 @@
if (isAutofillable() && isAttachedToWindow()) {
AutofillManager afm = getAutofillManager();
if (afm != null) {
- if (enter && hasWindowFocus() && isFocused()) {
+ if (enter && hasWindowFocus() && isFocused() && isVisibleToUser()) {
afm.notifyViewEntered(this);
} else if (!hasWindowFocus() || !isFocused()) {
afm.notifyViewExited(this);
@@ -20431,9 +20431,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/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 52c82a7..ecb25fe 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -98,7 +98,7 @@
* invoke the Browser application with a URL Intent rather than show it
* with a WebView. For example:
* <pre>
- * Uri uri = Uri.parse("http://www.example.com");
+ * Uri uri = Uri.parse("https://www.example.com");
* Intent intent = new Intent(Intent.ACTION_VIEW, uri);
* startActivity(intent);
* </pre>
@@ -116,7 +116,7 @@
* <pre>
* // Simplest usage: note that an exception will NOT be thrown
* // if there is an error loading this page (see below).
- * webview.loadUrl("http://slashdot.org/");
+ * webview.loadUrl("https://example.com/");
*
* // OR, you can also load from an HTML string:
* String summary = "<html><body>You scored <b>192</b> points.</body></html>";
@@ -175,7 +175,7 @@
* }
* });
*
- * webview.loadUrl("http://developer.android.com/");
+ * webview.loadUrl("https://developer.android.com/");
* </pre>
*
* <h3>Zoom</h3>
@@ -2705,7 +2705,7 @@
* <p>Example2: an IFRAME tag.
*
* <pre class="prettyprint">
- * <iframe src="http://example.com/login"/>
+ * <iframe src="https://example.com/login"/>
* </pre>
*
* <p>Would map to:
@@ -2714,7 +2714,7 @@
* int index = structure.addChildCount(1);
* ViewStructure iframe = structure.newChildFor(index);
* iframe.setHtmlInfo(child.newHtmlInfoBuilder("iframe")
- * .addAttribute("url", "http://example.com/login")
+ * .addAttribute("url", "https://example.com/login")
* .build());
* </pre>
*/
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java
index 2c97f8b..cd41f9e 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -286,6 +286,38 @@
}
/**
+ * Finds a suitable alpha such that there's enough contrast.
+ *
+ * @param color the color to start searching from.
+ * @param backgroundColor the color to ensure contrast against.
+ * @param minRatio the minimum contrast ratio required.
+ * @return the same color as {@param color} with potentially modified alpha to meet contrast
+ */
+ public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) {
+ int fg = color;
+ int bg = backgroundColor;
+ if (ColorUtilsFromCompat.calculateContrast(fg, bg) >= minRatio) {
+ return color;
+ }
+ int startAlpha = Color.alpha(color);
+ int r = Color.red(color);
+ int g = Color.green(color);
+ int b = Color.blue(color);
+
+ int low = startAlpha, high = 255;
+ for (int i = 0; i < 15 && high - low > 0; i++) {
+ final int alpha = (low + high) / 2;
+ fg = Color.argb(alpha, r, g, b);
+ if (ColorUtilsFromCompat.calculateContrast(fg, bg) > minRatio) {
+ high = alpha;
+ } else {
+ low = alpha;
+ }
+ }
+ return Color.argb(high, r, g, b);
+ }
+
+ /**
* Finds a suitable color such that there's enough contrast.
*
* @param color the color to start searching from.
@@ -373,19 +405,19 @@
* color for the Notification's action and header text.
*
* @param notificationColor the color of the notification or {@link Notification#COLOR_DEFAULT}
+ * @param backgroundColor the background color to ensure the contrast against.
* @return a color of the same hue with enough contrast against the backgrounds.
*/
- public static int resolveContrastColor(Context context, int notificationColor) {
+ public static int resolveContrastColor(Context context, int notificationColor,
+ int backgroundColor) {
final int resolvedColor = resolveColor(context, notificationColor);
final int actionBg = context.getColor(
com.android.internal.R.color.notification_action_list);
- final int notiBg = context.getColor(
- com.android.internal.R.color.notification_material_background_color);
int color = resolvedColor;
color = NotificationColorUtil.ensureLargeTextContrast(color, actionBg);
- color = NotificationColorUtil.ensureTextContrast(color, notiBg);
+ color = NotificationColorUtil.ensureTextContrast(color, backgroundColor);
if (color != resolvedColor) {
if (DEBUG){
@@ -394,7 +426,7 @@
+ " and %s (over background) by changing #%s to %s",
context.getPackageName(),
NotificationColorUtil.contrastChange(resolvedColor, color, actionBg),
- NotificationColorUtil.contrastChange(resolvedColor, color, notiBg),
+ NotificationColorUtil.contrastChange(resolvedColor, color, backgroundColor),
Integer.toHexString(resolvedColor), Integer.toHexString(color)));
}
}
@@ -502,6 +534,13 @@
}
/**
+ * Composite two potentially translucent colors over each other and returns the result.
+ */
+ public static int compositeColors(int foreground, int background) {
+ return ColorUtilsFromCompat.compositeColors(foreground, background);
+ }
+
+ /**
* Framework copy of functions needed from android.support.v4.graphics.ColorUtils.
*/
private static class ColorUtilsFromCompat {
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 4496a82..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
*/
@@ -1624,7 +1625,12 @@
/** Column definitions for the TV channels table. */
public static final class Channels implements BaseTvColumns {
- /** The content:// style URI for this table. */
+ /**
+ * The content:// style URI for this table.
+ *
+ * <p>SQL selection is not supported for {@link ContentResolver#query},
+ * {@link ContentResolver#update} and {@link ContentResolver#delete} operations.
+ */
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ PATH_CHANNEL);
@@ -2423,7 +2429,12 @@
*/
public static final class Programs implements BaseTvColumns, ProgramColumns {
- /** The content:// style URI for this table. */
+ /**
+ * The content:// style URI for this table.
+ *
+ * <p>SQL selection is not supported for {@link ContentResolver#query},
+ * {@link ContentResolver#update} and {@link ContentResolver#delete} operations.
+ */
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ PATH_PROGRAM);
@@ -2737,7 +2748,12 @@
*/
public static final class RecordedPrograms implements BaseTvColumns, ProgramColumns {
- /** The content:// style URI for this table. */
+ /**
+ * The content:// style URI for this table.
+ *
+ * <p>SQL selection is not supported for {@link ContentResolver#query},
+ * {@link ContentResolver#update} and {@link ContentResolver#delete} operations.
+ */
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ PATH_RECORDED_PROGRAM);
@@ -2858,7 +2874,12 @@
public static final class PreviewPrograms implements BaseTvColumns, ProgramColumns,
PreviewProgramColumns {
- /** The content:// style URI for this table. */
+ /**
+ * The content:// style URI for this table.
+ *
+ * <p>SQL selection is not supported for {@link ContentResolver#query},
+ * {@link ContentResolver#update} and {@link ContentResolver#delete} operations.
+ */
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ PATH_PREVIEW_PROGRAM);
@@ -2905,7 +2926,12 @@
public static final class WatchNextPrograms implements BaseTvColumns, ProgramColumns,
PreviewProgramColumns {
- /** The content:// style URI for this table. */
+ /**
+ * The content:// style URI for this table.
+ *
+ * <p>SQL selection is not supported for {@link ContentResolver#query},
+ * {@link ContentResolver#update} and {@link ContentResolver#delete} operations.
+ */
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ PATH_WATCH_NEXT_PROGRAM);
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/Android.mk b/packages/SystemUI/Android.mk
index 5ee0c64..2fd7e87 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -31,6 +31,7 @@
LOCAL_STATIC_ANDROID_LIBRARIES := \
SystemUIPluginLib \
+ android-support-v4 \
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
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/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index e34987b..4b614ed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -362,11 +362,17 @@
* Cancels any current transform animations.
*/
public void cancelTransformAnimation() {
+ cancelDimAnimationIfExists();
Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
- Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
Utilities.cancelAnimationWithoutCallbacks(mOutlineAnimator);
}
+ private void cancelDimAnimationIfExists() {
+ if (mDimAnimator != null) {
+ mDimAnimator.cancel();
+ }
+ }
+
/** Enables/disables handling touch on this task view. */
public void setTouchEnabled(boolean enabled) {
setOnClickListener(enabled ? this : null);
@@ -546,7 +552,7 @@
@Override
public void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger) {
- Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
+ cancelDimAnimationIfExists();
// Dim the view after the app window transitions down into recents
postAnimationTrigger.increment();
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index da56e62..90c65580 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -767,7 +767,7 @@
mDockedStackMinimized = minimized;
} else if (mDockedStackMinimized != minimized) {
mIsInMinimizeInteraction = true;
- if (minimized) {
+ if (minimized && (mCurrentAnimator == null || !mCurrentAnimator.isRunning())) {
mDividerPositionBeforeMinimized = getCurrentPosition();
}
mMinimizedSnapAlgorithm = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/Abortable.java b/packages/SystemUI/src/com/android/systemui/statusbar/Abortable.java
new file mode 100644
index 0000000..d5ec4f67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/Abortable.java
@@ -0,0 +1,24 @@
+/*
+ * 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.statusbar;
+
+/**
+ * An interface that allows aborting existing operations.
+ */
+public interface Abortable {
+ void abort();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index d7eab97..b91561e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -891,7 +891,7 @@
* @return the calculated background color
*/
private int calculateBgColor(boolean withTint, boolean withOverRide) {
- if (mDark) {
+ if (withTint && mDark) {
return getContext().getColor(R.color.notification_material_background_dark_color);
}
if (withOverRide && mOverrideTint != NO_COLOR) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8c1b334..9368747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -355,7 +355,8 @@
NotificationColorUtil.getInstance(mContext));
int color = StatusBarIconView.NO_COLOR;
if (colorize) {
- color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded());
+ color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(),
+ getBackgroundColorWithoutTint());
}
expandedIcon.setStaticDrawableColor(color);
}
@@ -859,7 +860,8 @@
private void updateNotificationColor() {
mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext,
- getStatusBarNotification().getNotification().color);
+ getStatusBarNotification().getNotification().color,
+ getBackgroundColorWithoutTint());
}
public HybridNotificationView getSingleLineView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 540c391..f8bad05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -33,7 +33,6 @@
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.view.View;
import android.widget.ImageView;
import android.widget.RemoteViews;
@@ -43,7 +42,6 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -86,7 +84,7 @@
public List<SnoozeCriterion> snoozeCriteria;
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
- private ArraySet<AsyncTask> mRunningTasks = new ArraySet();
+ private Abortable mRunningTask = null;
public Entry(StatusBarNotification n) {
this.key = n.getKey();
@@ -203,13 +201,15 @@
}
}
- public int getContrastedColor(Context context, boolean ambient) {
- int rawColor = ambient ? Notification.COLOR_DEFAULT :
+ public int getContrastedColor(Context context, boolean isLowPriority,
+ int backgroundColor) {
+ int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
notification.getNotification().color;
if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
return mCachedContrastColor;
}
- final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor);
+ final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor,
+ backgroundColor);
mCachedContrastColorIsFor = rawColor;
mCachedContrastColor = contrasted;
return mCachedContrastColor;
@@ -218,24 +218,26 @@
/**
* Abort all existing inflation tasks
*/
- public void abortInflation() {
- for (AsyncTask task : mRunningTasks) {
- task.cancel(true /* mayInterruptIfRunning */);
+ public void abortTask() {
+ if (mRunningTask != null) {
+ mRunningTask.abort();
+ mRunningTask = null;
}
- mRunningTasks.clear();
}
- public void addInflationTask(AsyncTask asyncInflationTask) {
- mRunningTasks.add(asyncInflationTask);
+ public void setInflationTask(Abortable abortableTask) {
+ // abort any existing inflation
+ abortTask();
+ mRunningTask = abortableTask;
}
- public void onInflationTaskFinished(AsyncTask asyncInflationTask) {
- mRunningTasks.remove(asyncInflationTask);
+ public void onInflationTaskFinished() {
+ mRunningTask = null;
}
@VisibleForTesting
- public ArraySet<AsyncTask> getRunningTasks() {
- return mRunningTasks;
+ public Abortable getRunningTask() {
+ return mRunningTask;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 4305bdef..dc538da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -35,6 +35,7 @@
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import android.service.notification.StatusBarNotification;
import android.view.LayoutInflater;
@@ -107,7 +108,7 @@
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
- mHandler = new Handler();
+ mHandler = new Handler(Looper.getMainLooper());
mMenuItems = new ArrayList<>();
mSnoozeItem = createSnoozeItem(context);
mInfoItem = createInfoItem(context);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
index 7cfc767..f1c26cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
@@ -16,19 +16,26 @@
package com.android.systemui.statusbar.notification;
+import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.os.AsyncTask;
+import android.os.CancellationSignal;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.Abortable;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationContentView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.Assert;
+
+import java.util.HashMap;
/**
* A utility that inflates the right kind of contentView based on the state
@@ -116,126 +123,303 @@
@VisibleForTesting
void inflateNotificationViews(int reInflateFlags) {
StatusBarNotification sbn = mRow.getEntry().notification;
- new AsyncInflationTask(mRow.getContext(), sbn, reInflateFlags).execute();
+ new AsyncInflationTask(sbn, reInflateFlags, mRow, mIsLowPriority,
+ mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
+ mCallback, mRemoteViewClickHandler).execute();
}
@VisibleForTesting
- void inflateNotificationViews(int reInflateFlags,
+ InflationProgress inflateNotificationViews(int reInflateFlags,
Notification.Builder builder, Context packageContext) {
- NotificationData.Entry entry = mRow.getEntry();
- NotificationContentView privateLayout = mRow.getPrivateLayout();
- NotificationContentView publicLayout = mRow.getPublicLayout();
+ InflationProgress result = createRemoteViews(reInflateFlags, builder, mIsLowPriority,
+ mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
+ mRedactAmbient, packageContext);
+ apply(result, reInflateFlags, mRow, mRedactAmbient, mRemoteViewClickHandler, null);
+ return result;
+ }
- boolean isLowPriority = mIsLowPriority && !mIsChildInGroup;
+ private static InflationProgress createRemoteViews(int reInflateFlags,
+ Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup,
+ boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
+ Context packageContext) {
+ InflationProgress result = new InflationProgress();
+ isLowPriority = isLowPriority && !isChildInGroup;
if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
- final RemoteViews newContentView = createContentView(builder,
- isLowPriority, mUsesIncreasedHeight);
- if (!compareRemoteViews(newContentView,
- entry.cachedContentView)) {
- View contentViewLocal = newContentView.apply(
- packageContext,
- privateLayout,
- mRemoteViewClickHandler);
- contentViewLocal.setIsRootNamespace(true);
- privateLayout.setContractedChild(contentViewLocal);
- } else {
- newContentView.reapply(packageContext,
- privateLayout.getContractedChild(),
- mRemoteViewClickHandler);
- }
- entry.cachedContentView = newContentView;
+ result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
}
if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
- final RemoteViews newBigContentView = createBigContentView(
- builder, isLowPriority);
- if (newBigContentView != null) {
- if (!compareRemoteViews(newBigContentView, entry.cachedBigContentView)) {
- View bigContentViewLocal = newBigContentView.apply(
- packageContext,
- privateLayout,
- mRemoteViewClickHandler);
- bigContentViewLocal.setIsRootNamespace(true);
- privateLayout.setExpandedChild(bigContentViewLocal);
- } else {
- newBigContentView.reapply(packageContext,
- privateLayout.getExpandedChild(),
- mRemoteViewClickHandler);
- }
- } else if (entry.cachedBigContentView != null) {
- privateLayout.setExpandedChild(null);
- }
- entry.cachedBigContentView = newBigContentView;
- mRow.setExpandable(newBigContentView != null);
+ result.newExpandedView = createExpandedView(builder, isLowPriority);
}
if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
- final RemoteViews newHeadsUpContentView =
- builder.createHeadsUpContentView(mUsesIncreasedHeadsUpHeight);
- if (newHeadsUpContentView != null) {
- if (!compareRemoteViews(newHeadsUpContentView,
- entry.cachedHeadsUpContentView)) {
- View headsUpContentViewLocal = newHeadsUpContentView.apply(
- packageContext,
- privateLayout,
- mRemoteViewClickHandler);
- headsUpContentViewLocal.setIsRootNamespace(true);
- privateLayout.setHeadsUpChild(headsUpContentViewLocal);
- } else {
- newHeadsUpContentView.reapply(packageContext,
- privateLayout.getHeadsUpChild(),
- mRemoteViewClickHandler);
- }
- } else if (entry.cachedHeadsUpContentView != null) {
- privateLayout.setHeadsUpChild(null);
- }
- entry.cachedHeadsUpContentView = newHeadsUpContentView;
+ result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
}
if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
- final RemoteViews newPublicNotification
- = builder.makePublicContentView();
- if (!compareRemoteViews(newPublicNotification, entry.cachedPublicContentView)) {
- View publicContentView = newPublicNotification.apply(
- packageContext,
- publicLayout,
- mRemoteViewClickHandler);
- publicContentView.setIsRootNamespace(true);
- publicLayout.setContractedChild(publicContentView);
- } else {
- newPublicNotification.reapply(packageContext,
- publicLayout.getContractedChild(),
- mRemoteViewClickHandler);
- }
- entry.cachedPublicContentView = newPublicNotification;
+ result.newPublicView = builder.makePublicContentView();
}
if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
- final RemoteViews newAmbientNotification = mRedactAmbient
- ? builder.makePublicAmbientNotification()
+ result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification()
: builder.makeAmbientNotification();
- NotificationContentView newParent = mRedactAmbient ? publicLayout : privateLayout;
- NotificationContentView otherParent = !mRedactAmbient ? publicLayout : privateLayout;
+ }
+ result.packageContext = packageContext;
+ return result;
+ }
- if (newParent.getAmbientChild() == null ||
- !compareRemoteViews(newAmbientNotification, entry.cachedAmbientContentView)) {
- View ambientContentView = newAmbientNotification.apply(
- packageContext,
- newParent,
- mRemoteViewClickHandler);
- ambientContentView.setIsRootNamespace(true);
- newParent.setAmbientChild(ambientContentView);
- otherParent.setAmbientChild(null);
- } else {
- newAmbientNotification.reapply(packageContext,
- newParent.getAmbientChild(),
- mRemoteViewClickHandler);
+ public static CancellationSignal apply(InflationProgress result, int reInflateFlags,
+ ExpandableNotificationRow row, boolean redactAmbient,
+ RemoteViews.OnClickHandler remoteViewClickHandler,
+ @Nullable InflationCallback callback) {
+ NotificationData.Entry entry = row.getEntry();
+ NotificationContentView privateLayout = row.getPrivateLayout();
+ NotificationContentView publicLayout = row.getPublicLayout();
+ final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
+
+ int flag = FLAG_REINFLATE_CONTENT_VIEW;
+ if ((reInflateFlags & flag) != 0) {
+ boolean isNewView = !compareRemoteViews(result.newContentView, entry.cachedContentView);
+ ApplyCallback applyCallback = new ApplyCallback() {
+ @Override
+ public void setResultView(View v) {
+ result.inflatedContentView = v;
+ }
+
+ @Override
+ public RemoteViews getRemoteView() {
+ return result.newContentView;
+ }
+ };
+ applyRemoteView(result, reInflateFlags, flag, row, redactAmbient,
+ isNewView, remoteViewClickHandler, callback, entry, privateLayout,
+ privateLayout.getContractedChild(),
+ runningInflations, applyCallback);
+ }
+
+ flag = FLAG_REINFLATE_EXPANDED_VIEW;
+ if ((reInflateFlags & flag) != 0) {
+ if (result.newExpandedView != null) {
+ boolean isNewView = !compareRemoteViews(result.newExpandedView,
+ entry.cachedBigContentView);
+ ApplyCallback applyCallback = new ApplyCallback() {
+ @Override
+ public void setResultView(View v) {
+ result.inflatedExpandedView = v;
+ }
+
+ @Override
+ public RemoteViews getRemoteView() {
+ return result.newExpandedView;
+ }
+ };
+ applyRemoteView(result, reInflateFlags, flag, row,
+ redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
+ privateLayout, privateLayout.getExpandedChild(), runningInflations,
+ applyCallback);
}
- entry.cachedAmbientContentView = newAmbientNotification;
+ }
+
+ flag = FLAG_REINFLATE_HEADS_UP_VIEW;
+ if ((reInflateFlags & flag) != 0) {
+ if (result.newHeadsUpView != null) {
+ boolean isNewView = !compareRemoteViews(result.newHeadsUpView,
+ entry.cachedHeadsUpContentView);
+ ApplyCallback applyCallback = new ApplyCallback() {
+ @Override
+ public void setResultView(View v) {
+ result.inflatedHeadsUpView = v;
+ }
+
+ @Override
+ public RemoteViews getRemoteView() {
+ return result.newHeadsUpView;
+ }
+ };
+ applyRemoteView(result, reInflateFlags, flag, row,
+ redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
+ privateLayout, privateLayout.getHeadsUpChild(), runningInflations,
+ applyCallback);
+ }
+ }
+
+ flag = FLAG_REINFLATE_PUBLIC_VIEW;
+ if ((reInflateFlags & flag) != 0) {
+ boolean isNewView = !compareRemoteViews(result.newPublicView,
+ entry.cachedPublicContentView);
+ ApplyCallback applyCallback = new ApplyCallback() {
+ @Override
+ public void setResultView(View v) {
+ result.inflatedPublicView = v;
+ }
+
+ @Override
+ public RemoteViews getRemoteView() {
+ return result.newPublicView;
+ }
+ };
+ applyRemoteView(result, reInflateFlags, flag, row,
+ redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
+ publicLayout, publicLayout.getContractedChild(), runningInflations,
+ applyCallback);
+ }
+
+ flag = FLAG_REINFLATE_AMBIENT_VIEW;
+ if ((reInflateFlags & flag) != 0) {
+ NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout;
+ boolean isNewView = !canReapplyAmbient(row, redactAmbient) ||
+ !compareRemoteViews(result.newAmbientView, entry.cachedAmbientContentView);
+ ApplyCallback applyCallback = new ApplyCallback() {
+ @Override
+ public void setResultView(View v) {
+ result.inflatedAmbientView = v;
+ }
+
+ @Override
+ public RemoteViews getRemoteView() {
+ return result.newAmbientView;
+ }
+ };
+ applyRemoteView(result, reInflateFlags, flag, row,
+ redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
+ newParent, newParent.getAmbientChild(), runningInflations,
+ applyCallback);
+ }
+
+ // Let's try to finish, maybe nobody is even inflating anything
+ finishIfDone(result, reInflateFlags, runningInflations, callback, row,
+ redactAmbient);
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ cancellationSignal.setOnCancelListener(
+ () -> runningInflations.values().forEach(CancellationSignal::cancel));
+ return cancellationSignal;
+ }
+
+ private static void applyRemoteView(final InflationProgress result,
+ final int reInflateFlags, int inflationId,
+ final ExpandableNotificationRow row,
+ final boolean redactAmbient, boolean isNewView,
+ RemoteViews.OnClickHandler remoteViewClickHandler,
+ @Nullable final InflationCallback callback, NotificationData.Entry entry,
+ NotificationContentView parentLayout, View existingView,
+ final HashMap<Integer, CancellationSignal> runningInflations,
+ ApplyCallback applyCallback) {
+ RemoteViews.OnViewAppliedListener listener
+ = new RemoteViews.OnViewAppliedListener() {
+
+ @Override
+ public void onViewApplied(View v) {
+ if (isNewView) {
+ v.setIsRootNamespace(true);
+ applyCallback.setResultView(v);
+ }
+ runningInflations.remove(inflationId);
+ finishIfDone(result, reInflateFlags, runningInflations, callback, row,
+ redactAmbient);
+ }
+
+ @Override
+ public void onError(Exception e) {
+ runningInflations.remove(inflationId);
+ handleInflationError(runningInflations, e, entry.notification, callback);
+ }
+ };
+ CancellationSignal cancellationSignal;
+ RemoteViews newContentView = applyCallback.getRemoteView();
+ if (isNewView) {
+ cancellationSignal = newContentView.applyAsync(
+ result.packageContext,
+ parentLayout,
+ null /* executor */,
+ listener,
+ remoteViewClickHandler);
+ } else {
+ cancellationSignal = newContentView.reapplyAsync(
+ result.packageContext,
+ existingView,
+ null /* executor */,
+ listener,
+ remoteViewClickHandler);
+ }
+ runningInflations.put(inflationId, cancellationSignal);
+ }
+
+ private static void handleInflationError(HashMap<Integer, CancellationSignal> runningInflations,
+ Exception e, StatusBarNotification notification, @Nullable InflationCallback callback) {
+ Assert.isMainThread();
+ runningInflations.values().forEach(CancellationSignal::cancel);
+ if (callback != null) {
+ callback.handleInflationException(notification, e);
}
}
- private RemoteViews createBigContentView(Notification.Builder builder,
+ /**
+ * Finish the inflation of the views
+ *
+ * @return true if the inflation was finished
+ */
+ private static boolean finishIfDone(InflationProgress result, int reInflateFlags,
+ HashMap<Integer, CancellationSignal> runningInflations,
+ @Nullable InflationCallback endListener, ExpandableNotificationRow row,
+ boolean redactAmbient) {
+ Assert.isMainThread();
+ NotificationData.Entry entry = row.getEntry();
+ NotificationContentView privateLayout = row.getPrivateLayout();
+ NotificationContentView publicLayout = row.getPublicLayout();
+ if (runningInflations.isEmpty()) {
+ if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
+ if (result.inflatedContentView != null) {
+ privateLayout.setContractedChild(result.inflatedContentView);
+ }
+ entry.cachedContentView = result.newContentView;
+ }
+
+ if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
+ if (result.inflatedExpandedView != null) {
+ privateLayout.setExpandedChild(result.inflatedExpandedView);
+ } else if (result.newExpandedView == null) {
+ privateLayout.setExpandedChild(null);
+ }
+ entry.cachedBigContentView = result.newExpandedView;
+ row.setExpandable(result.newExpandedView != null);
+ }
+
+ if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
+ if (result.inflatedHeadsUpView != null) {
+ privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
+ } else if (result.newHeadsUpView == null) {
+ privateLayout.setHeadsUpChild(null);
+ }
+ entry.cachedHeadsUpContentView = result.newHeadsUpView;
+ }
+
+ if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
+ if (result.inflatedPublicView != null) {
+ publicLayout.setContractedChild(result.inflatedPublicView);
+ }
+ entry.cachedPublicContentView = result.newPublicView;
+ }
+
+ if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
+ if (result.inflatedAmbientView != null) {
+ NotificationContentView newParent = redactAmbient
+ ? publicLayout : privateLayout;
+ NotificationContentView otherParent = !redactAmbient
+ ? publicLayout : privateLayout;
+ newParent.setAmbientChild(result.inflatedAmbientView);
+ otherParent.setAmbientChild(null);
+ }
+ entry.cachedAmbientContentView = result.newAmbientView;
+ }
+ if (endListener != null) {
+ endListener.onAsyncInflationFinished(row.getEntry());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private static RemoteViews createExpandedView(Notification.Builder builder,
boolean isLowPriority) {
RemoteViews bigContentView = builder.createBigContentView();
if (bigContentView != null) {
@@ -249,7 +433,7 @@
return null;
}
- private RemoteViews createContentView(Notification.Builder builder,
+ private static RemoteViews createContentView(Notification.Builder builder,
boolean isLowPriority, boolean useLarge) {
if (isLowPriority) {
return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
@@ -258,7 +442,7 @@
}
// Returns true if the RemoteViews are the same.
- private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
+ private static boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
return (a == null && b == null) ||
(a != null && b != null
&& b.getPackage() != null
@@ -272,7 +456,7 @@
}
public interface InflationCallback {
- void handleInflationException(StatusBarNotification notification, InflationException e);
+ void handleInflationException(StatusBarNotification notification, Exception e);
void onAsyncInflationFinished(NotificationData.Entry entry);
}
@@ -286,37 +470,73 @@
inflateNotificationViews();
}
- private class AsyncInflationTask extends AsyncTask<Void, Void, Notification.Builder> {
+ private static boolean canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient) {
+ NotificationContentView ambientView = redactAmbient ? row.getPublicLayout()
+ : row.getPrivateLayout(); ;
+ return ambientView.getAmbientChild() != null;
+ }
+
+ public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
+ implements InflationCallback, Abortable {
private final StatusBarNotification mSbn;
private final Context mContext;
private final int mReInflateFlags;
- private Context mPackageContext = null;
+ private final boolean mIsLowPriority;
+ private final boolean mIsChildInGroup;
+ private final boolean mUsesIncreasedHeight;
+ private final InflationCallback mCallback;
+ private final boolean mUsesIncreasedHeadsUpHeight;
+ private final boolean mRedactAmbient;
+ private ExpandableNotificationRow mRow;
private Exception mError;
+ private RemoteViews.OnClickHandler mRemoteViewClickHandler;
+ private CancellationSignal mCancellationSignal;
- private AsyncInflationTask(Context context, StatusBarNotification notification,
- int reInflateFlags) {
+ private AsyncInflationTask(StatusBarNotification notification,
+ int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority,
+ boolean isChildInGroup, boolean usesIncreasedHeight,
+ boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
+ InflationCallback callback,
+ RemoteViews.OnClickHandler remoteViewClickHandler) {
+ mRow = row;
+ NotificationData.Entry entry = row.getEntry();
+ entry.setInflationTask(this);
mSbn = notification;
- mContext = context;
mReInflateFlags = reInflateFlags;
- mRow.getEntry().addInflationTask(this);
+ mContext = mRow.getContext();
+ mIsLowPriority = isLowPriority;
+ mIsChildInGroup = isChildInGroup;
+ mUsesIncreasedHeight = usesIncreasedHeight;
+ mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
+ mRedactAmbient = redactAmbient;
+ mRemoteViewClickHandler = remoteViewClickHandler;
+ mCallback = callback;
}
@Override
- protected Notification.Builder doInBackground(Void... params) {
+ protected InflationProgress doInBackground(Void... params) {
try {
final Notification.Builder recoveredBuilder
= Notification.Builder.recoverBuilder(mContext,
mSbn.getNotification());
- mPackageContext = mSbn.getPackageContext(mContext);
+ Context packageContext = mSbn.getPackageContext(mContext);
Notification notification = mSbn.getNotification();
+ if (mIsLowPriority) {
+ int backgroundColor = mContext.getColor(
+ R.color.notification_material_background_low_priority_color);
+ recoveredBuilder.setBackgroundColorHint(backgroundColor);
+ }
if (notification.isMediaNotification()) {
MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
- mPackageContext);
+ packageContext);
processor.setIsLowPriority(mIsLowPriority);
processor.processNotification(notification, recoveredBuilder);
}
- return recoveredBuilder;
+ return createRemoteViews(mReInflateFlags,
+ recoveredBuilder, mIsLowPriority, mIsChildInGroup,
+ mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
+ packageContext);
} catch (Exception e) {
mError = e;
return null;
@@ -324,34 +544,64 @@
}
@Override
- protected void onPostExecute(Notification.Builder builder) {
- mRow.getEntry().onInflationTaskFinished(this);
+ protected void onPostExecute(InflationProgress result) {
if (mError == null) {
- finishInflation(mReInflateFlags, builder, mPackageContext);
+ mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient,
+ mRemoteViewClickHandler, this);
} else {
handleError(mError);
}
}
- }
- private void finishInflation(int reinflationFlags, Notification.Builder builder,
- Context context) {
- try {
- inflateNotificationViews(reinflationFlags, builder, context);
- } catch (RuntimeException e){
- handleError(e);
- return;
+ private void handleError(Exception e) {
+ mRow.getEntry().onInflationTaskFinished();
+ StatusBarNotification sbn = mRow.getStatusBarNotification();
+ final String ident = sbn.getPackageName() + "/0x"
+ + Integer.toHexString(sbn.getId());
+ Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
+ mCallback.handleInflationException(sbn,
+ new InflationException("Couldn't inflate contentViews" + e));
}
- mRow.onNotificationUpdated();
- mCallback.onAsyncInflationFinished(mRow.getEntry());
+
+ @Override
+ public void abort() {
+ cancel(true /* mayInterruptIfRunning */);
+ if (mCancellationSignal != null) {
+ mCancellationSignal.cancel();
+ }
+ }
+
+ @Override
+ public void handleInflationException(StatusBarNotification notification, Exception e) {
+ handleError(e);
+ }
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ mRow.getEntry().onInflationTaskFinished();
+ mRow.onNotificationUpdated();
+ mCallback.onAsyncInflationFinished(mRow.getEntry());
+ }
}
- private void handleError(Exception e) {
- StatusBarNotification sbn = mRow.getStatusBarNotification();
- final String ident = sbn.getPackageName() + "/0x"
- + Integer.toHexString(sbn.getId());
- Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
- mCallback.handleInflationException(sbn,
- new InflationException("Couldn't inflate contentViews" + e));
+ private static class InflationProgress {
+ private RemoteViews newContentView;
+ private RemoteViews newHeadsUpView;
+ private RemoteViews newExpandedView;
+ private RemoteViews newAmbientView;
+ private RemoteViews newPublicView;
+
+ private Context packageContext;
+
+ private View inflatedContentView;
+ private View inflatedHeadsUpView;
+ private View inflatedExpandedView;
+ private View inflatedAmbientView;
+ private View inflatedPublicView;
+ }
+
+ private abstract static class ApplyCallback {
+ public abstract void setResultView(View v);
+ public abstract RemoteViews getRemoteView();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java
new file mode 100644
index 0000000..1bfc0cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.content.Context;
+import android.support.v4.view.AsyncLayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.Abortable;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+
+/**
+ * An inflater task that asynchronously inflates a ExpandableNotificationRow
+ */
+public class RowInflaterTask implements Abortable, AsyncLayoutInflater.OnInflateFinishedListener {
+ private RowInflationFinishedListener mListener;
+ private NotificationData.Entry mEntry;
+ private boolean mCancelled;
+
+ /**
+ * Inflates a new notificationView. This should not be called twice on this object
+ */
+ public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
+ RowInflationFinishedListener listener) {
+ mListener = listener;
+ AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
+ mEntry = entry;
+ entry.setInflationTask(this);
+ inflater.inflate(R.layout.status_bar_notification_row, parent, this);
+ }
+
+ @Override
+ public void abort() {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onInflateFinished(View view, int resid, ViewGroup parent) {
+ if (!mCancelled) {
+ mEntry.onInflationTaskFinished();
+ mListener.onInflationFinished((ExpandableNotificationRow) view);
+ }
+ }
+
+ public interface RowInflationFinishedListener {
+ void onInflationFinished(ExpandableNotificationRow row);
+ }
+}
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 badbcb3..4610bc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -172,6 +172,7 @@
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
@@ -1588,12 +1589,12 @@
private void abortExistingInflation(String key) {
if (mPendingNotifications.containsKey(key)) {
Entry entry = mPendingNotifications.get(key);
- entry.abortInflation();
+ entry.abortTask();
mPendingNotifications.remove(key);
}
Entry addedEntry = mNotificationData.get(key);
if (addedEntry != null) {
- addedEntry.abortInflation();
+ addedEntry.abortTask();
}
}
@@ -1610,7 +1611,7 @@
}
@Override
- public void handleInflationException(StatusBarNotification notification, InflationException e) {
+ public void handleInflationException(StatusBarNotification notification, Exception e) {
handleNotificationError(notification, e.getMessage());
}
@@ -6172,50 +6173,57 @@
entry.notification.getUser().getIdentifier());
final StatusBarNotification sbn = entry.notification;
- ExpandableNotificationRow row;
if (entry.row != null) {
- row = entry.row;
entry.reset();
+ updateNotification(entry, pmUser, sbn, entry.row);
} else {
- // create the row view
- LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
- parent, false);
- row.setExpansionLogger(this, entry.notification.getKey());
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setRemoteInputController(mRemoteInputController);
- row.setOnExpandClickListener(this);
- row.setRemoteViewClickHandler(mOnClickHandler);
- row.setInflationCallback(this);
-
- // Get the app name.
- // Note that Notification.Builder#bindHeaderAppName has similar logic
- // but since this field is used in the guts, it must be accurate.
- // Therefore we will only show the application label, or, failing that, the
- // package name. No substitutions.
- final String pkg = sbn.getPackageName();
- String appname = pkg;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
- }
- } catch (NameNotFoundException e) {
- // Do nothing
- }
- row.setAppName(appname);
- row.setOnDismissRunnable(() ->
- performRemoveNotification(row.getStatusBarNotification()));
- row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- if (ENABLE_REMOTE_INPUT) {
- row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- }
+ new RowInflaterTask().inflate(mContext, parent, entry,
+ row -> {
+ bindRow(entry, pmUser, sbn, row);
+ updateNotification(entry, pmUser, sbn, row);
+ });
}
+ }
+
+ private void bindRow(Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setExpansionLogger(this, entry.notification.getKey());
+ row.setGroupManager(mGroupManager);
+ row.setHeadsUpManager(mHeadsUpManager);
+ row.setRemoteInputController(mRemoteInputController);
+ row.setOnExpandClickListener(this);
+ row.setRemoteViewClickHandler(mOnClickHandler);
+ row.setInflationCallback(this);
+
+ // Get the app name.
+ // Note that Notification.Builder#bindHeaderAppName has similar logic
+ // but since this field is used in the guts, it must be accurate.
+ // Therefore we will only show the application label, or, failing that, the
+ // package name. No substitutions.
+ final String pkg = sbn.getPackageName();
+ String appname = pkg;
+ try {
+ final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ if (info != null) {
+ appname = String.valueOf(pmUser.getApplicationLabel(info));
+ }
+ } catch (NameNotFoundException e) {
+ // Do nothing
+ }
+ row.setAppName(appname);
+ row.setOnDismissRunnable(() ->
+ performRemoveNotification(row.getStatusBarNotification()));
+ row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (ENABLE_REMOTE_INPUT) {
+ row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
+ }
+
+ private void updateNotification(Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setNeedsRedaction(needsRedaction(entry));
boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
row.setIsLowPriority(isLowPriority);
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/Android.mk b/packages/SystemUI/tests/Android.mk
index 5e8b3f9..5e71dd4 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -38,6 +38,7 @@
LOCAL_STATIC_ANDROID_LIBRARIES := \
SystemUIPluginLib \
+ android-support-v4 \
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
index fbb25e5..15381b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.widget.RemoteViews;
@@ -41,7 +42,6 @@
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
-import java.util.function.Function;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -67,7 +67,7 @@
mNotificationInflater.setInflationCallback(new NotificationInflater.InflationCallback() {
@Override
public void handleInflationException(StatusBarNotification notification,
- InflationException e) {
+ Exception e) {
}
@Override
@@ -77,6 +77,7 @@
}
@Test
+ @UiThreadTest
public void testIncreasedHeadsUpBeingUsed() {
mNotificationInflater.setUsesIncreasedHeadsUpHeight(true);
Notification.Builder builder = spy(mBuilder);
@@ -85,6 +86,7 @@
}
@Test
+ @UiThreadTest
public void testIncreasedHeightBeingUsed() {
mNotificationInflater.setUsesIncreasedHeight(true);
Notification.Builder builder = spy(mBuilder);
@@ -124,10 +126,10 @@
@Test
public void testAsyncTaskRemoved() throws Exception {
- mRow.getEntry().abortInflation();
+ mRow.getEntry().abortTask();
runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
mNotificationInflater);
- Assert.assertTrue(mRow.getEntry().getRunningTasks().size() == 0);
+ Assert.assertNull(mRow.getEntry().getRunningTask() );
}
public static void runThenWaitForInflation(Runnable block,
@@ -143,7 +145,7 @@
inflater.setInflationCallback(new NotificationInflater.InflationCallback() {
@Override
public void handleInflationException(StatusBarNotification notification,
- InflationException e) {
+ Exception e) {
if (!expectingException) {
exceptionHolder.setException(e);
}
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/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 922962f..31b4b55 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -16,6 +16,7 @@
package com.android.server.autofill.ui;
import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -265,8 +266,11 @@
mContentWidth = 0;
mContentHeight = 0;
- final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
- final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0);
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxSize.x,
+ MeasureSpec.AT_MOST);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxSize.y,
+ MeasureSpec.AT_MOST);
+
final int itemCount = Math.min(mAdapter.getCount(), VISIBLE_OPTIONS_MAX_COUNT);
for (int i = 0; i < itemCount; i++) {
View view = mAdapter.getItem(i).getView();
@@ -334,6 +338,11 @@
@Override
public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
boolean fitsSystemWindows, int layoutDirection) {
+ if (sVerbose) {
+ Slog.v(TAG, "AutofillWindowPresenter.show(): fit=" + fitsSystemWindows
+ + ", epicenter="+ transitionEpicenter + ", dir=" + layoutDirection
+ + ", params=" + p);
+ }
UiThread.getHandler().post(() -> mWindow.show(p));
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f0ae1a7..6aff600 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5485,7 +5485,8 @@
// Maintain fullscreen layout until incoming animation is complete.
topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw();
// Transient status bar on the lockscreen is not allowed
- if (mForceStatusBarFromKeyguard && mStatusBarController.isTransientShowing()) {
+ if ((mForceStatusBarFromKeyguard || statusBarExpanded)
+ && mStatusBarController.isTransientShowing()) {
mStatusBarController.updateVisibilityLw(false /*transientAllowed*/,
mLastSystemUiFlags, mLastSystemUiFlags);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 92d26cb..3a90a35 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1503,16 +1503,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 +1533,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() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a48397b..a7f6600 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2373,7 +2373,7 @@
try {
synchronized(mWindowMap) {
config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded,
- displayId, true /* includeAppContainers */);
+ displayId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2383,13 +2383,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 +2427,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 +2437,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/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 1bdcd7a..c722629 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -57,6 +57,7 @@
static jobject gPowerManagerServiceObj;
sp<IPower> gPowerHal = nullptr;
+bool gPowerHalExists = true;
std::mutex gPowerHalMutex;
static nsecs_t gLastEventTime[USER_ACTIVITY_EVENT_LAST + 1];
@@ -78,12 +79,13 @@
// Check validity of current handle to the power HAL service, and call getService() if necessary.
// The caller must be holding gPowerHalMutex.
bool getPowerHal() {
- if (gPowerHal == nullptr) {
+ if (gPowerHalExists && gPowerHal == nullptr) {
gPowerHal = IPower::getService();
if (gPowerHal != nullptr) {
ALOGI("Loaded power HAL service");
} else {
ALOGI("Couldn't load power HAL service");
+ gPowerHalExists = false;
}
}
return gPowerHal != nullptr;