Merge "Add no more than 20 timestamps for a config in StatsdStats."
diff --git a/api/current.txt b/api/current.txt
index 8329aca..00a9452 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23256,16 +23256,20 @@
field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
}
- public class MediaRecorder {
+ public class MediaRecorder implements android.media.AudioRouting {
ctor public MediaRecorder();
+ method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method protected void finalize();
method public static final int getAudioSourceMax();
method public int getMaxAmplitude() throws java.lang.IllegalStateException;
method public android.os.PersistableBundle getMetrics();
+ method public android.media.AudioDeviceInfo getPreferredDevice();
+ method public android.media.AudioDeviceInfo getRoutedDevice();
method public android.view.Surface getSurface();
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void release();
+ method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public void reset();
method public void resume() throws java.lang.IllegalStateException;
method public void setAudioChannels(int);
@@ -23288,6 +23292,7 @@
method public void setOutputFile(java.io.File);
method public void setOutputFile(java.lang.String) throws java.lang.IllegalStateException;
method public void setOutputFormat(int) throws java.lang.IllegalStateException;
+ method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
method public void setPreviewDisplay(android.view.Surface);
method public void setProfile(android.media.CamcorderProfile);
method public void setVideoEncoder(int) throws java.lang.IllegalStateException;
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 6ded246..3172281 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -159,7 +159,7 @@
void const* mapbase = MAP_FAILED;
ssize_t mapsize = -1;
- void const* base = NULL;
+ void* base = NULL;
uint32_t w, s, h, f;
android_dataspace d;
size_t size = 0;
@@ -179,7 +179,6 @@
ProcessState::self()->setThreadPoolMaxThreadCount(0);
ProcessState::self()->startThreadPool();
- ScreenshotClient screenshot;
sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId);
if (display == NULL) {
fprintf(stderr, "Unable to get handle for display %d\n", displayId);
@@ -199,51 +198,57 @@
uint8_t displayOrientation = configs[activeConfig].orientation;
uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation];
- status_t result = screenshot.update(display, Rect(),
- 0 /* reqWidth */, 0 /* reqHeight */,
- INT32_MIN, INT32_MAX, /* all layers */
- false, captureOrientation);
- if (result == NO_ERROR) {
- base = screenshot.getPixels();
- w = screenshot.getWidth();
- h = screenshot.getHeight();
- s = screenshot.getStride();
- f = screenshot.getFormat();
- d = screenshot.getDataSpace();
- size = screenshot.getSize();
+ sp<GraphicBuffer> outBuffer;
+ status_t result = ScreenshotClient::capture(display, Rect(), 0 /* reqWidth */,
+ 0 /* reqHeight */, INT32_MIN, INT32_MAX, /* all layers */ false, captureOrientation,
+ &outBuffer);
+ if (result != NO_ERROR) {
+ close(fd);
+ _exit(1);
}
- if (base != NULL) {
- if (png) {
- const SkImageInfo info =
- SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType,
- dataSpaceToColorSpace(d));
- SkPixmap pixmap(info, base, s * bytesPerPixel(f));
- struct FDWStream final : public SkWStream {
- size_t fBytesWritten = 0;
- int fFd;
- FDWStream(int f) : fFd(f) {}
- size_t bytesWritten() const override { return fBytesWritten; }
- bool write(const void* buffer, size_t size) override {
- fBytesWritten += size;
- return size == 0 || ::write(fFd, buffer, size) > 0;
- }
- } fdStream(fd);
- (void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100);
- if (fn != NULL) {
- notifyMediaScanner(fn);
- }
- } else {
- uint32_t c = dataSpaceToInt(d);
- write(fd, &w, 4);
- write(fd, &h, 4);
- write(fd, &f, 4);
- write(fd, &c, 4);
- size_t Bpp = bytesPerPixel(f);
- for (size_t y=0 ; y<h ; y++) {
- write(fd, base, w*Bpp);
- base = (void *)((char *)base + s*Bpp);
- }
+ result = outBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
+
+ if (base == NULL) {
+ close(fd);
+ _exit(1);
+ }
+
+ w = outBuffer->getWidth();
+ h = outBuffer->getHeight();
+ s = outBuffer->getStride();
+ f = outBuffer->getPixelFormat();
+ d = HAL_DATASPACE_UNKNOWN;
+ size = s * h * bytesPerPixel(f);
+
+ if (png) {
+ const SkImageInfo info =
+ SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType, dataSpaceToColorSpace(d));
+ SkPixmap pixmap(info, base, s * bytesPerPixel(f));
+ struct FDWStream final : public SkWStream {
+ size_t fBytesWritten = 0;
+ int fFd;
+ FDWStream(int f) : fFd(f) {}
+ size_t bytesWritten() const override { return fBytesWritten; }
+ bool write(const void* buffer, size_t size) override {
+ fBytesWritten += size;
+ return size == 0 || ::write(fFd, buffer, size) > 0;
+ }
+ } fdStream(fd);
+ (void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100);
+ if (fn != NULL) {
+ notifyMediaScanner(fn);
+ }
+ } else {
+ uint32_t c = dataSpaceToInt(d);
+ write(fd, &w, 4);
+ write(fd, &h, 4);
+ write(fd, &f, 4);
+ write(fd, &c, 4);
+ size_t Bpp = bytesPerPixel(f);
+ for (size_t y=0 ; y<h ; y++) {
+ write(fd, base, w*Bpp);
+ base = (void *)((char *)base + s*Bpp);
}
}
close(fd);
@@ -253,4 +258,4 @@
// b/36066697: Avoid running static destructors.
_exit(0);
-}
+}
\ No newline at end of file
diff --git a/core/java/android/database/OWNERS b/core/java/android/database/OWNERS
new file mode 100644
index 0000000..84e81e4
--- /dev/null
+++ b/core/java/android/database/OWNERS
@@ -0,0 +1,2 @@
+fkupolov@google.com
+omakoto@google.com
\ No newline at end of file
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 5dd8d05..068f5f7 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -555,6 +555,11 @@
int FORCE_ALL_APPS_STANDBY = 11;
/**
+ * Whether to enable background check on all apps or not.
+ */
+ int FORCE_BACKGROUND_CHECK = 12;
+
+ /**
* Whether to disable non-essential sensors. (e.g. edge sensors.)
*/
int OPTIONAL_SENSORS = 13;
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 953501c..cd362c7 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -438,8 +438,21 @@
* AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
*
* save(username, password);
- *
* </pre>
+ *
+ *
+ * <a name="Privacy"></a>
+ * <h3>Privacy</h3>
+ *
+ * <p>The {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} method is called
+ * without the user content. The Android system strips some properties of the
+ * {@link android.app.assist.AssistStructure.ViewNode view nodes} passed to these calls, but not all
+ * of them. For example, the data provided in the {@link android.view.ViewStructure.HtmlInfo}
+ * objects set by {@link android.webkit.WebView} is never stripped out.
+ *
+ * <p>Because this data could contain PII (Personally Identifiable Information, such as username or
+ * email address), the service should only use it locally (i.e., in the app's process) for
+ * heuristics purposes, but it should not be sent to external servers.
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 3d01ec2..cf05910 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -55,8 +55,6 @@
private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
- private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
- Rect sourceCrop, float frameScale);
private static native GraphicBuffer nativeCaptureLayers(IBinder layerHandleToken,
Rect sourceCrop, float frameScale);
@@ -1179,22 +1177,14 @@
* Captures a layer and its children into the provided {@link Surface}.
*
* @param layerHandleToken The root layer to capture.
- * @param consumer The {@link Surface} to capture the layer into.
* @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
* Rect()' or null if no cropping is desired.
* @param frameScale The desired scale of the returned buffer; the raw
* screen will be scaled up/down.
+ *
+ * @return Returns a GraphicBuffer that contains the layer capture.
*/
- public static void captureLayers(IBinder layerHandleToken, Surface consumer, Rect sourceCrop,
- float frameScale) {
- nativeCaptureLayers(layerHandleToken, consumer, sourceCrop, frameScale);
- }
-
- /**
- * Same as {@link #captureLayers(IBinder, Surface, Rect, float)} except this
- * captures to a {@link GraphicBuffer} instead of a {@link Surface}.
- */
- public static GraphicBuffer captureLayersToBuffer(IBinder layerHandleToken, Rect sourceCrop,
+ public static GraphicBuffer captureLayers(IBinder layerHandleToken, Rect sourceCrop,
float frameScale) {
return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale);
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 665d694..6f99254 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2758,7 +2758,9 @@
* <p>For example, an HTML form with 2 fields for username and password:
*
* <pre class="prettyprint">
+ * <label>Username:</label>
* <input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username">
+ * <label>Password:</label>
* <input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password">
* </pre>
*
@@ -2772,6 +2774,7 @@
* username.setHtmlInfo(username.newHtmlInfoBuilder("input")
* .addAttribute("type", "text")
* .addAttribute("name", "username")
+ * .addAttribute("label", "Username:")
* .build());
* username.setHint("Email or username");
* username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
@@ -2785,6 +2788,7 @@
* password.setHtmlInfo(password.newHtmlInfoBuilder("input")
* .addAttribute("type", "password")
* .addAttribute("name", "password")
+ * .addAttribute("label", "Password:")
* .build());
* password.setHint("Password");
* password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 3ad4da6..421e0de 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -517,23 +517,7 @@
jobject graphicBuffer) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
sp<GraphicBuffer> bp = graphicBufferForJavaObject(env, graphicBuffer);
- if (bp == nullptr) {
- return BAD_VALUE;
- }
- int err = ((ANativeWindow*)surface)->perform(surface, NATIVE_WINDOW_API_CONNECT,
- NATIVE_WINDOW_API_CPU);
- if (err != OK) {
- return err;
- }
- err = surface->attachBuffer(bp->getNativeBuffer());
- if (err != OK) {
- return err;
- }
- err = ((ANativeWindow*)surface)->queueBuffer(surface, bp->getNativeBuffer(), -1);
- if (err != OK) {
- return err;
- }
- err = surface->disconnect(NATIVE_WINDOW_API_CPU);
+ int err = Surface::attachAndQueueBuffer(surface, bp);
return err;
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f77e6c4..8c968a2 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -167,7 +167,7 @@
maxLayer = INT32_MAX;
}
sp<GraphicBuffer> buffer;
- status_t res = ScreenshotClient::captureToBuffer(displayToken,
+ status_t res = ScreenshotClient::capture(displayToken,
sourceCrop, width, height, minLayer, maxLayer, useIdentityTransform,
rotation, &buffer);
if (res != NO_ERROR) {
@@ -201,15 +201,18 @@
maxLayer = INT32_MAX;
}
- res = screenshot->update(displayToken, sourceCrop, width, height,
- minLayer, maxLayer, useIdentityTransform, static_cast<uint32_t>(rotation));
+ sp<GraphicBuffer> buffer;
+ res = ScreenshotClient::capture(displayToken, sourceCrop, width, height,
+ minLayer, maxLayer, useIdentityTransform, static_cast<uint32_t>(rotation), &buffer);
if (res != NO_ERROR) {
return NULL;
}
SkColorType colorType;
SkAlphaType alphaType;
- switch (screenshot->getFormat()) {
+
+ PixelFormat format = buffer->getPixelFormat();
+ switch (format) {
case PIXEL_FORMAT_RGBX_8888: {
colorType = kRGBA_8888_SkColorType;
alphaType = kOpaque_SkAlphaType;
@@ -235,66 +238,20 @@
}
}
- sk_sp<SkColorSpace> colorSpace;
- if (screenshot->getDataSpace() == HAL_DATASPACE_DISPLAY_P3) {
- colorSpace = SkColorSpace::MakeRGB(
- SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kDCIP3_D65_Gamut);
- } else {
- colorSpace = SkColorSpace::MakeSRGB();
- }
+ SkImageInfo info = SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(),
+ colorType, alphaType,
+ SkColorSpace::MakeSRGB());
- SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(),
- screenshot->getHeight(),
- colorType,
- alphaType,
- colorSpace);
-
- const size_t rowBytes =
- screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat());
-
- if (!screenshotInfo.width() || !screenshotInfo.height()) {
- return NULL;
- }
-
- auto bitmap = new Bitmap(
- (void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot,
- screenshotInfo, rowBytes);
- screenshot.release();
- bitmap->setImmutable();
- return bitmap::createBitmap(env, bitmap,
- android::bitmap::kBitmapCreateFlag_Premultiplied, NULL);
+ auto bitmap = sk_sp<Bitmap>(new Bitmap(buffer.get(), info));
+ return bitmap::createBitmap(env, bitmap.release(),
+ android::bitmap::kBitmapCreateFlag_Premultiplied, NULL);
}
static void nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj,
jobject surfaceObj, jobject sourceCropObj, jint width, jint height,
jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) {
sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
- if (displayToken != NULL) {
- sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj);
- if (consumer != NULL) {
- int left = env->GetIntField(sourceCropObj, gRectClassInfo.left);
- int top = env->GetIntField(sourceCropObj, gRectClassInfo.top);
- int right = env->GetIntField(sourceCropObj, gRectClassInfo.right);
- int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom);
- Rect sourceCrop(left, top, right, bottom);
-
- if (allLayers) {
- minLayer = INT32_MIN;
- maxLayer = INT32_MAX;
- }
- ScreenshotClient::capture(displayToken,
- consumer->getIGraphicBufferProducer(), sourceCrop,
- width, height, minLayer, maxLayer,
- useIdentityTransform);
- }
- }
-}
-
-static void nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken,
- jobject surfaceObj, jobject sourceCropObj, jfloat frameScale) {
-
- sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
- if (layerHandle == NULL) {
+ if (displayToken == NULL) {
return;
}
@@ -308,11 +265,19 @@
sourceCrop = rectFromObj(env, sourceCropObj);
}
- ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), sourceCrop,
- frameScale);
+ if (allLayers) {
+ minLayer = INT32_MIN;
+ maxLayer = INT32_MAX;
+ }
+
+ sp<GraphicBuffer> buffer;
+ ScreenshotClient::capture(displayToken, sourceCrop, width, height, minLayer, maxLayer,
+ useIdentityTransform, 0, &buffer);
+
+ Surface::attachAndQueueBuffer(consumer.get(), buffer);
}
-static jobject nativeCaptureLayersToBuffer(JNIEnv* env, jclass clazz, jobject layerHandleToken,
+static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken,
jobject sourceCropObj, jfloat frameScale) {
sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
@@ -326,8 +291,7 @@
}
sp<GraphicBuffer> buffer;
- status_t res = ScreenshotClient::captureLayersToBuffer(layerHandle, sourceCrop, frameScale,
- &buffer);
+ status_t res = ScreenshotClient::captureLayers(layerHandle, sourceCrop, frameScale, &buffer);
if (res != NO_ERROR) {
return NULL;
}
@@ -1010,10 +974,8 @@
{"nativeScreenshotToBuffer",
"(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/GraphicBuffer;",
(void*)nativeScreenshotToBuffer },
- {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;F)V",
- (void*)nativeCaptureLayers },
{"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/graphics/Rect;F)Landroid/graphics/GraphicBuffer;",
- (void*)nativeCaptureLayersToBuffer },
+ (void*)nativeCaptureLayers },
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
index d2cd190..d724437 100644
--- a/core/proto/android/server/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -20,6 +20,7 @@
import "frameworks/base/core/proto/android/app/pendingintent.proto";
import "frameworks/base/core/proto/android/internal/locallog.proto";
import "frameworks/base/core/proto/android/os/worksource.proto";
+import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
package com.android.server;
@@ -32,10 +33,9 @@
optional int64 last_time_change_realtime = 4;
// Current settings
optional ConstantsProto settings = 5;
- // UIDs currently in the foreground.
- repeated int32 foreground_uids = 6;
- // Packages forced into app standby.
- repeated string forced_app_standby_packages = 7;
+
+ // Dump from ForceAppStandbyTracker.
+ optional ForceAppStandbyTrackerProto force_app_standby_tracker = 6;
optional bool is_interactive = 8;
// Only valid if is_interactive is false.
diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto
new file mode 100644
index 0000000..8753bf7
--- /dev/null
+++ b/core/proto/android/server/forceappstandbytracker.proto
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.server;
+
+option java_multiple_files = true;
+
+// Dump from ForceAppStandbyTracker.
+message ForceAppStandbyTrackerProto {
+ // Whether all apps are forced standby or not.
+ optional bool force_all_apps_standby = 1;
+
+ // UIDs currently in the foreground.
+ repeated int32 foreground_uids = 2;
+
+ // App ids that are in power-save whitelist.
+ repeated int32 power_save_whitelist_app_ids = 3;
+
+ // App ids that are in temporary power-save whitelist.
+ repeated int32 temp_power_save_whitelist_app_ids = 4;
+
+ message RunAnyInBackgroundRestrictedPackages {
+ optional int32 uid = 1;
+ optional string package_name = 2;
+ }
+
+ // Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND.
+ repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5;
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3512793..65a7ec6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1955,7 +1955,7 @@
<string name="config_dozeLongPressSensorType" translatable="false"></string>
<!-- Control whether the always on display mode is available. This should only be enabled on
- devices where the display has be tuned to be power efficient in DOZE and/or DOZE_SUSPEND
+ devices where the display has been tuned to be power efficient in DOZE and/or DOZE_SUSPEND
states. -->
<bool name="config_dozeAlwaysOnDisplayAvailable">false</bool>
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 7678490..3c49b80 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -25,6 +25,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.Surface;
@@ -34,6 +35,8 @@
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
+import com.android.internal.annotations.GuardedBy;
+
/**
* Used to record audio and video. The recording control is based on a
* simple state machine (see below).
@@ -76,7 +79,7 @@
* <a href="{@docRoot}guide/topics/media/audio-capture.html">Audio Capture</a> developer guide.</p>
* </div>
*/
-public class MediaRecorder
+public class MediaRecorder implements AudioRouting
{
static {
System.loadLibrary("media_jni");
@@ -1243,6 +1246,7 @@
private static final int MEDIA_RECORDER_TRACK_EVENT_INFO = 101;
private static final int MEDIA_RECORDER_TRACK_EVENT_LIST_END = 1000;
+ private static final int MEDIA_RECORDER_AUDIO_ROUTING_CHANGED = 10000;
@Override
public void handleMessage(Message msg) {
@@ -1265,6 +1269,16 @@
return;
+ case MEDIA_RECORDER_AUDIO_ROUTING_CHANGED:
+ AudioManager.resetAudioPortGeneration();
+ synchronized (mRoutingChangeListeners) {
+ for (NativeRoutingEventHandlerDelegate delegate
+ : mRoutingChangeListeners.values()) {
+ delegate.notifyClient();
+ }
+ }
+ return;
+
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
@@ -1272,6 +1286,155 @@
}
}
+ //--------------------------------------------------------------------------
+ // Explicit Routing
+ //--------------------
+ private AudioDeviceInfo mPreferredDevice = null;
+
+ /**
+ * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+ * the input from this MediaRecorder.
+ * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio source.
+ * If deviceInfo is null, default routing is restored.
+ * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+ * does not correspond to a valid audio input device.
+ */
+ @Override
+ public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+ if (deviceInfo != null && !deviceInfo.isSource()) {
+ return false;
+ }
+ int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+ boolean status = native_setInputDevice(preferredDeviceId);
+ if (status == true) {
+ synchronized (this) {
+ mPreferredDevice = deviceInfo;
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Returns the selected input device specified by {@link #setPreferredDevice}. Note that this
+ * is not guaranteed to correspond to the actual device being used for recording.
+ */
+ @Override
+ public AudioDeviceInfo getPreferredDevice() {
+ synchronized (this) {
+ return mPreferredDevice;
+ }
+ }
+
+ /**
+ * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaRecorder
+ * Note: The query is only valid if the MediaRecorder is currently recording.
+ * If the recorder is not recording, the returned device can be null or correspond to previously
+ * selected device when the recorder was last active.
+ */
+ @Override
+ public AudioDeviceInfo getRoutedDevice() {
+ int deviceId = native_getRoutedDeviceId();
+ if (deviceId == 0) {
+ return null;
+ }
+ AudioDeviceInfo[] devices =
+ AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS);
+ for (int i = 0; i < devices.length; i++) {
+ if (devices[i].getId() == deviceId) {
+ return devices[i];
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
+ */
+ private void enableNativeRoutingCallbacksLocked(boolean enabled) {
+ if (mRoutingChangeListeners.size() == 0) {
+ native_enableDeviceCallback(enabled);
+ }
+ }
+
+ /**
+ * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+ * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+ * by an app to receive (re)routing notifications.
+ */
+ @GuardedBy("mRoutingChangeListeners")
+ private ArrayMap<AudioRouting.OnRoutingChangedListener,
+ NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this MediaRecorder.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the handler on the main looper will be used.
+ */
+ @Override
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ synchronized (mRoutingChangeListeners) {
+ if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+ enableNativeRoutingCallbacksLocked(true);
+ mRoutingChangeListeners.put(
+ listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
+ }
+ }
+ }
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ @Override
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mRoutingChangeListeners) {
+ if (mRoutingChangeListeners.containsKey(listener)) {
+ mRoutingChangeListeners.remove(listener);
+ enableNativeRoutingCallbacksLocked(false);
+ }
+ }
+ }
+
+ /**
+ * Helper class to handle the forwarding of native events to the appropriate listener
+ * (potentially) handled in a different thread
+ */
+ private class NativeRoutingEventHandlerDelegate {
+ private MediaRecorder mMediaRecorder;
+ private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
+ private Handler mHandler;
+
+ NativeRoutingEventHandlerDelegate(final MediaRecorder mediaRecorder,
+ final AudioRouting.OnRoutingChangedListener listener, Handler handler) {
+ mMediaRecorder = mediaRecorder;
+ mOnRoutingChangedListener = listener;
+ mHandler = handler != null ? handler : mEventHandler;
+ }
+
+ void notifyClient() {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mOnRoutingChangedListener != null) {
+ mOnRoutingChangedListener.onRoutingChanged(mMediaRecorder);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private native final boolean native_setInputDevice(int deviceId);
+ private native final int native_getRoutedDeviceId();
+ private native final void native_enableDeviceCallback(boolean enabled);
+
/**
* Called from native code when an interesting event happens. This method
* just uses the EventHandler system to post the event back to the main app thread.
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 497684c..d2bc174 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -657,6 +657,56 @@
return mybundle;
}
+
+static jboolean
+android_media_MediaRecorder_setInputDevice(JNIEnv *env, jobject thiz, jint device_id)
+{
+ ALOGV("android_media_MediaRecorder_setInputDevice");
+
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+ if (mr == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return false;
+ }
+
+ if (process_media_recorder_call(env, mr->setInputDevice(device_id),
+ "java/lang/RuntimeException", "setInputDevice failed.")) {
+ return false;
+ }
+ return true;
+}
+
+static jint
+android_media_MediaRecorder_getRoutedDeviceId(JNIEnv *env, jobject thiz)
+{
+ ALOGV("android_media_MediaRecorder_getRoutedDeviceId");
+
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+ if (mr == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return AUDIO_PORT_HANDLE_NONE;
+ }
+
+ audio_port_handle_t deviceId;
+ process_media_recorder_call(env, mr->getRoutedDeviceId(&deviceId),
+ "java/lang/RuntimeException", "getRoutedDeviceId failed.");
+ return (jint) deviceId;
+}
+
+static void
+android_media_MediaRecorder_enableDeviceCallback(JNIEnv *env, jobject thiz, jboolean enabled)
+{
+ ALOGV("android_media_MediaRecorder_enableDeviceCallback %d", enabled);
+
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+ if (mr == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ process_media_recorder_call(env, mr->enableAudioDeviceCallback(enabled),
+ "java/lang/RuntimeException", "enableDeviceCallback failed.");
+}
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -689,6 +739,10 @@
{"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
{"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaRecorder_native_getMetrics},
+
+ {"native_setInputDevice", "(I)Z", (void *)android_media_MediaRecorder_setInputDevice},
+ {"native_getRoutedDeviceId", "()I", (void *)android_media_MediaRecorder_getRoutedDeviceId},
+ {"native_enableDeviceCallback", "(Z)V", (void *)android_media_MediaRecorder_enableDeviceCallback},
};
// This function only registers the native methods, and is called from
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index cf79238..8e065d1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -250,10 +250,7 @@
<string name="doze_brightness_sensor_type" translatable="false"></string>
<!-- Doze: pulse parameter - how long does it take to fade in? -->
- <integer name="doze_pulse_duration_in">900</integer>
-
- <!-- Doze: pulse parameter - how long does it take to fade in after a pickup? -->
- <integer name="doze_pulse_duration_in_pickup">130</integer>
+ <integer name="doze_pulse_duration_in">130</integer>
<!-- Doze: pulse parameter - once faded in, how long does it stay visible? -->
<integer name="doze_pulse_duration_visible">6000</integer>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index cfd95b4..884e189 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -47,6 +47,7 @@
<item type="id" name="qs_icon_tag"/>
<item type="id" name="qs_slash_tag"/>
<item type="id" name="scrim"/>
+ <item type="id" name="scrim_blanking"/>
<item type="id" name="scrim_target"/>
<item type="id" name="scrim_alpha_start"/>
<item type="id" name="scrim_alpha_end"/>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 0c067ff..526a8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockIcon;
@@ -86,10 +87,10 @@
public ScrimController createScrimController(LightBarController lightBarController,
ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
- LockscreenWallpaper lockscreenWallpaper,
- Consumer<Boolean> scrimVisibleListener) {
+ LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
+ DozeParameters dozeParameters) {
return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
- scrimVisibleListener);
+ scrimVisibleListener, dozeParameters);
}
public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 7db118d..2f607ee 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -35,7 +35,6 @@
boolean isBlockingDoze();
void startPendingIntentDismissingKeyguard(PendingIntent intent);
- void abortPulsing();
void extendPulse();
void setAnimateWakeup(boolean animateWakeup);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b99e76a..c92acd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2028,12 +2028,9 @@
}
public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
- ViewGroup container,
- ScrimController scrimController,
- FingerprintUnlockController fingerprintUnlockController) {
+ ViewGroup container, FingerprintUnlockController fingerprintUnlockController) {
mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
- scrimController, fingerprintUnlockController,
- mDismissCallbackRegistry);
+ fingerprintUnlockController, mDismissCallbackRegistry);
return mStatusBarKeyguardViewManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 991c3c8..e909644 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -162,7 +162,7 @@
Matrix matrix = new Matrix();
int overlayColor = 0x40FFFFFF;
- Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, data.image.getConfig());
+ Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, Bitmap.Config.ARGB_8888);
matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2);
c.setBitmap(picture);
c.drawBitmap(data.image, matrix, paint);
@@ -171,7 +171,7 @@
// Note, we can't use the preview for the small icon, since it is non-square
float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
- Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, data.image.getConfig());
+ Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
matrix.setScale(scale, scale);
matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
(iconSize - (scale * mImageHeight)) / 2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index a53e348..fb9c037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -252,6 +252,13 @@
return false;
}
+ /**
+ * It might look counterintuitive to have another method to set the alpha instead of
+ * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
+ * optimizing blend modes, so it makes sense.
+ *
+ * @param alpha Gradient alpha from 0 to 1.
+ */
public void setViewAlpha(float alpha) {
if (alpha != mViewAlpha) {
mViewAlpha = alpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 6b7397b..3f57c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -46,10 +46,8 @@
public void dump(PrintWriter pw) {
pw.println(" DozeParameters:");
pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
- pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
- pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
- pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
- pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+ pw.print(" getPulseDuration(): "); pw.println(getPulseDuration());
+ pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration());
pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
@@ -81,14 +79,12 @@
return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
}
- public int getPulseDuration(boolean pickup) {
- return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+ public int getPulseDuration() {
+ return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
}
- public int getPulseInDuration(boolean pickupOrDoubleTap) {
- return pickupOrDoubleTap
- ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
- : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ public int getPulseInDuration() {
+ return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
}
public int getPulseVisibleDuration() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 8afb849..1011383 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -16,16 +16,11 @@
package com.android.systemui.statusbar.phone;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
-import android.view.animation.Interpolator;
-import com.android.systemui.Interpolators;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -40,74 +35,59 @@
private final Handler mHandler = new Handler();
private final ScrimController mScrimController;
- private final Context mContext;
-
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
private int mPulseReason;
- private Animator mInFrontAnimator;
- private Animator mBehindAnimator;
- private float mInFrontTarget;
- private float mBehindTarget;
- private boolean mDozingAborted;
- private boolean mWakeAndUnlocking;
private boolean mFullyPulsing;
- private float mAodFrontScrimOpacity = 0;
- private Runnable mSetDozeInFrontAlphaDelayed;
+ private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+ + DozeLog.pulseReasonToString(mPulseReason));
+ }
+ if (!mDozing) {
+ return;
+ }
+
+ // Signal that the pulse is ready to turn the screen on and draw.
+ pulseStarted();
+ }
+
+ @Override
+ public void onFinished() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+ }
+ if (!mDozing) {
+ return;
+ }
+ mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+ mHandler.postDelayed(mPulseOutExtended,
+ mDozeParameters.getPulseVisibleDurationExtended());
+ mFullyPulsing = true;
+ }
+
+ /**
+ * Transition was aborted before it was over.
+ */
+ @Override
+ public void onCancelled() {
+ pulseFinished();
+ }
+ };
public DozeScrimController(ScrimController scrimController, Context context) {
- mContext = context;
mScrimController = scrimController;
mDozeParameters = new DozeParameters(context);
}
- public void setDozing(boolean dozing, boolean animate) {
+ public void setDozing(boolean dozing) {
if (mDozing == dozing) return;
mDozing = dozing;
- mWakeAndUnlocking = false;
- if (mDozing) {
- mDozingAborted = false;
- abortAnimations();
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
- } else {
+ if (!mDozing) {
cancelPulsing();
- if (animate) {
- startScrimAnimation(false /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- startScrimAnimation(true /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- } else {
- abortAnimations();
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
- }
- }
- }
-
- /**
- * Set the opacity of the front scrim when showing AOD1
- *
- * Used to emulate lower brightness values than the hardware supports natively.
- */
- public void setAodDimmingScrim(float scrimOpacity) {
- mAodFrontScrimOpacity = scrimOpacity;
- if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
- && mDozeParameters.getAlwaysOn()) {
- setDozeInFrontAlpha(mAodFrontScrimOpacity);
- }
- }
-
- public void setWakeAndUnlocking() {
- // Immediately abort the doze scrims in case of wake-and-unlock
- // for pulsing so the Keyguard fade-out animation scrim can take over.
- if (!mWakeAndUnlocking) {
- mWakeAndUnlocking = true;
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
}
}
@@ -118,37 +98,21 @@
}
if (!mDozing || mPulseCallback != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+ + (mPulseCallback != null));
+ }
// Pulse suppressed.
callback.onPulseFinished();
return;
}
- // Begin pulse. Note that it's very important that the pulse finished callback
+ // Begin pulse. Note that it's very important that the pulse finished callback
// be invoked when we're done so that the caller can drop the pulse wakelock.
mPulseCallback = callback;
mPulseReason = reason;
- setDozeInFrontAlpha(1f);
- mHandler.post(mPulseIn);
- }
- /**
- * Aborts pulsing immediately.
- */
- public void abortPulsing() {
- cancelPulsing();
- if (mDozing && !mWakeAndUnlocking) {
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted
- ? mAodFrontScrimOpacity : 1f);
- }
- }
-
- /**
- * Aborts dozing immediately.
- */
- public void abortDoze() {
- mDozingAborted = true;
- abortPulsing();
+ mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback);
}
public void pulseOutNow() {
@@ -157,17 +121,6 @@
}
}
- public void onScreenTurnedOn() {
- if (isPulsing()) {
- final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
- || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
- startScrimAnimation(true /* inFront */, 0f,
- mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
- pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
- mPulseInFinished);
- }
- }
-
public boolean isPulsing() {
return mPulseCallback != null;
}
@@ -181,11 +134,9 @@
}
private void cancelPulsing() {
- if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
if (mPulseCallback != null) {
+ if (DEBUG) Log.d(TAG, "Cancel pulsing");
mFullyPulsing = false;
- mHandler.removeCallbacks(mPulseIn);
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
pulseFinished();
@@ -193,151 +144,20 @@
}
private void pulseStarted() {
+ DozeLog.tracePulseStart(mPulseReason);
if (mPulseCallback != null) {
mPulseCallback.onPulseStarted();
}
}
private void pulseFinished() {
+ DozeLog.tracePulseFinish();
if (mPulseCallback != null) {
mPulseCallback.onPulseFinished();
mPulseCallback = null;
}
}
- private void abortAnimations() {
- if (mInFrontAnimator != null) {
- mInFrontAnimator.cancel();
- }
- if (mBehindAnimator != null) {
- mBehindAnimator.cancel();
- }
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator) {
- startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator, final Runnable endRunnable) {
- Animator current = getCurrentAnimator(inFront);
- if (current != null) {
- float currentTarget = getCurrentTarget(inFront);
- if (currentTarget == target) {
- return;
- }
- current.cancel();
- }
- ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (float) animation.getAnimatedValue();
- setDozeAlpha(inFront, value);
- }
- });
- anim.setInterpolator(interpolator);
- anim.setDuration(duration);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setCurrentAnimator(inFront, null);
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- anim.start();
- setCurrentAnimator(inFront, anim);
- setCurrentTarget(inFront, target);
- }
-
- private float getCurrentTarget(boolean inFront) {
- return inFront ? mInFrontTarget : mBehindTarget;
- }
-
- private void setCurrentTarget(boolean inFront, float target) {
- if (inFront) {
- mInFrontTarget = target;
- } else {
- mBehindTarget = target;
- }
- }
-
- private Animator getCurrentAnimator(boolean inFront) {
- return inFront ? mInFrontAnimator : mBehindAnimator;
- }
-
- private void setCurrentAnimator(boolean inFront, Animator animator) {
- if (inFront) {
- mInFrontAnimator = animator;
- } else {
- mBehindAnimator = animator;
- }
- }
-
- private void setDozeAlpha(boolean inFront, float alpha) {
- if (mWakeAndUnlocking) {
- return;
- }
- if (inFront) {
- mScrimController.setDozeInFrontAlpha(alpha);
- } else {
- mScrimController.setDozeBehindAlpha(alpha);
- }
- }
-
- private float getDozeAlpha(boolean inFront) {
- return inFront
- ? mScrimController.getDozeInFrontAlpha()
- : mScrimController.getDozeBehindAlpha();
- }
-
- private void setDozeInFrontAlpha(float opacity) {
- setDozeInFrontAlphaDelayed(opacity, 0 /* delay */);
-
- }
-
- private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) {
- if (mSetDozeInFrontAlphaDelayed != null) {
- mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed);
- mSetDozeInFrontAlphaDelayed = null;
- }
- if (delayMs <= 0) {
- mScrimController.setDozeInFrontAlpha(opacity);
- } else {
- mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> {
- setDozeInFrontAlpha(opacity);
- }, delayMs);
- }
- }
-
- private final Runnable mPulseIn = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
- + DozeLog.pulseReasonToString(mPulseReason));
- if (!mDozing) return;
- DozeLog.tracePulseStart(mPulseReason);
-
- // Signal that the pulse is ready to turn the screen on and draw.
- pulseStarted();
- }
- };
-
- private final Runnable mPulseInFinished = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
- if (!mDozing) return;
- mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
- mHandler.postDelayed(mPulseOutExtended,
- mDozeParameters.getPulseVisibleDurationExtended());
- mFullyPulsing = true;
- }
- };
-
private final Runnable mPulseOutExtended = new Runnable() {
@Override
public void run() {
@@ -354,38 +174,13 @@
mHandler.removeCallbacks(mPulseOutExtended);
if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
if (!mDozing) return;
- startScrimAnimation(true /* inFront */, 1,
- mDozeParameters.getPulseOutDuration(),
- Interpolators.ALPHA_IN, mPulseOutFinishing);
+ mScrimController.transitionTo(ScrimState.AOD,
+ new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ pulseFinished();
+ }
+ });
}
};
-
- private final Runnable mPulseOutFinishing = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse out finished");
- DozeLog.tracePulseFinish();
- if (mDozeParameters.getAlwaysOn() && mDozing) {
- // Setting power states can block rendering. For AOD, delay finishing the pulse and
- // setting the power state until the fully black scrim had time to hit the
- // framebuffer.
- mHandler.postDelayed(mPulseOutFinished, 30);
- } else {
- mPulseOutFinished.run();
- }
- }
- };
-
- private final Runnable mPulseOutFinished = new Runnable() {
- @Override
- public void run() {
- // Signal that the pulse is all finished so we can turn the screen off now.
- DozeScrimController.this.pulseFinished();
- if (mDozeParameters.getAlwaysOn()) {
- // Setting power states can happen after we push out the frame. Make sure we
- // stay fully opaque until the power state request reaches the lower levels.
- setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
- }
- }
- };
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 91369db..80d4061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -181,9 +181,9 @@
}
private boolean pulsingOrAod() {
- boolean pulsing = mDozeScrimController.isPulsing();
- boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff();
- return pulsing || dozingWithScreenOn;
+ final ScrimState scrimState = mScrimController.getState();
+ return scrimState == ScrimState.AOD
+ || scrimState == ScrimState.PULSING;
}
@Override
@@ -246,15 +246,12 @@
true /* allowEnterAnimation */);
} else if (mMode == MODE_WAKE_AND_UNLOCK){
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
- mDozeScrimController.abortDoze();
} else {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
mUpdateMonitor.awakenFromDream();
}
mStatusBarWindowManager.setStatusBarFocusable(false);
mKeyguardViewMediator.onWakeAndUnlocking();
- mScrimController.setWakeAndUnlocking();
- mDozeScrimController.setWakeAndUnlocking();
if (mStatusBar.getNavigationBarView() != null) {
mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
}
@@ -269,6 +266,7 @@
}
private void showBouncer() {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
mStatusBarKeyguardViewManager.animateCollapsePanels(
FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 702afa3..dfd4c17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -25,7 +25,9 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -34,12 +36,14 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
import com.android.internal.graphics.ColorUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,7 +51,10 @@
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -56,33 +63,54 @@
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
- OnHeadsUpChangedListener, OnColorsChangedListener {
+ OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable {
+
+ private static final String TAG = "ScrimController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
= new PathInterpolator(0.3f, 0f, 0.8f, 1f);
- // Default alpha value for most scrims, if unsure use this constant
+ /**
+ * Default alpha value for most scrims.
+ */
public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
- // A scrim varies its opacity based on a busyness factor, for example
- // how many notifications are currently visible.
+ /**
+ * A scrim varies its opacity based on a busyness factor, for example
+ * how many notifications are currently visible.
+ */
public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f;
+ /**
+ * The most common scrim, the one under the keyguard.
+ */
protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
+ /**
+ * We fade out the bottom scrim when the bouncer is visible.
+ */
protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
- private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final int TAG_KEY_ANIM = R.id.scrim;
+ /**
+ * Opacity of the scrim behind the bouncer (the one doing actual background protection.)
+ */
+ protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
+
+ static final int TAG_KEY_ANIM = R.id.scrim;
+ static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
private static final float NOT_INITIALIZED = -1;
- private final LightBarController mLightBarController;
+ private ScrimState mState = ScrimState.UNINITIALIZED;
+ private final Context mContext;
protected final ScrimView mScrimBehind;
protected final ScrimView mScrimInFront;
- private final UnlockMethodCache mUnlockMethodCache;
private final View mHeadsUpScrim;
+ private final LightBarController mLightBarController;
+ private final UnlockMethodCache mUnlockMethodCache;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DozeParameters mDozeParameters;
private final SysuiColorExtractor mColorExtractor;
private GradientColors mLockColors;
@@ -94,61 +122,53 @@
protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
- protected boolean mKeyguardShowing;
private float mFraction;
private boolean mDarkenWhileDragging;
- protected boolean mBouncerShowing;
- protected boolean mBouncerIsKeyguard = false;
- private boolean mWakeAndUnlocking;
protected boolean mAnimateChange;
private boolean mUpdatePending;
private boolean mTracking;
private boolean mAnimateKeyguardFadingOut;
- protected long mDurationOverride = -1;
+ protected long mAnimationDuration = -1;
private long mAnimationDelay;
private Runnable mOnAnimationFinished;
private boolean mDeferFinishedListener;
private final Interpolator mInterpolator = new DecelerateInterpolator();
- private boolean mDozing;
- private float mDozeInFrontAlpha;
- private float mDozeBehindAlpha;
private float mCurrentInFrontAlpha = NOT_INITIALIZED;
private float mCurrentBehindAlpha = NOT_INITIALIZED;
- private float mCurrentHeadsUpAlpha = NOT_INITIALIZED;
+ private int mCurrentInFrontTint;
+ private int mCurrentBehindTint;
private int mPinnedHeadsUpCount;
private float mTopHeadsUpDragAmount;
private View mDraggedHeadsUpView;
- private boolean mForceHideScrims;
- private boolean mSkipFirstFrame;
- private boolean mDontAnimateBouncerChanges;
private boolean mKeyguardFadingOutInProgress;
- private boolean mAnimatingDozeUnlock;
private ValueAnimator mKeyguardFadeoutAnimation;
- /** Wake up from AOD transition is starting; need fully opaque front scrim */
- private boolean mWakingUpFromAodStarting;
- /** Wake up from AOD transition is in progress; need black tint */
- private boolean mWakingUpFromAodInProgress;
- /** Wake up from AOD transition is animating; need to reset when animation finishes */
- private boolean mWakingUpFromAodAnimationRunning;
- private boolean mScrimsVisble;
+ private boolean mScrimsVisible;
private final Consumer<Boolean> mScrimVisibleListener;
+ private boolean mBlankScreen;
+ private boolean mScreenBlankingCallbackCalled;
+ private Callback mCallback;
+
+ private final WakeLock mWakeLock;
+ private boolean mWakeLockHeld;
public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
- ScrimView scrimInFront, View headsUpScrim,
- Consumer<Boolean> scrimVisibleListener) {
+ ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
+ DozeParameters dozeParameters) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
mHeadsUpScrim = headsUpScrim;
mScrimVisibleListener = scrimVisibleListener;
- final Context context = scrimBehind.getContext();
- mUnlockMethodCache = UnlockMethodCache.getInstance(context);
- mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+ mContext = scrimBehind.getContext();
+ mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mLightBarController = lightBarController;
- mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mWakeLock = createWakeLock();
// Scrim alpha is initially set to the value on the resource but might be changed
// to make sure that text on top of it is legible.
mScrimBehindAlpha = mScrimBehindAlphaResValue;
+ mDozeParameters = dozeParameters;
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
@@ -158,22 +178,90 @@
ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
mNeedsDrawableColorUpdate = true;
+ final ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+ states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
+ }
+ mState = ScrimState.UNINITIALIZED;
+
updateHeadsUpScrim(false);
updateScrims();
}
- public void setKeyguardShowing(boolean showing) {
- mKeyguardShowing = showing;
+ public void transitionTo(ScrimState state) {
+ transitionTo(state, null);
+ }
- // Showing/hiding the keyguard means that scrim colors have to be switched
- mNeedsDrawableColorUpdate = true;
- scheduleUpdate();
+ public void transitionTo(ScrimState state, Callback callback) {
+ if (state == mState) {
+ return;
+ } else if (DEBUG) {
+ Log.d(TAG, "State changed to: " + state);
+ }
+
+ if (state == ScrimState.UNINITIALIZED) {
+ throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
+ }
+
+ if (mCallback != null) {
+ mCallback.onCancelled();
+ }
+ mCallback = callback;
+
+ state.prepare(mState);
+ mScreenBlankingCallbackCalled = false;
+ mAnimationDelay = 0;
+ mBlankScreen = state.getBlanksScreen();
+ mAnimateChange = state.getAnimateChange();
+ mAnimationDuration = state.getAnimationDuration();
+ mCurrentInFrontTint = state.getFrontTint();
+ mCurrentBehindTint = state.getBehindTint();
+ mCurrentInFrontAlpha = state.getFrontAlpha();
+ mCurrentBehindAlpha = state.getBehindAlpha();
+
+ // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
+ // to do the same when you're just showing the brightness mirror.
+ mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
+
+ if (mKeyguardFadeoutAnimation != null) {
+ mKeyguardFadeoutAnimation.cancel();
+ }
+
+ mState = state;
+
+ // Do not let the device sleep until we're done with all animations
+ if (!mWakeLockHeld) {
+ if (mWakeLock != null) {
+ mWakeLockHeld = true;
+ mWakeLock.acquire();
+ } else {
+ Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+ }
+ }
+
+ if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
+ scheduleUpdate();
+ } else {
+ // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
+ // with too many things at this case, in order to not skip the initial frames.
+ mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+ mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+ }
+ }
+
+ public ScrimState getState() {
+ return mState;
}
protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
float scrimBehindAlphaUnlocking) {
mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+ ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
+ }
scheduleUpdate();
}
@@ -186,131 +274,59 @@
mTracking = false;
}
+ /**
+ * Current state of the shade expansion when pulling it from the top.
+ * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
+ *
+ * The expansion fraction is tied to the scrim opacity.
+ *
+ * @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
+ */
public void setPanelExpansion(float fraction) {
if (mFraction != fraction) {
mFraction = fraction;
- scheduleUpdate();
+
+ if (mState == ScrimState.UNLOCKED) {
+ // Darken scrim as you pull down the shade when unlocked
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
+ mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ } else if (mState == ScrimState.KEYGUARD) {
+ if (mUpdatePending) {
+ return;
+ }
+
+ // Either darken of make the scrim transparent when you
+ // pull down the shade
+ float interpolatedFract = getInterpolatedFraction();
+ if (mDarkenWhileDragging) {
+ mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+ mScrimBehindAlphaKeyguard, interpolatedFract);
+ mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+ } else {
+ mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+ interpolatedFract);
+ mCurrentInFrontAlpha = 0;
+ }
+ } else {
+ Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState);
+ return;
+ }
+
if (mPinnedHeadsUpCount != 0) {
updateHeadsUpScrim(false);
}
- if (mKeyguardFadeoutAnimation != null && mTracking) {
- mKeyguardFadeoutAnimation.cancel();
- }
+
+ updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
+ updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
}
}
- public void setBouncerShowing(boolean showing) {
- mBouncerShowing = showing;
- mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress;
- scheduleUpdate();
- }
-
- /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */
- public void prepareWakeUpFromAod() {
- if (mWakingUpFromAodInProgress) {
- return;
- }
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = true;
- mAnimateChange = false;
- scheduleUpdate();
- onPreDraw();
- }
-
- /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */
- public void wakeUpFromAod() {
- if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) {
- // Wake and unlocking has a separate transition that must not be interfered with.
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- return;
- }
- if (mWakingUpFromAodStarting) {
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = false;
- mAnimateChange = true;
- scheduleUpdate();
- }
- }
-
- public void setWakeAndUnlocking() {
- mWakeAndUnlocking = true;
- mAnimatingDozeUnlock = true;
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- scheduleUpdate();
- }
-
- public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
- boolean skipFirstFrame) {
- mWakeAndUnlocking = false;
- mAnimateKeyguardFadingOut = true;
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- mSkipFirstFrame = skipFirstFrame;
- mOnAnimationFinished = onAnimationFinished;
-
- if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
- scheduleUpdate();
-
- // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
- // the changes we just scheduled.
- onPreDraw();
- } else {
-
- // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
- // with too many things in this case, in order to not skip the initial frames.
- mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
- }
- }
-
- public void abortKeyguardFadingOut() {
- if (mAnimateKeyguardFadingOut) {
- endAnimateKeyguardFadingOut(true /* force */);
- }
- }
-
- public void animateKeyguardUnoccluding(long duration) {
- mAnimateChange = false;
- setScrimBehindAlpha(0f);
- mAnimateChange = true;
- scheduleUpdate();
- mDurationOverride = duration;
- }
-
- public void animateGoingToFullShade(long delay, long duration) {
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- scheduleUpdate();
- }
-
- public void setDozing(boolean dozing) {
- if (mDozing != dozing) {
- mDozing = dozing;
- scheduleUpdate();
- }
- }
-
- public void setDozeInFrontAlpha(float alpha) {
- mDozeInFrontAlpha = alpha;
- updateScrimColor(mScrimInFront);
- }
-
- public void setDozeBehindAlpha(float alpha) {
- mDozeBehindAlpha = alpha;
- updateScrimColor(mScrimBehind);
- }
-
- public float getDozeBehindAlpha() {
- return mDozeBehindAlpha;
- }
-
- public float getDozeInFrontAlpha() {
- return mDozeInFrontAlpha;
- }
-
+ /**
+ * Keyguard and shade scrim opacity varies according to how many notifications are visible.
+ * @param notificationCount Number of visible notifications.
+ */
public void setNotificationCount(int notificationCount) {
final float maxNotificationDensity = 3;
float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
@@ -319,15 +335,11 @@
notificationDensity);
if (mScrimBehindAlphaKeyguard != newAlpha) {
mScrimBehindAlphaKeyguard = newAlpha;
- mAnimateChange = true;
- scheduleUpdate();
- }
- }
- private float getScrimInFrontAlpha() {
- return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
- ? SCRIM_IN_FRONT_ALPHA_LOCKED
- : SCRIM_IN_FRONT_ALPHA;
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
+ scheduleUpdate();
+ }
+ }
}
/**
@@ -352,7 +364,7 @@
if (mNeedsDrawableColorUpdate) {
mNeedsDrawableColorUpdate = false;
final GradientColors currentScrimColors;
- if (mKeyguardShowing) {
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
// Always animate color changes if we're seeing the keyguard
mScrimInFront.setColors(mLockColors, true /* animated */);
mScrimBehind.setColors(mLockColors, true /* animated */);
@@ -375,77 +387,31 @@
mLightBarController.setScrimColor(mScrimInFront.getColors());
}
- if (mAnimateKeyguardFadingOut || mForceHideScrims) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(0f);
- } else if (mWakeAndUnlocking) {
- // During wake and unlock, we first hide everything behind a black scrim, which then
- // gets faded out from animateKeyguardFadingOut. This must never be animated.
- mAnimateChange = false;
- if (mDozing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(1f);
- } else {
- setScrimInFrontAlpha(1f);
- setScrimBehindAlpha(0f);
- }
- } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) {
- updateScrimNormal();
- setScrimInFrontAlpha(0);
- } else {
- updateScrimKeyguard();
- }
- mAnimateChange = false;
+ setScrimInFrontAlpha(mCurrentInFrontAlpha);
+ setScrimBehindAlpha(mCurrentBehindAlpha);
+
dispatchScrimsVisible();
}
private void dispatchScrimsVisible() {
boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
- if (mScrimsVisble != scrimsVisible) {
- mScrimsVisble = scrimsVisible;
+ if (mScrimsVisible != scrimsVisible) {
+ mScrimsVisible = scrimsVisible;
mScrimVisibleListener.accept(scrimsVisible);
}
}
- private void updateScrimKeyguard() {
- if (mTracking && mDarkenWhileDragging) {
- float behindFraction = Math.max(0, Math.min(mFraction, 1));
- float fraction = 1 - behindFraction;
- fraction = (float) Math.pow(fraction, 0.8f);
- behindFraction = (float) Math.pow(behindFraction, 0.8f);
- setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
- setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
- } else if (mBouncerShowing && !mBouncerIsKeyguard) {
- setScrimInFrontAlpha(getScrimInFrontAlpha());
- updateScrimNormal();
- } else if (mBouncerShowing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(mScrimBehindAlpha);
- } else {
- float fraction = Math.max(0, Math.min(mFraction, 1));
- if (mWakingUpFromAodStarting) {
- setScrimInFrontAlpha(1f);
- } else {
- setScrimInFrontAlpha(0f);
- }
- setScrimBehindAlpha(fraction
- * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
- + mScrimBehindAlphaUnlocking);
- }
- }
-
- private void updateScrimNormal() {
+ private float getInterpolatedFraction() {
float frac = mFraction;
// let's start this 20% of the way down the screen
frac = frac * 1.2f - 0.2f;
if (frac <= 0) {
- setScrimBehindAlpha(0);
+ return 0;
} else {
// woo, special effects
- final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
- setScrimBehindAlpha(k * mScrimBehindAlpha);
+ return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
}
}
@@ -455,102 +421,76 @@
private void setScrimInFrontAlpha(float alpha) {
setScrimAlpha(mScrimInFront, alpha);
- if (alpha == 0f) {
- mScrimInFront.setClickable(false);
- } else {
- // Eat touch events (unless dozing).
- mScrimInFront.setClickable(!mDozing);
- }
}
private void setScrimAlpha(View scrim, float alpha) {
- updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
- }
-
- protected float getDozeAlpha(View scrim) {
- return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
- }
-
- protected float getCurrentScrimAlpha(View scrim) {
- return scrim == mScrimBehind ? mCurrentBehindAlpha
- : scrim == mScrimInFront ? mCurrentInFrontAlpha
- : mCurrentHeadsUpAlpha;
- }
-
- private void setCurrentScrimAlpha(View scrim, float alpha) {
- if (scrim == mScrimBehind) {
- mCurrentBehindAlpha = alpha;
- mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
- } else if (scrim == mScrimInFront) {
- mCurrentInFrontAlpha = alpha;
+ if (alpha == 0f) {
+ scrim.setClickable(false);
} else {
- alpha = Math.max(0.0f, Math.min(1.0f, alpha));
- mCurrentHeadsUpAlpha = alpha;
+ // Eat touch events (unless dozing).
+ scrim.setClickable(!(mState == ScrimState.AOD));
}
+ updateScrim(mAnimateChange, scrim, alpha);
}
- private void updateScrimColor(View scrim) {
- float alpha1 = getCurrentScrimAlpha(scrim);
+ private void updateScrimColor(View scrim, float alpha, int tint) {
+ alpha = Math.max(0, Math.min(1.0f, alpha));
if (scrim instanceof ScrimView) {
ScrimView scrimView = (ScrimView) scrim;
- float dozeAlpha = getDozeAlpha(scrim);
- float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
- alpha = Math.max(0, Math.min(1.0f, alpha));
- scrimView.setViewAlpha(alpha);
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
(int) (alpha * 255));
- int dozeTint = Color.TRANSPARENT;
-
- boolean dozing = mAnimatingDozeUnlock || mDozing;
- boolean frontScrimDozing = mWakingUpFromAodInProgress;
- if (dozing || frontScrimDozing && scrim == mScrimInFront) {
- dozeTint = Color.BLACK;
- }
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
- dozeTint == Color.BLACK ? 1 : 0);
+ Color.alpha(tint));
- scrimView.setTint(dozeTint);
+ scrimView.setTint(tint);
+ scrimView.setViewAlpha(alpha);
} else {
- scrim.setAlpha(alpha1);
+ scrim.setAlpha(alpha);
}
dispatchScrimsVisible();
}
- private void startScrimAnimation(final View scrim, float target) {
- float current = getCurrentScrimAlpha(scrim);
- ValueAnimator anim = ValueAnimator.ofFloat(current, target);
+ private int getCurrentScrimTint(View scrim) {
+ return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
+ }
+
+ private void startScrimAnimation(final View scrim, float current, float target) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
+ Color.TRANSPARENT;
anim.addUpdateListener(animation -> {
- float alpha = (float) animation.getAnimatedValue();
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ final float animAmount = (float) animation.getAnimatedValue();
+ final int finalScrimTint = scrim == mScrimInFront ?
+ mCurrentInFrontTint : mCurrentBehindTint;
+ float alpha = MathUtils.lerp(current, target, animAmount);
+ int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
+ updateScrimColor(scrim, alpha, tint);
dispatchScrimsVisible();
});
anim.setInterpolator(getInterpolator());
anim.setStartDelay(mAnimationDelay);
- anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+ anim.setDuration(mAnimationDuration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ if (mKeyguardFadingOutInProgress) {
+ mKeyguardFadeoutAnimation = null;
+ mKeyguardFadingOutInProgress = false;
+ }
+ onFinished();
+
+ scrim.setTag(TAG_KEY_ANIM, null);
+ scrim.setTag(TAG_KEY_ANIM_TARGET, null);
+ dispatchScrimsVisible();
+
if (!mDeferFinishedListener && mOnAnimationFinished != null) {
mOnAnimationFinished.run();
mOnAnimationFinished = null;
}
- if (mKeyguardFadingOutInProgress) {
- mKeyguardFadeoutAnimation = null;
- mKeyguardFadingOutInProgress = false;
- mAnimatingDozeUnlock = false;
- }
- if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
- mWakingUpFromAodAnimationRunning = false;
- mWakingUpFromAodInProgress = false;
- }
- scrim.setTag(TAG_KEY_ANIM, null);
- scrim.setTag(TAG_KEY_ANIM_TARGET, null);
- dispatchScrimsVisible();
}
});
anim.start();
@@ -558,12 +498,6 @@
mKeyguardFadingOutInProgress = true;
mKeyguardFadeoutAnimation = anim;
}
- if (mWakingUpFromAodInProgress) {
- mWakingUpFromAodAnimationRunning = true;
- }
- if (mSkipFirstFrame) {
- anim.setCurrentPlayTime(16);
- }
scrim.setTag(TAG_KEY_ANIM, anim);
scrim.setTag(TAG_KEY_ANIM_TARGET, target);
}
@@ -582,19 +516,33 @@
public boolean onPreDraw() {
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
mUpdatePending = false;
- if (mDontAnimateBouncerChanges) {
- mDontAnimateBouncerChanges = false;
+ if (mCallback != null) {
+ mCallback.onStart();
}
updateScrims();
- mDurationOverride = -1;
- mAnimationDelay = 0;
- mSkipFirstFrame = false;
// Make sure that we always call the listener even if we didn't start an animation.
endAnimateKeyguardFadingOut(false /* force */);
return true;
}
+ private void onFinished() {
+ if (mWakeLockHeld) {
+ mWakeLock.release();
+ mWakeLockHeld = false;
+ }
+ if (mCallback != null) {
+ mCallback.onFinished();
+ mCallback = null;
+ }
+ // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
+ // At the end of the animation we need to remove the tint.
+ if (mState == ScrimState.UNLOCKED) {
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ }
+ }
+
private void endAnimateKeyguardFadingOut(boolean force) {
mAnimateKeyguardFadingOut = false;
if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
@@ -603,8 +551,6 @@
mOnAnimationFinished = null;
}
mKeyguardFadingOutInProgress = false;
- if (!mWakeAndUnlocking || force)
- mAnimatingDozeUnlock = false;
}
}
@@ -641,16 +587,19 @@
}
private void updateHeadsUpScrim(boolean animate) {
- updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+ updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha());
}
- private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
- if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) {
- return;
- }
+ @VisibleForTesting
+ void setOnAnimationFinished(Runnable onAnimationFinished) {
+ mOnAnimationFinished = onAnimationFinished;
+ }
- ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
- TAG_KEY_ANIM);
+ private void updateScrim(boolean animate, View scrim, float alpha) {
+ final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha()
+ : scrim.getAlpha();
+
+ ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
float animEndValue = -1;
if (previousAnimator != null) {
if (animate || alpha == currentAlpha) {
@@ -664,9 +613,32 @@
animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
}
}
- if (alpha != currentAlpha && alpha != animEndValue) {
+
+ final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
+ if (mBlankScreen || blankingInProgress) {
+ if (!blankingInProgress) {
+ blankDisplay();
+ }
+ return;
+ } else if (!mScreenBlankingCallbackCalled) {
+ // Not blanking the screen. Letting the callback know that we're ready
+ // to replace what was on the screen before.
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ }
+
+ final ScrimView scrimView = scrim instanceof ScrimView ? (ScrimView) scrim : null;
+ final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue;
+ final boolean wantsTintUpdate = scrimView != null
+ && scrimView.getTint() != getCurrentScrimTint(scrimView);
+
+ if (wantsAlphaUpdate || wantsTintUpdate) {
if (animate) {
- startScrimAnimation(scrim, alpha);
+ final float fromAlpha = scrimView == null ? scrim.getAlpha()
+ : scrimView.getViewAlpha();
+ startScrimAnimation(scrim, fromAlpha, alpha);
scrim.setTag(TAG_START_ALPHA, currentAlpha);
scrim.setTag(TAG_END_ALPHA, alpha);
} else {
@@ -685,13 +657,62 @@
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
} else {
// update the alpha directly
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
+ onFinished();
}
}
+ } else {
+ onFinished();
}
}
+ private void blankDisplay() {
+ final float initialAlpha = mScrimInFront.getViewAlpha();
+ final int initialTint = mScrimInFront.getTint();
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.addUpdateListener(animation -> {
+ final float amount = (float) animation.getAnimatedValue();
+ float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
+ int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
+ updateScrimColor(mScrimInFront, animAlpha, animTint);
+ dispatchScrimsVisible();
+ });
+ anim.setInterpolator(getInterpolator());
+ anim.setDuration(mDozeParameters.getPulseInDuration());
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ Runnable blankingCallback = () -> {
+ mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
+ mBlankScreen = false;
+ // Try again.
+ updateScrims();
+ };
+
+ // Setting power states can happen after we push out the frame. Make sure we
+ // stay fully opaque until the power state request reaches the lower levels.
+ getHandler().postDelayed(blankingCallback, 100);
+
+ }
+ });
+ anim.start();
+ mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
+
+ // Finish animation if we're already at its final state
+ if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
+ anim.end();
+ }
+ }
+
+ @VisibleForTesting
+ protected Handler getHandler() {
+ return Handler.getMain();
+ }
+
/**
* Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
* the heads up is in its resting space and 1 means it's fully dragged out.
@@ -719,23 +740,13 @@
return alpha * expandFactor;
}
- public void forceHideScrims(boolean hide, boolean animated) {
- mForceHideScrims = hide;
- mAnimateChange = animated;
- scheduleUpdate();
- }
-
- public void dontAnimateBouncerChangesUntilNextFrame() {
- mDontAnimateBouncerChanges = true;
- }
-
public void setExcludedBackgroundArea(Rect area) {
mScrimBehind.setExcludedArea(area);
}
public int getBackgroundColor() {
int color = mLockColors.getMainColor();
- return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+ return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
Color.red(color), Color.green(color), Color.blue(color));
}
@@ -764,27 +775,41 @@
}
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
- ColorExtractor.TYPE_DARK, mKeyguardShowing);
+ ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED);
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
}
- public void dump(PrintWriter pw) {
- pw.println(" ScrimController:");
+ @VisibleForTesting
+ protected WakeLock createWakeLock() {
+ return new DelayedWakeLock(getHandler(),
+ WakeLock.createPartial(mContext, "Doze"));
+ }
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" ScrimController:");
+ pw.print(" state:"); pw.println(mState);
pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
- pw.print(" mBouncerShowing="); pw.println(mBouncerShowing);
pw.print(" mTracking="); pw.println(mTracking);
- pw.print(" mForceHideScrims="); pw.println(mForceHideScrims);
+ }
+
+ public interface Callback {
+ default void onStart() {
+ }
+ default void onDisplayBlanked() {
+ }
+ default void onFinished() {
+ }
+ default void onCancelled() {
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
new file mode 100644
index 0000000..0db98f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -0,0 +1,208 @@
+/*
+ * 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.phone;
+
+import android.graphics.Color;
+import android.os.Trace;
+
+import com.android.systemui.statusbar.ScrimView;
+
+/**
+ * Possible states of the ScrimController state machine.
+ */
+public enum ScrimState {
+
+ /**
+ * Initial state.
+ */
+ UNINITIALIZED,
+
+ /**
+ * On the lock screen.
+ */
+ KEYGUARD {
+
+ @Override
+ public void prepare(ScrimState previousState) {
+ // DisplayPowerManager will blank the screen, we'll just
+ // set our scrim to black in this frame to avoid flickering and
+ // fade it out afterwards.
+ mBlankScreen = previousState == ScrimState.AOD;
+ if (previousState == ScrimState.AOD) {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Showing password challenge.
+ */
+ BOUNCER {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING;
+ mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED;
+ }
+ },
+
+ /**
+ * Changing screen brightness from quick settings.
+ */
+ BRIGHTNESS_MIRROR {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Always on display or screen off.
+ */
+ AOD {
+ @Override
+ public void prepare(ScrimState previousState) {
+ if (previousState == ScrimState.PULSING) {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
+ mBlankScreen = previousState == ScrimState.PULSING;
+ mCurrentBehindAlpha = 1;
+ mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ // DisplayPowerManager will blank the screen for us, we just need
+ // to set our state.
+ mAnimateChange = false;
+ }
+ },
+
+ /**
+ * When phone wakes up because you received a notification.
+ */
+ PULSING {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 1;
+ mCurrentInFrontAlpha = 0;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ },
+
+ /**
+ * Unlocked on top of an app (launcher or any other activity.)
+ */
+ UNLOCKED {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
+
+ if (previousState == ScrimState.AOD) {
+ // Fade from black to transparent when coming directly from AOD
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1, Color.BLACK);
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ } else {
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ mBlankScreen = false;
+ }
+ }
+ };
+
+ boolean mBlankScreen = false;
+ long mAnimationDuration = ScrimController.ANIMATION_DURATION;
+ int mCurrentInFrontTint = Color.TRANSPARENT;
+ int mCurrentBehindTint = Color.TRANSPARENT;
+ boolean mAnimateChange = true;
+ float mCurrentInFrontAlpha;
+ float mCurrentBehindAlpha;
+ float mAodFrontScrimAlpha;
+ float mScrimBehindAlphaKeyguard;
+ ScrimView mScrimInFront;
+ ScrimView mScrimBehind;
+ DozeParameters mDozeParameters;
+
+ public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+ mScrimInFront = scrimInFront;
+ mScrimBehind = scrimBehind;
+ mDozeParameters = dozeParameters;
+ }
+
+ public void prepare(ScrimState previousState) {
+ }
+
+ public float getFrontAlpha() {
+ return mCurrentInFrontAlpha;
+ }
+
+ public float getBehindAlpha() {
+ return mCurrentBehindAlpha;
+ }
+
+ public int getFrontTint() {
+ return mCurrentInFrontTint;
+ }
+
+ public int getBehindTint() {
+ return mCurrentBehindTint;
+ }
+
+ public long getAnimationDuration() {
+ return mAnimationDuration;
+ }
+
+ public boolean getBlanksScreen() {
+ return mBlankScreen;
+ }
+
+ public void updateScrimColor(ScrimView scrim, float alpha, int tint) {
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+ (int) (alpha * 255));
+
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+ Color.alpha(tint));
+
+ scrim.setTint(tint);
+ scrim.setViewAlpha(alpha);
+ }
+
+ public boolean getAnimateChange() {
+ return mAnimateChange;
+ }
+
+ public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) {
+ mAodFrontScrimAlpha = aodFrontScrimAlpha;
+ }
+
+ public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
+ mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+ }
+}
\ No newline at end of file
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 43cc0f7..a5609da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -238,6 +238,7 @@
.OnChildLocationsChangedListener;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -386,6 +387,7 @@
private VolumeComponent mVolumeComponent;
private BrightnessMirrorController mBrightnessMirrorController;
+ private boolean mBrightnessMirrorVisible;
protected FingerprintUnlockController mFingerprintUnlockController;
private LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
@@ -646,6 +648,31 @@
}
};
+ // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+ // this animation is tied to the scrim for historic reasons.
+ // TODO: notify when keyguard has faded away instead of the scrim.
+ private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+ .Callback() {
+ @Override
+ public void onFinished() {
+ notifyKeyguardState();
+ }
+
+ @Override
+ public void onCancelled() {
+ notifyKeyguardState();
+ }
+
+ private void notifyKeyguardState() {
+ if (mStatusBarKeyguardViewManager == null) {
+ Log.w(TAG, "Tried to notify keyguard visibility when "
+ + "mStatusBarKeyguardViewManager was null");
+ return;
+ }
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ };
+
private NotificationMessagingUtil mMessagingUtil;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private UserSwitcherController mUserSwitcherController;
@@ -1045,7 +1072,7 @@
if (mStatusBarWindowManager != null) {
mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
}
- });
+ }, new DozeParameters(mContext));
if (mScrimSrcModeEnabled) {
Runnable runnable = () -> {
boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1081,7 +1108,10 @@
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
- mScrimController);
+ (visible) -> {
+ mBrightnessMirrorVisible = visible;
+ updateScrimController();
+ });
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
@@ -1457,8 +1487,7 @@
mDozeScrimController, keyguardViewMediator,
mScrimController, this, UnlockMethodCache.getInstance(mContext));
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
- getBouncerContainer(), mScrimController,
- mFingerprintUnlockController);
+ getBouncerContainer(), mFingerprintUnlockController);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -2640,7 +2669,7 @@
}
public boolean isPulsing() {
- return mDozeScrimController.isPulsing();
+ return mDozeScrimController != null && mDozeScrimController.isPulsing();
}
@Override
@@ -3348,7 +3377,7 @@
}
if (mScrimController != null) {
- mScrimController.dump(pw);
+ mScrimController.dump(fd, pw, args);
}
if (DUMPTRUCK) {
@@ -4081,7 +4110,6 @@
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mLaunchTransitionFadingAway = false;
- mScrimController.forceHideScrims(false /* hide */, false /* animated */);
updateMediaMetaData(true /* metaDataChanged */, true);
}
@@ -4114,7 +4142,7 @@
if (beforeFading != null) {
beforeFading.run();
}
- mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+ updateScrimController();
updateMediaMetaData(false, true);
mNotificationPanel.setAlpha(1);
mStackScroller.setParentNotFullyVisible(true);
@@ -4145,6 +4173,13 @@
.setStartDelay(0)
.setDuration(FADE_KEYGUARD_DURATION_PULSING)
.setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ hideKeyguard();
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ })
.start();
}
@@ -4152,7 +4187,6 @@
* Plays the animation when an activity that was occluding Keyguard goes away.
*/
public void animateKeyguardUnoccluding() {
- mScrimController.animateKeyguardUnoccluding(500);
mNotificationPanel.setExpandedFraction(0f);
animateExpandNotificationsPanel();
}
@@ -4340,11 +4374,6 @@
mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
}
}
- if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
- mScrimController.setKeyguardShowing(true);
- } else {
- mScrimController.setKeyguardShowing(false);
- }
mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
updateTheme();
updateDozingState();
@@ -4352,6 +4381,7 @@
updateStackScrollerState(goingToFullShade, fromShadeLocked);
updateNotifications();
checkBarModes();
+ updateScrimController();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mUnlockMethodCache.isMethodSecure(),
@@ -4415,11 +4445,10 @@
boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
- mScrimController.setDozing(mDozing);
+ mDozeScrimController.setDozing(mDozing);
mKeyguardIndicationController.setDozing(mDozing);
mNotificationPanel.setDark(mDozing, animate);
updateQsExpansionEnabled();
- mDozeScrimController.setDozing(mDozing, animate);
updateRowStates();
Trace.endSection();
}
@@ -4914,6 +4943,7 @@
if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
updateHideIconsForBouncer(true /* animate */);
recomputeDisableFlags(true /* animate */);
+ updateScrimController();
}
public void cancelCurrentTouch() {
@@ -4965,12 +4995,10 @@
mStackScroller.setAnimationsEnabled(true);
mVisualStabilityManager.setScreenOn(true);
mNotificationPanel.setTouchDisabled(false);
-
- maybePrepareWakeUpFromAod();
-
mDozeServiceHost.stopDozing();
updateVisibleToUser();
updateIsKeyguard();
+ updateScrimController();
}
};
@@ -4980,18 +5008,16 @@
mFalsingManager.onScreenTurningOn();
mNotificationPanel.onScreenTurningOn();
- maybePrepareWakeUpFromAod();
-
if (mLaunchCameraOnScreenTurningOn) {
mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
mLaunchCameraOnScreenTurningOn = false;
}
+
+ updateScrimController();
}
@Override
public void onScreenTurnedOn() {
- mScrimController.wakeUpFromAod();
- mDozeScrimController.onScreenTurnedOn();
}
@Override
@@ -5009,13 +5035,6 @@
return mWakefulnessLifecycle.getWakefulness();
}
- private void maybePrepareWakeUpFromAod() {
- int wakefulness = mWakefulnessLifecycle.getWakefulness();
- if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) {
- mScrimController.prepareWakeUpFromAod();
- }
- }
-
private void vibrateForCameraGesture() {
// Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
@@ -5098,12 +5117,12 @@
if (!mDeviceInteractive) {
// Avoid flickering of the scrim when we instant launch the camera and the bouncer
// comes on.
- mScrimController.dontAnimateBouncerChangesUntilNextFrame();
mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
}
if (isScreenTurningOnOrOn()) {
if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+ updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
// we will dismiss us too early since we are waiting on an activity to be drawn and
@@ -5145,15 +5164,16 @@
private void updateDozing() {
Trace.beginSection("StatusBar#updateDozing");
// When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
- mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
+ boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
|| mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
// When in wake-and-unlock we may not have received a change to mState
// but we still should not be dozing, manually set to false.
if (mFingerprintUnlockController.getMode() ==
FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
- mDozing = false;
+ dozing = false;
}
+ mDozing = dozing;
mStatusBarWindowManager.setDozing(mDozing);
mStatusBarKeyguardViewManager.setDozing(mDozing);
if (mAmbientIndicationContainer instanceof DozeReceiver) {
@@ -5163,6 +5183,24 @@
Trace.endSection();
}
+ public void updateScrimController() {
+ if (mBouncerShowing) {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+ } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ } else if (mBrightnessMirrorVisible) {
+ mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ } else if (isPulsing()) {
+ // Handled in DozeScrimController#setPulsing
+ } else if (mDozing) {
+ mScrimController.transitionTo(ScrimState.AOD);
+ } else if (mIsKeyguard) {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ } else {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ }
+ }
+
public boolean isKeyguardShowing() {
if (mStatusBarKeyguardViewManager == null) {
Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
@@ -5222,7 +5260,6 @@
}
mDozeScrimController.pulse(new PulseCallback() {
-
@Override
public void onPulseStarted() {
callback.onPulseStarted();
@@ -5307,11 +5344,6 @@
}
@Override
- public void abortPulsing() {
- mDozeScrimController.abortPulsing();
- }
-
- @Override
public void extendPulse() {
mDozeScrimController.extendPulse();
}
@@ -5347,7 +5379,7 @@
@Override
public void setAodDimmingScrim(float scrimOpacity) {
- mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+ ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity);
}
public void dispatchDoubleTap(float viewX, float viewY) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bcde556..ef05bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
-import android.os.Trace;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -71,17 +70,14 @@
protected final Context mContext;
private final StatusBarWindowManager mStatusBarWindowManager;
- private final boolean mDisplayBlanksAfterDoze;
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
protected StatusBar mStatusBar;
- private ScrimController mScrimController;
private FingerprintUnlockController mFingerprintUnlockController;
private ViewGroup mContainer;
- private boolean mScreenTurnedOn;
protected KeyguardBouncer mBouncer;
protected boolean mShowing;
protected boolean mOccluded;
@@ -95,12 +91,10 @@
private boolean mLastBouncerDismissible;
protected boolean mLastRemoteInputActive;
private boolean mLastDozing;
- private boolean mLastDeferScrimFadeOut;
private int mLastFpMode;
private OnDismissAction mAfterKeyguardGoneAction;
private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
- private boolean mDeferScrimFadeOut;
// Dismiss action to be launched when we stop dozing or the keyguard is gone.
private DismissWithActionRequest mPendingWakeupAction;
@@ -125,18 +119,14 @@
mLockPatternUtils = lockPatternUtils;
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
- mDisplayBlanksAfterDoze = context.getResources().getBoolean(
- com.android.internal.R.bool.config_displayBlanksAfterDoze);
}
public void registerStatusBar(StatusBar statusBar,
ViewGroup container,
- ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController,
DismissCallbackRegistry dismissCallbackRegistry) {
mStatusBar = statusBar;
mContainer = container;
- mScrimController = scrimController;
mFingerprintUnlockController = fingerprintUnlockController;
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
@@ -149,7 +139,6 @@
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowManager.setKeyguardShowing(true);
- mScrimController.abortKeyguardFadingOut();
reset(true /* hideBouncerWhenShowing */);
}
@@ -253,15 +242,7 @@
}
public void onScreenTurnedOn() {
- Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
- mScreenTurnedOn = true;
- if (mDeferScrimFadeOut) {
- mDeferScrimFadeOut = false;
- animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- updateStates();
- }
- Trace.endSection();
+ // TODO: remove
}
@Override
@@ -285,7 +266,7 @@
}
public void onScreenTurnedOff() {
- mScreenTurnedOn = false;
+ // TODO: remove
}
public void notifyDeviceWakeUpRequested() {
@@ -374,10 +355,6 @@
mStatusBarWindowManager.setKeyguardFadingAway(true);
hideBouncer(true /* destroyView */);
updateStates();
- mScrimController.animateKeyguardFadingOut(
- StatusBar.FADE_KEYGUARD_START_DELAY,
- StatusBar.FADE_KEYGUARD_DURATION, null,
- false /* skipFirstFrame */);
}
}, new Runnable() {
@Override
@@ -400,36 +377,16 @@
mFingerprintUnlockController.startKeyguardFadingAway();
hideBouncer(true /* destroyView */);
if (wakeUnlockPulsing) {
- mStatusBarWindowManager.setKeyguardFadingAway(true);
mStatusBar.fadeKeyguardWhilePulsing();
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- mStatusBar::hideKeyguard, false /* skipFirstFrame */);
+ wakeAndUnlockDejank();
} else {
mFingerprintUnlockController.startKeyguardFadingAway();
mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
boolean staying = mStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
- if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
- boolean turnedOnSinceAuth =
- mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
- if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
- // Not ready to animate yet; either because the screen is not on yet,
- // or it is on but will turn off before waking out of doze.
- mDeferScrimFadeOut = true;
- } else {
-
- // Screen is already on, don't defer with fading out.
- animateScrimControllerKeyguardFadingOut(0,
- WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- }
- } else {
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- false /* skipFirstFrame */);
- }
+ wakeAndUnlockDejank();
} else {
- mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
mStatusBar.finishKeyguardFadingAway();
mFingerprintUnlockController.finishKeyguardFadingAway();
}
@@ -449,30 +406,17 @@
mBouncer.prepare();
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- boolean skipFirstFrame) {
- animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
- skipFirstFrame);
+ public void onKeyguardFadedAway() {
+ mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
+ 100);
+ mStatusBar.finishKeyguardFadingAway();
+ mFingerprintUnlockController.finishKeyguardFadingAway();
+ WindowManagerGlobal.getInstance().trimMemory(
+ ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- final Runnable endRunnable, boolean skipFirstFrame) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
- 100);
- mStatusBar.finishKeyguardFadingAway();
- mFingerprintUnlockController.finishKeyguardFadingAway();
- WindowManagerGlobal.getInstance().trimMemory(
- ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- }
- }, skipFirstFrame);
+ private void wakeAndUnlockDejank() {
if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
&& LatencyTracker.isEnabled(mContext)) {
DejankUtils.postAfterTraversal(() ->
@@ -593,7 +537,6 @@
if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
mStatusBar.setBouncerShowing(bouncerShowing);
- mScrimController.setBouncerShowing(bouncerShowing);
}
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
@@ -611,7 +554,6 @@
mLastBouncerDismissible = bouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
- mLastDeferScrimFadeOut = mDeferScrimFadeOut;
mLastFpMode = mFingerprintUnlockController.getMode();
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
@@ -624,7 +566,7 @@
boolean keyguardShowing = mShowing && !mOccluded;
boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
- || mRemoteInputActive) && !mDeferScrimFadeOut;
+ || mRemoteInputActive);
}
/**
@@ -634,7 +576,7 @@
boolean keyguardShowing = mLastShowing && !mLastOccluded;
boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
- || mLastRemoteInputActive) && !mLastDeferScrimFadeOut;
+ || mLastRemoteInputActive);
}
public boolean shouldDismissOnMenuPressed() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index b0553d7..a011952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.util.ArraySet;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -26,46 +27,47 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import java.util.function.Consumer;
+
/**
* Controls showing and hiding of the brightness mirror.
*/
public class BrightnessMirrorController
implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
- private final NotificationStackScrollLayout mStackScroller;
- public long TRANSITION_DURATION_OUT = 150;
- public long TRANSITION_DURATION_IN = 200;
+ private final static long TRANSITION_DURATION_OUT = 150;
+ private final static long TRANSITION_DURATION_IN = 200;
private final StatusBarWindowView mStatusBarWindow;
- private final ScrimController mScrimController;
+ private final NotificationStackScrollLayout mStackScroller;
+ private final Consumer<Boolean> mVisibilityCallback;
private final View mNotificationPanel;
private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
private final int[] mInt2Cache = new int[2];
private View mBrightnessMirror;
public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
- ScrimController scrimController) {
+ @NonNull Consumer<Boolean> visibilityCallback) {
mStatusBarWindow = statusBarWindow;
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller);
- mScrimController = scrimController;
+ mVisibilityCallback = visibilityCallback;
}
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mStackScroller.setFadingOut(true);
- mScrimController.forceHideScrims(true /* hide */, true /* animated */);
+ mVisibilityCallback.accept(true);
outAnimation(mNotificationPanel.animate())
.withLayer();
}
public void hideMirror() {
- mScrimController.forceHideScrims(false /* hide */, true /* animated */);
+ mVisibilityCallback.accept(false);
inAnimation(mNotificationPanel.animate())
.withLayer()
.withEndAction(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index b0c9f328..1c104cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -19,12 +19,13 @@
import android.annotation.NonNull;
import android.app.PendingIntent;
+import com.android.systemui.util.wakelock.WakeLock;
+
/**
* A rudimentary fake for DozeHost.
*/
class DozeHostFake implements DozeHost {
Callback callback;
- boolean pulseAborted;
boolean pulseExtended;
boolean animateWakeup;
boolean dozing;
@@ -92,11 +93,6 @@
}
@Override
- public void abortPulsing() {
- pulseAborted = true;
- }
-
- @Override
public void extendPulse() {
pulseExtended = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
index 4c3bf10..a4bcc69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
@@ -86,9 +86,9 @@
@Test
public void testSetViewAlpha_propagatesToDrawable() {
- float alpha = 0.5f;
+ final float alpha = 0.5f;
mView.setViewAlpha(alpha);
- assertEquals(mView.getViewAlpha(), alpha);
+ assertEquals("View alpha did not propagate to drawable", alpha, mView.getViewAlpha());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
new file mode 100644
index 0000000..ca2f713
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.phone;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.Debug;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.DozeHost;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class DozeScrimControllerTest extends SysuiTestCase {
+
+ private ScrimController mScrimController;
+ private DozeScrimController mDozeScrimController;
+
+ @Before
+ public void setup() {
+ mScrimController = mock(ScrimController.class);
+ // Make sure callbacks will be invoked to complete the lifecycle.
+ doAnswer(invocationOnMock -> {
+ ScrimController.Callback callback = invocationOnMock.getArgument(1);
+ callback.onStart();
+ callback.onDisplayBlanked();
+ callback.onFinished();
+ return null;
+ }).when(mScrimController).transitionTo(any(ScrimState.class),
+ any(ScrimController.Callback.class));
+
+ mDozeScrimController = new DozeScrimController(mScrimController, getContext());
+ mDozeScrimController.setDozing(true);
+ }
+
+ @Test
+ public void changesScrimControllerState() {
+ mDozeScrimController.pulse(mock(DozeHost.PulseCallback.class), 0);
+ verify(mScrimController).transitionTo(eq(ScrimState.PULSING),
+ any(ScrimController.Callback.class));
+ }
+
+ @Test
+ public void callsPulseCallback() {
+ DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class);
+ mDozeScrimController.pulse(callback, 0);
+
+ verify(callback).onPulseStarted();
+ mDozeScrimController.pulseOutNow();
+ verify(callback).onPulseFinished();
+ }
+
+ @Test
+ public void secondPulseIsSuppressed() {
+ DozeHost.PulseCallback callback1 = mock(DozeHost.PulseCallback.class);
+ DozeHost.PulseCallback callback2 = mock(DozeHost.PulseCallback.class);
+ mDozeScrimController.pulse(callback1, 0);
+ mDozeScrimController.pulse(callback2, 0);
+
+ verify(callback1, never()).onPulseFinished();
+ verify(callback2).onPulseFinished();
+ }
+
+ @Test
+ public void suppressesPulseIfNotDozing() {
+ mDozeScrimController.setDozing(false);
+ DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class);
+ mDozeScrimController.pulse(callback, 0);
+
+ verify(callback).onPulseFinished();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
new file mode 100644
index 0000000..b9f695b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -0,0 +1,300 @@
+/*
+ * 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.phone;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.utils.os.FakeHandler;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class ScrimControllerTest extends SysuiTestCase {
+
+ private SynchronousScrimController mScrimController;
+ private ScrimView mScrimBehind;
+ private ScrimView mScrimInFront;
+ private View mHeadsUpScrim;
+ private Consumer<Boolean> mScrimVisibilityCallback;
+ private Boolean mScrimVisibile;
+ private LightBarController mLightBarController;
+ private DozeParameters mDozeParamenters;
+ private WakeLock mWakeLock;
+ private boolean mAlwaysOnEnabled;
+
+ @Before
+ public void setup() {
+ mLightBarController = mock(LightBarController.class);
+ mScrimBehind = new ScrimView(getContext());
+ mScrimInFront = new ScrimView(getContext());
+ mHeadsUpScrim = mock(View.class);
+ mWakeLock = mock(WakeLock.class);
+ mAlwaysOnEnabled = true;
+ mScrimVisibilityCallback = (Boolean visible) -> mScrimVisibile = visible;
+ mDozeParamenters = mock(DozeParameters.class);
+ when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
+ mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind,
+ mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters);
+ }
+
+ @Test
+ public void initialState() {
+ Assert.assertEquals("ScrimController should start initialized",
+ mScrimController.getState(), ScrimState.UNINITIALIZED);
+ }
+
+ @Test
+ public void transitionToKeyguard() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible without tint
+ assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimTint(mScrimBehind, false /* tinted */);
+ }
+
+ @Test
+ public void transitionToAod() {
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible with tint
+ assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ }
+
+ @Test
+ public void transitionToPulsing() {
+ mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible with tint
+ // Pulse callback should have been invoked
+ assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ }
+
+ @Test
+ public void transitionToBouncer() {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible without tint
+ assertScrimVisibility(true /* front */, true /* behind */);
+ assertScrimTint(mScrimBehind, false /* tinted */);
+ }
+
+ @Test
+ public void transitionToUnlocked() {
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be transparent
+ assertScrimVisibility(false /* front */, false /* behind */);
+ assertScrimTint(mScrimBehind, false /* tinted */);
+ assertScrimTint(mScrimInFront, false /* tinted */);
+
+ // Back scrim should be visible after start dragging
+ mScrimController.setPanelExpansion(0.5f);
+ assertScrimVisibility(false /* front */, true /* behind */);
+ }
+
+ @Test
+ public void transitionToUnlockedFromAod() {
+ // Simulate unlock with fingerprint
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ // Immediately tinted after the transition starts
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be transparent
+ // Neither scrims should be tinted anymore after the animation.
+ assertScrimVisibility(false /* front */, false /* behind */);
+ assertScrimTint(mScrimInFront, false /* tinted */);
+ assertScrimTint(mScrimBehind, false /* tinted */);
+ }
+
+ @Test
+ public void scrimBlanksBeforeLeavingAoD() {
+ // Simulate unlock with fingerprint
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ mScrimController.transitionTo(ScrimState.UNLOCKED,
+ new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ // Front scrim should be black in the middle of the transition
+ Assert.assertTrue("Scrim should be visible during transition. Alpha: "
+ + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ Assert.assertTrue("Scrim should be visible during transition.",
+ mScrimVisibile);
+ }
+ });
+ mScrimController.finishAnimationsImmediately();
+ }
+
+ @Test
+ public void testScrimCallback() {
+ int[] callOrder = {0, 0, 0};
+ int[] currentCall = {0};
+ mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
+ @Override
+ public void onStart() {
+ callOrder[0] = ++currentCall[0];
+ }
+
+ @Override
+ public void onDisplayBlanked() {
+ callOrder[1] = ++currentCall[0];
+ }
+
+ @Override
+ public void onFinished() {
+ callOrder[2] = ++currentCall[0];
+ }
+ });
+ mScrimController.finishAnimationsImmediately();
+ Assert.assertEquals("onStart called in wrong order", 1, callOrder[0]);
+ Assert.assertEquals("onDisplayBlanked called in wrong order", 2, callOrder[1]);
+ Assert.assertEquals("onFinished called in wrong order", 3, callOrder[2]);
+ }
+
+ @Test
+ public void testScrimCallbacksWithoutAmbientDisplay() {
+ mAlwaysOnEnabled = false;
+ testScrimCallback();
+ }
+
+ @Test
+ public void testScrimCallbackCancelled() {
+ boolean[] cancelledCalled = {false};
+ mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
+ @Override
+ public void onCancelled() {
+ cancelledCalled[0] = true;
+ }
+ });
+ mScrimController.transitionTo(ScrimState.PULSING);
+ Assert.assertTrue("onCancelled should have been called", cancelledCalled[0]);
+ }
+
+ @Test
+ public void testHoldsWakeLock() {
+ mScrimController.transitionTo(ScrimState.AOD);
+ verify(mWakeLock, times(1)).acquire();
+ verify(mWakeLock, never()).release();
+ mScrimController.finishAnimationsImmediately();
+ verify(mWakeLock, times(1)).release();
+ }
+
+ private void assertScrimTint(ScrimView scrimView, boolean tinted) {
+ final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
+ final String name = scrimView == mScrimInFront ? "front" : "back";
+ Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+ +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
+ tinted, viewIsTinted);
+ }
+
+ private void assertScrimVisibility(boolean inFront, boolean behind) {
+ Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
+ + mScrimInFront.getViewAlpha(), inFront, mScrimInFront.getViewAlpha() > 0);
+ Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
+ + mScrimBehind.getViewAlpha(), behind, mScrimBehind.getViewAlpha() > 0);
+ Assert.assertEquals("Invalid visibility.", inFront || behind, mScrimVisibile);
+ }
+
+ /**
+ * Special version of ScrimController where animations have 0 duration for test purposes.
+ */
+ private class SynchronousScrimController extends ScrimController {
+
+ private FakeHandler mHandler;
+
+ public SynchronousScrimController(LightBarController lightBarController,
+ ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
+ Consumer<Boolean> scrimVisibleListener, DozeParameters dozeParameters) {
+ super(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
+ scrimVisibleListener, dozeParameters);
+ mHandler = new FakeHandler(Looper.myLooper());
+ }
+
+ public void finishAnimationsImmediately() {
+ boolean[] animationFinished = {false};
+ setOnAnimationFinished(()-> animationFinished[0] = true);
+
+ // Execute code that will trigger animations.
+ onPreDraw();
+
+ // Force finish screen blanking.
+ endAnimation(mScrimInFront, TAG_KEY_ANIM_BLANK);
+ mHandler.dispatchQueuedMessages();
+ // Force finish all animations.
+ endAnimation(mScrimBehind, TAG_KEY_ANIM);
+ endAnimation(mScrimInFront, TAG_KEY_ANIM);
+
+ if (!animationFinished[0]) {
+ throw new IllegalStateException("Animation never finished");
+ }
+ }
+
+ private void endAnimation(ScrimView scrimView, int tag) {
+ Animator animator = (Animator) scrimView.getTag(tag);
+ if (animator != null) {
+ animator.end();
+ }
+ }
+
+ @Override
+ protected Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ protected WakeLock createWakeLock() {
+ return mWakeLock;
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 3904fc9..ca15249 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -46,16 +46,15 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -81,6 +80,7 @@
import java.util.Random;
import java.util.TimeZone;
import java.util.TreeSet;
+import java.util.function.Predicate;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.RTC;
@@ -88,11 +88,17 @@
import static android.app.AlarmManager.ELAPSED_REALTIME;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.LocalLog;
+import com.android.server.ForceAppStandbyTracker.Listener;
+/**
+ * Alarm manager implementaion.
+ *
+ * Unit test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+ */
class AlarmManagerService extends SystemService {
private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
private static final int RTC_MASK = 1 << RTC;
@@ -131,13 +137,10 @@
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
- IAppOpsService mAppOpsService;
DeviceIdleController.LocalService mLocalDeviceIdleController;
final Object mLock = new Object();
- ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
- SparseBooleanArray mForegroundUids = new SparseBooleanArray();
// List of alarms per uid deferred due to user applied background restrictions on the source app
SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
long mNativeData;
@@ -184,12 +187,6 @@
int mSystemUiUid;
/**
- * The current set of user whitelisted apps for device idle mode, meaning these are allowed
- * to freely schedule alarms.
- */
- int[] mDeviceIdleUserWhitelist = new int[0];
-
- /**
* For each uid, this is the last time we dispatched an "allow while idle" alarm,
* used to determine the earliest we can dispatch the next such alarm. Times are in the
* 'elapsed' timebase.
@@ -223,6 +220,8 @@
private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
new SparseArray<>();
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
@@ -757,6 +756,9 @@
public AlarmManagerService(Context context) {
super(context);
mConstants = new Constants(mHandler);
+
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
}
static long convertToElapsed(long when, int type) {
@@ -894,17 +896,48 @@
deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
- void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+ /**
+ * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
+ * restricted.
+ *
+ * This is only called when the global "force all apps-standby" flag changes or when the
+ * power save whitelist changes, so it's okay to be slow.
+ */
+ void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
- for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
- final int uid = mPendingBackgroundAlarms.keyAt(i);
- final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
- if (UserHandle.getAppId(uid) == appId) {
- alarmsToDeliver.addAll(alarmsForUid);
- mPendingBackgroundAlarms.removeAt(i);
+
+ findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
+
+ if (alarmsToDeliver.size() > 0) {
+ deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+ }
+ }
+
+ @VisibleForTesting
+ static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms,
+ Predicate<Alarm> isBackgroundRestricted) {
+
+ for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
+ final int uid = pendingAlarms.keyAt(uidIndex);
+ final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
+
+ for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
+ final Alarm alarm = alarmsForUid.get(alarmIndex);
+
+ if (isBackgroundRestricted.test(alarm)) {
+ continue;
+ }
+
+ unrestrictedAlarms.add(alarm);
+ alarmsForUid.remove(alarmIndex);
+ }
+
+ if (alarmsForUid.size() == 0) {
+ pendingAlarms.removeAt(uidIndex);
}
}
- deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
@@ -1234,10 +1267,8 @@
} catch (RemoteException e) {
// ignored; both services live in system_server
}
- mAppOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
publishBinderService(Context.ALARM_SERVICE, mService);
- publishLocalService(LocalService.class, new LocalService());
+ mForceAppStandbyTracker.start();
}
@Override
@@ -1247,13 +1278,6 @@
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
- try {
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
- new AppOpsWatcher());
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(TAG, "AppOps service not reachable", rexc);
- }
}
}
@@ -1582,8 +1606,7 @@
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
|| callingUid == mSystemUiUid
- || Arrays.binarySearch(mDeviceIdleUserWhitelist,
- UserHandle.getAppId(callingUid)) >= 0)) {
+ || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
}
@@ -1660,24 +1683,14 @@
}
};
- public final class LocalService {
- public void setDeviceIdleUserWhitelist(int[] appids) {
- setDeviceIdleUserWhitelistImpl(appids);
- }
- }
-
void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
mConstants.dump(pw);
pw.println();
- pw.print(" Foreground uids: [");
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
- }
- pw.println("]");
- pw.println(" Forced app standby packages: " + mForcedAppStandbyPackages);
+ mForceAppStandbyTracker.dump(pw, " ");
+
final long nowRTC = System.currentTimeMillis();
final long nowELAPSED = SystemClock.elapsedRealtime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1717,7 +1730,6 @@
pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
pw.println();
pw.print(" Num time change events: "); pw.println(mNumTimeChanged);
- pw.println(" mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
pw.println();
pw.println(" Next alarm clock information: ");
@@ -1990,15 +2002,8 @@
mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
- final int foregroundUidsSize = mForegroundUids.size();
- for (int i = 0; i < foregroundUidsSize; i++) {
- if (mForegroundUids.valueAt(i)) {
- proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
- }
- }
- for (String pkg : mForcedAppStandbyPackages) {
- proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
- }
+ mForceAppStandbyTracker.dumpProto(proto,
+ AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
if (!mInteractive) {
@@ -2022,9 +2027,6 @@
proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
nowElapsed - mLastWakeupSet);
proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
- for (int i : mDeviceIdleUserWhitelist) {
- proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
- }
final TreeSet<Integer> users = new TreeSet<>();
final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
@@ -2266,28 +2268,6 @@
}
}
- void setDeviceIdleUserWhitelistImpl(int[] appids) {
- synchronized (mLock) {
- // appids are sorted, just send pending alarms for any new appids added to the whitelist
- int i = 0, j = 0;
- while (i < appids.length) {
- while (j < mDeviceIdleUserWhitelist.length
- && mDeviceIdleUserWhitelist[j] < appids[i]) {
- j++;
- }
- if (j < mDeviceIdleUserWhitelist.length
- && appids[i] != mDeviceIdleUserWhitelist[j]) {
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
- }
- sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
- }
- i++;
- }
- mDeviceIdleUserWhitelist = appids;
- }
- }
-
AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
synchronized (mLock) {
return mNextAlarmClockForUser.get(userId);
@@ -2710,9 +2690,7 @@
final String sourcePackage =
(alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
final int sourceUid = alarm.creatorUid;
- return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid)
- && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid))
- < 0;
+ return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
}
private native long init();
@@ -2859,7 +2837,8 @@
}
}
- private static class Alarm {
+ @VisibleForTesting
+ static class Alarm {
public final int type;
public final long origWhen;
public final boolean wakeup;
@@ -3073,6 +3052,11 @@
for (int i=0; i<triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
final boolean allowWhileIdle = (alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
+ if (alarm.wakeup) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName);
+ } else {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch non-wakeup alarm to " + alarm.packageName);
+ }
try {
if (localLOGV) {
Slog.v(TAG, "sending alarm " + alarm);
@@ -3092,6 +3076,7 @@
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
@@ -3476,17 +3461,10 @@
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@Override public void onUidActive(int uid) {
- synchronized (mLock) {
- if (!mForegroundUids.get(uid)) {
- mForegroundUids.put(uid, true);
- sendPendingBackgroundAlarmsLocked(uid, null);
- }
- }
}
@Override public void onUidIdle(int uid, boolean disabled) {
@@ -3494,7 +3472,6 @@
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@@ -3502,27 +3479,29 @@
}
};
- private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+
+ private final Listener mForceAppStandbyListener = new Listener() {
@Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ public void unblockAllUnrestrictedAlarms() {
synchronized (mLock) {
- final int mode = mAppOpsService.checkOperation(op, uid, packageName);
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG,
- "Appop changed for " + uid + ", " + packageName + " to " + mode);
- }
- final boolean changed;
- if (mode != AppOpsManager.MODE_ALLOWED) {
- changed = mForcedAppStandbyPackages.add(packageName);
- } else {
- changed = mForcedAppStandbyPackages.remove(packageName);
- }
- if (changed && mode == AppOpsManager.MODE_ALLOWED) {
- sendPendingBackgroundAlarmsLocked(uid, packageName);
- }
+ sendAllUnrestrictedPendingBackgroundAlarmsLocked();
}
}
- }
+
+ @Override
+ public void unblockAlarmsForUid(int uid) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, null);
+ }
+ }
+
+ @Override
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, packageName);
+ }
+ }
+ };
private final BroadcastStats getStatsLocked(PendingIntent pi) {
String pkg = pi.getCreatorPackage();
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 0921a00..d7aeb8c 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -119,7 +119,6 @@
private PowerManagerInternal mLocalPowerManager;
private PowerManager mPowerManager;
private ConnectivityService mConnectivityService;
- private AlarmManagerService.LocalService mLocalAlarmManager;
private INetworkPolicyManager mNetworkPolicyManager;
private SensorManager mSensorManager;
private Sensor mMotionSensor;
@@ -1435,7 +1434,6 @@
mGoingIdleWakeLock.setReferenceCounted(true);
mConnectivityService = (ConnectivityService)ServiceManager.getService(
Context.CONNECTIVITY_SERVICE);
- mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
@@ -1500,8 +1498,8 @@
mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
- mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
+ passWhiteListToForceAppStandbyTrackerLocked();
updateInteractivityLocked();
}
updateConnectivityState(null);
@@ -2477,13 +2475,7 @@
}
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
}
- if (mLocalAlarmManager != null) {
- if (DEBUG) {
- Slog.d(TAG, "Setting alarm whitelist to "
- + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
- }
- mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
- }
+ passWhiteListToForceAppStandbyTrackerLocked();
}
private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) {
@@ -2509,6 +2501,7 @@
}
mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);
}
+ passWhiteListToForceAppStandbyTrackerLocked();
}
private void reportPowerSaveWhitelistChangedLocked() {
@@ -2523,6 +2516,12 @@
getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
}
+ private void passWhiteListToForceAppStandbyTrackerLocked() {
+ ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
+ mPowerSaveWhitelistAllAppIdArray,
+ mTempWhitelistAppIdArray);
+ }
+
void readConfigFileLocked() {
if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile());
mPowerSaveWhitelistUserApps.clear();
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 5dd3ee0..61d3833 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -16,13 +16,18 @@
package com.android.server;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
@@ -30,21 +35,36 @@
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Pair;
-import android.util.Slog;
import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
+import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.List;
/**
- * Class to track OP_RUN_ANY_IN_BACKGROUND, UID foreground state and "force all app standby".
+ * Class to keep track of the information related to "force app standby", which includes:
+ * - OP_RUN_ANY_IN_BACKGROUND for each package
+ * - UID foreground state
+ * - User+system power save whitelist
+ * - Temporary power save whitelist
+ * - Global "force all apps standby" mode enforced by battery saver.
*
- * TODO Clean up cache when a user is deleted.
- * TODO Add unit tests. b/68769804.
+ * TODO: In general, we can reduce the number of callbacks by checking all signals before sending
+ * each callback. For example, even when an UID comes into the foreground, if it wasn't
+ * originally restricted, then there's no need to send an event.
+ * Doing this would be error-prone, so we punt it for now, but we should revisit it later.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
*/
public class ForceAppStandbyTracker {
private static final String TAG = "ForceAppStandbyTracker";
@@ -55,22 +75,32 @@
private final Object mLock = new Object();
private final Context mContext;
+ @VisibleForTesting
+ static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+
+ IActivityManager mIActivityManager;
AppOpsManager mAppOpsManager;
IAppOpsService mAppOpsService;
PowerManagerInternal mPowerManagerInternal;
- private final Handler mCallbackHandler;
+ private final MyHandler mHandler;
/**
* Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
*/
@GuardedBy("mLock")
- final ArraySet<Pair<Integer, String>> mForcedAppStandbyUidPackages = new ArraySet<>();
+ final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>();
@GuardedBy("mLock")
final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
@GuardedBy("mLock")
+ private int[] mPowerWhitelistedAllAppIds = new int[0];
+
+ @GuardedBy("mLock")
+ private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;
+
+ @GuardedBy("mLock")
final ArraySet<Listener> mListeners = new ArraySet<>();
@GuardedBy("mLock")
@@ -80,16 +110,116 @@
boolean mForceAllAppsStandby;
public static abstract class Listener {
- public void onRestrictionChanged(int uid, @Nullable String packageName) {
+ /**
+ * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
+ */
+ private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
+ int uid, @NonNull String packageName) {
+ updateJobsForUidPackage(uid, packageName);
+
+ if (!sender.areAlarmsRestricted(uid, packageName)) {
+ unblockAlarmsForUidPackage(uid, packageName);
+ }
}
- public void onGlobalRestrictionChanged() {
+ /**
+ * This is called when the foreground state changed for a UID.
+ */
+ private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
+ updateJobsForUid(uid);
+
+ if (sender.isInForeground(uid)) {
+ unblockAlarmsForUid(uid);
+ }
+ }
+
+ /**
+ * This is called when an app-id(s) is removed from the power save whitelist.
+ */
+ private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+ unblockAllUnrestrictedAlarms();
+ }
+
+ /**
+ * This is called when the power save whitelist changes, excluding the
+ * {@link #onPowerSaveUnwhitelisted} case.
+ */
+ private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+ }
+
+ /**
+ * This is called when the temp whitelist changes.
+ */
+ private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
+
+ // TODO This case happens rather frequently; consider optimizing and update jobs
+ // only for affected app-ids.
+
+ updateAllJobs();
+ }
+
+ /**
+ * This is called when the global "force all apps standby" flag changes.
+ */
+ private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+
+ if (!sender.isForceAllAppsStandbyEnabled()) {
+ unblockAllUnrestrictedAlarms();
+ }
+ }
+
+ /**
+ * Called when the job restrictions for multiple UIDs might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateAllJobs() {
+ }
+
+ /**
+ * Called when the job restrictions for a UID might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateJobsForUid(int uid) {
+ }
+
+ /**
+ * Called when the job restrictions for a UID - package might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateJobsForUidPackage(int uid, String packageName) {
+ }
+
+ /**
+ * Called when the job restrictions for multiple UIDs might have changed, so the alarm
+ * manager should re-evaluate all restrictions for all blocked jobs.
+ */
+ public void unblockAllUnrestrictedAlarms() {
+ }
+
+ /**
+ * Called when all jobs for a specific UID are unblocked.
+ */
+ public void unblockAlarmsForUid(int uid) {
+ }
+
+ /**
+ * Called when all alarms for a specific UID - package are unblocked.
+ */
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
}
}
- private ForceAppStandbyTracker(Context context) {
+ @VisibleForTesting
+ ForceAppStandbyTracker(Context context, Looper looper) {
mContext = context;
- mCallbackHandler = FgThread.getHandler();
+ mHandler = new MyHandler(looper);
+ }
+
+ private ForceAppStandbyTracker(Context context) {
+ this(context, FgThread.get().getLooper());
}
/**
@@ -112,45 +242,65 @@
}
mStarted = true;
- mAppOpsManager = Preconditions.checkNotNull(
- mContext.getSystemService(AppOpsManager.class));
- mAppOpsService = Preconditions.checkNotNull(
- IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE)));
- mPowerManagerInternal = Preconditions.checkNotNull(
- LocalServices.getService(PowerManagerInternal.class));
+ mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
+ mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
+ mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
+ mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
try {
- ActivityManager.getService().registerUidObserver(new UidObserver(),
+ mIActivityManager.registerUidObserver(new UidObserver(),
ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
| ActivityManager.UID_OBSERVER_ACTIVE,
ActivityManager.PROCESS_STATE_UNKNOWN, null);
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
+ mAppOpsService.startWatchingMode(TARGET_OP, null,
new AppOpsWatcher());
} catch (RemoteException e) {
// shouldn't happen.
}
- mPowerManagerInternal.registerLowPowerModeObserver(
- ServiceType.FORCE_ALL_APPS_STANDBY,
- state -> updateForceAllAppsStandby(state.batterySaverEnabled));
-
- updateForceAllAppsStandby(
- mPowerManagerInternal.getLowPowerState(ServiceType.FORCE_ALL_APPS_STANDBY)
- .batterySaverEnabled);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(new MyReceiver(), filter);
refreshForcedAppStandbyUidPackagesLocked();
+
+ mPowerManagerInternal.registerLowPowerModeObserver(
+ ServiceType.FORCE_ALL_APPS_STANDBY,
+ (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
+
+ updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+ ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
}
}
+ @VisibleForTesting
+ AppOpsManager injectAppOpsManager() {
+ return mContext.getSystemService(AppOpsManager.class);
+ }
+
+ @VisibleForTesting
+ IAppOpsService injectIAppOpsService() {
+ return IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ }
+
+ @VisibleForTesting
+ IActivityManager injectIActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ @VisibleForTesting
+ PowerManagerInternal injectPowerManagerInternal() {
+ return LocalServices.getService(PowerManagerInternal.class);
+ }
+
/**
- * Update {@link #mForcedAppStandbyUidPackages} with the current app ops state.
+ * Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
*/
private void refreshForcedAppStandbyUidPackagesLocked() {
- final int op = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
-
- mForcedAppStandbyUidPackages.clear();
- final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(new int[] {op});
+ mRunAnyRestrictedPackages.clear();
+ final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(
+ new int[] {TARGET_OP});
if (ops == null) {
return;
@@ -162,31 +312,38 @@
for (int j = 0; j < entries.size(); j++) {
AppOpsManager.OpEntry ent = entries.get(j);
- if (ent.getOp() != op) {
+ if (ent.getOp() != TARGET_OP) {
continue;
}
if (ent.getMode() != AppOpsManager.MODE_ALLOWED) {
- mForcedAppStandbyUidPackages.add(Pair.create(
+ mRunAnyRestrictedPackages.add(Pair.create(
pkg.getUid(), pkg.getPackageName()));
}
}
}
}
- boolean isRunAnyInBackgroundAppOpRestricted(int uid, @NonNull String packageName) {
- try {
- return mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
- uid, packageName) != AppOpsManager.MODE_ALLOWED;
- } catch (RemoteException e) {
- return false; // shouldn't happen.
+ /**
+ * Update {@link #mForceAllAppsStandby} and notifies the listeners.
+ */
+ void updateForceAllAppsStandby(boolean enable) {
+ synchronized (mLock) {
+ if (enable == mForceAllAppsStandby) {
+ return;
+ }
+ mForceAllAppsStandby = enable;
+
+ mHandler.notifyForceAllAppsStandbyChanged();
}
}
private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
- // TODO Maybe we should switch to indexOf(Pair.create()) if the array size is too big.
- final int size = mForcedAppStandbyUidPackages.size();
+ final int size = mRunAnyRestrictedPackages.size();
+ if (size > 8) {
+ return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName));
+ }
for (int i = 0; i < size; i++) {
- final Pair<Integer, String> pair = mForcedAppStandbyUidPackages.valueAt(i);
+ final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
if ((pair.first == uid) && packageName.equals(pair.second)) {
return i;
@@ -196,13 +353,16 @@
}
/**
- * @return whether a uid package-name pair is in mForcedAppStandbyUidPackages.
+ * @return whether a uid package-name pair is in mRunAnyRestrictedPackages.
*/
- boolean isUidPackageRestrictedLocked(int uid, @NonNull String packageName) {
+ boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) {
return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
}
- boolean updateRestrictedUidPackageLocked(int uid, @NonNull String packageName,
+ /**
+ * Add to / remove from {@link #mRunAnyRestrictedPackages}.
+ */
+ boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
boolean restricted) {
final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
final boolean wasRestricted = index >= 0;
@@ -210,13 +370,16 @@
return false;
}
if (restricted) {
- mForcedAppStandbyUidPackages.add(Pair.create(uid, packageName));
+ mRunAnyRestrictedPackages.add(Pair.create(uid, packageName));
} else {
- mForcedAppStandbyUidPackages.removeAt(index);
+ mRunAnyRestrictedPackages.removeAt(index);
}
return true;
}
+ /**
+ * Puts a UID to {@link #mForegroundUids}.
+ */
void uidToForeground(int uid) {
synchronized (mLock) {
if (!UserHandle.isApp(uid)) {
@@ -228,10 +391,13 @@
return;
}
mForegroundUids.put(uid, true);
- notifyForUidPackage(uid, null);
+ mHandler.notifyUidForegroundStateChanged(uid);
}
}
+ /**
+ * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true.
+ */
void uidToBackground(int uid, boolean remove) {
synchronized (mLock) {
if (!UserHandle.isApp(uid)) {
@@ -247,13 +413,11 @@
} else {
mForegroundUids.put(uid, false);
}
- notifyForUidPackage(uid, null);
+ mHandler.notifyUidForegroundStateChanged(uid);
}
}
- // Event handlers
-
- final class UidObserver extends IUidObserver.Stub {
+ private final class UidObserver extends IUidObserver.Stub {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
}
@@ -277,11 +441,28 @@
private final class AppOpsWatcher extends IAppOpsCallback.Stub {
@Override
public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ boolean restricted = false;
+ try {
+ restricted = mAppOpsService.checkOperation(TARGET_OP,
+ uid, packageName) != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
synchronized (mLock) {
- final boolean restricted = isRunAnyInBackgroundAppOpRestricted(uid, packageName);
+ if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) {
+ mHandler.notifyRunAnyAppOpsChanged(uid, packageName);
+ }
+ }
+ }
+ }
- if (updateRestrictedUidPackageLocked(uid, packageName, restricted)) {
- notifyForUidPackage(uid, packageName);
+ private final class MyReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId > 0) {
+ mHandler.doUserRemoved(userId);
}
}
}
@@ -293,31 +474,183 @@
}
}
- void notifyForUidPackage(int uid, String packageName) {
- mCallbackHandler.post(() -> {
- for (Listener l : cloneListeners()) {
- l.onRestrictionChanged(uid, packageName);
- }
- });
- }
+ private class MyHandler extends Handler {
+ private static final int MSG_UID_STATE_CHANGED = 1;
+ private static final int MSG_RUN_ANY_CHANGED = 2;
+ private static final int MSG_ALL_UNWHITELISTED = 3;
+ private static final int MSG_ALL_WHITELIST_CHANGED = 4;
+ private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
+ private static final int MSG_FORCE_ALL_CHANGED = 6;
- void notifyGlobal() {
- mCallbackHandler.post(() -> {
- for (Listener l : cloneListeners()) {
- l.onGlobalRestrictionChanged();
- }
- });
- }
+ private static final int MSG_USER_REMOVED = 7;
- void updateForceAllAppsStandby(boolean forceAllAppsStandby) {
- synchronized (mLock) {
- if (mForceAllAppsStandby == forceAllAppsStandby) {
- return;
- }
- mForceAllAppsStandby = forceAllAppsStandby;
- Slog.i(TAG, "Force all app standby: " + mForceAllAppsStandby);
- notifyGlobal();
+ public MyHandler(Looper looper) {
+ super(looper);
}
+
+ public void notifyUidForegroundStateChanged(int uid) {
+ obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget();
+ }
+ public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
+ obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
+ }
+
+ public void notifyAllUnwhitelisted() {
+ obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget();
+ }
+
+ public void notifyAllWhitelistChanged() {
+ obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget();
+ }
+
+ public void notifyTempWhitelistChanged() {
+ obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget();
+ }
+
+ public void notifyForceAllAppsStandbyChanged() {
+ obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
+ }
+
+ public void doUserRemoved(int userId) {
+ obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
+ }
+
+ @Override
+ public void dispatchMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ return;
+ }
+
+ // Only notify the listeners when started.
+ synchronized (mLock) {
+ if (!mStarted) {
+ return;
+ }
+ }
+ final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
+
+ switch (msg.what) {
+ case MSG_UID_STATE_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onUidForegroundStateChanged(sender, msg.arg1);
+ }
+ return;
+ case MSG_RUN_ANY_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
+ }
+ return;
+ case MSG_ALL_UNWHITELISTED:
+ for (Listener l : cloneListeners()) {
+ l.onPowerSaveUnwhitelisted(sender);
+ }
+ return;
+ case MSG_ALL_WHITELIST_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onPowerSaveWhitelistedChanged(sender);
+ }
+ return;
+ case MSG_TEMP_WHITELIST_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onTempPowerSaveWhitelistChanged(sender);
+ }
+ return;
+ case MSG_FORCE_ALL_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onForceAllAppsStandbyChanged(sender);
+ }
+ return;
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ return;
+ }
+ }
+ }
+
+ void handleUserRemoved(int removedUserId) {
+ synchronized (mLock) {
+ for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) {
+ final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+ final int uid = pair.first;
+ final int userId = UserHandle.getUserId(uid);
+
+ if (userId == removedUserId) {
+ mRunAnyRestrictedPackages.removeAt(i);
+ }
+ }
+ for (int i = mForegroundUids.size() - 1; i >= 0; i--) {
+ final int uid = mForegroundUids.keyAt(i);
+ final int userId = UserHandle.getUserId(uid);
+
+ if (userId == removedUserId) {
+ mForegroundUids.removeAt(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by device idle controller to update the power save whitelists.
+ */
+ public void setPowerSaveWhitelistAppIds(
+ int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) {
+ synchronized (mLock) {
+ final int[] previousWhitelist = mPowerWhitelistedAllAppIds;
+ final int[] previousTempWhitelist = mTempWhitelistedAppIds;
+
+ mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray;
+ mTempWhitelistedAppIds = tempWhitelistAppIdArray;
+
+ if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+ mHandler.notifyAllUnwhitelisted();
+ } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+ mHandler.notifyAllWhitelistChanged();
+ }
+
+ if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) {
+ mHandler.notifyTempWhitelistChanged();
+ }
+
+ }
+ }
+
+ /**
+ * @retunr true if a sorted app-id array {@code prevArray} has at least one element
+ * that's not in a sorted app-id array {@code newArray}.
+ */
+ @VisibleForTesting
+ static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) {
+ int i1 = 0;
+ int i2 = 0;
+ boolean prevFinished;
+ boolean newFinished;
+
+ for (;;) {
+ prevFinished = i1 >= prevArray.length;
+ newFinished = i2 >= newArray.length;
+ if (prevFinished || newFinished) {
+ break;
+ }
+ int a1 = prevArray[i1];
+ int a2 = newArray[i2];
+
+ if (a1 == a2) {
+ i1++;
+ i2++;
+ continue;
+ }
+ if (a1 < a2) {
+ // prevArray has an element that's not in a2.
+ return true;
+ }
+ i2++;
+ }
+ if (prevFinished) {
+ return false;
+ }
+ return newFinished;
}
// Public interface.
@@ -332,21 +665,51 @@
}
/**
- * Whether force-app-standby is effective for a UID package-name.
+ * @return whether alarms should be restricted for a UID package-name.
*/
- public boolean isRestricted(int uid, @NonNull String packageName) {
+ public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+ return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false);
+ }
+
+ /**
+ * @return whether jobs should be restricted for a UID package-name.
+ */
+ public boolean areJobsRestricted(int uid, @NonNull String packageName) {
+ return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true);
+ }
+
+ /**
+ * @return whether force-app-standby is effective for a UID package-name.
+ */
+ private boolean isRestricted(int uid, @NonNull String packageName,
+ boolean useTempWhitelistToo) {
if (isInForeground(uid)) {
return false;
}
synchronized (mLock) {
+ // Whitelisted?
+ final int appId = UserHandle.getAppId(uid);
+ if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) {
+ return false;
+ }
+ if (useTempWhitelistToo &&
+ ArrayUtils.contains(mTempWhitelistedAppIds, appId)) {
+ return false;
+ }
+
if (mForceAllAppsStandby) {
return true;
}
- return isUidPackageRestrictedLocked(uid, packageName);
+
+ return isRunAnyRestrictedLocked(uid, packageName);
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
+ /**
+ * @return whether a UID is in the foreground or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
public boolean isInForeground(int uid) {
if (!UserHandle.isApp(uid)) {
return true;
@@ -356,31 +719,120 @@
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
- public boolean isForceAllAppsStandbyEnabled() {
+ /**
+ * @return whether force all apps standby is enabled or not.
+ *
+ * Note clients normally shouldn't need to access it.
+ */
+ boolean isForceAllAppsStandbyEnabled() {
synchronized (mLock) {
return mForceAllAppsStandby;
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
+ /**
+ * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) {
synchronized (mLock) {
- return !isUidPackageRestrictedLocked(uid, packageName);
+ return !isRunAnyRestrictedLocked(uid, packageName);
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
- public SparseBooleanArray getForegroudUids() {
+ /**
+ * @return whether a UID is in the user / system defined power-save whitelist or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isUidPowerSaveWhitelisted(int uid) {
synchronized (mLock) {
- return mForegroundUids.clone();
+ return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid));
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
- public ArraySet<Pair<Integer, String>> getRestrictedUidPackages() {
+ /**
+ * @return whether a UID is in the temp power-save whitelist or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isUidTempPowerSaveWhitelisted(int uid) {
synchronized (mLock) {
- return new ArraySet(mForcedAppStandbyUidPackages);
+ return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid));
+ }
+ }
+
+ public void dump(PrintWriter pw, String indent) {
+ synchronized (mLock) {
+ pw.print(indent);
+ pw.print("Force all apps standby: ");
+ pw.println(isForceAllAppsStandbyEnabled());
+
+ pw.print(indent);
+ pw.print("Foreground uids: [");
+
+ String sep = "";
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ pw.print(sep);
+ pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i)));
+ sep = " ";
+ }
+ }
+ pw.println("]");
+
+ pw.print(indent);
+ pw.print("Whitelist appids: ");
+ pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
+
+ pw.print(indent);
+ pw.print("Temp whitelist appids: ");
+ pw.println(Arrays.toString(mTempWhitelistedAppIds));
+
+ pw.print(indent);
+ pw.println("Restricted packages:");
+ for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+ pw.print(indent);
+ pw.print(" ");
+ pw.print(UserHandle.formatUid(uidAndPackage.first));
+ pw.print(" ");
+ pw.print(uidAndPackage.second);
+ pw.println();
+ }
+ }
+ }
+
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ synchronized (mLock) {
+ final long token = proto.start(fieldId);
+
+ proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
+
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS,
+ mForegroundUids.keyAt(i));
+ }
+ }
+
+ for (int appId : mPowerWhitelistedAllAppIds) {
+ proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId);
+ }
+
+ for (int appId : mTempWhitelistedAppIds) {
+ proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId);
+ }
+
+ for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+ final long token2 = proto.start(
+ ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
+ proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first);
+ proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME,
+ uidAndPackage.second);
+ proto.end(token2);
+ }
+ proto.end(token);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 3dbee42..b11b16e1 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -399,6 +399,16 @@
otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
}
} finally {
+ if (mHomeStack != null && !isTopStack(mHomeStack)) {
+ // Whenever split-screen is dismissed we want the home stack directly behind the
+ // currently top stack so it shows up when the top stack is finished.
+ final ActivityStack topStack = getTopStack();
+ // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+ // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+ // once we have that.
+ mHomeStack.moveToFront("onSplitScreenModeDismissed");
+ topStack.moveToFront("onSplitScreenModeDismissed");
+ }
mSupervisor.mWindowManager.continueSurfaceLayout();
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 85af2cc..fe992da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -318,6 +318,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -1781,6 +1782,11 @@
final boolean mPermissionReviewRequired;
+ /**
+ * Whether to force background check on all apps (for battery saver) or not.
+ */
+ boolean mForceBackgroundCheck;
+
private static String sTheRealBuildSerial = Build.UNKNOWN;
/**
@@ -8620,6 +8626,16 @@
}
switch (appop) {
case AppOpsManager.MODE_ALLOWED:
+ // If force-background-check is enabled, restrict all apps that aren't whitelisted.
+ if (mForceBackgroundCheck &&
+ UserHandle.isApp(uid) &&
+ !isOnDeviceIdleWhitelistLocked(uid)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check: " +
+ uid + "/" + packageName + " restricted");
+ }
+ return ActivityManager.APP_START_MODE_DELAYED;
+ }
return ActivityManager.APP_START_MODE_NORMAL;
case AppOpsManager.MODE_IGNORED:
return ActivityManager.APP_START_MODE_DELAYED;
@@ -8719,6 +8735,9 @@
return ActivityManager.APP_START_MODE_NORMAL;
}
+ /**
+ * @return whether a UID is in the system, user or temp doze whitelist.
+ */
boolean isOnDeviceIdleWhitelistLocked(int uid) {
final int appId = UserHandle.getAppId(uid);
return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0
@@ -10503,12 +10522,12 @@
+ " non-standard task " + taskId + " to windowing mode="
+ windowingMode);
}
- final ActivityDisplay display = task.getStack().getDisplay();
- final ActivityStack stack = display.getOrCreateStack(windowingMode,
- task.getStack().getActivityType(), toTop);
- // TODO: Use ActivityStack.setWindowingMode instead of re-parenting.
- task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
- "moveTaskToStack");
+
+ final ActivityStack stack = task.getStack();
+ if (toTop) {
+ stack.moveToFront("setTaskWindowingMode", task);
+ }
+ stack.setWindowingMode(windowingMode);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -14176,6 +14195,16 @@
readGrantedUriPermissionsLocked();
}
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ if (pmi != null) {
+ pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK,
+ state -> updateForceBackgroundCheck(state.batterySaverEnabled));
+ updateForceBackgroundCheck(
+ pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled);
+ } else {
+ Slog.wtf(TAG, "PowerManagerInternal not found.");
+ }
+
if (goingCallback != null) goingCallback.run();
traceLog.traceBegin("ActivityManagerStartApps");
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
@@ -14274,6 +14303,23 @@
}
}
+ private void updateForceBackgroundCheck(boolean enabled) {
+ synchronized (this) {
+ if (mForceBackgroundCheck != enabled) {
+ mForceBackgroundCheck = enabled;
+
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
+ }
+
+ if (mForceBackgroundCheck) {
+ // Stop background services for idle UIDs.
+ doStopUidForIdleUidsLocked();
+ }
+ }
+ }
+ }
+
void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
synchronized (this) {
mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
@@ -15625,7 +15671,6 @@
void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
boolean needSep = false;
- boolean printedAnything = false;
int numPers = 0;
pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
@@ -15643,7 +15688,6 @@
if (!needSep) {
pw.println(" All known processes:");
needSep = true;
- printedAnything = true;
}
pw.print(r.persistent ? " *PERS*" : " *APP*");
pw.print(" UID "); pw.print(procs.keyAt(ia));
@@ -15668,7 +15712,6 @@
pw.println();
}
pw.println(" Isolated process list (sorted by uid):");
- printedAnything = true;
printed = true;
needSep = true;
}
@@ -15690,7 +15733,6 @@
pw.println();
}
pw.println(" Active instrumentation:");
- printedAnything = true;
printed = true;
needSep = true;
}
@@ -15702,14 +15744,14 @@
if (mActiveUids.size() > 0) {
if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) {
- printedAnything = needSep = true;
+ needSep = true;
}
}
if (dumpAll) {
if (mValidateUids.size() > 0) {
if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
needSep)) {
- printedAnything = needSep = true;
+ needSep = true;
}
}
}
@@ -15726,7 +15768,6 @@
pw.println("):");
dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", false, dumpPackage);
needSep = true;
- printedAnything = true;
}
if (dumpAll || dumpPackage != null) {
@@ -15742,7 +15783,6 @@
needSep = true;
pw.println(" PID mappings:");
printed = true;
- printedAnything = true;
}
pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i));
pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
@@ -15765,7 +15805,6 @@
needSep = true;
pw.println(" Foreground Processes:");
printed = true;
- printedAnything = true;
}
pw.print(" PID #"); pw.print(mImportantProcesses.keyAt(i));
pw.print(": "); pw.println(mImportantProcesses.valueAt(i));
@@ -15776,7 +15815,6 @@
if (mPersistentStartingProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Persisent processes that are starting:");
dumpProcessList(pw, this, mPersistentStartingProcesses, " ",
"Starting Norm", "Restarting PERS", dumpPackage);
@@ -15785,7 +15823,6 @@
if (mRemovedProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Processes that are being removed:");
dumpProcessList(pw, this, mRemovedProcesses, " ",
"Removed Norm", "Removed PERS", dumpPackage);
@@ -15794,7 +15831,6 @@
if (mProcessesOnHold.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Processes that are on old until the system is ready:");
dumpProcessList(pw, this, mProcessesOnHold, " ",
"OnHold Norm", "OnHold PERS", dumpPackage);
@@ -15803,9 +15839,6 @@
needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
- if (needSep) {
- printedAnything = true;
- }
if (dumpPackage == null) {
pw.println();
@@ -16077,10 +16110,7 @@
pw.println();
}
}
-
- if (!printedAnything) {
- pw.println(" (nothing)");
- }
+ pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck);
}
boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
@@ -23341,6 +23371,24 @@
}
}
+ /**
+ * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs.
+ */
+ void doStopUidForIdleUidsLocked() {
+ final int size = mActiveUids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = mActiveUids.keyAt(i);
+ if (!UserHandle.isApp(uid)) {
+ continue;
+ }
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (!uidRec.idle) {
+ continue;
+ }
+ doStopUidLocked(uidRec.uid, uidRec);
+ }
+ }
+
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 883019e..a089e6c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -647,8 +647,13 @@
return;
}
+ if (task.getStack().deferScheduleMultiWindowModeChanged()) {
+ // Don't do anything if we are currently deferring multi-window mode change.
+ return;
+ }
+
// An activity is considered to be in multi-window mode if its task isn't fullscreen.
- final boolean inMultiWindowMode = task.inMultiWindowMode();
+ final boolean inMultiWindowMode = inMultiWindowMode();
if (inMultiWindowMode != mLastReportedMultiWindowMode) {
mLastReportedMultiWindowMode = inMultiWindowMode;
scheduleMultiWindowModeChanged(getConfiguration());
@@ -675,7 +680,7 @@
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order
mLastReportedPictureInPictureMode = inPictureInPictureMode;
- mLastReportedMultiWindowMode = inPictureInPictureMode;
+ mLastReportedMultiWindowMode = inMultiWindowMode();
final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
targetStackBounds, null);
schedulePictureInPictureModeChanged(newConfig);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 3cf2283..ced8621 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -494,7 +494,7 @@
// Need to make sure windowing mode is supported.
int windowingMode = display.resolveWindowingMode(
- null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());;
+ null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());
if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
// Resolution to split-screen secondary for the primary split-screen stack means we want
// to go fullscreen.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 52ee67e..445bf67 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -4308,6 +4308,10 @@
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
mPipModeChangedActivities.add(r);
+ // If we are scheduling pip change, then remove this activity from multi-window
+ // change list as the processing of pip change will make sure multi-window changed
+ // message is processed in the right order relative to pip changed.
+ mMultiWindowModeChangedActivities.remove(r);
}
}
mPipModeChangedTargetStackBounds = targetStackBounds;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 6010422..27eb985 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -2123,18 +2123,13 @@
return mReuseTask.getStack();
}
- final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
- ? mPreferredDisplayId : INVALID_DISPLAY;
- final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
- vrDisplayId);
-
- if (launchStack != null) {
- return launchStack;
- }
-
if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
|| mPreferredDisplayId != DEFAULT_DISPLAY) {
- return null;
+ // We don't pass in the default display id into the get launch stack call so it can do a
+ // full resolution.
+ final int candidateDisplay =
+ mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
+ return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay);
}
// Otherwise handle adjacent launch.
@@ -2166,7 +2161,7 @@
mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
if (dockedStack != null && !dockedStack.shouldBeVisible(r)) {
// There is a docked stack, but it isn't visible, so we can't launch into that.
- return null;
+ return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
} else {
return dockedStack;
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index fd6d618..83965ee 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1991,7 +1991,6 @@
final Rect currentBounds = getOverrideBounds();
mTmpConfig.setTo(getOverrideConfiguration());
- final boolean oldMatchParentBounds = matchParentBounds();
final Configuration newConfig = getOverrideConfiguration();
final boolean matchParentBounds = bounds == null || bounds.isEmpty();
@@ -2014,12 +2013,17 @@
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
onOverrideConfigurationChanged(newConfig);
+ return !mTmpConfig.equals(newConfig);
+ }
- if (matchParentBounds != oldMatchParentBounds) {
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ final boolean wasInMultiWindowMode = inMultiWindowMode();
+ super.onConfigurationChanged(newParentConfig);
+ if (wasInMultiWindowMode != inMultiWindowMode()) {
mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
}
-
- return !mTmpConfig.equals(newConfig);
+ // TODO: Should also take care of Pip mode changes here.
}
/** Clears passed config and fills it with new override values. */
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index f67bd04..fc4015d 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,19 +16,12 @@
package com.android.server.job.controllers;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.IDeviceIdleController;
-import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.Pair;
import android.util.Slog;
-import android.util.SparseBooleanArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.ForceAppStandbyTracker;
@@ -37,7 +30,6 @@
import com.android.server.job.JobStore;
import java.io.PrintWriter;
-import java.util.function.Predicate;
public final class BackgroundJobsController extends StateController {
@@ -51,9 +43,6 @@
private final JobSchedulerService mJobSchedulerService;
private final IDeviceIdleController mDeviceIdleController;
- private int[] mPowerWhitelistedUserAppIds;
- private int[] mTempWhitelistedAppIds;
-
private final ForceAppStandbyTracker mForceAppStandbyTracker;
@@ -67,28 +56,6 @@
}
}
- private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- try {
- switch (intent.getAction()) {
- case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
- mPowerWhitelistedUserAppIds =
- mDeviceIdleController.getAppIdUserWhitelist();
- break;
- case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
- mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
- break;
- }
- } catch (RemoteException rexc) {
- Slog.e(LOG_TAG, "Device idle controller not reachable");
- }
- updateAllJobRestrictionsLocked();
- }
- }
- };
-
private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
super(service, context, lock);
mJobSchedulerService = service;
@@ -97,19 +64,6 @@
mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
- try {
- mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist();
- mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
- }
- IntentFilter powerWhitelistFilter = new IntentFilter();
- powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
- powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
- context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
- null, null);
-
mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
mForceAppStandbyTracker.start();
}
@@ -128,31 +82,7 @@
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("BackgroundJobsController");
- pw.print("Force all apps standby: ");
- pw.println(mForceAppStandbyTracker.isForceAllAppsStandbyEnabled());
-
- pw.print("Foreground uids: [");
- final SparseBooleanArray foregroundUids = mForceAppStandbyTracker.getForegroudUids();
-
- String sep = "";
- for (int i = 0; i < foregroundUids.size(); i++) {
- if (foregroundUids.valueAt(i)) {
- pw.print(sep);
- pw.print(UserHandle.formatUid(foregroundUids.keyAt(i)));
- sep = " ";
- }
- }
- pw.println("]");
-
- pw.println("Restricted packages:");
- for (Pair<Integer, String> uidAndPackage
- : mForceAppStandbyTracker.getRestrictedUidPackages()) {
- pw.print(" ");
- pw.print(UserHandle.formatUid(uidAndPackage.first));
- pw.print(" ");
- pw.print(uidAndPackage.second);
- pw.println();
- }
+ mForceAppStandbyTracker.dump(pw, "");
pw.println("Job state:");
mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
@@ -165,15 +95,17 @@
pw.print(" from ");
UserHandle.formatUid(pw, uid);
pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background");
- if (isWhitelistedLocked(uid)) {
+ if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
+ mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
pw.print(", whitelisted");
}
pw.print(": ");
pw.print(jobStatus.getSourcePackageName());
- pw.print(" [background restrictions ");
+ pw.print(" [RUN_ANY_IN_BACKGROUND ");
pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
- jobStatus.getSourceUid(), jobStatus.getSourcePackageName()) ? "off]" : "on]");
+ jobStatus.getSourceUid(), jobStatus.getSourcePackageName())
+ ? "allowed]" : "disallowed]");
if ((jobStatus.satisfiedConstraints
& JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
@@ -218,19 +150,12 @@
}
}
- private boolean isWhitelistedLocked(int uid) {
- final int appId = UserHandle.getAppId(uid);
- return ArrayUtils.contains(mTempWhitelistedAppIds, appId)
- || ArrayUtils.contains(mPowerWhitelistedUserAppIds, appId);
- }
-
boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) {
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
- final boolean canRun = isWhitelistedLocked(uid)
- || !mForceAppStandbyTracker.isRestricted(uid, packageName);
+ final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
}
@@ -261,13 +186,18 @@
private final Listener mForceAppStandbyListener = new Listener() {
@Override
- public void onRestrictionChanged(int uid, String packageName) {
+ public void updateAllJobs() {
+ updateAllJobRestrictionsLocked();
+ }
+
+ @Override
+ public void updateJobsForUid(int uid) {
updateJobRestrictionsForUidLocked(uid);
}
@Override
- public void onGlobalRestrictionChanged() {
- updateAllJobRestrictionsLocked();
+ public void updateJobsForUidPackage(int uid, String packageName) {
+ updateJobRestrictionsForUidLocked(uid);
}
};
}
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 87c9274..7c234f9 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -69,6 +69,7 @@
private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
+ private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check";
private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
@@ -190,6 +191,12 @@
private boolean mForceAllAppsStandby;
/**
+ * Whether to put all apps in the stand-by mode.
+ */
+ @GuardedBy("mLock")
+ private boolean mForceBackgroundCheck;
+
+ /**
* Weather to show non-essential sensors (e.g. edge sensors) or not.
*/
@GuardedBy("mLock")
@@ -326,6 +333,7 @@
mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
+ mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true);
mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
// Get default value from Settings.Secure
@@ -395,6 +403,12 @@
case ServiceType.VIBRATION:
return builder.setBatterySaverEnabled(mVibrationDisabled)
.build();
+ case ServiceType.FORCE_ALL_APPS_STANDBY:
+ return builder.setBatterySaverEnabled(mForceAllAppsStandby)
+ .build();
+ case ServiceType.FORCE_BACKGROUND_CHECK:
+ return builder.setBatterySaverEnabled(mForceBackgroundCheck)
+ .build();
case ServiceType.OPTIONAL_SENSORS:
return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
.build();
@@ -438,6 +452,7 @@
pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby);
+ pw.println(" " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck);
pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
pw.println();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 84e475a..43a0893 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -226,7 +226,7 @@
final Rect taskFrame = new Rect();
task.getBounds(taskFrame);
- final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer(
+ final GraphicBuffer buffer = SurfaceControl.captureLayers(
task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
diff --git a/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
new file mode 100644
index 0000000..918807d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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.server;
+
+import static android.app.AlarmManager.RTC;
+import static android.app.AlarmManager.RTC_WAKEUP;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.util.ObjectUtils;
+import com.android.server.AlarmManagerService.Alarm;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AlarmManagerServiceTest {
+ private SparseArray<ArrayList<Alarm>> addPendingAlarm(
+ SparseArray<ArrayList<Alarm>> all, int uid, String name, boolean removeIt) {
+ ArrayList<Alarm> uidAlarms = all.get(uid);
+ if (uidAlarms == null) {
+ all.put(uid, uidAlarms = new ArrayList<>());
+ }
+ // Details don't matter.
+ uidAlarms.add(new Alarm(
+ removeIt ? RTC : RTC_WAKEUP,
+ 0, 0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
+ return all;
+ }
+
+ private static String toString(SparseArray<ArrayList<Alarm>> pendingAlarms) {
+ final StringBuilder sb = new StringBuilder();
+
+ String sep = "";
+ for (int i = 0; i < pendingAlarms.size(); i++) {
+ sb.append(sep);
+ sep = ", ";
+ sb.append("[");
+ sb.append(pendingAlarms.keyAt(i));
+ sb.append(": ");
+ sb.append(toString(pendingAlarms.valueAt(i)));
+ sb.append("]");
+ }
+ return sb.toString();
+ }
+
+ private static String toString(ArrayList<Alarm> alarms) {
+ final StringBuilder sb = new StringBuilder();
+
+ alarms.sort((a, b) -> ObjectUtils.compare(a.packageName, b.packageName));
+
+ String sep = "";
+ for (Alarm a : alarms) {
+ sb.append(sep);
+ sep = ", ";
+ sb.append(a.packageName);
+ }
+ return sb.toString();
+ }
+
+ private void runCheckAllPendingAlarms(
+ SparseArray<ArrayList<Alarm>> pending, ArrayList<Alarm> alarmsToDeliver) {
+ // RTC_WAKEUP alarms are restricted.
+ AlarmManagerService.findAllUnrestrictedPendingBackgroundAlarmsLockedInner(pending,
+ alarmsToDeliver, alarm -> alarm.type == RTC_WAKEUP);
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_empty() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+ assertEquals("", toString(pending));
+ assertEquals("", toString(alarmsToDeliver));
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_single_remove() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ addPendingAlarm(pending, 100001, "a1", false);
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+ assertEquals("[100001: a1]", toString(pending));
+ assertEquals("", toString(alarmsToDeliver));
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_single_nonremove() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ addPendingAlarm(pending, 100001, "a1", true);
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+ assertEquals("", toString(pending));
+ assertEquals("a1", toString(alarmsToDeliver));
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_complex() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ addPendingAlarm(pending, 100001, "a11", false);
+ addPendingAlarm(pending, 100001, "a12", true);
+ addPendingAlarm(pending, 100001, "a13", false);
+ addPendingAlarm(pending, 100001, "a14", true);
+
+ addPendingAlarm(pending, 100002, "a21", false);
+
+ addPendingAlarm(pending, 100003, "a31", true);
+
+ addPendingAlarm(pending, 100004, "a41", false);
+ addPendingAlarm(pending, 100004, "a42", false);
+
+ addPendingAlarm(pending, 100005, "a51", true);
+ addPendingAlarm(pending, 100005, "a52", true);
+
+ addPendingAlarm(pending, 100006, "a61", true);
+ addPendingAlarm(pending, 100006, "a62", false);
+ addPendingAlarm(pending, 100006, "a63", true);
+ addPendingAlarm(pending, 100006, "a64", false);
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+ assertEquals("[100001: a11, a13], [100002: a21], [100004: a41, a42], [100006: a62, a64]",
+ toString(pending));
+ assertEquals("a12, a14, a31, a51, a52, a61, a63", toString(alarmsToDeliver));
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_complex_allRemove() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ addPendingAlarm(pending, 100001, "a11", true);
+ addPendingAlarm(pending, 100001, "a12", true);
+ addPendingAlarm(pending, 100001, "a13", true);
+ addPendingAlarm(pending, 100001, "a14", true);
+
+ addPendingAlarm(pending, 100002, "a21", true);
+
+ addPendingAlarm(pending, 100003, "a31", true);
+
+ addPendingAlarm(pending, 100004, "a41", true);
+ addPendingAlarm(pending, 100004, "a42", true);
+
+ addPendingAlarm(pending, 100005, "a51", true);
+ addPendingAlarm(pending, 100005, "a52", true);
+
+ addPendingAlarm(pending, 100006, "a61", true);
+ addPendingAlarm(pending, 100006, "a62", true);
+ addPendingAlarm(pending, 100006, "a63", true);
+ addPendingAlarm(pending, 100006, "a64", true);
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+ assertEquals("", toString(pending));
+ assertEquals("a11, a12, a13, a14, a21, a31, a41, a42, a51, a52, a61, a62, a63, a64",
+ toString(alarmsToDeliver));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
new file mode 100644
index 0000000..66d0da1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -0,0 +1,900 @@
+/*
+ * 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.server;
+
+import static com.android.server.ForceAppStandbyTracker.TARGET_OP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager.ServiceType;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.server.ForceAppStandbyTracker.Listener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ForceAppStandbyTrackerTest {
+
+ private class ForceAppStandbyTrackerTestable extends ForceAppStandbyTracker {
+ ForceAppStandbyTrackerTestable() {
+ super(mMockContext, Looper.getMainLooper());
+ }
+
+ @Override
+ AppOpsManager injectAppOpsManager() {
+ return mMockAppOpsManager;
+ }
+
+ @Override
+ IAppOpsService injectIAppOpsService() {
+ return mMockIAppOpsService;
+ }
+
+ @Override
+ IActivityManager injectIActivityManager() {
+ return mMockIActivityManager;
+ }
+
+ @Override
+ PowerManagerInternal injectPowerManagerInternal() {
+ return mMockPowerManagerInternal;
+ }
+ }
+
+ private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1;
+ private static final int UID_2 = Process.FIRST_APPLICATION_UID + 2;
+ private static final int UID_3 = Process.FIRST_APPLICATION_UID + 3;
+ private static final int UID_10_1 = UserHandle.getUid(10, UID_1);
+ private static final int UID_10_2 = UserHandle.getUid(10, UID_2);
+ private static final int UID_10_3 = UserHandle.getUid(10, UID_3);
+ private static final String PACKAGE_1 = "package1";
+ private static final String PACKAGE_2 = "package2";
+ private static final String PACKAGE_3 = "package3";
+ private static final String PACKAGE_SYSTEM = "android";
+
+ private Handler mMainHandler;
+
+ @Mock
+ private Context mMockContext;
+
+ @Mock
+ private IActivityManager mMockIActivityManager;
+
+ @Mock
+ private AppOpsManager mMockAppOpsManager;
+
+ @Mock
+ private IAppOpsService mMockIAppOpsService;
+
+ @Mock
+ private PowerManagerInternal mMockPowerManagerInternal;
+
+ private IUidObserver mIUidObserver;
+ private IAppOpsCallback.Stub mAppOpsCallback;
+ private Consumer<PowerSaveState> mPowerSaveObserver;
+ private BroadcastReceiver mReceiver;
+
+ private boolean mPowerSaveMode;
+
+ private final ArraySet<Pair<Integer, String>> mRestrictedPackages = new ArraySet();
+
+ @Before
+ public void setUp() {
+ mMainHandler = new Handler(Looper.getMainLooper());
+ }
+
+ private void waitUntilMainHandlerDrain() throws Exception {
+ final CountDownLatch l = new CountDownLatch(1);
+ mMainHandler.post(() -> {
+ l.countDown();
+ });
+ assertTrue(l.await(5, TimeUnit.SECONDS));
+ }
+
+ private PowerSaveState getPowerSaveState() {
+ return new PowerSaveState.Builder().setBatterySaverEnabled(mPowerSaveMode).build();
+ }
+
+ private ForceAppStandbyTrackerTestable newInstance() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockIAppOpsService.checkOperation(eq(TARGET_OP), anyInt(), anyString()))
+ .thenAnswer(inv -> {
+ return mRestrictedPackages.indexOf(
+ Pair.create(inv.getArgument(1), inv.getArgument(2))) >= 0 ?
+ AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED;
+ });
+
+ final ForceAppStandbyTrackerTestable instance = new ForceAppStandbyTrackerTestable();
+
+ return instance;
+ }
+
+ private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException {
+
+ // Set up functions that start() calls.
+ when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY)))
+ .thenAnswer(inv -> getPowerSaveState());
+ when(mMockAppOpsManager.getPackagesForOps(
+ any(int[].class)
+ )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>());
+
+ // Call start.
+ instance.start();
+
+ // Capture the listeners.
+ ArgumentCaptor<IUidObserver> uidObserverArgumentCaptor =
+ ArgumentCaptor.forClass(IUidObserver.class);
+ ArgumentCaptor<IAppOpsCallback.Stub> appOpsCallbackCaptor =
+ ArgumentCaptor.forClass(IAppOpsCallback.Stub.class);
+ ArgumentCaptor<Consumer<PowerSaveState>> powerSaveObserverCaptor =
+ ArgumentCaptor.forClass(Consumer.class);
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ verify(mMockIActivityManager).registerUidObserver(
+ uidObserverArgumentCaptor.capture(),
+ eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
+ | ActivityManager.UID_OBSERVER_ACTIVE),
+ eq(ActivityManager.PROCESS_STATE_UNKNOWN),
+ isNull());
+ verify(mMockIAppOpsService).startWatchingMode(
+ eq(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND),
+ isNull(),
+ appOpsCallbackCaptor.capture());
+ verify(mMockPowerManagerInternal).registerLowPowerModeObserver(
+ eq(ServiceType.FORCE_ALL_APPS_STANDBY),
+ powerSaveObserverCaptor.capture());
+
+ verify(mMockContext).registerReceiver(
+ receiverCaptor.capture(), any(IntentFilter.class));
+
+ mIUidObserver = uidObserverArgumentCaptor.getValue();
+ mAppOpsCallback = appOpsCallbackCaptor.getValue();
+ mPowerSaveObserver = powerSaveObserverCaptor.getValue();
+ mReceiver = receiverCaptor.getValue();
+
+ assertNotNull(mIUidObserver);
+ assertNotNull(mAppOpsCallback);
+ assertNotNull(mPowerSaveObserver);
+ assertNotNull(mReceiver);
+ }
+
+ private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException {
+ final Pair p = Pair.create(uid, packageName);
+ if (restrict) {
+ mRestrictedPackages.add(p);
+ } else {
+ mRestrictedPackages.remove(p);
+ }
+ if (mAppOpsCallback != null) {
+ mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName);
+ }
+ }
+
+ private static final int NONE = 0;
+ private static final int ALARMS_ONLY = 1 << 0;
+ private static final int JOBS_ONLY = 1 << 1;
+ private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY;
+
+ private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
+ int restrictionTypes) {
+ assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
+ instance.areJobsRestricted(uid, packageName));
+ assertEquals(((restrictionTypes & ALARMS_ONLY) != 0),
+ instance.areAlarmsRestricted(uid, packageName));
+ }
+
+ @Test
+ public void testAll() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+ callStart(instance);
+
+ assertFalse(instance.isForceAllAppsStandbyEnabled());
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ assertTrue(instance.isForceAllAppsStandbyEnabled());
+
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ // Toggle the foreground state.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ assertFalse(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_2));
+ assertTrue(instance.isInForeground(Process.SYSTEM_UID));
+
+ mIUidObserver.onUidActive(UID_1);
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ assertTrue(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_2));
+
+ mIUidObserver.onUidGone(UID_1, /*disable=*/ false);
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ assertFalse(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_2));
+
+ mIUidObserver.onUidActive(UID_1);
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ mIUidObserver.onUidIdle(UID_1, /*disable=*/ false);
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ assertFalse(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_2));
+
+ // Toggle the app ops.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areRestricted(instance, UID_10_2, PACKAGE_2, NONE);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ setAppOps(UID_1, PACKAGE_1, true);
+ setAppOps(UID_10_2, PACKAGE_2, true);
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ // Toggle power saver, should still be the same.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ // Clear the app ops and update the whitelist.
+ setAppOps(UID_1, PACKAGE_1, false);
+ setAppOps(UID_10_2, PACKAGE_2, false);
+
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_1}, new int[] {UID_2});
+
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY);
+ areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY);
+ areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ // Again, make sure toggling the global state doesn't change it.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY);
+ areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY);
+ areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ assertTrue(instance.isUidPowerSaveWhitelisted(UID_1));
+ assertTrue(instance.isUidPowerSaveWhitelisted(UID_10_1));
+ assertFalse(instance.isUidPowerSaveWhitelisted(UID_2));
+ assertFalse(instance.isUidPowerSaveWhitelisted(UID_10_2));
+
+ assertFalse(instance.isUidTempPowerSaveWhitelisted(UID_1));
+ assertFalse(instance.isUidTempPowerSaveWhitelisted(UID_10_1));
+ assertTrue(instance.isUidTempPowerSaveWhitelisted(UID_2));
+ assertTrue(instance.isUidTempPowerSaveWhitelisted(UID_10_2));
+ }
+
+ public void loadPersistedAppOps() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+
+ final List<PackageOps> ops = new ArrayList<>();
+
+ //--------------------------------------------------
+ List<OpEntry> entries = new ArrayList<>();
+ entries.add(new AppOpsManager.OpEntry(
+ AppOpsManager.OP_ACCESS_NOTIFICATIONS,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+ entries.add(new AppOpsManager.OpEntry(
+ ForceAppStandbyTracker.TARGET_OP,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+ ops.add(new PackageOps(PACKAGE_1, UID_1, entries));
+
+ //--------------------------------------------------
+ entries = new ArrayList<>();
+ entries.add(new AppOpsManager.OpEntry(
+ ForceAppStandbyTracker.TARGET_OP,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+ ops.add(new PackageOps(PACKAGE_2, UID_2, entries));
+
+ //--------------------------------------------------
+ entries = new ArrayList<>();
+ entries.add(new AppOpsManager.OpEntry(
+ ForceAppStandbyTracker.TARGET_OP,
+ AppOpsManager.MODE_ALLOWED, 0, 0, 0, 0, null));
+
+ ops.add(new PackageOps(PACKAGE_1, UID_10_1, entries));
+
+ //--------------------------------------------------
+ entries = new ArrayList<>();
+ entries.add(new AppOpsManager.OpEntry(
+ ForceAppStandbyTracker.TARGET_OP,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+ entries.add(new AppOpsManager.OpEntry(
+ AppOpsManager.OP_ACCESS_NOTIFICATIONS,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+ ops.add(new PackageOps(PACKAGE_3, UID_10_3, entries));
+
+ callStart(instance);
+
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_3, PACKAGE_3));
+
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_3, PACKAGE_3));
+ }
+
+ private void assertNoCallbacks(Listener l) throws Exception {
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+ }
+
+ @Test
+ public void testPowerSaveListener() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+ callStart(instance);
+
+ ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
+ instance.addListener(l);
+
+ // Power save on.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Power save off.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Power save on.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ assertNoCallbacks(l);
+ }
+
+ @Test
+ public void testAllListeners() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+ callStart(instance);
+
+ ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
+ instance.addListener(l);
+
+ // -------------------------------------------------------------------------
+ // Test with apppops.
+
+ setAppOps(UID_10_2, PACKAGE_2, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ setAppOps(UID_10_2, PACKAGE_2, false);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+ reset(l);
+
+ setAppOps(UID_10_2, PACKAGE_2, false);
+
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+
+ // Unrestrict while battery saver is on. Shouldn't fire.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ // Note toggling appops while BS is on will suppress unblockAlarmsForUidPackage().
+ setAppOps(UID_10_2, PACKAGE_2, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Battery saver off.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // -------------------------------------------------------------------------
+ // Tests with system/user/temp whitelist.
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_1, UID_2}, new int[] {});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Update temp whitelist.
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_1, UID_3});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_3});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Do the same thing with battery saver on. (Currently same callbacks are called.)
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_1, UID_2}, new int[] {});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Update temp whitelist.
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_1, UID_3});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_3});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+
+ // -------------------------------------------------------------------------
+ // Tests with proc state changes.
+
+ // With battery save.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ mIUidObserver.onUidActive(UID_10_1);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidGone(UID_10_1, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidActive(UID_10_1);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidIdle(UID_10_1, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Without battery save.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ mIUidObserver.onUidActive(UID_10_1);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidGone(UID_10_1, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidActive(UID_10_1);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidIdle(UID_10_1, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+ }
+
+ @Test
+ public void testUserRemoved() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+ callStart(instance);
+
+ mIUidObserver.onUidActive(UID_1);
+ mIUidObserver.onUidActive(UID_10_1);
+
+ setAppOps(UID_2, PACKAGE_2, true);
+ setAppOps(UID_10_2, PACKAGE_2, true);
+
+ assertTrue(instance.isInForeground(UID_1));
+ assertTrue(instance.isInForeground(UID_10_1));
+
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+ final Intent intent = new Intent(Intent.ACTION_USER_REMOVED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, 10);
+ mReceiver.onReceive(mMockContext, intent);
+
+ waitUntilMainHandlerDrain();
+
+ assertTrue(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_10_1));
+
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+ }
+
+ static int[] array(int... appIds) {
+ Arrays.sort(appIds);
+ return appIds;
+ }
+
+ private final Random mRandom = new Random();
+
+ int[] makeRandomArray() {
+ final ArrayList<Integer> list = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ if (mRandom.nextDouble() < 0.5) {
+ list.add(i);
+ }
+ }
+ return Arrays.stream(list.toArray(new Integer[list.size()]))
+ .mapToInt(Integer::intValue).toArray();
+ }
+
+ static boolean isAnyAppIdUnwhitelistedSlow(int[] prevArray, int[] newArray) {
+ Arrays.sort(newArray); // Just in case...
+ for (int p : prevArray) {
+ if (Arrays.binarySearch(newArray, p) < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void checkAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray, boolean expected) {
+ assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
+ expected, ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(prevArray, newArray));
+
+ // Also test isAnyAppIdUnwhitelistedSlow.
+ assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
+ expected, isAnyAppIdUnwhitelistedSlow(prevArray, newArray));
+ }
+
+ @Test
+ public void isAnyAppIdUnwhitelisted() {
+ checkAnyAppIdUnwhitelisted(array(), array(), false);
+
+ checkAnyAppIdUnwhitelisted(array(1), array(), true);
+ checkAnyAppIdUnwhitelisted(array(1), array(1), false);
+ checkAnyAppIdUnwhitelisted(array(1), array(0, 1), false);
+ checkAnyAppIdUnwhitelisted(array(1), array(0, 1, 2), false);
+ checkAnyAppIdUnwhitelisted(array(1), array(0, 1, 2), false);
+
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(), true);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(1, 2), true);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(1, 2, 10), false);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(2, 10), true);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(0, 1, 2, 4, 3, 10), false);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(0, 0, 1, 2, 10), false);
+
+ // Random test
+ int trueCount = 0;
+ final int count = 10000;
+ for (int i = 0; i < count; i++) {
+ final int[] array1 = makeRandomArray();
+ final int[] array2 = makeRandomArray();
+
+ final boolean expected = isAnyAppIdUnwhitelistedSlow(array1, array2);
+ final boolean actual = ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(array1, array2);
+
+ assertEquals("Input: " + Arrays.toString(array1) + " " + Arrays.toString(array2),
+ expected, actual);
+ if (expected) {
+ trueCount++;
+ }
+ }
+
+ // Make sure makeRandomArray() didn't generate all same arrays by accident.
+ assertTrue(trueCount > 0);
+ assertTrue(trueCount < count);
+ }
+}