summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/ActivityThread.java32
-rw-r--r--core/java/android/app/Notification.java42
-rw-r--r--core/java/android/app/VrManager.java30
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java4
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java1
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java1
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java7
-rw-r--r--core/java/android/os/BatteryStats.java35
-rw-r--r--core/java/android/os/VibrationEffect.java25
-rw-r--r--core/java/android/os/Vibrator.java2
-rw-r--r--core/java/android/service/autofill/SaveInfo.java1
-rw-r--r--core/java/android/service/vr/IVrManager.aidl12
-rw-r--r--core/java/android/view/View.java7
-rw-r--r--core/java/android/webkit/WebView.java10
-rw-r--r--core/java/com/android/internal/util/NotificationColorUtil.java49
-rw-r--r--media/java/android/media/tv/TvContract.java35
-rw-r--r--packages/SettingsLib/res/layout/preference_two_target.xml2
-rw-r--r--packages/SystemUI/Android.mk1
-rw-r--r--packages/SystemUI/res/values/arrays_tv.xml1
-rw-r--r--packages/SystemUI/res/values/config_tv.xml10
-rw-r--r--packages/SystemUI/res/values/strings_tv.xml8
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java225
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/Abortable.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java510
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java10
-rw-r--r--packages/SystemUI/tests/Android.mk1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java12
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/autofill/java/com/android/server/autofill/Helper.java6
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java33
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/FillUi.java13
-rw-r--r--services/core/Android.mk1
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java2
-rw-r--r--services/core/java/com/android/server/am/UserController.java11
-rw-r--r--services/core/java/com/android/server/display/NightDisplayService.java22
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java3
-rw-r--r--services/core/java/com/android/server/vr/VrManagerService.java30
-rw-r--r--services/core/jni/com_android_server_power_PowerManagerService.cpp4
-rw-r--r--services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java493
50 files changed, 1539 insertions, 408 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4f6c0c9d8f91..abe5dc347963 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7443,7 +7443,7 @@ public class Activity extends ContextThemeWrapper
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/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 87fcc0ad0fd3..928ef7e3863d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5094,24 +5094,26 @@ public final class ActivityThread {
// caused by other sources, such as overlays. That means we want to be as conservative
// about code changes as possible. Take the diff of the old ApplicationInfo and the new
// to see if anything needs to change.
+ LoadedApk apk;
+ LoadedApk resApk;
+ // Update all affected loaded packages with new package information
synchronized (mResourcesManager) {
- // Update all affected loaded packages with new package information
WeakReference<LoadedApk> ref = mPackages.get(ai.packageName);
- LoadedApk apk = ref != null ? ref.get() : null;
- if (apk != null) {
- final ArrayList<String> oldPaths = new ArrayList<>();
- LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths);
- apk.updateApplicationInfo(ai, oldPaths);
- }
-
- ref = mResourcePackages.get(ai.packageName);
apk = ref != null ? ref.get() : null;
- if (apk != null) {
- final ArrayList<String> oldPaths = new ArrayList<>();
- LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths);
- apk.updateApplicationInfo(ai, oldPaths);
- }
-
+ ref = mResourcePackages.get(ai.packageName);
+ resApk = ref != null ? ref.get() : null;
+ }
+ if (apk != null) {
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths);
+ apk.updateApplicationInfo(ai, oldPaths);
+ }
+ if (resApk != null) {
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths);
+ resApk.updateApplicationInfo(ai, oldPaths);
+ }
+ synchronized (mResourcesManager) {
// Update all affected Resources objects to use new ResourcesImpl
mResourcesManager.applyNewResourceDirsLocked(ai.sourceDir, ai.resourceDirs);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2e56bcf8a3de..0041879453a8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2675,6 +2675,7 @@ public class Notification implements Parcelable
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 @@ public class Notification implements Parcelable
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 @@ public class Notification implements Parcelable
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 @@ public class Notification implements Parcelable
if (isColorized()) {
return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : mN.color;
} else {
- return COLOR_DEFAULT;
+ return mBackgroundColorHint != COLOR_INVALID ? mBackgroundColorHint
+ : COLOR_DEFAULT;
}
}
@@ -4913,6 +4938,17 @@ public class Notification implements Parcelable
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/app/VrManager.java b/core/java/android/app/VrManager.java
index 040b330c2f30..8014ecafa9a2 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -6,8 +6,6 @@ import android.content.ComponentName;
import android.os.RemoteException;
import android.service.vr.IVrManager;
-import java.io.FileDescriptor;
-
/**
* Used to control aspects of a devices Virtual Reality (VR) capabilities.
* <p>
@@ -63,32 +61,4 @@ public class VrManager {
e.rethrowFromSystemServer();
}
}
-
- /**
- * Initiate connection for system controller data.
- *
- * @param fd Controller data file descriptor.
- *
- * {@hide}
- */
- public void connectController(FileDescriptor fd) {
- try {
- mService.connectController(fd);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Sever connection for system controller data.
- *
- * {@hide}
- */
- public void disconnectController() {
- try {
- mService.disconnectController();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 211d54d1673f..63eedf539bfe 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -107,7 +107,8 @@ public abstract class CameraDevice implements AutoCloseable {
/**
* 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 @@ public abstract class CameraDevice implements AutoCloseable {
* 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 279d73d215e3..c41fc0207d92 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1669,6 +1669,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <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 aedfc4b702a7..6d80c20a84af 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2174,6 +2174,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <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 7947620d3a94..d61fb979900b 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -42,8 +42,8 @@ import javax.crypto.Cipher;
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 @@ public class FingerprintManager {
} 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 @@ public class FingerprintManager {
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/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 235f24cd3602..37c153fe6314 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -183,8 +183,10 @@ public abstract class BatteryStats implements Parcelable {
* - Wakelock data (wl) gets current and max times.
* New in version 20:
* - Background timers and counters for: Sensor, BluetoothScan, WifiScan, Jobs, Syncs.
+ * New in version 21:
+ * - Actual (not just apportioned) Wakelock time is also recorded.
*/
- static final String CHECKIN_VERSION = "20";
+ static final String CHECKIN_VERSION = "21";
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -194,7 +196,7 @@ public abstract class BatteryStats implements Parcelable {
private static final long BYTES_PER_KB = 1024;
private static final long BYTES_PER_MB = 1048576; // 1024^2
private static final long BYTES_PER_GB = 1073741824; //1024^3
-
+
private static final String VERSION_DATA = "vers";
private static final String UID_DATA = "uid";
private static final String WAKEUP_ALARM_DATA = "wua";
@@ -205,6 +207,12 @@ public abstract class BatteryStats implements Parcelable {
private static final String VIBRATOR_DATA = "vib";
private static final String FOREGROUND_DATA = "fg";
private static final String STATE_TIME_DATA = "st";
+ // wl line is:
+ // BATTERY_STATS_CHECKIN_VERSION, uid, which, "wl", name,
+ // full totalTime, 'f', count, current duration, max duration, total duration,
+ // partial totalTime, 'p', count, current duration, max duration, total duration,
+ // window totalTime, 'w', count, current duration, max duration, total duration
+ // [Currently, full and window wakelocks have durations current = max = total = -1]
private static final String WAKELOCK_DATA = "wl";
private static final String SYNC_DATA = "sy";
private static final String JOB_DATA = "jb";
@@ -2659,6 +2667,12 @@ public abstract class BatteryStats implements Parcelable {
sb.append(" max=");
sb.append(maxDurationMs);
}
+ // Put actual time if it is available and different from totalTimeMillis.
+ final long totalDurMs = timer.getTotalDurationMsLocked(elapsedRealtimeUs/1000);
+ if (totalDurMs > totalTimeMillis) {
+ sb.append(" actual=");
+ sb.append(totalDurMs);
+ }
if (timer.isRunningLocked()) {
final long currentMs = timer.getCurrentDurationMsLocked(elapsedRealtimeUs/1000);
if (currentMs >= 0) {
@@ -2742,13 +2756,15 @@ public abstract class BatteryStats implements Parcelable {
long elapsedRealtimeUs, String name, int which, String linePrefix) {
long totalTimeMicros = 0;
int count = 0;
- long max = -1;
- long current = -1;
+ long max = 0;
+ long current = 0;
+ long totalDuration = 0;
if (timer != null) {
totalTimeMicros = timer.getTotalTimeLocked(elapsedRealtimeUs, which);
- count = timer.getCountLocked(which);
+ count = timer.getCountLocked(which);
current = timer.getCurrentDurationMsLocked(elapsedRealtimeUs/1000);
max = timer.getMaxDurationMsLocked(elapsedRealtimeUs/1000);
+ totalDuration = timer.getTotalDurationMsLocked(elapsedRealtimeUs/1000);
}
sb.append(linePrefix);
sb.append((totalTimeMicros + 500) / 1000); // microseconds to milliseconds with rounding
@@ -2759,9 +2775,16 @@ public abstract class BatteryStats implements Parcelable {
sb.append(current);
sb.append(',');
sb.append(max);
+ // Partial, full, and window wakelocks are pooled, so totalDuration is meaningful (albeit
+ // not always tracked). Kernel wakelocks (which have name == null) have no notion of
+ // totalDuration independent of totalTimeMicros (since they are not pooled).
+ if (name != null) {
+ sb.append(',');
+ sb.append(totalDuration);
+ }
return ",";
}
-
+
private static final void dumpLineHeader(PrintWriter pw, int uid, String category,
String type) {
pw.print(BATTERY_STATS_CHECKIN_VERSION);
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index eceaa31b9cf8..6aa601a98f3b 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -189,10 +189,11 @@ public abstract class VibrationEffect implements Parcelable {
if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
throw new IllegalArgumentException(
"amplitude must either be DEFAULT_AMPLITUDE, " +
- "or between 1 and 255 inclusive");
+ "or between 1 and 255 inclusive (amplitude=" + mAmplitude + ")");
}
if (mTiming <= 0) {
- throw new IllegalArgumentException("timing must be positive");
+ throw new IllegalArgumentException(
+ "timing must be positive (timing=" + mTiming + ")");
}
}
@@ -274,24 +275,31 @@ public abstract class VibrationEffect implements Parcelable {
public void validate() {
if (mTimings.length != mAmplitudes.length) {
throw new IllegalArgumentException(
- "timing and amplitude arrays must be of equal length");
+ "timing and amplitude arrays must be of equal length" +
+ " (timings.length=" + mTimings.length +
+ ", amplitudes.length=" + mAmplitudes.length + ")");
}
if (!hasNonZeroEntry(mTimings)) {
- throw new IllegalArgumentException("at least one timing must be non-zero");
+ throw new IllegalArgumentException("at least one timing must be non-zero" +
+ " (timings=" + Arrays.toString(mTimings) + ")");
}
for (long timing : mTimings) {
if (timing < 0) {
- throw new IllegalArgumentException("timings must all be >= 0");
+ throw new IllegalArgumentException("timings must all be >= 0" +
+ " (timings=" + Arrays.toString(mTimings) + ")");
}
}
for (int amplitude : mAmplitudes) {
if (amplitude < -1 || amplitude > 255) {
throw new IllegalArgumentException(
- "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255");
+ "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255" +
+ " (amplitudes=" + Arrays.toString(mAmplitudes) + ")");
}
}
if (mRepeat < -1 || mRepeat >= mTimings.length) {
- throw new IllegalArgumentException("repeat index must be >= -1");
+ throw new IllegalArgumentException(
+ "repeat index must be within the bounds of the timings array" +
+ " (timings.length=" + mTimings.length + ", index=" + mRepeat +")");
}
}
@@ -375,7 +383,8 @@ public abstract class VibrationEffect implements Parcelable {
@Override
public void validate() {
if (mEffectId != EFFECT_CLICK) {
- throw new IllegalArgumentException("Unknown prebaked effect type");
+ throw new IllegalArgumentException(
+ "Unknown prebaked effect type (value=" + mEffectId + ")");
}
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 1e55c78845d1..2f0eecae2c96 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -157,6 +157,8 @@ public abstract class Vibrator {
// This call needs to continue throwing ArrayIndexOutOfBoundsException but ignore all other
// exceptions for compatibility purposes
if (repeat < -1 || repeat >= pattern.length) {
+ Log.e(TAG, "vibrate called with repeat index out of bounds" +
+ " (pattern.length=" + pattern.length + ", index=" + repeat + ")");
throw new ArrayIndexOutOfBoundsException();
}
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 277c6223985b..9487760780e3 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -174,6 +174,7 @@ public final class SaveInfo implements Parcelable {
SAVE_DATA_TYPE_PASSWORD,
SAVE_DATA_TYPE_ADDRESS,
SAVE_DATA_TYPE_CREDIT_CARD,
+ SAVE_DATA_TYPE_USERNAME,
SAVE_DATA_TYPE_EMAIL_ADDRESS})
@Retention(RetentionPolicy.SOURCE)
@interface SaveDataType{}
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index fc8afe9131b1..9b37a654ab98 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -86,17 +86,5 @@ interface IVrManager {
* currently, else return the display id of the virtual display
*/
int getVr2dDisplayId();
-
- /**
- * Initiate connection for system controller data.
- *
- * @param fd Controller data file descriptor.
- */
- void connectController(in FileDescriptor fd);
-
- /**
- * Sever connection for system controller data.
- */
- void disconnectController();
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c250ca03565e..7399f771859a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6844,7 +6844,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
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);
@@ -20430,9 +20430,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@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 52c82a71fb7e..ecb25fead18c 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -98,7 +98,7 @@ import java.util.Map;
* 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 @@ import java.util.Map;
* <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 = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
@@ -175,7 +175,7 @@ import java.util.Map;
* }
* });
*
- * webview.loadUrl("http://developer.android.com/");
+ * webview.loadUrl("https://developer.android.com/");
* </pre>
*
* <h3>Zoom</h3>
@@ -2705,7 +2705,7 @@ public class WebView extends AbsoluteLayout
* <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 @@ public class WebView extends AbsoluteLayout
* 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 2c97f8bd5971..cd41f9e9f902 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -286,6 +286,38 @@ public class NotificationColorUtil {
}
/**
+ * 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 @@ public class NotificationColorUtil {
* 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 @@ public class NotificationColorUtil {
+ " 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 @@ public class NotificationColorUtil {
}
/**
+ * 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 4496a82e55d5..d82e5496443e 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1624,7 +1624,12 @@ public final class TvContract {
/** 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 +2428,12 @@ public final class TvContract {
*/
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 +2747,12 @@ public final class TvContract {
*/
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 +2873,12 @@ public final class TvContract {
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 +2925,12 @@ public final class TvContract {
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 5446acebeeb3..9fb956ecf22e 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 5ee0c64c9591..2fd7e87a683e 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -31,6 +31,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-unde
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 e52c5db9d227..7541b0e8c084 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 40e3b128a55e..ffd58dcfd50d 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 e578068163d7..a9bdb71de039 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 9735bfc666d7..6667b71efc0e 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 @@ import static android.view.Display.DEFAULT_DISPLAY;
*/
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 @@ public class PipManager implements BasePipManager {
private ComponentName mPipComponentName;
private MediaController mPipMediaController;
private String[] mLastPackagesResourceGranted;
+ private PipNotification mPipNotification;
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
@@ -246,6 +248,8 @@ public class PipManager implements BasePipManager {
} 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 class PipManager implements BasePipManager {
*/
public void onConfigurationChanged() {
loadConfigurationsAndApply();
+ mPipNotification.onConfigurationChanged(mContext);
}
/**
@@ -345,7 +350,7 @@ public class PipManager implements BasePipManager {
* @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 @@ public class PipManager implements BasePipManager {
/**
* 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 000000000000..727eb5a3dba4
--- /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/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index e34987b1d47a..4b614eda941a 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 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
* 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 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
@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 da56e62f3c6e..90c65580c7fd 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 @@ public class DividerView extends FrameLayout implements OnTouchListener,
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 000000000000..d5ec4f67e82d
--- /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 d7eab9772677..b91561e01290 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 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
* @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 8c1b334fe570..93687478fc86 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
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 540c39150188..f8bad053c3ee 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.NotificationListenerService.RankingMap;
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.annotations.VisibleForTesting;
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 class NotificationData {
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 class NotificationData {
}
}
- 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 @@ public class NotificationData {
/**
* 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 4305bdef6fef..dc538dac6856 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.Context;
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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
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 7cfc767f89b7..f1c26cd2daa8 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 @@ public class NotificationInflater {
@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;
-
- 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);
+ }
+ result.packageContext = packageContext;
+ return result;
+ }
+
+ 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 RemoteViews createBigContentView(Notification.Builder builder,
+ 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);
+ }
+ }
+
+ /**
+ * 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 @@ public class NotificationInflater {
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 @@ public class NotificationInflater {
}
// 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 class NotificationInflater {
}
public interface InflationCallback {
- void handleInflationException(StatusBarNotification notification, InflationException e);
+ void handleInflationException(StatusBarNotification notification, Exception e);
void onAsyncInflationFinished(NotificationData.Entry entry);
}
@@ -286,37 +470,73 @@ public class NotificationInflater {
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 AsyncInflationTask(Context context, StatusBarNotification notification,
- int reInflateFlags) {
+ private RemoteViews.OnClickHandler mRemoteViewClickHandler;
+ private CancellationSignal mCancellationSignal;
+
+ 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 @@ public class NotificationInflater {
}
@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){
+ 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));
+ }
+
+ @Override
+ public void abort() {
+ cancel(true /* mayInterruptIfRunning */);
+ if (mCancellationSignal != null) {
+ mCancellationSignal.cancel();
+ }
+ }
+
+ @Override
+ public void handleInflationException(StatusBarNotification notification, Exception e) {
handleError(e);
- return;
}
- mRow.onNotificationUpdated();
- mCallback.onAsyncInflationFinished(mRow.getEntry());
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ mRow.getEntry().onInflationTaskFinished();
+ mRow.onNotificationUpdated();
+ mCallback.onAsyncInflationFinished(mRow.getEntry());
+ }
+ }
+
+ 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 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 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 000000000000..1bfc0cc6a6df
--- /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/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5690495c47b3..41fb5f7c220c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -579,6 +579,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
.setComponent(aiaComponent)
.setAction(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
+ .addCategory("unique:" + System.currentTimeMillis())
.putExtra(Intent.EXTRA_PACKAGE_NAME, appInfo.packageName)
.putExtra(Intent.EXTRA_VERSION_CODE, appInfo.versionCode)
.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, pendingIntent);
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 badbcb3da845..4610bc8fe54a 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.ScrimView;
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 @@ public class StatusBar extends SystemUI implements DemoMode,
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 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
- public void handleInflationException(StatusBarNotification notification, InflationException e) {
+ public void handleInflationException(StatusBarNotification notification, Exception e) {
handleNotificationError(notification, e.getMessage());
}
@@ -6172,50 +6173,57 @@ public class StatusBar extends SystemUI implements DemoMode,
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 cd85a760159b..ae8afe4dcad7 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 class NotificationChannels extends SystemUI {
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 @@ public class NotificationChannels extends SystemUI {
? 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 5e8b3f905258..5e71dd4684c5 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -38,6 +38,7 @@ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
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 fbb25e5484ba..15381b7e9425 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.app.Notification;
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.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
-import java.util.function.Function;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -67,7 +67,7 @@ public class NotificationInflaterTest {
mNotificationInflater.setInflationCallback(new NotificationInflater.InflationCallback() {
@Override
public void handleInflationException(StatusBarNotification notification,
- InflationException e) {
+ Exception e) {
}
@Override
@@ -77,6 +77,7 @@ public class NotificationInflaterTest {
}
@Test
+ @UiThreadTest
public void testIncreasedHeadsUpBeingUsed() {
mNotificationInflater.setUsesIncreasedHeadsUpHeight(true);
Notification.Builder builder = spy(mBuilder);
@@ -85,6 +86,7 @@ public class NotificationInflaterTest {
}
@Test
+ @UiThreadTest
public void testIncreasedHeightBeingUsed() {
mNotificationInflater.setUsesIncreasedHeight(true);
Notification.Builder builder = spy(mBuilder);
@@ -124,10 +126,10 @@ public class NotificationInflaterTest {
@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 @@ public class NotificationInflaterTest {
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 53b3fe9c1a0d..2f6b7e619e68 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -212,6 +212,10 @@ message SystemMessage {
// 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/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index cbf97ddc8abb..8d947b92159c 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -20,9 +20,7 @@ import android.annotation.NonNull;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.os.Bundle;
-import android.util.DebugUtils;
import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
import java.util.Arrays;
import java.util.Objects;
@@ -68,10 +66,6 @@ public final class Helper {
return builder.toString();
}
- static String getUpdateActionAsString(int action) {
- return DebugUtils.flagsToString(AutofillManager.class, "ACTION_", action);
- }
-
static ViewNode findViewNodeById(@NonNull AssistStructure structure, @NonNull AutofillId id) {
final int size = structure.getWindowNodeCount();
for (int i = 0; i < size; i++) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3c85034e98ac..a98821de56ba 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -26,7 +26,6 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
import static com.android.server.autofill.Helper.findViewNodeById;
-import static com.android.server.autofill.Helper.getUpdateActionAsString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import static com.android.server.autofill.ViewState.STATE_AUTOFILLED;
@@ -689,6 +688,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
} else {
final Parcelable result = data.getParcelable(
AutofillManager.EXTRA_AUTHENTICATION_RESULT);
+ if (sVerbose) Slog.d(TAG, "setAuthenticationResultLocked() for " + result);
+
if (result instanceof FillResponse) {
FillResponse response = (FillResponse) result;
@@ -697,6 +698,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mResponseWaitingAuth = null;
if (requestIndex >= 0) {
response.setRequestId(mResponses.keyAt(requestIndex));
+ if (response.getDatasets() == null || response.getDatasets().isEmpty()) {
+ // TODO(b/37424539): there is a race condition that causes the authentication
+ // dialog to be shown again after the service authreplied with a no-datasets
+ // response. We're fixing it by hiding the UI when that happens, but that
+ // sounds like a hack - hopefully the real problem will go away when we
+ // 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();
+ }
processResponseLocked(response);
} else {
Slog.e(TAG, "Error cannot find id for auth response");
@@ -1005,18 +1016,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
if (sVerbose) {
- Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + getUpdateActionAsString(action)
- + ", flags=" + flags);
+ Slog.v(TAG, "updateLocked(): id=" + id + ", action=" + action + ", flags=" + flags);
}
ViewState viewState = mViewStates.get(id);
if (viewState == null) {
if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED
|| action == ACTION_VIEW_ENTERED) {
- if (sVerbose) {
- Slog.v(TAG,
- "Creating viewState for " + id + " on " + getActionAsString(action));
- }
+ if (sVerbose) Slog.v(TAG, "Creating viewState for " + id + " on " + action);
boolean isIgnored = isIgnoredLocked(id);
viewState = new ViewState(this, id, value, this,
isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
@@ -1026,7 +1033,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
} else {
- if (sVerbose) Slog.v(TAG, "Ignored " + getActionAsString(action) + " for " + id);
+ if (sVerbose) Slog.v(TAG, "Ignored action " + action + " for " + id);
return;
}
}
@@ -1078,6 +1085,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
break;
case ACTION_VIEW_EXITED:
if (mCurrentViewId == viewState.id) {
+ if (sVerbose) Slog.d(TAG, "Exiting view " + id);
mUi.hideFillUi(viewState.id);
mCurrentViewId = null;
}
@@ -1118,10 +1126,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
}
- String getActionAsString(int flag) {
- return DebugUtils.flagsToString(AutofillManager.class, "ACTION_", flag);
- }
-
boolean isDestroyed() {
synchronized (mLock) {
return mDestroyed;
@@ -1180,14 +1184,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
private void processResponseLocked(@NonNull FillResponse response) {
+ final int requestId = response.getRequestId();
if (sVerbose) {
- Slog.v(TAG, "processResponseLocked(mCurrentViewId=" + mCurrentViewId + "):" + response);
+ Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
+ + ", reqId=" + requestId + ", resp=" + response);
}
if (mResponses == null) {
mResponses = new SparseArray<>(4);
}
- final int requestId = response.getRequestId();
mResponses.put(requestId, response);
mClientState = response.getClientState();
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 922962f1b63b..31b4b55818ac 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 @@ final class FillUi {
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 @@ final class FillUi {
@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/Android.mk b/services/core/Android.mk
index f89647897d09..154934676798 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -13,7 +13,6 @@ LOCAL_SRC_FILES += \
../../../../system/netd/server/binder/android/net/INetd.aidl \
../../../../system/netd/server/binder/android/net/metrics/INetdEventListener.aidl \
../../../native/cmds/installd/binder/android/os/IInstalld.aidl \
- ../../../native/services/vr/vr_window_manager/aidl/android/service/vr/IVrWindowManager.aidl \
LOCAL_AIDL_INCLUDES += \
system/netd/server/binder
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index d6bfb35b1935..cfb5478f22d4 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -740,6 +740,8 @@ class AppErrors {
}
// If we've created a crash dialog, show it without the lock held
if(data.proc.crashDialog != null) {
+ Slog.i(TAG, "Showing crash dialog for package " + data.proc.info.packageName
+ + " u" + data.proc.userId);
data.proc.crashDialog.show();
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3b5e5bcce7ae..6a310f22627c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -439,7 +439,7 @@ final class UserController {
}
}
- Slog.d(TAG, "Sending BOOT_COMPLETE user #" + userId);
+ Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
// Do not report secondary users, runtime restarts or first boot/upgrade
if (userId == UserHandle.USER_SYSTEM
&& !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
@@ -451,7 +451,14 @@ final class UserController {
bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mInjector.broadcastIntentLocked(bootIntent, null, null, 0, null, null,
+ mInjector.broadcastIntentLocked(bootIntent, null, new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser)
+ throws RemoteException {
+ Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
+ }
+ }, 0, null, null,
new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
}
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index 78498967b344..d5742657c5e2 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -567,22 +567,27 @@ public final class NightDisplayService extends SystemService
private final TwilightManager mTwilightManager;
- private Calendar mLastActivatedTime;
-
TwilightAutoMode() {
mTwilightManager = getLocalService(TwilightManager.class);
}
private void updateActivated(TwilightState state) {
- boolean activate = state != null && state.isNight();
- if (state != null && mLastActivatedTime != null) {
+ if (state == null) {
+ // If there isn't a valid TwilightState then just keep the current activated
+ // state.
+ return;
+ }
+
+ boolean activate = state.isNight();
+ final Calendar lastActivatedTime = getLastActivatedTime();
+ if (lastActivatedTime != null) {
final Calendar now = Calendar.getInstance();
final Calendar sunrise = state.sunrise();
final Calendar sunset = state.sunset();
// Maintain the existing activated state if within the current period.
- if (mLastActivatedTime.before(now)
- && (mLastActivatedTime.after(sunrise) ^ mLastActivatedTime.after(sunset))) {
+ if (lastActivatedTime.before(now)
+ && (lastActivatedTime.after(sunrise) ^ lastActivatedTime.after(sunset))) {
activate = mController.isActivated();
}
}
@@ -595,7 +600,6 @@ public final class NightDisplayService extends SystemService
@Override
public void onStart() {
mTwilightManager.registerListener(this, mHandler);
- mLastActivatedTime = getLastActivatedTime();
// Force an update to initialize state.
updateActivated(mTwilightManager.getLastTwilightState());
@@ -604,14 +608,10 @@ public final class NightDisplayService extends SystemService
@Override
public void onStop() {
mTwilightManager.unregisterListener(this);
- mLastActivatedTime = null;
}
@Override
public void onActivated(boolean activated) {
- if (mIsActivated != null) {
- mLastActivatedTime = getLastActivatedTime();
- }
}
@Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f0ae1a735443..6aff600287cc 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 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// 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/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 0e183f0a50b8..9ef74100e2ab 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -39,7 +39,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
@@ -47,7 +46,6 @@ import android.service.vr.IPersistentVrStateCallbacks;
import android.service.vr.IVrListener;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
-import android.service.vr.IVrWindowManager;
import android.service.vr.VrListenerService;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -440,18 +438,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
@Override
- public void connectController(FileDescriptor fd) throws android.os.RemoteException {
- enforceCallerPermission(Manifest.permission.RESTRICTED_VR_ACCESS);
- VrManagerService.this.connectController(fd);
- }
-
- @Override
- public void disconnectController() throws android.os.RemoteException {
- enforceCallerPermission(Manifest.permission.RESTRICTED_VR_ACCESS);
- VrManagerService.this.disconnectController();
- }
-
- @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1184,20 +1170,4 @@ public class VrManagerService extends SystemService implements EnabledComponentC
return mVrModeEnabled;
}
}
-
- private void connectController(FileDescriptor fd) throws android.os.RemoteException {
- // TODO(b/36506799): move vr_wm code to VrCore and remove this.
- IVrWindowManager remote =
- IVrWindowManager.Stub.asInterface(
- ServiceManager.getService(IVrWindowManager.SERVICE_NAME));
- remote.connectController(fd);
- }
-
- private void disconnectController() throws android.os.RemoteException {
- // TODO(b/36506799): move vr_wm code to VrCore and remove this.
- IVrWindowManager remote =
- IVrWindowManager.Stub.asInterface(
- ServiceManager.getService(IVrWindowManager.SERVICE_NAME));
- remote.disconnectController();
- }
}
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 1bdcd7aa9bd6..c722629a28f4 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 struct {
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 @@ static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodNa
// 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;
diff --git a/services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java
index 9a9c243023eb..58a4456ff4d7 100644
--- a/services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java
@@ -16,10 +16,12 @@
package com.android.server;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
import android.content.ContextWrapper;
+import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Secure;
@@ -32,6 +34,7 @@ import com.android.internal.app.NightDisplayController.LocalTime;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.display.DisplayTransformManager;
import com.android.server.display.NightDisplayService;
+import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
import org.junit.After;
@@ -41,6 +44,10 @@ import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.doReturn;
@@ -51,7 +58,7 @@ public class NightDisplayServiceTest {
private Context mContext;
private int mUserId;
- private TwilightManager mTwilightManager;
+ private MockTwilightManager mTwilightManager;
private NightDisplayController mNightDisplayController;
private NightDisplayService mNightDisplayService;
@@ -73,7 +80,7 @@ public class NightDisplayServiceTest {
final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class);
LocalServices.addService(DisplayTransformManager.class, dtm);
- mTwilightManager = Mockito.mock(TwilightManager.class);
+ mTwilightManager = new MockTwilightManager();
LocalServices.addService(TwilightManager.class, mTwilightManager);
mNightDisplayController = new NightDisplayController(mContext, mUserId);
@@ -526,23 +533,371 @@ public class NightDisplayServiceTest {
assertActivated(true /* activated */);
}
- /**
- * Convenience for making a {@link LocalTime} instance with an offset relative to now.
- *
- * @param offsetMinutes the offset relative to now (in minutes)
- * @return the LocalTime instance
- */
- private LocalTime getLocalTimeRelativeToNow(int offsetMinutes) {
- final Calendar c = Calendar.getInstance();
- c.add(Calendar.MINUTE, offsetMinutes);
- return new LocalTime(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+ @Test
+ public void twilightSchedule_whenRebootedAfterNight_ifOffAfterNight_turnsOff() {
+ setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+ setActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedAfterNight_ifOffBeforeNight_turnsOff() {
+ setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+ setActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedAfterNight_ifOffDuringNight_turnsOff() {
+ setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+ setActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedAfterNight_ifOffInFuture_turnsOff() {
+ setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+ setActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedAfterNight_ifOnAfterNight_turnsOn() {
+ setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+ setActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedAfterNight_ifOnBeforeNight_turnsOff() {
+ setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+ setActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedAfterNight_ifOnDuringNight_turnsOff() {
+ setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+ setActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedAfterNight_ifOnInFuture_turnsOff() {
+ setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+ setActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedBeforeNight_ifOffAfterNight_turnsOff() {
+ setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+ setActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedBeforeNight_ifOffBeforeNight_turnsOff() {
+ setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+ setActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedBeforeNight_ifOffDuringNight_turnsOff() {
+ setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+ setActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedBeforeNight_ifOffInPast_turnsOff() {
+ setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+ setActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedBeforeNight_ifOnAfterNight_turnsOff() {
+ setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+ setActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedBeforeNight_ifOnBeforeNight_turnsOff() {
+ setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+ setActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedBeforeNight_ifOnDuringNight_turnsOff() {
+ setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+ setActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedBeforeNight_ifOnInPast_turnsOn() {
+ setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+ setActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedDuringNight_ifOffAfterNight_turnsOn() {
+ setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+ setActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedDuringNight_ifOffBeforeNight_turnsOn() {
+ setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+ setActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInFuture_turnsOn() {
+ setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+ setActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInPast_turnsOff() {
+ setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+ setActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(false /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(false /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedDuringNight_ifOnAfterNight_turnsOn() {
+ setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+ setActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedDuringNight_ifOnBeforeNight_turnsOn() {
+ setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+ setActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInFuture_turnsOn() {
+ setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+ setActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
+ }
+
+ @Test
+ public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInPast_turnsOn() {
+ setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+ setActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+ final TwilightState state = mTwilightManager.getLastTwilightState();
+ mTwilightManager.setTwilightState(null);
+
+ startService();
+ assertActivated(true /* activated */);
+
+ mTwilightManager.setTwilightState(state);
+ assertActivated(true /* activated */);
}
/**
* Configures Night display to use a custom schedule.
*
* @param startTimeOffset the offset relative to now to activate Night display (in minutes)
- * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
+ * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
*/
private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
mNightDisplayController.setAutoMode(NightDisplayController.AUTO_MODE_CUSTOM);
@@ -553,34 +908,21 @@ public class NightDisplayServiceTest {
/**
* Configures Night display to use the twilight schedule.
*
- * @param sunsetOffset the offset relative to now for sunset (in minutes)
+ * @param sunsetOffset the offset relative to now for sunset (in minutes)
* @param sunriseOffset the offset relative to now for sunrise (in minutes)
*/
private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
mNightDisplayController.setAutoMode(NightDisplayController.AUTO_MODE_TWILIGHT);
-
- final LocalTime sunset = getLocalTimeRelativeToNow(sunsetOffset);
- final LocalTime sunrise = getLocalTimeRelativeToNow(sunriseOffset);
-
- final Calendar now = Calendar.getInstance();
- long sunsetMillis = sunset.getDateTimeBefore(now).getTimeInMillis();
- long sunriseMillis = sunrise.getDateTimeBefore(now).getTimeInMillis();
- if (sunsetMillis < sunriseMillis) {
- sunsetMillis = sunset.getDateTimeAfter(now).getTimeInMillis();
- } else {
- sunriseMillis = sunrise.getDateTimeAfter(now).getTimeInMillis();
- }
-
- final TwilightState state = new TwilightState(sunriseMillis, sunsetMillis);
- doReturn(state).when(mTwilightManager).getLastTwilightState();
+ mTwilightManager.setTwilightState(
+ getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
}
/**
* Configures the Night display activated state.
*
- * @param activated {@code true} if Night display should be activated
+ * @param activated {@code true} if Night display should be activated
* @param lastActivatedTimeOffset the offset relative to now to record that Night display was
- activated (in minutes)
+ * activated (in minutes)
*/
private void setActivated(boolean activated, int lastActivatedTimeOffset) {
mNightDisplayController.setActivated(activated);
@@ -617,4 +959,93 @@ public class NightDisplayServiceTest {
.that(mNightDisplayController.isActivated())
.isEqualTo(activated);
}
+
+ /**
+ * Convenience for making a {@link LocalTime} instance with an offset relative to now.
+ *
+ * @param offsetMinutes the offset relative to now (in minutes)
+ * @return the LocalTime instance
+ */
+ private static LocalTime getLocalTimeRelativeToNow(int offsetMinutes) {
+ final Calendar c = Calendar.getInstance();
+ c.add(Calendar.MINUTE, offsetMinutes);
+ return new LocalTime(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+ }
+
+ /**
+ * Convenience for making a {@link TwilightState} instance with sunrise/sunset relative to now.
+ *
+ * @param sunsetOffset the offset relative to now for sunset (in minutes)
+ * @param sunriseOffset the offset relative to now for sunrise (in minutes)
+ * @return the TwilightState instance
+ */
+ private static TwilightState getTwilightStateRelativeToNow(int sunsetOffset,
+ int sunriseOffset) {
+ final LocalTime sunset = getLocalTimeRelativeToNow(sunsetOffset);
+ final LocalTime sunrise = getLocalTimeRelativeToNow(sunriseOffset);
+
+ final Calendar now = Calendar.getInstance();
+ long sunsetMillis = sunset.getDateTimeBefore(now).getTimeInMillis();
+ long sunriseMillis = sunrise.getDateTimeBefore(now).getTimeInMillis();
+ if (sunsetMillis < sunriseMillis) {
+ sunsetMillis = sunset.getDateTimeAfter(now).getTimeInMillis();
+ } else {
+ sunriseMillis = sunrise.getDateTimeAfter(now).getTimeInMillis();
+ }
+
+ return new TwilightState(sunriseMillis, sunsetMillis);
+ }
+
+ private static class MockTwilightManager implements TwilightManager {
+
+ private final Map<TwilightListener, Handler> mListeners = new HashMap<>();
+ private TwilightState mTwilightState;
+
+ /**
+ * Updates the TwilightState and notifies any registered listeners.
+ *
+ * @param state the new TwilightState to use
+ */
+ void setTwilightState(TwilightState state) {
+ synchronized (mListeners) {
+ mTwilightState = state;
+
+ final CountDownLatch latch = new CountDownLatch(mListeners.size());
+ for (Map.Entry<TwilightListener, Handler> entry : mListeners.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onTwilightStateChanged(state);
+ latch.countDown();
+ }
+ });
+ }
+
+ try {
+ latch.await(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public void registerListener(@NonNull TwilightListener listener, @NonNull Handler handler) {
+ synchronized (mListeners) {
+ mListeners.put(listener, handler);
+ }
+ }
+
+ @Override
+ public void unregisterListener(@NonNull TwilightListener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public TwilightState getLastTwilightState() {
+ return mTwilightState;
+ }
+ }
}